--- /dev/null
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+setupEnvironment();
+process(is_array($argv) ? $argv : array());
+
+/**
+ * Initializes various values
+ *
+ * @throws RuntimeException If uopz extension prevents exit calls
+ */
+function setupEnvironment()
+{
+ ini_set('display_errors', 1);
+
+ if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
+ // uopz works at opcode level and disables exit calls
+ if (function_exists('uopz_allow_exit')) {
+ @uopz_allow_exit(true);
+ } else {
+ throw new RuntimeException('The uopz extension ignores exit calls and breaks this installer.');
+ }
+ }
+
+ $installer = 'ComposerInstaller';
+
+ if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
+ if ($version = getenv('COMPOSERSETUP')) {
+ $installer = sprintf('Composer-Setup.exe/%s', $version);
+ }
+ }
+
+ define('COMPOSER_INSTALLER', $installer);
+}
+
+/**
+ * Processes the installer
+ */
+function process($argv)
+{
+ // Determine ANSI output from --ansi and --no-ansi flags
+ setUseAnsi($argv);
+
+ $help = in_array('--help', $argv) || in_array('-h', $argv);
+ if ($help) {
+ displayHelp();
+ exit(0);
+ }
+
+ $check = in_array('--check', $argv);
+ $force = in_array('--force', $argv);
+ $quiet = in_array('--quiet', $argv);
+ $channel = 'stable';
+ if (in_array('--snapshot', $argv)) {
+ $channel = 'snapshot';
+ } elseif (in_array('--preview', $argv)) {
+ $channel = 'preview';
+ } elseif (in_array('--1', $argv)) {
+ $channel = '1';
+ } elseif (in_array('--2', $argv)) {
+ $channel = '2';
+ } elseif (in_array('--2.2', $argv)) {
+ $channel = '2.2';
+ }
+ $disableTls = in_array('--disable-tls', $argv);
+ $installDir = getOptValue('--install-dir', $argv, false);
+ $version = getOptValue('--version', $argv, false);
+ $filename = getOptValue('--filename', $argv, 'composer.phar');
+ $cafile = getOptValue('--cafile', $argv, false);
+
+ if (!checkParams($installDir, $version, $cafile)) {
+ exit(1);
+ }
+
+ $ok = checkPlatform($warnings, $quiet, $disableTls, true);
+
+ if ($check) {
+ // Only show warnings if we haven't output any errors
+ if ($ok) {
+ showWarnings($warnings);
+ showSecurityWarning($disableTls);
+ }
+ exit($ok ? 0 : 1);
+ }
+
+ if ($ok || $force) {
+ if ($channel === '1' && !$quiet) {
+ out('Warning: You forced the install of Composer 1.x via --1, but Composer 2.x is the latest stable version. Updating to it via composer self-update --stable is recommended.', 'error');
+ }
+
+ $installer = new Installer($quiet, $disableTls, $cafile);
+ if ($installer->run($version, $installDir, $filename, $channel)) {
+ showWarnings($warnings);
+ showSecurityWarning($disableTls);
+ exit(0);
+ }
+ }
+
+ exit(1);
+}
+
+/**
+ * Displays the help
+ */
+function displayHelp()
+{
+ echo <<<EOF
+Composer Installer
+------------------
+Options
+--help this help
+--check for checking environment only
+--force forces the installation
+--ansi force ANSI color output
+--no-ansi disable ANSI color output
+--quiet do not output unimportant messages
+--install-dir="..." accepts a target installation directory
+--preview install the latest version from the preview (alpha/beta/rc) channel instead of stable
+--snapshot install the latest version from the snapshot (dev builds) channel instead of stable
+--1 install the latest stable Composer 1.x (EOL) version
+--2 install the latest stable Composer 2.x version
+--2.2 install the latest stable Composer 2.2.x (LTS) version
+--version="..." accepts a specific version to install instead of the latest
+--filename="..." accepts a target filename (default: composer.phar)
+--disable-tls disable SSL/TLS security for file downloads
+--cafile="..." accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification
+
+EOF;
+}
+
+/**
+ * Sets the USE_ANSI define for colorizing output
+ *
+ * @param array $argv Command-line arguments
+ */
+function setUseAnsi($argv)
+{
+ // --no-ansi wins over --ansi
+ if (in_array('--no-ansi', $argv)) {
+ define('USE_ANSI', false);
+ } elseif (in_array('--ansi', $argv)) {
+ define('USE_ANSI', true);
+ } else {
+ define('USE_ANSI', outputSupportsColor());
+ }
+}
+
+/**
+ * Returns whether color output is supported
+ *
+ * @return bool
+ */
+function outputSupportsColor()
+{
+ if (false !== getenv('NO_COLOR') || !defined('STDOUT')) {
+ return false;
+ }
+
+ if ('Hyper' === getenv('TERM_PROGRAM')) {
+ return true;
+ }
+
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ return (function_exists('sapi_windows_vt100_support')
+ && sapi_windows_vt100_support(STDOUT))
+ || false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM');
+ }
+
+ if (function_exists('stream_isatty')) {
+ return stream_isatty(STDOUT);
+ }
+
+ if (function_exists('posix_isatty')) {
+ return posix_isatty(STDOUT);
+ }
+
+ $stat = fstat(STDOUT);
+ // Check if formatted mode is S_IFCHR
+ return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+}
+
+/**
+ * Returns the value of a command-line option
+ *
+ * @param string $opt The command-line option to check
+ * @param array $argv Command-line arguments
+ * @param mixed $default Default value to be returned
+ *
+ * @return mixed The command-line value or the default
+ */
+function getOptValue($opt, $argv, $default)
+{
+ $optLength = strlen($opt);
+
+ foreach ($argv as $key => $value) {
+ $next = $key + 1;
+ if (0 === strpos($value, $opt)) {
+ if ($optLength === strlen($value) && isset($argv[$next])) {
+ return trim($argv[$next]);
+ } else {
+ return trim(substr($value, $optLength + 1));
+ }
+ }
+ }
+
+ return $default;
+}
+
+/**
+ * Checks that user-supplied params are valid
+ *
+ * @param mixed $installDir The required istallation directory
+ * @param mixed $version The required composer version to install
+ * @param mixed $cafile Certificate Authority file
+ *
+ * @return bool True if the supplied params are okay
+ */
+function checkParams($installDir, $version, $cafile)
+{
+ $result = true;
+
+ if (false !== $installDir && !is_dir($installDir)) {
+ out("The defined install dir ({$installDir}) does not exist.", 'info');
+ $result = false;
+ }
+
+ if (false !== $version && 1 !== preg_match('/^\d+\.\d+\.\d+(\-(alpha|beta|RC)\d*)*$/', $version)) {
+ out("The defined install version ({$version}) does not match release pattern.", 'info');
+ $result = false;
+ }
+
+ if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) {
+ out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info');
+ $result = false;
+ }
+ return $result;
+}
+
+/**
+ * Checks the platform for possible issues running Composer
+ *
+ * Errors are written to the output, warnings are saved for later display.
+ *
+ * @param array $warnings Populated by method, to be shown later
+ * @param bool $quiet Quiet mode
+ * @param bool $disableTls Bypass tls
+ * @param bool $install If we are installing, rather than diagnosing
+ *
+ * @return bool True if there are no errors
+ */
+function checkPlatform(&$warnings, $quiet, $disableTls, $install)
+{
+ getPlatformIssues($errors, $warnings, $install);
+
+ // Make openssl warning an error if tls has not been specifically disabled
+ if (isset($warnings['openssl']) && !$disableTls) {
+ $errors['openssl'] = $warnings['openssl'];
+ unset($warnings['openssl']);
+ }
+
+ if (!empty($errors)) {
+ // Composer-Setup.exe uses "Some settings" to flag platform errors
+ out('Some settings on your machine make Composer unable to work properly.', 'error');
+ out('Make sure that you fix the issues listed below and run this script again:', 'error');
+ outputIssues($errors);
+ return false;
+ }
+
+ if (empty($warnings) && !$quiet) {
+ out('All settings correct for using Composer', 'success');
+ }
+ return true;
+}
+
+/**
+ * Checks platform configuration for common incompatibility issues
+ *
+ * @param array $errors Populated by method
+ * @param array $warnings Populated by method
+ * @param bool $install If we are installing, rather than diagnosing
+ *
+ * @return bool If any errors or warnings have been found
+ */
+function getPlatformIssues(&$errors, &$warnings, $install)
+{
+ $errors = array();
+ $warnings = array();
+
+ $iniMessage = PHP_EOL.getIniMessage();
+ $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';
+
+ if (ini_get('detect_unicode')) {
+ $errors['unicode'] = array(
+ 'The detect_unicode setting must be disabled.',
+ 'Add the following to the end of your `php.ini`:',
+ ' detect_unicode = Off',
+ $iniMessage
+ );
+ }
+
+ if (extension_loaded('suhosin')) {
+ $suhosin = ini_get('suhosin.executor.include.whitelist');
+ $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist');
+ if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) {
+ $errors['suhosin'] = array(
+ 'The suhosin.executor.include.whitelist setting is incorrect.',
+ 'Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):',
+ ' suhosin.executor.include.whitelist = phar '.$suhosin,
+ $iniMessage
+ );
+ }
+ }
+
+ if (!function_exists('json_decode')) {
+ $errors['json'] = array(
+ 'The json extension is missing.',
+ 'Install it or recompile php without --disable-json'
+ );
+ }
+
+ if (!extension_loaded('Phar')) {
+ $errors['phar'] = array(
+ 'The phar extension is missing.',
+ 'Install it or recompile php without --disable-phar'
+ );
+ }
+
+ if (!extension_loaded('filter')) {
+ $errors['filter'] = array(
+ 'The filter extension is missing.',
+ 'Install it or recompile php without --disable-filter'
+ );
+ }
+
+ if (!extension_loaded('hash')) {
+ $errors['hash'] = array(
+ 'The hash extension is missing.',
+ 'Install it or recompile php without --disable-hash'
+ );
+ }
+
+ if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
+ $errors['iconv_mbstring'] = array(
+ 'The iconv OR mbstring extension is required and both are missing.',
+ 'Install either of them or recompile php without --disable-iconv'
+ );
+ }
+
+ if (!ini_get('allow_url_fopen')) {
+ $errors['allow_url_fopen'] = array(
+ 'The allow_url_fopen setting is incorrect.',
+ 'Add the following to the end of your `php.ini`:',
+ ' allow_url_fopen = On',
+ $iniMessage
+ );
+ }
+
+ if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
+ $ioncube = ioncube_loader_version();
+ $errors['ioncube'] = array(
+ 'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.',
+ 'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:',
+ ' zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so',
+ $iniMessage
+ );
+ }
+
+ if (version_compare(PHP_VERSION, '5.3.2', '<')) {
+ $errors['php'] = array(
+ 'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.'
+ );
+ }
+
+ if (version_compare(PHP_VERSION, '5.3.4', '<')) {
+ $warnings['php'] = array(
+ 'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.',
+ 'Composer works with 5.3.2+ for most people, but there might be edge case issues.'
+ );
+ }
+
+ if (!extension_loaded('openssl')) {
+ $warnings['openssl'] = array(
+ 'The openssl extension is missing, which means that secure HTTPS transfers are impossible.',
+ 'If possible you should enable it or recompile php with --with-openssl'
+ );
+ }
+
+ if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
+ // Attempt to parse version number out, fallback to whole string value.
+ $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' '));
+ $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' '));
+ $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT;
+
+ $warnings['openssl_version'] = array(
+ 'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.',
+ 'If possible you should upgrade OpenSSL to version 1.0.1 or above.'
+ );
+ }
+
+ if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) {
+ $warnings['apc_cli'] = array(
+ 'The apc.enable_cli setting is incorrect.',
+ 'Add the following to the end of your `php.ini`:',
+ ' apc.enable_cli = Off',
+ $iniMessage
+ );
+ }
+
+ if (!$install && extension_loaded('xdebug')) {
+ $warnings['xdebug_loaded'] = array(
+ 'The xdebug extension is loaded, this can slow down Composer a little.',
+ 'Disabling it when using Composer is recommended.'
+ );
+
+ if (ini_get('xdebug.profiler_enabled')) {
+ $warnings['xdebug_profile'] = array(
+ 'The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.',
+ 'Add the following to the end of your `php.ini` to disable it:',
+ ' xdebug.profiler_enabled = 0',
+ $iniMessage
+ );
+ }
+ }
+
+ if (!extension_loaded('zlib')) {
+ $warnings['zlib'] = array(
+ 'The zlib extension is not loaded, this can slow down Composer a lot.',
+ 'If possible, install it or recompile php with --with-zlib',
+ $iniMessage
+ );
+ }
+
+ if (defined('PHP_WINDOWS_VERSION_BUILD')
+ && (version_compare(PHP_VERSION, '7.2.23', '<')
+ || (version_compare(PHP_VERSION, '7.3.0', '>=')
+ && version_compare(PHP_VERSION, '7.3.10', '<')))) {
+ $warnings['onedrive'] = array(
+ 'The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.',
+ 'Upgrade your PHP ('.PHP_VERSION.') to use this location with Composer.'
+ );
+ }
+
+ if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
+ $warnings['uopz'] = array(
+ 'The uopz extension ignores exit calls and may not work with all Composer commands.',
+ 'Disabling it when using Composer is recommended.'
+ );
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $phpinfo = (string) ob_get_clean();
+ if (preg_match('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
+ $configure = $match[1];
+
+ if (false !== strpos($configure, '--enable-sigchild')) {
+ $warnings['sigchild'] = array(
+ 'PHP was compiled with --enable-sigchild which can cause issues on some platforms.',
+ 'Recompile it without this flag if possible, see also:',
+ ' https://bugs.php.net/bug.php?id=22999'
+ );
+ }
+
+ if (false !== strpos($configure, '--with-curlwrappers')) {
+ $warnings['curlwrappers'] = array(
+ 'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.',
+ 'Recompile it without this flag if possible'
+ );
+ }
+ }
+
+ // Stringify the message arrays
+ foreach ($errors as $key => $value) {
+ $errors[$key] = PHP_EOL.implode(PHP_EOL, $value);
+ }
+
+ foreach ($warnings as $key => $value) {
+ $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value);
+ }
+
+ return !empty($errors) || !empty($warnings);
+}
+
+
+/**
+ * Outputs an array of issues
+ *
+ * @param array $issues
+ */
+function outputIssues($issues)
+{
+ foreach ($issues as $issue) {
+ out($issue, 'info');
+ }
+ out('');
+}
+
+/**
+ * Outputs any warnings found
+ *
+ * @param array $warnings
+ */
+function showWarnings($warnings)
+{
+ if (!empty($warnings)) {
+ out('Some settings on your machine may cause stability issues with Composer.', 'error');
+ out('If you encounter issues, try to change the following:', 'error');
+ outputIssues($warnings);
+ }
+}
+
+/**
+ * Outputs an end of process warning if tls has been bypassed
+ *
+ * @param bool $disableTls Bypass tls
+ */
+function showSecurityWarning($disableTls)
+{
+ if ($disableTls) {
+ out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info');
+ out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info');
+ }
+}
+
+/**
+ * colorize output
+ */
+function out($text, $color = null, $newLine = true)
+{
+ $styles = array(
+ 'success' => "\033[0;32m%s\033[0m",
+ 'error' => "\033[31;31m%s\033[0m",
+ 'info' => "\033[33;33m%s\033[0m"
+ );
+
+ $format = '%s';
+
+ if (is_string($color) && isset($styles[$color]) && USE_ANSI) {
+ $format = $styles[$color];
+ }
+
+ if ($newLine) {
+ $format .= PHP_EOL;
+ }
+
+ printf($format, $text);
+}
+
+/**
+ * Returns the system-dependent Composer home location, which may not exist
+ *
+ * @return string
+ */
+function getHomeDir()
+{
+ $home = getenv('COMPOSER_HOME');
+ if ($home) {
+ return $home;
+ }
+
+ $userDir = getUserDir();
+
+ if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
+ return $userDir.'/Composer';
+ }
+
+ $dirs = array();
+
+ if (useXdg()) {
+ // XDG Base Directory Specifications
+ $xdgConfig = getenv('XDG_CONFIG_HOME');
+ if (!$xdgConfig) {
+ $xdgConfig = $userDir . '/.config';
+ }
+
+ $dirs[] = $xdgConfig . '/composer';
+ }
+
+ $dirs[] = $userDir . '/.composer';
+
+ // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer
+ foreach ($dirs as $dir) {
+ if (is_dir($dir)) {
+ return $dir;
+ }
+ }
+
+ // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise)
+ return $dirs[0];
+}
+
+/**
+ * Returns the location of the user directory from the environment
+ * @throws RuntimeException If the environment value does not exists
+ *
+ * @return string
+ */
+function getUserDir()
+{
+ $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME';
+ $userDir = getenv($userEnv);
+
+ if (!$userDir) {
+ throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly');
+ }
+
+ return rtrim(strtr($userDir, '\\', '/'), '/');
+}
+
+/**
+ * @return bool
+ */
+function useXdg()
+{
+ foreach (array_keys($_SERVER) as $key) {
+ if (strpos((string) $key, 'XDG_') === 0) {
+ return true;
+ }
+ }
+
+ if (is_dir('/etc/xdg')) {
+ return true;
+ }
+
+ return false;
+}
+
+function validateCaFile($contents)
+{
+ // assume the CA is valid if php is vulnerable to
+ // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
+ if (
+ PHP_VERSION_ID <= 50327
+ || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
+ || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
+ ) {
+ return !empty($contents);
+ }
+
+ return (bool) openssl_x509_parse($contents);
+}
+
+/**
+ * Returns php.ini location information
+ *
+ * @return string
+ */
+function getIniMessage()
+{
+ $paths = array((string) php_ini_loaded_file());
+ $scanned = php_ini_scanned_files();
+
+ if ($scanned !== false) {
+ $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
+ }
+
+ // We will have at least one value, which may be empty
+ if ($paths[0] === '') {
+ array_shift($paths);
+ }
+
+ $ini = array_shift($paths);
+
+ if ($ini === null) {
+ return 'A php.ini file does not exist. You will have to create one.';
+ }
+
+ if (count($paths) > 1) {
+ return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.';
+ }
+
+ return 'The php.ini used by your command-line PHP is: '.$ini;
+}
+
+class Installer
+{
+ private $quiet;
+ private $disableTls;
+ private $cafile;
+ private $displayPath;
+ private $target;
+ private $tmpFile;
+ private $tmpCafile;
+ private $baseUrl;
+ private $algo;
+ private $errHandler;
+ private $httpClient;
+ private $pubKeys = array();
+ private $installs = array();
+
+ /**
+ * Constructor - must not do anything that throws an exception
+ *
+ * @param bool $quiet Quiet mode
+ * @param bool $disableTls Bypass tls
+ * @param mixed $cafile Path to CA bundle, or false
+ */
+ public function __construct($quiet, $disableTls, $caFile)
+ {
+ if (($this->quiet = $quiet)) {
+ ob_start();
+ }
+ $this->disableTls = $disableTls;
+ $this->cafile = $caFile;
+ $this->errHandler = new ErrorHandler();
+ }
+
+ /**
+ * Runs the installer
+ *
+ * @param mixed $version Specific version to install, or false
+ * @param mixed $installDir Specific installation directory, or false
+ * @param string $filename Specific filename to save to, or composer.phar
+ * @param string $channel Specific version channel to use
+ * @throws Exception If anything other than a RuntimeException is caught
+ *
+ * @return bool If the installation succeeded
+ */
+ public function run($version, $installDir, $filename, $channel)
+ {
+ try {
+ $this->initTargets($installDir, $filename);
+ $this->initTls();
+ $this->httpClient = new HttpClient($this->disableTls, $this->cafile);
+ $result = $this->install($version, $channel);
+
+ // in case --1 or --2 is passed, we leave the default channel for next self-update to stable
+ if (1 === preg_match('{^\d+$}D', $channel)) {
+ $channel = 'stable';
+ }
+
+ if ($result && $channel !== 'stable' && !$version && defined('PHP_BINARY')) {
+ $null = (defined('PHP_WINDOWS_VERSION_MAJOR') ? 'NUL' : '/dev/null');
+ @exec(escapeshellarg(PHP_BINARY) .' '.escapeshellarg($this->target).' self-update --'.$channel.' --set-channel-only -q > '.$null.' 2> '.$null, $output);
+ }
+ } catch (Exception $e) {
+ $result = false;
+ }
+
+ // Always clean up
+ $this->cleanUp($result);
+
+ if (isset($e)) {
+ // Rethrow anything that is not a RuntimeException
+ if (!$e instanceof RuntimeException) {
+ throw $e;
+ }
+ out($e->getMessage(), 'error');
+ }
+ return $result;
+ }
+
+ /**
+ * Initialization methods to set the required filenames and composer url
+ *
+ * @param mixed $installDir Specific installation directory, or false
+ * @param string $filename Specific filename to save to, or composer.phar
+ * @throws RuntimeException If the installation directory is not writable
+ */
+ protected function initTargets($installDir, $filename)
+ {
+ $this->displayPath = ($installDir ? rtrim($installDir, '/').'/' : '').$filename;
+ $installDir = $installDir ? realpath($installDir) : getcwd();
+
+ if (!is_writeable($installDir)) {
+ throw new RuntimeException('The installation directory "'.$installDir.'" is not writable');
+ }
+
+ $this->target = $installDir.DIRECTORY_SEPARATOR.$filename;
+ $this->tmpFile = $installDir.DIRECTORY_SEPARATOR.basename($this->target, '.phar').'-temp.phar';
+
+ $uriScheme = $this->disableTls ? 'http' : 'https';
+ $this->baseUrl = $uriScheme.'://getcomposer.org';
+ }
+
+ /**
+ * A wrapper around methods to check tls and write public keys
+ * @throws RuntimeException If SHA384 is not supported
+ */
+ protected function initTls()
+ {
+ if ($this->disableTls) {
+ return;
+ }
+
+ if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) {
+ throw new RuntimeException('SHA384 is not supported by your openssl extension');
+ }
+
+ $this->algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
+ $home = $this->getComposerHome();
+
+ $this->pubKeys = array(
+ 'dev' => $this->installKey(self::getPKDev(), $home, 'keys.dev.pub'),
+ 'tags' => $this->installKey(self::getPKTags(), $home, 'keys.tags.pub')
+ );
+
+ if (empty($this->cafile) && !HttpClient::getSystemCaRootBundlePath()) {
+ $this->cafile = $this->tmpCafile = $this->installKey(HttpClient::getPackagedCaFile(), $home, 'cacert-temp.pem');
+ }
+ }
+
+ /**
+ * Returns the Composer home directory, creating it if required
+ * @throws RuntimeException If the directory cannot be created
+ *
+ * @return string
+ */
+ protected function getComposerHome()
+ {
+ $home = getHomeDir();
+
+ if (!is_dir($home)) {
+ $this->errHandler->start();
+
+ if (!mkdir($home, 0777, true)) {
+ throw new RuntimeException(sprintf(
+ 'Unable to create Composer home directory "%s": %s',
+ $home,
+ $this->errHandler->message
+ ));
+ }
+ $this->installs[] = $home;
+ $this->errHandler->stop();
+ }
+ return $home;
+ }
+
+ /**
+ * Writes public key data to disc
+ *
+ * @param string $data The public key(s) in pem format
+ * @param string $path The directory to write to
+ * @param string $filename The name of the file
+ * @throws RuntimeException If the file cannot be written
+ *
+ * @return string The path to the saved data
+ */
+ protected function installKey($data, $path, $filename)
+ {
+ $this->errHandler->start();
+
+ $target = $path.DIRECTORY_SEPARATOR.$filename;
+ $installed = file_exists($target);
+ $write = file_put_contents($target, $data, LOCK_EX);
+ @chmod($target, 0644);
+
+ $this->errHandler->stop();
+
+ if (!$write) {
+ throw new RuntimeException(sprintf('Unable to write %s to: %s', $filename, $path));
+ }
+
+ if (!$installed) {
+ $this->installs[] = $target;
+ }
+
+ return $target;
+ }
+
+ /**
+ * The main install function
+ *
+ * @param mixed $version Specific version to install, or false
+ * @param string $channel Version channel to use
+ *
+ * @return bool If the installation succeeded
+ */
+ protected function install($version, $channel)
+ {
+ $retries = 3;
+ $result = false;
+ $infoMsg = 'Downloading...';
+ $infoType = 'info';
+
+ while ($retries--) {
+ if (!$this->quiet) {
+ out($infoMsg, $infoType);
+ $infoMsg = 'Retrying...';
+ $infoType = 'error';
+ }
+
+ if (!$this->getVersion($channel, $version, $url, $error)) {
+ out($error, 'error');
+ continue;
+ }
+
+ if (!$this->downloadToTmp($url, $signature, $error)) {
+ out($error, 'error');
+ continue;
+ }
+
+ if (!$this->verifyAndSave($version, $signature, $error)) {
+ out($error, 'error');
+ continue;
+ }
+
+ $result = true;
+ break;
+ }
+
+ if (!$this->quiet) {
+ if ($result) {
+ out(PHP_EOL."Composer (version {$version}) successfully installed to: {$this->target}", 'success');
+ out("Use it: php {$this->displayPath}", 'info');
+ out('');
+ } else {
+ out('The download failed repeatedly, aborting.', 'error');
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Sets the version url, downloading version data if required
+ *
+ * @param string $channel Version channel to use
+ * @param false|string $version Version to install, or set by method
+ * @param null|string $url The versioned url, set by method
+ * @param null|string $error Set by method on failure
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function getVersion($channel, &$version, &$url, &$error)
+ {
+ $error = '';
+
+ if ($version) {
+ if (empty($url)) {
+ $url = $this->baseUrl."/download/{$version}/composer.phar";
+ }
+ return true;
+ }
+
+ $this->errHandler->start();
+
+ if ($this->downloadVersionData($data, $error)) {
+ $this->parseVersionData($data, $channel, $version, $url);
+ }
+
+ $this->errHandler->stop();
+ return empty($error);
+ }
+
+ /**
+ * Downloads and json-decodes version data
+ *
+ * @param null|array $data Downloaded version data, set by method
+ * @param null|string $error Set by method on failure
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function downloadVersionData(&$data, &$error)
+ {
+ $url = $this->baseUrl.'/versions';
+ $errFmt = 'The "%s" file could not be %s: %s';
+
+ if (!$json = $this->httpClient->get($url)) {
+ $error = sprintf($errFmt, $url, 'downloaded', $this->errHandler->message);
+ return false;
+ }
+
+ if (!$data = json_decode($json, true)) {
+ $error = sprintf($errFmt, $url, 'json-decoded', $this->getJsonError());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * A wrapper around the methods needed to download and save the phar
+ *
+ * @param string $url The versioned download url
+ * @param null|string $signature Set by method on successful download
+ * @param null|string $error Set by method on failure
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function downloadToTmp($url, &$signature, &$error)
+ {
+ $error = '';
+ $errFmt = 'The "%s" file could not be downloaded: %s';
+ $sigUrl = $url.'.sig';
+ $this->errHandler->start();
+
+ if (!$fh = fopen($this->tmpFile, 'w')) {
+ $error = sprintf('Could not create file "%s": %s', $this->tmpFile, $this->errHandler->message);
+
+ } elseif (!$this->getSignature($sigUrl, $signature)) {
+ $error = sprintf($errFmt, $sigUrl, $this->errHandler->message);
+
+ } elseif (!fwrite($fh, $this->httpClient->get($url))) {
+ $error = sprintf($errFmt, $url, $this->errHandler->message);
+ }
+
+ if (is_resource($fh)) {
+ fclose($fh);
+ }
+ $this->errHandler->stop();
+ return empty($error);
+ }
+
+ /**
+ * Verifies the downloaded file and saves it to the target location
+ *
+ * @param string $version The composer version downloaded
+ * @param string $signature The digital signature to check
+ * @param null|string $error Set by method on failure
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function verifyAndSave($version, $signature, &$error)
+ {
+ $error = '';
+
+ if (!$this->validatePhar($this->tmpFile, $pharError)) {
+ $error = 'The download is corrupt: '.$pharError;
+
+ } elseif (!$this->verifySignature($version, $signature, $this->tmpFile)) {
+ $error = 'Signature mismatch, could not verify the phar file integrity';
+
+ } else {
+ $this->errHandler->start();
+
+ if (!rename($this->tmpFile, $this->target)) {
+ $error = sprintf('Could not write to file "%s": %s', $this->target, $this->errHandler->message);
+ }
+ chmod($this->target, 0755);
+ $this->errHandler->stop();
+ }
+
+ return empty($error);
+ }
+
+ /**
+ * Parses an array of version data to match the required channel
+ *
+ * @param array $data Downloaded version data
+ * @param mixed $channel Version channel to use
+ * @param false|string $version Set by method
+ * @param mixed $url The versioned url, set by method
+ */
+ protected function parseVersionData(array $data, $channel, &$version, &$url)
+ {
+ foreach ($data[$channel] as $candidate) {
+ if ($candidate['min-php'] <= PHP_VERSION_ID) {
+ $version = $candidate['version'];
+ $url = $this->baseUrl.$candidate['path'];
+ break;
+ }
+ }
+
+ if (!$version) {
+ $error = sprintf(
+ 'None of the %d %s version(s) of Composer matches your PHP version (%s / ID: %d)',
+ count($data[$channel]),
+ $channel,
+ PHP_VERSION,
+ PHP_VERSION_ID
+ );
+ throw new RuntimeException($error);
+ }
+ }
+
+ /**
+ * Downloads the digital signature of required phar file
+ *
+ * @param string $url The signature url
+ * @param null|string $signature Set by method on success
+ *
+ * @return bool If the download succeeded
+ */
+ protected function getSignature($url, &$signature)
+ {
+ if (!$result = $this->disableTls) {
+ $signature = $this->httpClient->get($url);
+
+ if ($signature) {
+ $signature = json_decode($signature, true);
+ $signature = base64_decode($signature['sha384']);
+ $result = true;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Verifies the signature of the downloaded phar
+ *
+ * @param string $version The composer versione
+ * @param string $signature The downloaded digital signature
+ * @param string $file The temp phar file
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function verifySignature($version, $signature, $file)
+ {
+ if (!$result = $this->disableTls) {
+ $path = preg_match('{^[0-9a-f]{40}$}', $version) ? $this->pubKeys['dev'] : $this->pubKeys['tags'];
+ $pubkeyid = openssl_pkey_get_public('file://'.$path);
+
+ $result = 1 === openssl_verify(
+ file_get_contents($file),
+ $signature,
+ $pubkeyid,
+ $this->algo
+ );
+
+ // PHP 8 automatically frees the key instance and deprecates the function
+ if (PHP_VERSION_ID < 80000) {
+ openssl_free_key($pubkeyid);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validates the downloaded phar file
+ *
+ * @param string $pharFile The temp phar file
+ * @param null|string $error Set by method on failure
+ *
+ * @return bool If the operation succeeded
+ */
+ protected function validatePhar($pharFile, &$error)
+ {
+ if (ini_get('phar.readonly')) {
+ return true;
+ }
+
+ try {
+ // Test the phar validity
+ $phar = new Phar($pharFile);
+ // Free the variable to unlock the file
+ unset($phar);
+ $result = true;
+
+ } catch (Exception $e) {
+ if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) {
+ throw $e;
+ }
+ $error = $e->getMessage();
+ $result = false;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns a string representation of the last json error
+ *
+ * @return string The error string or code
+ */
+ protected function getJsonError()
+ {
+ if (function_exists('json_last_error_msg')) {
+ return json_last_error_msg();
+ } else {
+ return 'json_last_error = '.json_last_error();
+ }
+ }
+
+ /**
+ * Cleans up resources at the end of the installation
+ *
+ * @param bool $result If the installation succeeded
+ */
+ protected function cleanUp($result)
+ {
+ if ($this->quiet) {
+ // Ensure output buffers are emptied
+ $errors = explode(PHP_EOL, (string) ob_get_clean());
+ }
+
+ if (!$result) {
+ // Output buffered errors
+ if ($this->quiet) {
+ $this->outputErrors($errors);
+ }
+ // Clean up stuff we created
+ $this->uninstall();
+ } elseif ($this->tmpCafile !== null) {
+ @unlink($this->tmpCafile);
+ }
+ }
+
+ /**
+ * Outputs unique errors when in quiet mode
+ *
+ */
+ protected function outputErrors(array $errors)
+ {
+ $shown = array();
+
+ foreach ($errors as $error) {
+ if ($error && !in_array($error, $shown)) {
+ out($error, 'error');
+ $shown[] = $error;
+ }
+ }
+ }
+
+ /**
+ * Uninstalls newly-created files and directories on failure
+ *
+ */
+ protected function uninstall()
+ {
+ foreach (array_reverse($this->installs) as $target) {
+ if (is_file($target)) {
+ @unlink($target);
+ } elseif (is_dir($target)) {
+ @rmdir($target);
+ }
+ }
+
+ if ($this->tmpFile !== null && file_exists($this->tmpFile)) {
+ @unlink($this->tmpFile);
+ }
+ }
+
+ public static function getPKDev()
+ {
+ return <<<PKDEV
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
+FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
+i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
+hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
+o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
+8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
+8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
+TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
+pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
+8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
+r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
+wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
+-----END PUBLIC KEY-----
+PKDEV;
+ }
+
+ public static function getPKTags()
+ {
+ return <<<PKTAGS
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
+MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
+vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
+bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
+mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
+noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
+nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
+rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
+RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
+tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
+TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
+RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
+-----END PUBLIC KEY-----
+PKTAGS;
+ }
+}
+
+class ErrorHandler
+{
+ public $message;
+ protected $active;
+
+ /**
+ * Handle php errors
+ *
+ * @param mixed $code The error code
+ * @param mixed $msg The error message
+ */
+ public function handleError($code, $msg)
+ {
+ if ($this->message) {
+ $this->message .= PHP_EOL;
+ }
+ $this->message .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
+ }
+
+ /**
+ * Starts error-handling if not already active
+ *
+ * Any message is cleared
+ */
+ public function start()
+ {
+ if (!$this->active) {
+ set_error_handler(array($this, 'handleError'));
+ $this->active = true;
+ }
+ $this->message = '';
+ }
+
+ /**
+ * Stops error-handling if active
+ *
+ * Any message is preserved until the next call to start()
+ */
+ public function stop()
+ {
+ if ($this->active) {
+ restore_error_handler();
+ $this->active = false;
+ }
+ }
+}
+
+class NoProxyPattern
+{
+ private $composerInNoProxy = false;
+ private $rulePorts = array();
+
+ public function __construct($pattern)
+ {
+ $rules = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY);
+
+ if ($matches = preg_grep('{getcomposer\.org(?::\d+)?}i', $rules)) {
+ $this->composerInNoProxy = true;
+
+ foreach ($matches as $match) {
+ if (strpos($match, ':') !== false) {
+ list(, $port) = explode(':', $match);
+ $this->rulePorts[] = (int) $port;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if NO_PROXY contains getcomposer.org
+ *
+ * @param string $url http(s)://getcomposer.org
+ *
+ * @return bool
+ */
+ public function test($url)
+ {
+ if (!$this->composerInNoProxy) {
+ return false;
+ }
+
+ if (empty($this->rulePorts)) {
+ return true;
+ }
+
+ if (strpos($url, 'http://') === 0) {
+ $port = 80;
+ } else {
+ $port = 443;
+ }
+
+ return in_array($port, $this->rulePorts);
+ }
+}
+
+class HttpClient {
+
+ /** @var null|string */
+ private static $caPath;
+
+ private $options = array('http' => array());
+ private $disableTls = false;
+
+ public function __construct($disableTls = false, $cafile = false)
+ {
+ $this->disableTls = $disableTls;
+ if ($this->disableTls === false) {
+ if (!empty($cafile) && !is_dir($cafile)) {
+ if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) {
+ throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.');
+ }
+ }
+ $options = $this->getTlsStreamContextDefaults($cafile);
+ $this->options = array_replace_recursive($this->options, $options);
+ }
+ }
+
+ public function get($url)
+ {
+ if (function_exists('http_clear_last_response_headers')) {
+ $http_response_header = http_clear_last_response_headers();
+ }
+
+ $context = $this->getStreamContext($url);
+ $result = file_get_contents($url, false, $context);
+
+ if ($result && extension_loaded('zlib')) {
+ if (function_exists('http_get_last_response_headers')) {
+ $http_response_header = http_get_last_response_headers();
+ }
+ $headers = $http_response_header;
+ $decode = false;
+ foreach ($headers as $header) {
+ if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
+ $decode = true;
+ continue;
+ } elseif (preg_match('{^HTTP/}i', $header)) {
+ $decode = false;
+ }
+ }
+
+ if ($decode) {
+ if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+ $result = zlib_decode($result);
+ } else {
+ // work around issue with gzuncompress & co that do not work with all gzip checksums
+ $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
+ }
+
+ if (!$result) {
+ throw new RuntimeException('Failed to decode zlib stream');
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ protected function getStreamContext($url)
+ {
+ if ($this->disableTls === false) {
+ if (PHP_VERSION_ID < 50600) {
+ $this->options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
+ }
+ }
+ // Keeping the above mostly isolated from the code copied from Composer.
+ return $this->getMergedStreamContext($url);
+ }
+
+ protected function getTlsStreamContextDefaults($cafile)
+ {
+ $ciphers = implode(':', array(
+ 'ECDHE-RSA-AES128-GCM-SHA256',
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
+ 'ECDHE-RSA-AES256-GCM-SHA384',
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
+ 'DHE-RSA-AES128-GCM-SHA256',
+ 'DHE-DSS-AES128-GCM-SHA256',
+ 'kEDH+AESGCM',
+ 'ECDHE-RSA-AES128-SHA256',
+ 'ECDHE-ECDSA-AES128-SHA256',
+ 'ECDHE-RSA-AES128-SHA',
+ 'ECDHE-ECDSA-AES128-SHA',
+ 'ECDHE-RSA-AES256-SHA384',
+ 'ECDHE-ECDSA-AES256-SHA384',
+ 'ECDHE-RSA-AES256-SHA',
+ 'ECDHE-ECDSA-AES256-SHA',
+ 'DHE-RSA-AES128-SHA256',
+ 'DHE-RSA-AES128-SHA',
+ 'DHE-DSS-AES128-SHA256',
+ 'DHE-RSA-AES256-SHA256',
+ 'DHE-DSS-AES256-SHA',
+ 'DHE-RSA-AES256-SHA',
+ 'AES128-GCM-SHA256',
+ 'AES256-GCM-SHA384',
+ 'AES128-SHA256',
+ 'AES256-SHA256',
+ 'AES128-SHA',
+ 'AES256-SHA',
+ 'AES',
+ 'CAMELLIA',
+ 'DES-CBC3-SHA',
+ '!aNULL',
+ '!eNULL',
+ '!EXPORT',
+ '!DES',
+ '!RC4',
+ '!MD5',
+ '!PSK',
+ '!aECDH',
+ '!EDH-DSS-DES-CBC3-SHA',
+ '!EDH-RSA-DES-CBC3-SHA',
+ '!KRB5-DES-CBC3-SHA',
+ ));
+
+ /**
+ * CN_match and SNI_server_name are only known once a URL is passed.
+ * They will be set in the getOptionsForUrl() method which receives a URL.
+ *
+ * cafile or capath can be overridden by passing in those options to constructor.
+ */
+ $options = array(
+ 'ssl' => array(
+ 'ciphers' => $ciphers,
+ 'verify_peer' => true,
+ 'verify_depth' => 7,
+ 'SNI_enabled' => true,
+ )
+ );
+
+ /**
+ * Attempt to find a local cafile or throw an exception.
+ * The user may go download one if this occurs.
+ */
+ if (!$cafile) {
+ $cafile = self::getSystemCaRootBundlePath();
+ }
+ if (is_dir($cafile)) {
+ $options['ssl']['capath'] = $cafile;
+ } elseif ($cafile) {
+ $options['ssl']['cafile'] = $cafile;
+ } else {
+ throw new RuntimeException('A valid cafile could not be located automatically.');
+ }
+
+ /**
+ * Disable TLS compression to prevent CRIME attacks where supported.
+ */
+ if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
+ $options['ssl']['disable_compression'] = true;
+ }
+
+ return $options;
+ }
+
+ /**
+ * function copied from Composer\Util\StreamContextFactory::initOptions
+ *
+ * Any changes should be applied there as well, or backported here.
+ *
+ * @param string $url URL the context is to be used for
+ * @return resource Default context
+ * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
+ */
+ protected function getMergedStreamContext($url)
+ {
+ $options = $this->options;
+
+ // Handle HTTP_PROXY/http_proxy on CLI only for security reasons
+ if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
+ $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
+ }
+
+ // Prefer CGI_HTTP_PROXY if available
+ if (!empty($_SERVER['CGI_HTTP_PROXY'])) {
+ $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']);
+ }
+
+ // Override with HTTPS proxy if present and URL is https
+ if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
+ $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
+ }
+
+ // Remove proxy if URL matches no_proxy directive
+ if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
+ $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']);
+ if ($pattern->test($url)) {
+ unset($proxy);
+ }
+ }
+
+ if (!empty($proxy)) {
+ $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
+ $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
+
+ if (isset($proxy['port'])) {
+ $proxyURL .= ":" . $proxy['port'];
+ } elseif (strpos($proxyURL, 'http://') === 0) {
+ $proxyURL .= ":80";
+ } elseif (strpos($proxyURL, 'https://') === 0) {
+ $proxyURL .= ":443";
+ }
+
+ // check for a secure proxy
+ if (strpos($proxyURL, 'https://') === 0) {
+ if (!extension_loaded('openssl')) {
+ throw new RuntimeException('You must enable the openssl extension to use a secure proxy.');
+ }
+ if (strpos($url, 'https://') === 0) {
+ throw new RuntimeException('PHP does not support https requests through a secure proxy.');
+ }
+ }
+
+ // http(s):// is not supported in proxy
+ $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);
+
+ $options['http'] = array(
+ 'proxy' => $proxyURL,
+ );
+
+ // add request_fulluri for http requests
+ if ('http' === parse_url($url, PHP_URL_SCHEME)) {
+ $options['http']['request_fulluri'] = true;
+ }
+
+ // handle proxy auth if present
+ if (isset($proxy['user'])) {
+ $auth = rawurldecode($proxy['user']);
+ if (isset($proxy['pass'])) {
+ $auth .= ':' . rawurldecode($proxy['pass']);
+ }
+ $auth = base64_encode($auth);
+
+ $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
+ }
+ }
+
+ if (isset($options['http']['header'])) {
+ $options['http']['header'] .= "Connection: close\r\n";
+ } else {
+ $options['http']['header'] = "Connection: close\r\n";
+ }
+ if (extension_loaded('zlib')) {
+ $options['http']['header'] .= "Accept-Encoding: gzip\r\n";
+ }
+ $options['http']['header'] .= "User-Agent: ".COMPOSER_INSTALLER."\r\n";
+ $options['http']['protocol_version'] = 1.1;
+ $options['http']['timeout'] = 600;
+
+ return stream_context_create($options);
+ }
+
+ /**
+ * This method was adapted from Sslurp.
+ * https://github.com/EvanDotPro/Sslurp
+ *
+ * (c) Evan Coury <me@evancoury.com>
+ *
+ * For the full copyright and license information, please see below:
+ *
+ * Copyright (c) 2013, Evan Coury
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ public static function getSystemCaRootBundlePath()
+ {
+ if (self::$caPath !== null) {
+ return self::$caPath;
+ }
+
+ // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
+ // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
+ $envCertFile = getenv('SSL_CERT_FILE');
+ if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) {
+ return self::$caPath = $envCertFile;
+ }
+
+ // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
+ // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
+ $envCertDir = getenv('SSL_CERT_DIR');
+ if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) {
+ return self::$caPath = $envCertDir;
+ }
+
+ $configured = ini_get('openssl.cafile');
+ if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) {
+ return self::$caPath = $configured;
+ }
+
+ $configured = ini_get('openssl.capath');
+ if ($configured && is_dir($configured) && is_readable($configured)) {
+ return self::$caPath = $configured;
+ }
+
+ $caBundlePaths = array(
+ '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
+ '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
+ '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
+ '/usr/ssl/certs/ca-bundle.crt', // Cygwin
+ '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
+ '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
+ '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
+ '/etc/ssl/cert.pem', // OpenBSD
+ '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
+ '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
+ '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
+ '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package
+ '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package
+ );
+
+ foreach ($caBundlePaths as $caBundle) {
+ if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) {
+ return self::$caPath = $caBundle;
+ }
+ }
+
+ foreach ($caBundlePaths as $caBundle) {
+ $caBundle = dirname($caBundle);
+ if (is_dir($caBundle) && glob($caBundle.'/*')) {
+ return self::$caPath = $caBundle;
+ }
+ }
+
+ return self::$caPath = false;
+ }
+
+ public static function getPackagedCaFile()
+ {
+ return <<<CACERT
+##
+## Bundle of CA Root Certificates for Let's Encrypt
+##
+## See https://letsencrypt.org/certificates/#root-certificates
+##
+## ISRG Root X1 (RSA 4096) expires Jun 04 11:04:38 2035 GMT
+## ISRG Root X2 (ECDSA P-384) expires Sep 17 16:00:00 2040 GMT
+##
+## Both these are self-signed CA root certificates
+##
+
+ISRG Root X1
+============
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----
+
+ISRG Root X2
+============
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+CACERT;
+ }
+}
--- /dev/null
+{
+ "require": {
+ "web-auth/webauthn-lib": "^3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "include/"
+ }
+ }
+}
--- /dev/null
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "e54b3dc12d564adf08ae4c70663f0154",
+ "packages": [
+ {
+ "name": "beberlei/assert",
+ "version": "v3.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/beberlei/assert.git",
+ "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd",
+ "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-simplexml": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "*",
+ "phpstan/phpstan": "*",
+ "phpunit/phpunit": ">=6.0.0",
+ "yoast/phpunit-polyfills": "^0.1.0"
+ },
+ "suggest": {
+ "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/Assert/functions.php"
+ ],
+ "psr-4": {
+ "Assert\\": "lib/Assert"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Richard Quadling",
+ "email": "rquadling@gmail.com",
+ "role": "Collaborator"
+ }
+ ],
+ "description": "Thin assertion library for input validation in business models.",
+ "keywords": [
+ "assert",
+ "assertion",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/beberlei/assert/issues",
+ "source": "https://github.com/beberlei/assert/tree/v3.3.3"
+ },
+ "time": "2024-07-15T13:18:35+00:00"
+ },
+ {
+ "name": "brick/math",
+ "version": "0.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "vimeo/psalm": "4.9.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "brick",
+ "math"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/brick/math",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-15T20:50:18+00:00"
+ },
+ {
+ "name": "fgrosse/phpasn1",
+ "version": "v2.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fgrosse/PHPASN1.git",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "~2.0",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "suggest": {
+ "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
+ "ext-curl": "For loading OID information from the web if they have not bee defined statically",
+ "ext-gmp": "GMP is the preferred extension for big integer calculations",
+ "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "FG\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Friedrich Große",
+ "email": "friedrich.grosse@gmail.com",
+ "homepage": "https://github.com/FGrosse",
+ "role": "Author"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/FGrosse/PHPASN1/contributors"
+ }
+ ],
+ "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
+ "homepage": "https://github.com/FGrosse/PHPASN1",
+ "keywords": [
+ "DER",
+ "asn.1",
+ "asn1",
+ "ber",
+ "binary",
+ "decoding",
+ "encoding",
+ "x.509",
+ "x.690",
+ "x509",
+ "x690"
+ ],
+ "support": {
+ "issues": "https://github.com/fgrosse/PHPASN1/issues",
+ "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
+ },
+ "abandoned": true,
+ "time": "2022-12-19T11:08:26+00:00"
+ },
+ {
+ "name": "league/uri",
+ "version": "6.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri.git",
+ "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39",
+ "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "league/uri-interfaces": "^2.3",
+ "php": "^8.1",
+ "psr/http-message": "^1.0.1"
+ },
+ "conflict": {
+ "league/uri-schemes": "^1.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^v3.9.5",
+ "nyholm/psr7": "^1.5.1",
+ "php-http/psr7-integration-tests": "^1.1.1",
+ "phpbench/phpbench": "^1.2.6",
+ "phpstan/phpstan": "^1.8.5",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.1.1",
+ "phpstan/phpstan-strict-rules": "^1.4.3",
+ "phpunit/phpunit": "^9.5.24",
+ "psr/http-factory": "^1.0.1"
+ },
+ "suggest": {
+ "ext-fileinfo": "Needed to create Data URI from a filepath",
+ "ext-intl": "Needed to improve host validation",
+ "league/uri-components": "Needed to easily manipulate URI objects",
+ "psr/http-factory": "Needed to use the URI factory"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "URI manipulation library",
+ "homepage": "https://uri.thephpleague.com",
+ "keywords": [
+ "data-uri",
+ "file-uri",
+ "ftp",
+ "hostname",
+ "http",
+ "https",
+ "middleware",
+ "parse_str",
+ "parse_url",
+ "psr-7",
+ "query-string",
+ "querystring",
+ "rfc3986",
+ "rfc3987",
+ "rfc6570",
+ "uri",
+ "uri-template",
+ "url",
+ "ws"
+ ],
+ "support": {
+ "docs": "https://uri.thephpleague.com",
+ "forum": "https://thephpleague.slack.com",
+ "issues": "https://github.com/thephpleague/uri/issues",
+ "source": "https://github.com/thephpleague/uri/tree/6.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2022-09-13T19:58:47+00:00"
+ },
+ {
+ "name": "league/uri-interfaces",
+ "version": "2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri-interfaces.git",
+ "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
+ "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.19",
+ "phpstan/phpstan": "^0.12.90",
+ "phpstan/phpstan-phpunit": "^0.12.19",
+ "phpstan/phpstan-strict-rules": "^0.12.9",
+ "phpunit/phpunit": "^8.5.15 || ^9.5"
+ },
+ "suggest": {
+ "ext-intl": "to use the IDNA feature",
+ "symfony/intl": "to use the IDNA feature via Symfony Polyfill"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "Common interface for URI representation",
+ "homepage": "http://github.com/thephpleague/uri-interfaces",
+ "keywords": [
+ "rfc3986",
+ "rfc3987",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/uri-interfaces/issues",
+ "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2021-06-28T04:27:21+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
+ },
+ "time": "2023-04-04T09:50:52+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/1.1.4"
+ },
+ "time": "2021-05-03T11:20:27+00:00"
+ },
+ {
+ "name": "ramsey/collection",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/collection.git",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "captainhook/plugin-composer": "^5.3",
+ "ergebnis/composer-normalize": "^2.45",
+ "fakerphp/faker": "^1.24",
+ "hamcrest/hamcrest-php": "^2.0",
+ "jangregor/phpstan-prophecy": "^2.1",
+ "mockery/mockery": "^1.6",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpspec/prophecy-phpunit": "^2.3",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5",
+ "ramsey/coding-standard": "^2.3",
+ "ramsey/conventional-commits": "^1.6",
+ "roave/security-advisories": "dev-latest"
+ },
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ },
+ "ramsey/conventional-commits": {
+ "configFile": "conventional-commits.json"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "A PHP library for representing and manipulating collections.",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/collection/issues",
+ "source": "https://github.com/ramsey/collection/tree/2.1.1"
+ },
+ "time": "2025-03-22T05:38:12+00:00"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "4.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
+ "php": "^8.0",
+ "ramsey/collection": "^1.2 || ^2.0"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.25",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "ergebnis/composer-normalize": "^2.47",
+ "mockery/mockery": "^1.6",
+ "paragonie/random-lib": "^2",
+ "php-mock/php-mock": "^2.6",
+ "php-mock/php-mock-mockery": "^1.5",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpbench/phpbench": "^1.2.14",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "slevomat/coding-standard": "^8.18",
+ "squizlabs/php_codesniffer": "^3.13"
+ },
+ "suggest": {
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/uuid/issues",
+ "source": "https://github.com/ramsey/uuid/tree/4.9.2"
+ },
+ "time": "2025-12-14T04:43:48+00:00"
+ },
+ {
+ "name": "spomky-labs/base64url",
+ "version": "v2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/base64url.git",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.11|^0.12",
+ "phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
+ "phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
+ "phpstan/phpstan-phpunit": "^0.11|^0.12",
+ "phpstan/phpstan-strict-rules": "^0.11|^0.12"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Base64Url\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky-Labs/base64url/contributors"
+ }
+ ],
+ "description": "Base 64 URL Safe Encoding/Decoding PHP Library",
+ "homepage": "https://github.com/Spomky-Labs/base64url",
+ "keywords": [
+ "base64",
+ "rfc4648",
+ "safe",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/Spomky-Labs/base64url/issues",
+ "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "time": "2020-11-03T09:10:25+00:00"
+ },
+ {
+ "name": "spomky-labs/cbor-php",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/cbor-php.git",
+ "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/28e2712cfc0b48fae661a48ffc6896d7abe83684",
+ "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.15|^0.9.0",
+ "ext-mbstring": "*",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ekino/phpstan-banned-code": "^1.0",
+ "ext-json": "*",
+ "infection/infection": "^0.18|^0.25",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-beberlei-assert": "^1.0",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.12",
+ "roave/security-advisories": "dev-latest",
+ "symplify/easy-coding-standard": "^10.0"
+ },
+ "suggest": {
+ "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags",
+ "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "CBOR\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors"
+ }
+ ],
+ "description": "CBOR Encoder/Decoder for PHP",
+ "keywords": [
+ "Concise Binary Object Representation",
+ "RFC7049",
+ "cbor"
+ ],
+ "support": {
+ "issues": "https://github.com/Spomky-Labs/cbor-php/issues",
+ "source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "time": "2021-12-13T12:46:26+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.36.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-04-10T16:19:22+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.4.51",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+ "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v5.4.51"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-26T15:53:37+00:00"
+ },
+ {
+ "name": "thecodingmachine/safe",
+ "version": "v1.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thecodingmachine/safe.git",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12",
+ "squizlabs/php_codesniffer": "^3.2",
+ "thecodingmachine/phpstan-strict-rules": "^0.12"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "deprecated/apc.php",
+ "deprecated/libevent.php",
+ "deprecated/mssql.php",
+ "deprecated/stats.php",
+ "lib/special_cases.php",
+ "generated/apache.php",
+ "generated/apcu.php",
+ "generated/array.php",
+ "generated/bzip2.php",
+ "generated/calendar.php",
+ "generated/classobj.php",
+ "generated/com.php",
+ "generated/cubrid.php",
+ "generated/curl.php",
+ "generated/datetime.php",
+ "generated/dir.php",
+ "generated/eio.php",
+ "generated/errorfunc.php",
+ "generated/exec.php",
+ "generated/fileinfo.php",
+ "generated/filesystem.php",
+ "generated/filter.php",
+ "generated/fpm.php",
+ "generated/ftp.php",
+ "generated/funchand.php",
+ "generated/gmp.php",
+ "generated/gnupg.php",
+ "generated/hash.php",
+ "generated/ibase.php",
+ "generated/ibmDb2.php",
+ "generated/iconv.php",
+ "generated/image.php",
+ "generated/imap.php",
+ "generated/info.php",
+ "generated/ingres-ii.php",
+ "generated/inotify.php",
+ "generated/json.php",
+ "generated/ldap.php",
+ "generated/libxml.php",
+ "generated/lzf.php",
+ "generated/mailparse.php",
+ "generated/mbstring.php",
+ "generated/misc.php",
+ "generated/msql.php",
+ "generated/mysql.php",
+ "generated/mysqli.php",
+ "generated/mysqlndMs.php",
+ "generated/mysqlndQc.php",
+ "generated/network.php",
+ "generated/oci8.php",
+ "generated/opcache.php",
+ "generated/openssl.php",
+ "generated/outcontrol.php",
+ "generated/password.php",
+ "generated/pcntl.php",
+ "generated/pcre.php",
+ "generated/pdf.php",
+ "generated/pgsql.php",
+ "generated/posix.php",
+ "generated/ps.php",
+ "generated/pspell.php",
+ "generated/readline.php",
+ "generated/rpminfo.php",
+ "generated/rrd.php",
+ "generated/sem.php",
+ "generated/session.php",
+ "generated/shmop.php",
+ "generated/simplexml.php",
+ "generated/sockets.php",
+ "generated/sodium.php",
+ "generated/solr.php",
+ "generated/spl.php",
+ "generated/sqlsrv.php",
+ "generated/ssdeep.php",
+ "generated/ssh2.php",
+ "generated/stream.php",
+ "generated/strings.php",
+ "generated/swoole.php",
+ "generated/uodbc.php",
+ "generated/uopz.php",
+ "generated/url.php",
+ "generated/var.php",
+ "generated/xdiff.php",
+ "generated/xml.php",
+ "generated/xmlrpc.php",
+ "generated/yaml.php",
+ "generated/yaz.php",
+ "generated/zip.php",
+ "generated/zlib.php"
+ ],
+ "psr-4": {
+ "Safe\\": [
+ "lib/",
+ "deprecated/",
+ "generated/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+ "support": {
+ "issues": "https://github.com/thecodingmachine/safe/issues",
+ "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
+ },
+ "time": "2020-10-28T17:51:34+00:00"
+ },
+ {
+ "name": "web-auth/cose-lib",
+ "version": "v3.3.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/cose-lib.git",
+ "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/efa6ec2ba4e840bc1316a493973c9916028afeeb",
+ "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "fgrosse/phpasn1": "^2.1",
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cose\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/cose/contributors"
+ }
+ ],
+ "description": "CBOR Object Signing and Encryption (COSE) For PHP",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "COSE",
+ "RFC8152"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/cose-lib/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "time": "2021-12-04T12:13:35+00:00"
+ },
+ {
+ "name": "web-auth/metadata-service",
+ "version": "v3.3.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/webauthn-metadata-service.git",
+ "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
+ "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "league/uri": "^6.0",
+ "php": ">=7.2",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
+ "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\MetadataService\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/metadata-service/contributors"
+ }
+ ],
+ "description": "Metadata Service for FIDO2/Webauthn",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "FIDO2",
+ "fido",
+ "webauthn"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-auth/webauthn-lib",
+ "time": "2021-11-21T11:14:31+00:00"
+ },
+ {
+ "name": "web-auth/webauthn-lib",
+ "version": "v3.3.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/webauthn-lib.git",
+ "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
+ "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "fgrosse/phpasn1": "^2.1",
+ "php": ">=7.2",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0",
+ "psr/log": "^1.1",
+ "ramsey/uuid": "^3.8|^4.0",
+ "spomky-labs/base64url": "^2.0",
+ "spomky-labs/cbor-php": "^1.0|^2.0",
+ "symfony/process": "^3.0|^4.0|^5.0",
+ "thecodingmachine/safe": "^1.1",
+ "web-auth/cose-lib": "self.version",
+ "web-auth/metadata-service": "self.version"
+ },
+ "suggest": {
+ "psr/log-implementation": "Recommended to receive logs from the library",
+ "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/webauthn-library/contributors"
+ }
+ ],
+ "description": "FIDO2/Webauthn Support For PHP",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "FIDO2",
+ "fido",
+ "webauthn"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "time": "2022-02-18T07:13:44+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.9.0"
+}
<?php
// includes/WebAuthnManager.php
-require_once __DIR__ . '/../vendor/autoload.php'; // Assure-toi d'avoir installé webauthn-lib via Composer
+require_once __DIR__ . '/../vendor/autoload.php';
use Webauthn\{
PublicKeyCredentialCreationOptions,
-- Table des utilisateurs
CREATE TABLE IF NOT EXISTS users (
user_id SERIAL PRIMARY KEY,
- username VARCHAR(255) NOT NULL UNIQUE,
- yubikey_id INT UNIQUE,
- FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id) ON DELETE SET NULL
+ username VARCHAR(255) NOT NULL UNIQUE
);
-- Table des YubiKeys
CREATE TABLE IF NOT EXISTS yubikeys (
yubikey_id SERIAL PRIMARY KEY,
- user_id INT UNIQUE,
+ user_id INT NOT NULL UNIQUE,
key_data TEXT NOT NULL,
public_key TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
);
+-- Ajout de la colonne yubikey_id à la table users
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'users' AND column_name = 'yubikey_id'
+ ) THEN
+ ALTER TABLE users ADD COLUMN yubikey_id INT UNIQUE;
+ END IF;
+END $$;
+
+-- Ajout de la contrainte de clé étrangère
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.table_constraints
+ WHERE constraint_name = 'users_yubikey_id_fkey'
+ ) THEN
+ ALTER TABLE users
+ ADD CONSTRAINT users_yubikey_id_fkey
+ FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id) ON DELETE SET NULL;
+ END IF;
+END $$;
+
-- Table des triplets
CREATE TABLE IF NOT EXISTS triplets (
triplet_id SERIAL PRIMARY KEY,
CREATE INDEX IF NOT EXISTS idx_triplets_user_id ON triplets(user_id);
CREATE INDEX IF NOT EXISTS idx_yubikeys_user_id ON yubikeys(user_id);
--- Commentaire pour documenter la base de données
-COMMENT ON TABLE users IS 'Stocke les informations d\'identification des utilisateurs et l\'association YubiKey';
-COMMENT ON TABLE yubikeys IS 'Stocke les informations d\'identification YubiKey et l\'association utilisateur';
-COMMENT ON TABLE triplets IS 'Stocke les triplets (ID unique, libellé, mot-clé, action) pour chaque utilisateur';
+-- Commentaires pour documenter la base de données
+COMMENT ON TABLE users IS 'Stocke les informations des utilisateurs';
+COMMENT ON TABLE yubikeys IS 'Stocke les informations des YubiKeys';
+COMMENT ON TABLE triplets IS 'Stocke les triplets pour chaque utilisateur';
--- /dev/null
+-- sql/migrate_db.sql
+-- Script de migration pour adapter la base de données existante
+
+-- Vérifier si la table yubikeys existe
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.tables
+ WHERE table_name = 'yubikeys'
+ ) THEN
+ -- Créer la table yubikeys
+ CREATE TABLE yubikeys (
+ yubikey_id SERIAL PRIMARY KEY,
+ user_id INT NOT NULL UNIQUE,
+ key_data TEXT NOT NULL,
+ public_key TEXT NOT NULL,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+ );
+
+ -- Mettre à jour la colonne yubikey_id dans users pour correspondre à yubikeys
+ UPDATE users SET yubikey_id = NULL WHERE yubikey_id IS NOT NULL;
+
+ RAISE NOTICE 'Table yubikeys créée avec succès';
+ ELSE
+ RAISE NOTICE 'Table yubikeys existe déjà';
+ END IF;
+END $$;
+
+-- Vérifier si la contrainte de clé étrangère existe
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.table_constraints
+ WHERE constraint_name = 'users_yubikey_id_fkey'
+ ) THEN
+ -- Ajouter la contrainte de clé étrangère
+ ALTER TABLE users
+ ADD CONSTRAINT users_yubikey_id_fkey
+ FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id) ON DELETE SET NULL;
+
+ RAISE NOTICE 'Contrainte de clé étrangère ajoutée';
+ ELSE
+ RAISE NOTICE 'Contrainte de clé étrangère existe déjà';
+ END IF;
+END $$;
+
+-- Vérifier si l'index existe
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_indexes
+ WHERE indexname = 'idx_yubikeys_user_id'
+ ) THEN
+ CREATE INDEX idx_yubikeys_user_id ON yubikeys(user_id);
+ RAISE NOTICE 'Index idx_yubikeys_user_id créé';
+ ELSE
+ RAISE NOTICE 'Index idx_yubikeys_user_id existe déjà';
+ END IF;
+END $$;
+
+-- Vérifier la structure de la table triplets
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'triplets' AND column_name = 'user_id'
+ ) THEN
+ ALTER TABLE triplets ADD COLUMN user_id INT;
+ ALTER TABLE triplets ADD CONSTRAINT triplets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
+ RAISE NOTICE 'Colonne user_id ajoutée à triplets';
+ ELSE
+ RAISE NOTICE 'Colonne user_id existe déjà dans triplets';
+ END IF;
+END $$;
--- /dev/null
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, $err);
+ } elseif (!headers_sent()) {
+ echo $err;
+ }
+ }
+ throw new RuntimeException($err);
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInite54b3dc12d564adf08ae4c70663f0154::getLoader();
--- /dev/null
+Copyright (c) 2011-2013, Benjamin Eberlei
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
--- /dev/null
+{
+ "name": "beberlei/assert",
+ "description": "Thin assertion library for input validation in business models.",
+ "authors": [
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Richard Quadling",
+ "email": "rquadling@gmail.com",
+ "role": "Collaborator"
+ }
+ ],
+ "license": "BSD-2-Clause",
+ "keywords": [
+ "assert",
+ "assertion",
+ "validation"
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "ext-simplexml": "*",
+ "ext-mbstring": "*",
+ "ext-ctype": "*",
+ "ext-json": "*"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "*",
+ "phpstan/phpstan": "*",
+ "phpunit/phpunit": ">=6.0.0",
+ "yoast/phpunit-polyfills": "^0.1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Assert\\": "lib/Assert"
+ },
+ "files": [
+ "lib/Assert/functions.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Assert\\Tests\\": "tests/Assert/Tests"
+ },
+ "files": [
+ "tests/Assert/Tests/Fixtures/functions.php"
+ ]
+ },
+ "scripts": {
+ "assert:generate-docs": "php bin/generate_method_docs.php",
+ "assert:cs-lint": "php-cs-fixer fix --diff -vvv --dry-run",
+ "assert:cs-fix": "php-cs-fixer fix . -vvv || true",
+ "assert:sa-code": "vendor/bin/phpstan analyse --configuration=phpstan-code.neon --no-progress --ansi -l 7 bin lib",
+ "assert:sa-tests": "vendor/bin/phpstan analyse --configuration=phpstan-tests.neon --no-progress --ansi -l 7 tests"
+ },
+ "suggest": {
+ "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+/**
+ * AssertionChain factory.
+ */
+abstract class Assert
+{
+ /** @var string */
+ protected static $lazyAssertionExceptionClass = LazyAssertionException::class;
+
+ /** @var string */
+ protected static $assertionClass = Assertion::class;
+
+ /**
+ * Start validation on a value, returns {@link AssertionChain}.
+ *
+ * The invocation of this method starts an assertion chain
+ * that is happening on the passed value.
+ *
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ *
+ * @example
+ *
+ * Assert::that($value)->notEmpty()->integer();
+ * Assert::that($value)->nullOr()->string()->startsWith("Foo");
+ *
+ * The assertion chain can be stateful, that means be careful when you reuse
+ * it. You should never pass around the chain.
+ */
+ public static function that($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+ {
+ $assertionChain = new AssertionChain($value, $defaultMessage, $defaultPropertyPath);
+
+ return $assertionChain->setAssertionClassName(static::$assertionClass);
+ }
+
+ /**
+ * Start validation on a set of values, returns {@link AssertionChain}.
+ *
+ * @param mixed $values
+ * @param string|callable|null $defaultMessage
+ */
+ public static function thatAll($values, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+ {
+ return static::that($values, $defaultMessage, $defaultPropertyPath)->all();
+ }
+
+ /**
+ * Start validation and allow NULL, returns {@link AssertionChain}.
+ *
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ */
+ public static function thatNullOr($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+ {
+ return static::that($value, $defaultMessage, $defaultPropertyPath)->nullOr();
+ }
+
+ /**
+ * Create a lazy assertion object.
+ */
+ public static function lazy(): LazyAssertion
+ {
+ $lazyAssertion = new LazyAssertion();
+
+ return $lazyAssertion
+ ->setAssertClass(\get_called_class())
+ ->setExceptionClass(static::$lazyAssertionExceptionClass);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+use ArrayAccess;
+use BadMethodCallException;
+use Countable;
+use DateTime;
+use ReflectionClass;
+use ReflectionException;
+use ResourceBundle;
+use SimpleXMLElement;
+use Throwable;
+use Traversable;
+
+/**
+ * Assert library.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ *
+ * @method static bool allAlnum(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values.
+ * @method static bool allBase64(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values.
+ * @method static bool allBetween(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit for all values.
+ * @method static bool allBetweenExclusive(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit for all values.
+ * @method static bool allBetweenLength(mixed[] $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths for all values.
+ * @method static bool allBoolean(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean for all values.
+ * @method static bool allChoice(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices for all values.
+ * @method static bool allChoicesNotEmpty(array[] $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content for all values.
+ * @method static bool allClassExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists for all values.
+ * @method static bool allContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars for all values.
+ * @method static bool allCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count for all values.
+ * @method static bool allDate(string[] $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format for all values.
+ * @method static bool allDefined(mixed[] $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values.
+ * @method static bool allDigit(mixed[] $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit for all values.
+ * @method static bool allDirectory(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists for all values.
+ * @method static bool allE164(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number for all values.
+ * @method static bool allEmail(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) for all values.
+ * @method static bool allEndsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars for all values.
+ * @method static bool allEq(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) for all values.
+ * @method static bool allEqArraySubset(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset for all values.
+ * @method static bool allExtensionLoaded(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded for all values.
+ * @method static bool allExtensionVersion(string[] $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed for all values.
+ * @method static bool allFalse(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False for all values.
+ * @method static bool allFile(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists for all values.
+ * @method static bool allFloat(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float for all values.
+ * @method static bool allGreaterOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit for all values.
+ * @method static bool allGreaterThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit for all values.
+ * @method static bool allImplementsInterface(mixed[] $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface for all values.
+ * @method static bool allInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() for all values.
+ * @method static bool allInteger(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer for all values.
+ * @method static bool allIntegerish(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish for all values.
+ * @method static bool allInterfaceExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists for all values.
+ * @method static bool allIp(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address for all values.
+ * @method static bool allIpv4(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values.
+ * @method static bool allIpv6(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address for all values.
+ * @method static bool allIsArray(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array for all values.
+ * @method static bool allIsArrayAccessible(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object for all values.
+ * @method static bool allIsCallable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable for all values.
+ * @method static bool allIsCountable(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable for all values.
+ * @method static bool allIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name for all values.
+ * @method static bool allIsJsonString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string for all values.
+ * @method static bool allIsObject(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object for all values.
+ * @method static bool allIsResource(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource for all values.
+ * @method static bool allIsTraversable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object for all values.
+ * @method static bool allKeyExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array for all values.
+ * @method static bool allKeyIsset(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() for all values.
+ * @method static bool allKeyNotExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array for all values.
+ * @method static bool allLength(mixed[] $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length for all values.
+ * @method static bool allLessOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit for all values.
+ * @method static bool allLessThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit for all values.
+ * @method static bool allMax(mixed[] $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit for all values.
+ * @method static bool allMaxCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements for all values.
+ * @method static bool allMaxLength(mixed[] $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars for all values.
+ * @method static bool allMethodExists(string[] $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object for all values.
+ * @method static bool allMin(mixed[] $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit for all values.
+ * @method static bool allMinCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements for all values.
+ * @method static bool allMinLength(mixed[] $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long for all values.
+ * @method static bool allNoContent(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty for all values.
+ * @method static bool allNotBlank(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank for all values.
+ * @method static bool allNotContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars for all values.
+ * @method static bool allNotEmpty(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty for all values.
+ * @method static bool allNotEmptyKey(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty for all values.
+ * @method static bool allNotEq(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) for all values.
+ * @method static bool allNotInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices for all values.
+ * @method static bool allNotIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name for all values.
+ * @method static bool allNotNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null for all values.
+ * @method static bool allNotRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex for all values.
+ * @method static bool allNotSame(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) for all values.
+ * @method static bool allNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is null for all values.
+ * @method static bool allNumeric(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric for all values.
+ * @method static bool allObjectOrClass(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists for all values.
+ * @method static bool allPhpVersion(string[] $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version for all values.
+ * @method static bool allPropertiesExist(mixed[] $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist for all values.
+ * @method static bool allPropertyExists(mixed[] $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists for all values.
+ * @method static bool allRange(mixed[] $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers for all values.
+ * @method static bool allReadable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable for all values.
+ * @method static bool allRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex for all values.
+ * @method static bool allSame(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) for all values.
+ * @method static bool allSatisfy(mixed[] $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback for all values.
+ * @method static bool allScalar(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar for all values.
+ * @method static bool allStartsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars for all values.
+ * @method static bool allString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string for all values.
+ * @method static bool allSubclassOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name for all values.
+ * @method static bool allTrue(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True for all values.
+ * @method static bool allUniqueValues(array[] $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) for all values.
+ * @method static bool allUrl(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL for all values.
+ * @method static bool allUuid(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID for all values.
+ * @method static bool allVersion(string[] $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions for all values.
+ * @method static bool allWriteable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable for all values.
+ * @method static bool nullOrAlnum(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric or that the value is null.
+ * @method static bool nullOrBase64(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null.
+ * @method static bool nullOrBetween(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit or that the value is null.
+ * @method static bool nullOrBetweenExclusive(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit or that the value is null.
+ * @method static bool nullOrBetweenLength(mixed|null $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths or that the value is null.
+ * @method static bool nullOrBoolean(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean or that the value is null.
+ * @method static bool nullOrChoice(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices or that the value is null.
+ * @method static bool nullOrChoicesNotEmpty(array|null $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content or that the value is null.
+ * @method static bool nullOrClassExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists or that the value is null.
+ * @method static bool nullOrContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars or that the value is null.
+ * @method static bool nullOrCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count or that the value is null.
+ * @method static bool nullOrDate(string|null $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format or that the value is null.
+ * @method static bool nullOrDefined(mixed|null $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null.
+ * @method static bool nullOrDigit(mixed|null $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit or that the value is null.
+ * @method static bool nullOrDirectory(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists or that the value is null.
+ * @method static bool nullOrE164(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number or that the value is null.
+ * @method static bool nullOrEmail(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) or that the value is null.
+ * @method static bool nullOrEndsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars or that the value is null.
+ * @method static bool nullOrEq(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) or that the value is null.
+ * @method static bool nullOrEqArraySubset(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset or that the value is null.
+ * @method static bool nullOrExtensionLoaded(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded or that the value is null.
+ * @method static bool nullOrExtensionVersion(string|null $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed or that the value is null.
+ * @method static bool nullOrFalse(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False or that the value is null.
+ * @method static bool nullOrFile(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists or that the value is null.
+ * @method static bool nullOrFloat(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float or that the value is null.
+ * @method static bool nullOrGreaterOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit or that the value is null.
+ * @method static bool nullOrGreaterThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit or that the value is null.
+ * @method static bool nullOrImplementsInterface(mixed|null $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface or that the value is null.
+ * @method static bool nullOrInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() or that the value is null.
+ * @method static bool nullOrInteger(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer or that the value is null.
+ * @method static bool nullOrIntegerish(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish or that the value is null.
+ * @method static bool nullOrInterfaceExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists or that the value is null.
+ * @method static bool nullOrIp(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address or that the value is null.
+ * @method static bool nullOrIpv4(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address or that the value is null.
+ * @method static bool nullOrIpv6(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address or that the value is null.
+ * @method static bool nullOrIsArray(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or that the value is null.
+ * @method static bool nullOrIsArrayAccessible(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object or that the value is null.
+ * @method static bool nullOrIsCallable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable or that the value is null.
+ * @method static bool nullOrIsCountable(array|Countable|ResourceBundle|SimpleXMLElement|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable or that the value is null.
+ * @method static bool nullOrIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name or that the value is null.
+ * @method static bool nullOrIsJsonString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string or that the value is null.
+ * @method static bool nullOrIsObject(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object or that the value is null.
+ * @method static bool nullOrIsResource(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource or that the value is null.
+ * @method static bool nullOrIsTraversable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object or that the value is null.
+ * @method static bool nullOrKeyExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array or that the value is null.
+ * @method static bool nullOrKeyIsset(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() or that the value is null.
+ * @method static bool nullOrKeyNotExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array or that the value is null.
+ * @method static bool nullOrLength(mixed|null $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length or that the value is null.
+ * @method static bool nullOrLessOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit or that the value is null.
+ * @method static bool nullOrLessThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit or that the value is null.
+ * @method static bool nullOrMax(mixed|null $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit or that the value is null.
+ * @method static bool nullOrMaxCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements or that the value is null.
+ * @method static bool nullOrMaxLength(mixed|null $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars or that the value is null.
+ * @method static bool nullOrMethodExists(string|null $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object or that the value is null.
+ * @method static bool nullOrMin(mixed|null $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit or that the value is null.
+ * @method static bool nullOrMinCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements or that the value is null.
+ * @method static bool nullOrMinLength(mixed|null $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long or that the value is null.
+ * @method static bool nullOrNoContent(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty or that the value is null.
+ * @method static bool nullOrNotBlank(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank or that the value is null.
+ * @method static bool nullOrNotContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars or that the value is null.
+ * @method static bool nullOrNotEmpty(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty or that the value is null.
+ * @method static bool nullOrNotEmptyKey(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty or that the value is null.
+ * @method static bool nullOrNotEq(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) or that the value is null.
+ * @method static bool nullOrNotInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices or that the value is null.
+ * @method static bool nullOrNotIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name or that the value is null.
+ * @method static bool nullOrNotNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null or that the value is null.
+ * @method static bool nullOrNotRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex or that the value is null.
+ * @method static bool nullOrNotSame(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) or that the value is null.
+ * @method static bool nullOrNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is null or that the value is null.
+ * @method static bool nullOrNumeric(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric or that the value is null.
+ * @method static bool nullOrObjectOrClass(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists or that the value is null.
+ * @method static bool nullOrPhpVersion(string|null $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version or that the value is null.
+ * @method static bool nullOrPropertiesExist(mixed|null $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist or that the value is null.
+ * @method static bool nullOrPropertyExists(mixed|null $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists or that the value is null.
+ * @method static bool nullOrRange(mixed|null $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers or that the value is null.
+ * @method static bool nullOrReadable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable or that the value is null.
+ * @method static bool nullOrRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex or that the value is null.
+ * @method static bool nullOrSame(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) or that the value is null.
+ * @method static bool nullOrSatisfy(mixed|null $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback or that the value is null.
+ * @method static bool nullOrScalar(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar or that the value is null.
+ * @method static bool nullOrStartsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars or that the value is null.
+ * @method static bool nullOrString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string or that the value is null.
+ * @method static bool nullOrSubclassOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name or that the value is null.
+ * @method static bool nullOrTrue(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True or that the value is null.
+ * @method static bool nullOrUniqueValues(array|null $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) or that the value is null.
+ * @method static bool nullOrUrl(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL or that the value is null.
+ * @method static bool nullOrUuid(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID or that the value is null.
+ * @method static bool nullOrVersion(string|null $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions or that the value is null.
+ * @method static bool nullOrWriteable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable or that the value is null.
+ */
+class Assertion
+{
+ const INVALID_FLOAT = 9;
+ const INVALID_INTEGER = 10;
+ const INVALID_DIGIT = 11;
+ const INVALID_INTEGERISH = 12;
+ const INVALID_BOOLEAN = 13;
+ const VALUE_EMPTY = 14;
+ const VALUE_NULL = 15;
+ const VALUE_NOT_NULL = 25;
+ const INVALID_STRING = 16;
+ const INVALID_REGEX = 17;
+ const INVALID_MIN_LENGTH = 18;
+ const INVALID_MAX_LENGTH = 19;
+ const INVALID_STRING_START = 20;
+ const INVALID_STRING_CONTAINS = 21;
+ const INVALID_CHOICE = 22;
+ const INVALID_NUMERIC = 23;
+ const INVALID_ARRAY = 24;
+ const INVALID_KEY_EXISTS = 26;
+ const INVALID_NOT_BLANK = 27;
+ const INVALID_INSTANCE_OF = 28;
+ const INVALID_SUBCLASS_OF = 29;
+ const INVALID_RANGE = 30;
+ const INVALID_ALNUM = 31;
+ const INVALID_TRUE = 32;
+ const INVALID_EQ = 33;
+ const INVALID_SAME = 34;
+ const INVALID_MIN = 35;
+ const INVALID_MAX = 36;
+ const INVALID_LENGTH = 37;
+ const INVALID_FALSE = 38;
+ const INVALID_STRING_END = 39;
+ const INVALID_UUID = 40;
+ const INVALID_COUNT = 41;
+ const INVALID_NOT_EQ = 42;
+ const INVALID_NOT_SAME = 43;
+ const INVALID_TRAVERSABLE = 44;
+ const INVALID_ARRAY_ACCESSIBLE = 45;
+ const INVALID_KEY_ISSET = 46;
+ const INVALID_VALUE_IN_ARRAY = 47;
+ const INVALID_E164 = 48;
+ const INVALID_BASE64 = 49;
+ const INVALID_NOT_REGEX = 50;
+ const INVALID_DIRECTORY = 101;
+ const INVALID_FILE = 102;
+ const INVALID_READABLE = 103;
+ const INVALID_WRITEABLE = 104;
+ const INVALID_CLASS = 105;
+ const INVALID_INTERFACE = 106;
+ const INVALID_FILE_NOT_EXISTS = 107;
+ const INVALID_EMAIL = 201;
+ const INTERFACE_NOT_IMPLEMENTED = 202;
+ const INVALID_URL = 203;
+ const INVALID_NOT_INSTANCE_OF = 204;
+ const VALUE_NOT_EMPTY = 205;
+ const INVALID_JSON_STRING = 206;
+ const INVALID_OBJECT = 207;
+ const INVALID_METHOD = 208;
+ const INVALID_SCALAR = 209;
+ const INVALID_LESS = 210;
+ const INVALID_LESS_OR_EQUAL = 211;
+ const INVALID_GREATER = 212;
+ const INVALID_GREATER_OR_EQUAL = 213;
+ const INVALID_DATE = 214;
+ const INVALID_CALLABLE = 215;
+ const INVALID_KEY_NOT_EXISTS = 216;
+ const INVALID_SATISFY = 217;
+ const INVALID_IP = 218;
+ const INVALID_BETWEEN = 219;
+ const INVALID_BETWEEN_EXCLUSIVE = 220;
+ const INVALID_EXTENSION = 222;
+ const INVALID_CONSTANT = 221;
+ const INVALID_VERSION = 223;
+ const INVALID_PROPERTY = 224;
+ const INVALID_RESOURCE = 225;
+ const INVALID_COUNTABLE = 226;
+ const INVALID_MIN_COUNT = 227;
+ const INVALID_MAX_COUNT = 228;
+ const INVALID_STRING_NOT_CONTAINS = 229;
+ const INVALID_UNIQUE_VALUES = 230;
+
+ /**
+ * Exception to throw when an assertion failed.
+ *
+ * @var string
+ */
+ protected static $exceptionClass = InvalidArgumentException::class;
+
+ /**
+ * Assert that two values are equal (using ==).
+ *
+ * @param mixed $value
+ * @param mixed $value2
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function eq($value, $value2, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value != $value2) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" does not equal expected value "%s".'),
+ static::stringify($value),
+ static::stringify($value2)
+ );
+
+ throw static::createException($value, $message, static::INVALID_EQ, $propertyPath, ['expected' => $value2]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the array contains the subset.
+ *
+ * @param mixed $value
+ * @param mixed $value2
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function eqArraySubset($value, $value2, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isArray($value, $message, $propertyPath);
+ static::isArray($value2, $message, $propertyPath);
+
+ $patched = \array_replace_recursive($value, $value2);
+ static::eq($patched, $value, $message, $propertyPath);
+
+ return true;
+ }
+
+ /**
+ * Assert that two values are the same (using ===).
+ *
+ * @param mixed $value
+ * @param mixed $value2
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-template ExpectedType
+ * @psalm-param ExpectedType $value2
+ * @psalm-assert =ExpectedType $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function same($value, $value2, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value !== $value2) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not the same as expected value "%s".'),
+ static::stringify($value),
+ static::stringify($value2)
+ );
+
+ throw static::createException($value, $message, static::INVALID_SAME, $propertyPath, ['expected' => $value2]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that two values are not equal (using ==).
+ *
+ * @param mixed $value1
+ * @param mixed $value2
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notEq($value1, $value2, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value1 == $value2) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was not expected to be equal to value "%s".'),
+ static::stringify($value1),
+ static::stringify($value2)
+ );
+ throw static::createException($value1, $message, static::INVALID_NOT_EQ, $propertyPath, ['expected' => $value2]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that two values are not the same (using ===).
+ *
+ * @param mixed $value1
+ * @param mixed $value2
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-template ExpectedType
+ * @psalm-param ExpectedType $value2
+ * @psalm-assert !=ExpectedType $value1
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notSame($value1, $value2, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value1 === $value2) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was not expected to be the same as value "%s".'),
+ static::stringify($value1),
+ static::stringify($value2)
+ );
+ throw static::createException($value1, $message, static::INVALID_NOT_SAME, $propertyPath, ['expected' => $value2]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is not in array of choices.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notInArray($value, array $choices, $message = null, ?string $propertyPath = null): bool
+ {
+ if (true === \in_array($value, $choices)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was not expected to be an element of the values: %s'),
+ static::stringify($value),
+ static::stringify($choices)
+ );
+ throw static::createException($value, $message, static::INVALID_VALUE_IN_ARRAY, $propertyPath, ['choices' => $choices]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a php integer.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert int $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function integer($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_int($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an integer.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_INTEGER, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a php float.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert float $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function float($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_float($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a float.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_FLOAT, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates if an integer or integerish is a digit.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =numeric $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function digit($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\ctype_digit((string)$value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a digit.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_DIGIT, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a php integer'ish.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function integerish($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (
+ \is_resource($value) ||
+ \is_object($value) ||
+ \is_bool($value) ||
+ \is_null($value) ||
+ \is_array($value) ||
+ (\is_string($value) && '' == $value) ||
+ (
+ \strval(\intval($value)) !== \strval($value) &&
+ \strval(\intval($value)) !== \strval(\ltrim($value, '0')) &&
+ '' !== \strval(\intval($value)) &&
+ '' !== \strval(\ltrim($value, '0'))
+ )
+ ) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an integer or a number castable to integer.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_INTEGERISH, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is php boolean.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert bool $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function boolean($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_bool($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a boolean.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_BOOLEAN, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a PHP scalar.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert scalar $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function scalar($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_scalar($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a scalar.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_SCALAR, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is not empty.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert !empty $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notEmpty($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (empty($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is empty, but non empty value was expected.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::VALUE_EMPTY, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is empty.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert empty $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function noContent($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!empty($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not empty, but empty value was expected.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::VALUE_NOT_EMPTY, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is null.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert null $value
+ *
+ * @return bool
+ */
+ public static function null($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (null !== $value) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not null, but null value was expected.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::VALUE_NOT_NULL, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is not null.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert !null $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notNull($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (null === $value) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is null, but non null value was expected.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::VALUE_NULL, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a string.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function string($value, $message = null, ?string $propertyPath = null)
+ {
+ if (!\is_string($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" expected to be string, type %s given.'),
+ static::stringify($value),
+ \gettype($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_STRING, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value matches a regex.
+ *
+ * @param mixed $value
+ * @param string $pattern
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function regex($value, $pattern, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (!\preg_match($pattern, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" does not match expression.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_REGEX, $propertyPath, ['pattern' => $pattern]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value does not match a regex.
+ *
+ * @param mixed $value
+ * @param string $pattern
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert !=string $value
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notRegex($value, $pattern, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (\preg_match($pattern, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" matches expression.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_NOT_REGEX, $propertyPath, ['pattern' => $pattern]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string has a given length.
+ *
+ * @param mixed $value
+ * @param int $length
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function length($value, $length, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (\mb_strlen($value, $encoding) !== $length) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" has to be %d exactly characters long, but length is %d.'),
+ static::stringify($value),
+ $length,
+ \mb_strlen($value, $encoding)
+ );
+
+ throw static::createException($value, $message, static::INVALID_LENGTH, $propertyPath, ['length' => $length, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a string is at least $minLength chars long.
+ *
+ * @param mixed $value
+ * @param int $minLength
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function minLength($value, $minLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (\mb_strlen($value, $encoding) < $minLength) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is too short, it should have at least %d characters, but only has %d characters.'),
+ static::stringify($value),
+ $minLength,
+ \mb_strlen($value, $encoding)
+ );
+
+ throw static::createException($value, $message, static::INVALID_MIN_LENGTH, $propertyPath, ['min_length' => $minLength, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string value is not longer than $maxLength chars.
+ *
+ * @param mixed $value
+ * @param int $maxLength
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function maxLength($value, $maxLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (\mb_strlen($value, $encoding) > $maxLength) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is too long, it should have no more than %d characters, but has %d characters.'),
+ static::stringify($value),
+ $maxLength,
+ \mb_strlen($value, $encoding)
+ );
+
+ throw static::createException($value, $message, static::INVALID_MAX_LENGTH, $propertyPath, ['max_length' => $maxLength, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string length is between min and max lengths.
+ *
+ * @param mixed $value
+ * @param int $minLength
+ * @param int $maxLength
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function betweenLength($value, $minLength, $maxLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($value, $message, $propertyPath);
+ static::minLength($value, $minLength, $message, $propertyPath, $encoding);
+ static::maxLength($value, $maxLength, $message, $propertyPath, $encoding);
+
+ return true;
+ }
+
+ /**
+ * Assert that string starts with a sequence of chars.
+ *
+ * @param mixed $string
+ * @param string $needle
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $string
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function startsWith($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($string, $message, $propertyPath);
+
+ if (0 !== \mb_strpos($string, $needle, 0, $encoding)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" does not start with "%s".'),
+ static::stringify($string),
+ static::stringify($needle)
+ );
+
+ throw static::createException($string, $message, static::INVALID_STRING_START, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string ends with a sequence of chars.
+ *
+ * @param mixed $string
+ * @param string $needle
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $string
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function endsWith($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($string, $message, $propertyPath);
+
+ $stringPosition = \mb_strlen($string, $encoding) - \mb_strlen($needle, $encoding);
+
+ if (\mb_strripos($string, $needle, 0, $encoding) !== $stringPosition) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" does not end with "%s".'),
+ static::stringify($string),
+ static::stringify($needle)
+ );
+
+ throw static::createException($string, $message, static::INVALID_STRING_END, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string contains a sequence of chars.
+ *
+ * @param mixed $string
+ * @param string $needle
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $string
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function contains($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($string, $message, $propertyPath);
+
+ if (false === \mb_strpos($string, $needle, 0, $encoding)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" does not contain "%s".'),
+ static::stringify($string),
+ static::stringify($needle)
+ );
+
+ throw static::createException($string, $message, static::INVALID_STRING_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that string does not contains a sequence of chars.
+ *
+ * @param mixed $string
+ * @param string $needle
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ * @param string $encoding
+ *
+ * @psalm-assert =string $string
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notContains($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool
+ {
+ static::string($string, $message, $propertyPath);
+
+ if (false !== \mb_strpos($string, $needle, 0, $encoding)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" contains "%s".'),
+ static::stringify($string),
+ static::stringify($needle)
+ );
+
+ throw static::createException($string, $message, static::INVALID_STRING_NOT_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is in array of choices.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function choice($value, array $choices, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\in_array($value, $choices, true)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an element of the valid values: %s'),
+ static::stringify($value),
+ \implode(', ', \array_map([\get_called_class(), 'stringify'], $choices))
+ );
+
+ throw static::createException($value, $message, static::INVALID_CHOICE, $propertyPath, ['choices' => $choices]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is in array of choices.
+ *
+ * This is an alias of {@see choice()}.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function inArray($value, array $choices, $message = null, ?string $propertyPath = null): bool
+ {
+ return static::choice($value, $choices, $message, $propertyPath);
+ }
+
+ /**
+ * Assert that value is numeric.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert numeric $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function numeric($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_numeric($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not numeric.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_NUMERIC, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is a resource.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert resource $value
+ *
+ * @return bool
+ */
+ public static function isResource($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_resource($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a resource.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_RESOURCE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an array.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert array $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isArray($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_array($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an array.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_ARRAY, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an array or a traversable object.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert iterable $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isTraversable($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_array($value) && !$value instanceof Traversable) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Traversable.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_TRAVERSABLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an array or an array-accessible object.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isArrayAccessible($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_array($value) && !$value instanceof ArrayAccess) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an array and does not implement ArrayAccess.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_ARRAY_ACCESSIBLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is countable.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert countable $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isCountable($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (\function_exists('is_countable')) {
+ $assert = \is_countable($value);
+ } else {
+ $assert = \is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXMLElement;
+ }
+
+ if (!$assert) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Countable.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_COUNTABLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that key exists in an array.
+ *
+ * @param mixed $value
+ * @param string|int $key
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function keyExists($value, $key, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isArray($value, $message, $propertyPath);
+
+ if (!\array_key_exists($key, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Array does not contain an element with key "%s"'),
+ static::stringify($key)
+ );
+
+ throw static::createException($value, $message, static::INVALID_KEY_EXISTS, $propertyPath, ['key' => $key]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that key does not exist in an array.
+ *
+ * @param mixed $value
+ * @param string|int $key
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function keyNotExists($value, $key, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isArray($value, $message, $propertyPath);
+
+ if (\array_key_exists($key, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Array contains an element with key "%s"'),
+ static::stringify($key)
+ );
+
+ throw static::createException($value, $message, static::INVALID_KEY_NOT_EXISTS, $propertyPath, ['key' => $key]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that values in array are unique (using strict equality).
+ *
+ * @param mixed[] $values
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function uniqueValues(array $values, $message = null, ?string $propertyPath = null): bool
+ {
+ foreach ($values as $key => $value) {
+ if (\array_search($value, $values, true) !== $key) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" occurs more than once in array'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_UNIQUE_VALUES, $propertyPath, ['value' => $value]);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that key exists in an array/array-accessible object using isset().
+ *
+ * @param mixed $value
+ * @param string|int $key
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function keyIsset($value, $key, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isArrayAccessible($value, $message, $propertyPath);
+
+ if (!isset($value[$key])) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'The element with key "%s" was not found'),
+ static::stringify($key)
+ );
+
+ throw static::createException($value, $message, static::INVALID_KEY_ISSET, $propertyPath, ['key' => $key]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that key exists in an array/array-accessible object and its value is not empty.
+ *
+ * @param mixed $value
+ * @param string|int $key
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notEmptyKey($value, $key, $message = null, ?string $propertyPath = null): bool
+ {
+ static::keyIsset($value, $key, $message, $propertyPath);
+ static::notEmpty($value[$key], $message, $propertyPath);
+
+ return true;
+ }
+
+ /**
+ * Assert that value is not blank.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notBlank($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (false === $value || (empty($value) && '0' != $value) || (\is_string($value) && '' === \trim($value))) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is blank, but was expected to contain a value.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_NOT_BLANK, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is instance of given class-name.
+ *
+ * @param mixed $value
+ * @param string $className
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-template ExpectedType of object
+ * @psalm-param class-string<ExpectedType> $className
+ * @psalm-assert ExpectedType $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isInstanceOf($value, $className, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!($value instanceof $className)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" was expected to be instanceof of "%s" but is not.'),
+ static::stringify($value),
+ $className
+ );
+
+ throw static::createException($value, $message, static::INVALID_INSTANCE_OF, $propertyPath, ['class' => $className]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is not instance of given class-name.
+ *
+ * @param mixed $value
+ * @param string $className
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-template ExpectedType of object
+ * @psalm-param class-string<ExpectedType> $className
+ * @psalm-assert !ExpectedType $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function notIsInstanceOf($value, $className, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value instanceof $className) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" was not expected to be instanceof of "%s".'),
+ static::stringify($value),
+ $className
+ );
+
+ throw static::createException($value, $message, static::INVALID_NOT_INSTANCE_OF, $propertyPath, ['class' => $className]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is subclass of given class-name.
+ *
+ * @param mixed $value
+ * @param string $className
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function subclassOf($value, $className, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_subclass_of($value, $className)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" was expected to be subclass of "%s".'),
+ static::stringify($value),
+ $className
+ );
+
+ throw static::createException($value, $message, static::INVALID_SUBCLASS_OF, $propertyPath, ['class' => $className]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is in range of numbers.
+ *
+ * @param mixed $value
+ * @param mixed $minValue
+ * @param mixed $maxValue
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =numeric $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function range($value, $minValue, $maxValue, $message = null, ?string $propertyPath = null): bool
+ {
+ static::numeric($value, $message, $propertyPath);
+
+ if ($value < $minValue || $value > $maxValue) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Number "%s" was expected to be at least "%d" and at most "%d".'),
+ static::stringify($value),
+ static::stringify($minValue),
+ static::stringify($maxValue)
+ );
+
+ throw static::createException($value, $message, static::INVALID_RANGE, $propertyPath, ['min' => $minValue, 'max' => $maxValue]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a value is at least as big as a given limit.
+ *
+ * @param mixed $value
+ * @param mixed $minValue
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =numeric $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function min($value, $minValue, $message = null, ?string $propertyPath = null): bool
+ {
+ static::numeric($value, $message, $propertyPath);
+
+ if ($value < $minValue) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Number "%s" was expected to be at least "%s".'),
+ static::stringify($value),
+ static::stringify($minValue)
+ );
+
+ throw static::createException($value, $message, static::INVALID_MIN, $propertyPath, ['min' => $minValue]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a number is smaller as a given limit.
+ *
+ * @param mixed $value
+ * @param mixed $maxValue
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =numeric $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function max($value, $maxValue, $message = null, ?string $propertyPath = null): bool
+ {
+ static::numeric($value, $message, $propertyPath);
+
+ if ($value > $maxValue) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Number "%s" was expected to be at most "%s".'),
+ static::stringify($value),
+ static::stringify($maxValue)
+ );
+
+ throw static::createException($value, $message, static::INVALID_MAX, $propertyPath, ['max' => $maxValue]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a file exists.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function file($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+ static::notEmpty($value, $message, $propertyPath);
+
+ if (!\is_file($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'File "%s" was expected to exist.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_FILE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a directory exists.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function directory($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (!\is_dir($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Path "%s" was expected to be a directory.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_DIRECTORY, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is something readable.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function readable($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (!\is_readable($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Path "%s" was expected to be readable.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_READABLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is something writeable.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function writeable($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (!\is_writable($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Path "%s" was expected to be writeable.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_WRITEABLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL).
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function email($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ if (!\filter_var($value, FILTER_VALIDATE_EMAIL)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was expected to be a valid e-mail address.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_EMAIL, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an URL.
+ *
+ * This code snipped was taken from the Symfony project and modified to the special demands of this method.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ *
+ * @see https://github.com/symfony/Validator/blob/master/Constraints/UrlValidator.php
+ * @see https://github.com/symfony/Validator/blob/master/Constraints/Url.php
+ */
+ public static function url($value, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+
+ $protocols = ['http', 'https'];
+
+ $pattern = '~^
+ (%s):// # protocol
+ (([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth
+ (
+ ([\pL\pN\pS\-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
+ | # or
+ \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address
+ | # or
+ \[
+ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
+ \] # an IPv6 address
+ )
+ (:[0-9]+)? # a port (optional)
+ (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path
+ (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional)
+ (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional)
+ $~ixu';
+
+ $pattern = \sprintf($pattern, \implode('|', $protocols));
+
+ if (!\preg_match($pattern, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was expected to be a valid URL starting with http or https'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_URL, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is alphanumeric.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function alnum($value, $message = null, ?string $propertyPath = null): bool
+ {
+ try {
+ static::regex($value, '(^([a-zA-Z]{1}[a-zA-Z0-9]*)$)', $message, $propertyPath);
+ } catch (Throwable $e) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not alphanumeric, starting with letters and containing only letters and numbers.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_ALNUM, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is boolean True.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert true $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function true($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (true !== $value) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not TRUE.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_TRUE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is boolean False.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert false $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function false($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (false !== $value) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not FALSE.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_FALSE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the class exists.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert class-string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function classExists($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\class_exists($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" does not exist.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_CLASS, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the interface exists.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert class-string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function interfaceExists($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\interface_exists($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Interface "%s" does not exist.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_INTERFACE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the class implements the interface.
+ *
+ * @param mixed $class
+ * @param string $interfaceName
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function implementsInterface($class, $interfaceName, $message = null, ?string $propertyPath = null): bool
+ {
+ try {
+ $reflection = new ReflectionClass($class);
+ if (!$reflection->implementsInterface($interfaceName)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" does not implement interface "%s".'),
+ static::stringify($class),
+ static::stringify($interfaceName)
+ );
+
+ throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]);
+ }
+ } catch (ReflectionException $e) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" failed reflection.'),
+ static::stringify($class)
+ );
+ throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the given string is a valid json string.
+ *
+ * NOTICE:
+ * Since this does a json_decode to determine its validity
+ * you probably should consider, when using the variable
+ * content afterwards, just to decode and check for yourself instead
+ * of using this assertion.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert =string $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isJsonString($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (null === \json_decode($value) && JSON_ERROR_NONE !== \json_last_error()) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a valid JSON string.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_JSON_STRING, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the given string is a valid UUID.
+ *
+ * Uses code from {@link https://github.com/ramsey/uuid} that is MIT licensed.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function uuid($value, $message = null, ?string $propertyPath = null): bool
+ {
+ $value = \str_replace(['urn:', 'uuid:', '{', '}'], '', $value);
+
+ if ('00000000-0000-0000-0000-000000000000' === $value) {
+ return true;
+ }
+
+ if (!\preg_match('/^[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}$/', $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a valid UUID.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_UUID, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the given string is a valid E164 Phone Number.
+ *
+ * @see https://en.wikipedia.org/wiki/E.164
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function e164($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\preg_match('/^\+?[1-9]\d{1,14}$/', $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" is not a valid E164.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_E164, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the count of countable is equal to count.
+ *
+ * @param array|Countable|ResourceBundle|SimpleXMLElement $countable
+ * @param int $count
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function count($countable, $count, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($count !== \count($countable)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'List does not contain exactly %d elements (%d given).'),
+ static::stringify($count),
+ static::stringify(\count($countable))
+ );
+
+ throw static::createException($countable, $message, static::INVALID_COUNT, $propertyPath, ['count' => $count]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the countable have at least $count elements.
+ *
+ * @param array|Countable|ResourceBundle|SimpleXMLElement $countable
+ * @param int $count
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function minCount($countable, $count, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($count > \count($countable)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'List should have at least %d elements, but has %d elements.'),
+ static::stringify($count),
+ static::stringify(\count($countable))
+ );
+
+ throw static::createException($countable, $message, static::INVALID_MIN_COUNT, $propertyPath, ['count' => $count]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the countable have at most $count elements.
+ *
+ * @param array|Countable|ResourceBundle|SimpleXMLElement $countable
+ * @param int $count
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function maxCount($countable, $count, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($count < \count($countable)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'List should have at most %d elements, but has %d elements.'),
+ static::stringify($count),
+ static::stringify(\count($countable))
+ );
+
+ throw static::createException($countable, $message, static::INVALID_MAX_COUNT, $propertyPath, ['count' => $count]);
+ }
+
+ return true;
+ }
+
+ /**
+ * static call handler to implement:
+ * - "null or assertion" delegation
+ * - "all" delegation.
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return bool|mixed
+ *
+ * @throws AssertionFailedException
+ */
+ public static function __callStatic($method, $args)
+ {
+ if (0 === \strpos($method, 'nullOr')) {
+ if (!\array_key_exists(0, $args)) {
+ throw new BadMethodCallException('Missing the first argument.');
+ }
+
+ if (null === $args[0]) {
+ return true;
+ }
+
+ $method = \substr($method, 6);
+
+ return \call_user_func_array([\get_called_class(), $method], $args);
+ }
+
+ if (0 === \strpos($method, 'all')) {
+ if (!\array_key_exists(0, $args)) {
+ throw new BadMethodCallException('Missing the first argument.');
+ }
+
+ static::isTraversable($args[0]);
+
+ $method = \substr($method, 3);
+ $values = \array_shift($args);
+ $calledClass = \get_called_class();
+
+ foreach ($values as $value) {
+ \call_user_func_array([$calledClass, $method], \array_merge([$value], $args));
+ }
+
+ return true;
+ }
+
+ throw new BadMethodCallException('No assertion Assertion#'.$method.' exists.');
+ }
+
+ /**
+ * Determines if the values array has every choice as key and that this choice has content.
+ *
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function choicesNotEmpty(array $values, array $choices, $message = null, ?string $propertyPath = null): bool
+ {
+ static::notEmpty($values, $message, $propertyPath);
+
+ foreach ($choices as $choice) {
+ static::notEmptyKey($values, $choice, $message, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines that the named method is defined in the provided object.
+ *
+ * @param string $value
+ * @param mixed $object
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function methodExists($value, $object, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isObject($object, $message, $propertyPath);
+
+ if (!\method_exists($object, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Expected "%s" does not exist in provided object.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_METHOD, $propertyPath, ['object' => \get_class($object)]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines that the provided value is an object.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert object $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isObject($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_object($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not a valid object.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_OBJECT, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the value is less than given limit.
+ *
+ * @param mixed $value
+ * @param mixed $limit
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function lessThan($value, $limit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value >= $limit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not less than "%s".'),
+ static::stringify($value),
+ static::stringify($limit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_LESS, $propertyPath, ['limit' => $limit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the value is less or equal than given limit.
+ *
+ * @param mixed $value
+ * @param mixed $limit
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function lessOrEqualThan($value, $limit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value > $limit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not less or equal than "%s".'),
+ static::stringify($value),
+ static::stringify($limit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_LESS_OR_EQUAL, $propertyPath, ['limit' => $limit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the value is greater than given limit.
+ *
+ * @param mixed $value
+ * @param mixed $limit
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function greaterThan($value, $limit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value <= $limit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not greater than "%s".'),
+ static::stringify($value),
+ static::stringify($limit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_GREATER, $propertyPath, ['limit' => $limit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the value is greater or equal than given limit.
+ *
+ * @param mixed $value
+ * @param mixed $limit
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function greaterOrEqualThan($value, $limit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($value < $limit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not greater or equal than "%s".'),
+ static::stringify($value),
+ static::stringify($limit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_GREATER_OR_EQUAL, $propertyPath, ['limit' => $limit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit.
+ *
+ * @param mixed $value
+ * @param mixed $lowerLimit
+ * @param mixed $upperLimit
+ * @param string|callable|null $message
+ * @param string $propertyPath
+ *
+ * @throws AssertionFailedException
+ */
+ public static function between($value, $lowerLimit, $upperLimit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($lowerLimit > $value || $value > $upperLimit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is neither greater than or equal to "%s" nor less than or equal to "%s".'),
+ static::stringify($value),
+ static::stringify($lowerLimit),
+ static::stringify($upperLimit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_BETWEEN, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a value is greater than a lower limit, and less than an upper limit.
+ *
+ * @param mixed $value
+ * @param mixed $lowerLimit
+ * @param mixed $upperLimit
+ * @param string|callable|null $message
+ * @param string $propertyPath
+ *
+ * @throws AssertionFailedException
+ */
+ public static function betweenExclusive($value, $lowerLimit, $upperLimit, $message = null, ?string $propertyPath = null): bool
+ {
+ if ($lowerLimit >= $value || $value >= $upperLimit) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is neither greater than "%s" nor less than "%s".'),
+ static::stringify($value),
+ static::stringify($lowerLimit),
+ static::stringify($upperLimit)
+ );
+
+ throw static::createException($value, $message, static::INVALID_BETWEEN_EXCLUSIVE, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that extension is loaded.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function extensionLoaded($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\extension_loaded($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Extension "%s" is required.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_EXTENSION, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that date is valid and corresponds to the given format.
+ *
+ * @param string $value
+ * @param string $format supports all of the options date(), except for the following:
+ * N, w, W, t, L, o, B, a, A, g, h, I, O, P, Z, c, r
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ *
+ * @see http://php.net/manual/function.date.php#refsect1-function.date-parameters
+ */
+ public static function date($value, $format, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+ static::string($format, $message, $propertyPath);
+
+ $dateTime = DateTime::createFromFormat('!'.$format, $value);
+
+ if (false === $dateTime || $value !== $dateTime->format($format)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Date "%s" is invalid or does not match format "%s".'),
+ static::stringify($value),
+ static::stringify($format)
+ );
+
+ throw static::createException($value, $message, static::INVALID_DATE, $propertyPath, ['format' => $format]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is an object, or a class that exists.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function objectOrClass($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_object($value)) {
+ static::classExists($value, $message, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is an object or class, and that the property exists.
+ *
+ * @param mixed $value
+ * @param string $property
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function propertyExists($value, $property, $message = null, ?string $propertyPath = null): bool
+ {
+ static::objectOrClass($value);
+
+ if (!\property_exists($value, $property)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" does not have property "%s".'),
+ static::stringify($value),
+ static::stringify($property)
+ );
+
+ throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['property' => $property]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the value is an object or class, and that the properties all exist.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function propertiesExist($value, array $properties, $message = null, ?string $propertyPath = null): bool
+ {
+ static::objectOrClass($value);
+ static::allString($properties, $message, $propertyPath);
+
+ $invalidProperties = [];
+ foreach ($properties as $property) {
+ if (!\property_exists($value, $property)) {
+ $invalidProperties[] = $property;
+ }
+ }
+
+ if ($invalidProperties) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Class "%s" does not have these properties: %s.'),
+ static::stringify($value),
+ static::stringify(\implode(', ', $invalidProperties))
+ );
+
+ throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['properties' => $properties]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert comparison of two versions.
+ *
+ * @param string $version1
+ * @param string $operator
+ * @param string $version2
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function version($version1, $operator, $version2, $message = null, ?string $propertyPath = null): bool
+ {
+ static::notEmpty($operator, 'versionCompare operator is required and cannot be empty.');
+
+ if (true !== \version_compare($version1, $version2, $operator)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Version "%s" is not "%s" version "%s".'),
+ static::stringify($version1),
+ static::stringify($operator),
+ static::stringify($version2)
+ );
+
+ throw static::createException($version1, $message, static::INVALID_VERSION, $propertyPath, ['operator' => $operator, 'version' => $version2]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert on PHP version.
+ *
+ * @param string $operator
+ * @param mixed $version
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function phpVersion($operator, $version, $message = null, ?string $propertyPath = null): bool
+ {
+ static::defined('PHP_VERSION');
+
+ return static::version(PHP_VERSION, $operator, $version, $message, $propertyPath);
+ }
+
+ /**
+ * Assert that extension is loaded and a specific version is installed.
+ *
+ * @param string $extension
+ * @param string $operator
+ * @param mixed $version
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function extensionVersion($extension, $operator, $version, $message = null, ?string $propertyPath = null): bool
+ {
+ static::extensionLoaded($extension, $message, $propertyPath);
+
+ return static::version(\phpversion($extension), $operator, $version, $message, $propertyPath);
+ }
+
+ /**
+ * Determines that the provided value is callable.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param string|null $propertyPath
+ *
+ * @psalm-assert callable $value
+ *
+ * @return bool
+ *
+ * @throws AssertionFailedException
+ */
+ public static function isCallable($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\is_callable($value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is not a callable.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_CALLABLE, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the provided value is valid according to a callback.
+ *
+ * If the callback returns `false` the assertion will fail.
+ *
+ * @param mixed $value
+ * @param callable $callback
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function satisfy($value, $callback, $message = null, ?string $propertyPath = null): bool
+ {
+ static::isCallable($callback);
+
+ if (false === \call_user_func($callback, $value)) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Provided "%s" is invalid according to custom rule.'),
+ static::stringify($value)
+ );
+
+ throw static::createException($value, $message, static::INVALID_SATISFY, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an IPv4 or IPv6 address
+ * (using input_filter/FILTER_VALIDATE_IP).
+ *
+ * @param string $value
+ * @param int|null $flag
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ *
+ * @see http://php.net/manual/filter.filters.flags.php
+ */
+ public static function ip($value, $flag = null, $message = null, ?string $propertyPath = null): bool
+ {
+ static::string($value, $message, $propertyPath);
+ if ($flag === null) {
+ $filterVarResult = \filter_var($value, FILTER_VALIDATE_IP);
+ } else {
+ $filterVarResult = \filter_var($value, FILTER_VALIDATE_IP, $flag);
+ }
+ if (!$filterVarResult) {
+ $message = \sprintf(
+ static::generateMessage($message ?: 'Value "%s" was expected to be a valid IP address.'),
+ static::stringify($value)
+ );
+ throw static::createException($value, $message, static::INVALID_IP, $propertyPath, ['flag' => $flag]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an IPv4 address
+ * (using input_filter/FILTER_VALIDATE_IP).
+ *
+ * @param string $value
+ * @param int|null $flag
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ *
+ * @see http://php.net/manual/filter.filters.flags.php
+ */
+ public static function ipv4($value, $flag = null, $message = null, ?string $propertyPath = null): bool
+ {
+ static::ip($value, $flag | FILTER_FLAG_IPV4, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv4 address.'), $propertyPath);
+
+ return true;
+ }
+
+ /**
+ * Assert that value is an IPv6 address
+ * (using input_filter/FILTER_VALIDATE_IP).
+ *
+ * @param string $value
+ * @param int|null $flag
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ *
+ * @see http://php.net/manual/filter.filters.flags.php
+ */
+ public static function ipv6($value, $flag = null, $message = null, ?string $propertyPath = null): bool
+ {
+ static::ip($value, $flag | FILTER_FLAG_IPV6, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv6 address.'), $propertyPath);
+
+ return true;
+ }
+
+ /**
+ * Assert that a constant is defined.
+ *
+ * @param mixed $constant
+ * @param string|callable|null $message
+ */
+ public static function defined($constant, $message = null, ?string $propertyPath = null): bool
+ {
+ if (!\defined($constant)) {
+ $message = \sprintf(static::generateMessage($message ?: 'Value "%s" expected to be a defined constant.'), $constant);
+
+ throw static::createException($constant, $message, static::INVALID_CONSTANT, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that a constant is defined.
+ *
+ * @param string $value
+ * @param string|callable|null $message
+ *
+ * @throws AssertionFailedException
+ */
+ public static function base64($value, $message = null, ?string $propertyPath = null): bool
+ {
+ if (false === \base64_decode($value, true)) {
+ $message = \sprintf(static::generateMessage($message ?: 'Value "%s" is not a valid base64 string.'), $value);
+
+ throw static::createException($value, $message, static::INVALID_BASE64, $propertyPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper method that handles building the assertion failure exceptions.
+ * They are returned from this method so that the stack trace still shows
+ * the assertions method.
+ *
+ * @param mixed $value
+ * @param string|callable|null $message
+ * @param int $code
+ *
+ * @return mixed
+ */
+ protected static function createException($value, $message, $code, $propertyPath = null, array $constraints = [])
+ {
+ $exceptionClass = static::$exceptionClass;
+
+ return new $exceptionClass($message, $code, $propertyPath, $value, $constraints);
+ }
+
+ /**
+ * Make a string version of a value.
+ *
+ * @param mixed $value
+ */
+ protected static function stringify($value): string
+ {
+ $result = \gettype($value);
+
+ if (\is_bool($value)) {
+ $result = $value ? '<TRUE>' : '<FALSE>';
+ } elseif (\is_scalar($value)) {
+ $val = (string)$value;
+
+ if (\mb_strlen($val) > 100) {
+ $val = \mb_substr($val, 0, 97).'...';
+ }
+
+ $result = $val;
+ } elseif (\is_array($value)) {
+ $result = '<ARRAY>';
+ } elseif (\is_object($value)) {
+ $result = \get_class($value);
+ } elseif (\is_resource($value)) {
+ $result = \get_resource_type($value);
+ } elseif (null === $value) {
+ $result = '<NULL>';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate the message.
+ *
+ * @param string|callable|null $message
+ */
+ protected static function generateMessage($message): string
+ {
+ if (\is_callable($message)) {
+ $traces = \debug_backtrace(0);
+
+ $parameters = [];
+
+ try {
+ $reflection = new ReflectionClass($traces[1]['class']);
+ $method = $reflection->getMethod($traces[1]['function']);
+ foreach ($method->getParameters() as $index => $parameter) {
+ if ('message' !== $parameter->getName()) {
+ $parameters[$parameter->getName()] = \array_key_exists($index, $traces[1]['args'])
+ ? $traces[1]['args'][$index]
+ : $parameter->getDefaultValue();
+ }
+ }
+
+ $parameters['::assertion'] = \sprintf('%s%s%s', $traces[1]['class'], $traces[1]['type'], $traces[1]['function']);
+
+ $message = \call_user_func_array($message, [$parameters]);
+ } // @codeCoverageIgnoreStart
+ catch (Throwable $exception) {
+ $message = \sprintf('Unable to generate message : %s', $exception->getMessage());
+ } // @codeCoverageIgnoreEnd
+ }
+
+ return (string)$message;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+use LogicException;
+
+/**
+ * Chaining builder for assertions.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ *
+ * @method AssertionChain alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric.
+ * @method AssertionChain base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined.
+ * @method AssertionChain between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit.
+ * @method AssertionChain betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit.
+ * @method AssertionChain betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths.
+ * @method AssertionChain boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean.
+ * @method AssertionChain choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices.
+ * @method AssertionChain choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content.
+ * @method AssertionChain classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists.
+ * @method AssertionChain contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars.
+ * @method AssertionChain count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count.
+ * @method AssertionChain date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format.
+ * @method AssertionChain defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined.
+ * @method AssertionChain digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit.
+ * @method AssertionChain directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists.
+ * @method AssertionChain e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number.
+ * @method AssertionChain email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL).
+ * @method AssertionChain endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars.
+ * @method AssertionChain eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==).
+ * @method AssertionChain eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset.
+ * @method AssertionChain extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded.
+ * @method AssertionChain extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed.
+ * @method AssertionChain false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False.
+ * @method AssertionChain file(string|callable $message = null, string $propertyPath = null) Assert that a file exists.
+ * @method AssertionChain float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float.
+ * @method AssertionChain greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit.
+ * @method AssertionChain greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit.
+ * @method AssertionChain implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface.
+ * @method AssertionChain inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice().
+ * @method AssertionChain integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer.
+ * @method AssertionChain integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish.
+ * @method AssertionChain interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists.
+ * @method AssertionChain ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address.
+ * @method AssertionChain ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address.
+ * @method AssertionChain ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address.
+ * @method AssertionChain isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array.
+ * @method AssertionChain isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object.
+ * @method AssertionChain isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable.
+ * @method AssertionChain isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable.
+ * @method AssertionChain isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name.
+ * @method AssertionChain isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string.
+ * @method AssertionChain isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object.
+ * @method AssertionChain isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource.
+ * @method AssertionChain isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object.
+ * @method AssertionChain keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array.
+ * @method AssertionChain keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset().
+ * @method AssertionChain keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array.
+ * @method AssertionChain length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length.
+ * @method AssertionChain lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit.
+ * @method AssertionChain lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit.
+ * @method AssertionChain max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit.
+ * @method AssertionChain maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements.
+ * @method AssertionChain maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars.
+ * @method AssertionChain methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object.
+ * @method AssertionChain min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit.
+ * @method AssertionChain minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements.
+ * @method AssertionChain minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long.
+ * @method AssertionChain noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty.
+ * @method AssertionChain notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank.
+ * @method AssertionChain notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars.
+ * @method AssertionChain notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty.
+ * @method AssertionChain notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty.
+ * @method AssertionChain notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==).
+ * @method AssertionChain notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices.
+ * @method AssertionChain notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name.
+ * @method AssertionChain notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null.
+ * @method AssertionChain notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex.
+ * @method AssertionChain notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===).
+ * @method AssertionChain null(string|callable $message = null, string $propertyPath = null) Assert that value is null.
+ * @method AssertionChain numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric.
+ * @method AssertionChain objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists.
+ * @method AssertionChain phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version.
+ * @method AssertionChain propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist.
+ * @method AssertionChain propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists.
+ * @method AssertionChain range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers.
+ * @method AssertionChain readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable.
+ * @method AssertionChain regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex.
+ * @method AssertionChain same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===).
+ * @method AssertionChain satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback.
+ * @method AssertionChain scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar.
+ * @method AssertionChain startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars.
+ * @method AssertionChain string(string|callable $message = null, string $propertyPath = null) Assert that value is a string.
+ * @method AssertionChain subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name.
+ * @method AssertionChain true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True.
+ * @method AssertionChain uniqueValues(string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality).
+ * @method AssertionChain url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL.
+ * @method AssertionChain uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID.
+ * @method AssertionChain version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions.
+ * @method AssertionChain writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable.
+ */
+class AssertionChain
+{
+ /**
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * @var string|callable|null
+ */
+ private $defaultMessage;
+
+ /**
+ * @var string|null
+ */
+ private $defaultPropertyPath;
+
+ /**
+ * Return each assertion as always valid.
+ *
+ * @var bool
+ */
+ private $alwaysValid = false;
+
+ /**
+ * Perform assertion on every element of array or traversable.
+ *
+ * @var bool
+ */
+ private $all = false;
+
+ /** @var string|Assertion Class to use for assertion calls */
+ private $assertionClassName = 'Assert\Assertion';
+
+ /**
+ * AssertionChain constructor.
+ *
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ */
+ public function __construct($value, $defaultMessage = null, ?string $defaultPropertyPath = null)
+ {
+ $this->value = $value;
+ $this->defaultMessage = $defaultMessage;
+ $this->defaultPropertyPath = $defaultPropertyPath;
+ }
+
+ /**
+ * Call assertion on the current value in the chain.
+ *
+ * @param string $methodName
+ * @param array $args
+ */
+ public function __call($methodName, $args): AssertionChain
+ {
+ if (true === $this->alwaysValid) {
+ return $this;
+ }
+
+ try {
+ $method = new \ReflectionMethod($this->assertionClassName, $methodName);
+ } catch (\ReflectionException $exception) {
+ throw new \RuntimeException("Assertion '".$methodName."' does not exist.");
+ }
+
+ \array_unshift($args, $this->value);
+ $params = $method->getParameters();
+
+ foreach ($params as $idx => $param) {
+ if (isset($args[$idx])) {
+ continue;
+ }
+
+ switch ($param->getName()) {
+ case 'message':
+ $args[$idx] = $this->defaultMessage;
+ break;
+ case 'propertyPath':
+ $args[$idx] = $this->defaultPropertyPath;
+ break;
+ }
+ }
+
+ if ($this->all) {
+ $methodName = 'all'.$methodName;
+ }
+
+ \call_user_func_array([$this->assertionClassName, $methodName], $args);
+
+ return $this;
+ }
+
+ /**
+ * Switch chain into validation mode for an array of values.
+ */
+ public function all(): AssertionChain
+ {
+ $this->all = true;
+
+ return $this;
+ }
+
+ /**
+ * Switch chain into mode allowing nulls, ignoring further assertions.
+ */
+ public function nullOr(): AssertionChain
+ {
+ if (null === $this->value) {
+ $this->alwaysValid = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $className
+ *
+ * @return $this
+ */
+ public function setAssertionClassName($className): AssertionChain
+ {
+ if (!\is_string($className)) {
+ throw new LogicException('Exception class name must be passed as a string');
+ }
+
+ if (Assertion::class !== $className && !\is_subclass_of($className, Assertion::class)) {
+ throw new LogicException($className.' is not (a subclass of) '.Assertion::class);
+ }
+
+ $this->assertionClassName = $className;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+use Throwable;
+
+interface AssertionFailedException extends Throwable
+{
+ /**
+ * @return string|null
+ */
+ public function getPropertyPath();
+
+ /**
+ * @return mixed
+ */
+ public function getValue();
+
+ public function getConstraints(): array;
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+class InvalidArgumentException extends \InvalidArgumentException implements AssertionFailedException
+{
+ /**
+ * @var string|null
+ */
+ private $propertyPath;
+
+ /**
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * @var array
+ */
+ private $constraints;
+
+ public function __construct($message, $code, ?string $propertyPath = null, $value = null, array $constraints = [])
+ {
+ parent::__construct($message, $code);
+
+ $this->propertyPath = $propertyPath;
+ $this->value = $value;
+ $this->constraints = $constraints;
+ }
+
+ /**
+ * User controlled way to define a sub-property causing
+ * the failure of a currently asserted objects.
+ *
+ * Useful to transport information about the nature of the error
+ * back to higher layers.
+ *
+ * @return string|null
+ */
+ public function getPropertyPath()
+ {
+ return $this->propertyPath;
+ }
+
+ /**
+ * Get the value that caused the assertion to fail.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get the constraints that applied to the failed assertion.
+ */
+ public function getConstraints(): array
+ {
+ return $this->constraints;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+use LogicException;
+
+/**
+ * Chaining builder for lazy assertions.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ *
+ * @method LazyAssertion alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric.
+ * @method LazyAssertion base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined.
+ * @method LazyAssertion between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit.
+ * @method LazyAssertion betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit.
+ * @method LazyAssertion betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths.
+ * @method LazyAssertion boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean.
+ * @method LazyAssertion choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices.
+ * @method LazyAssertion choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content.
+ * @method LazyAssertion classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists.
+ * @method LazyAssertion contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars.
+ * @method LazyAssertion count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count.
+ * @method LazyAssertion date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format.
+ * @method LazyAssertion defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined.
+ * @method LazyAssertion digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit.
+ * @method LazyAssertion directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists.
+ * @method LazyAssertion e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number.
+ * @method LazyAssertion email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL).
+ * @method LazyAssertion endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars.
+ * @method LazyAssertion eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==).
+ * @method LazyAssertion eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset.
+ * @method LazyAssertion extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded.
+ * @method LazyAssertion extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed.
+ * @method LazyAssertion false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False.
+ * @method LazyAssertion file(string|callable $message = null, string $propertyPath = null) Assert that a file exists.
+ * @method LazyAssertion float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float.
+ * @method LazyAssertion greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit.
+ * @method LazyAssertion greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit.
+ * @method LazyAssertion implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface.
+ * @method LazyAssertion inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice().
+ * @method LazyAssertion integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer.
+ * @method LazyAssertion integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish.
+ * @method LazyAssertion interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists.
+ * @method LazyAssertion ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address.
+ * @method LazyAssertion ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address.
+ * @method LazyAssertion ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address.
+ * @method LazyAssertion isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array.
+ * @method LazyAssertion isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object.
+ * @method LazyAssertion isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable.
+ * @method LazyAssertion isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable.
+ * @method LazyAssertion isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name.
+ * @method LazyAssertion isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string.
+ * @method LazyAssertion isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object.
+ * @method LazyAssertion isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource.
+ * @method LazyAssertion isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object.
+ * @method LazyAssertion keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array.
+ * @method LazyAssertion keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset().
+ * @method LazyAssertion keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array.
+ * @method LazyAssertion length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length.
+ * @method LazyAssertion lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit.
+ * @method LazyAssertion lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit.
+ * @method LazyAssertion max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit.
+ * @method LazyAssertion maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements.
+ * @method LazyAssertion maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars.
+ * @method LazyAssertion methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object.
+ * @method LazyAssertion min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit.
+ * @method LazyAssertion minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements.
+ * @method LazyAssertion minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long.
+ * @method LazyAssertion noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty.
+ * @method LazyAssertion notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank.
+ * @method LazyAssertion notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars.
+ * @method LazyAssertion notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty.
+ * @method LazyAssertion notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty.
+ * @method LazyAssertion notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==).
+ * @method LazyAssertion notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices.
+ * @method LazyAssertion notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name.
+ * @method LazyAssertion notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null.
+ * @method LazyAssertion notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex.
+ * @method LazyAssertion notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===).
+ * @method LazyAssertion null(string|callable $message = null, string $propertyPath = null) Assert that value is null.
+ * @method LazyAssertion numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric.
+ * @method LazyAssertion objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists.
+ * @method LazyAssertion phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version.
+ * @method LazyAssertion propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist.
+ * @method LazyAssertion propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists.
+ * @method LazyAssertion range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers.
+ * @method LazyAssertion readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable.
+ * @method LazyAssertion regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex.
+ * @method LazyAssertion same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===).
+ * @method LazyAssertion satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback.
+ * @method LazyAssertion scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar.
+ * @method LazyAssertion startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars.
+ * @method LazyAssertion string(string|callable $message = null, string $propertyPath = null) Assert that value is a string.
+ * @method LazyAssertion subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name.
+ * @method LazyAssertion true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True.
+ * @method LazyAssertion uniqueValues(string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality).
+ * @method LazyAssertion url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL.
+ * @method LazyAssertion uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID.
+ * @method LazyAssertion version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions.
+ * @method LazyAssertion writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable.
+ * @method LazyAssertion all() Switch chain into validation mode for an array of values.
+ * @method LazyAssertion nullOr() Switch chain into mode allowing nulls, ignoring further assertions.
+ */
+class LazyAssertion
+{
+ private $currentChainFailed = false;
+ private $alwaysTryAll = false;
+ private $thisChainTryAll = false;
+ private $currentChain;
+ private $errors = [];
+
+ /** @var string The class to use as AssertionChain factory */
+ private $assertClass = Assert::class;
+
+ /** @var string|LazyAssertionException The class to use for exceptions */
+ private $exceptionClass = LazyAssertionException::class;
+
+ /**
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ *
+ * @return static
+ */
+ public function that($value, ?string $propertyPath = null, $defaultMessage = null)
+ {
+ $this->currentChainFailed = false;
+ $this->thisChainTryAll = false;
+ $assertClass = $this->assertClass;
+ $this->currentChain = $assertClass::that($value, $defaultMessage, $propertyPath);
+
+ return $this;
+ }
+
+ /**
+ * @return static
+ */
+ public function tryAll()
+ {
+ if (!$this->currentChain) {
+ $this->alwaysTryAll = true;
+ }
+
+ $this->thisChainTryAll = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $method
+ * @param array $args
+ *
+ * @return static
+ */
+ public function __call($method, $args)
+ {
+ if (false === $this->alwaysTryAll
+ && false === $this->thisChainTryAll
+ && true === $this->currentChainFailed
+ ) {
+ return $this;
+ }
+
+ try {
+ \call_user_func_array([$this->currentChain, $method], $args);
+ } catch (AssertionFailedException $e) {
+ $this->errors[] = $e;
+ $this->currentChainFailed = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @throws LazyAssertionException
+ */
+ public function verifyNow(): bool
+ {
+ if ($this->errors) {
+ throw \call_user_func([$this->exceptionClass, 'fromErrors'], $this->errors);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $className
+ *
+ * @return static
+ */
+ public function setAssertClass(string $className): LazyAssertion
+ {
+ if (Assert::class !== $className && !\is_subclass_of($className, Assert::class)) {
+ throw new LogicException($className.' is not (a subclass of) '.Assert::class);
+ }
+
+ $this->assertClass = $className;
+
+ return $this;
+ }
+
+ /**
+ * @param string $className
+ *
+ * @return static
+ */
+ public function setExceptionClass(string $className): LazyAssertion
+ {
+ if (LazyAssertionException::class !== $className && !\is_subclass_of($className, LazyAssertionException::class)) {
+ throw new LogicException($className.' is not (a subclass of) '.LazyAssertionException::class);
+ }
+
+ $this->exceptionClass = $className;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+class LazyAssertionException extends InvalidArgumentException
+{
+ /**
+ * @var InvalidArgumentException[]
+ */
+ private $errors = [];
+
+ /**
+ * @param InvalidArgumentException[] $errors
+ */
+ public static function fromErrors(array $errors): self
+ {
+ $message = \sprintf('The following %d assertions failed:', \count($errors))."\n";
+
+ $i = 1;
+ foreach ($errors as $error) {
+ $message .= \sprintf("%d) %s: %s\n", $i++, $error->getPropertyPath(), $error->getMessage());
+ }
+
+ return new static($message, $errors);
+ }
+
+ public function __construct($message, array $errors)
+ {
+ parent::__construct($message, 0, null, null);
+
+ $this->errors = $errors;
+ }
+
+ /**
+ * @return InvalidArgumentException[]
+ */
+ public function getErrorExceptions(): array
+ {
+ return $this->errors;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Assert
+ *
+ * LICENSE
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this package in the file LICENSE.txt.
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to kontakt@beberlei.de so I can send you a copy immediately.
+ */
+
+namespace Assert;
+
+/**
+ * Start validation on a value, returns {@link AssertionChain}.
+ *
+ * The invocation of this method starts an assertion chain
+ * that is happening on the passed value.
+ *
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ * @param string $defaultPropertyPath
+ *
+ * @example
+ *
+ * \Assert\that($value)->notEmpty()->integer();
+ * \Assert\that($value)->nullOr()->string()->startsWith("Foo");
+ *
+ * The assertion chain can be stateful, that means be careful when you reuse
+ * it. You should never pass around the chain.
+ */
+function that($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+{
+ return Assert::that($value, $defaultMessage, $defaultPropertyPath);
+}
+
+/**
+ * Start validation on a set of values, returns {@link AssertionChain}.
+ *
+ * @param mixed $values
+ * @param string|callable|null $defaultMessage
+ * @param string $defaultPropertyPath
+ */
+function thatAll($values, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+{
+ return Assert::thatAll($values, $defaultMessage, $defaultPropertyPath);
+}
+
+/**
+ * Start validation and allow NULL, returns {@link AssertionChain}.
+ *
+ * @param mixed $value
+ * @param string|callable|null $defaultMessage
+ * @param string $defaultPropertyPath
+ *
+ * @deprecated In favour of Assert::thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null)
+ */
+function thatNullOr($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain
+{
+ return Assert::thatNullOr($value, $defaultMessage, $defaultPropertyPath);
+}
+
+/**
+ * Create a lazy assertion object.
+ */
+function lazy(): LazyAssertion
+{
+ return Assert::lazy();
+}
--- /dev/null
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
+
+🚀 **Compatibility with PHP 8.1**
+
+- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (thanks @TRowbotham)
+
+## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
+
+🐛 **Bug fix**
+
+- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
+
+## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
+
+✨ New features
+
+- `BigInteger::not()` returns the bitwise `NOT` value
+
+🐛 **Bug fixes**
+
+- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
+- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
+
+## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
+
+👌 **Improvements**
+
+- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
+
+💥 **Breaking changes**
+
+- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
+- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
+
+## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
+
+🐛 **Bug fix**
+
+- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
+- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
+
+## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
+
+🚑 **Critical fix**
+
+- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
+
+✨ **New features**
+
+- `BigInteger::modInverse()` calculates a modular multiplicative inverse
+- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
+- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
+- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
+- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
+
+💩 **Deprecations**
+
+- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
+
+## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
+
+🐛 **Fixes**
+
+- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
+
+⚡️ **Optimizations**
+
+- additional optimization in `BigInteger::remainder()`
+
+## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
+
+✨ **New features**
+
+- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
+
+## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
+
+✨ **New features**
+
+- `BigInteger::isEven()` tests whether the number is even
+- `BigInteger::isOdd()` tests whether the number is odd
+- `BigInteger::testBit()` tests if a bit is set
+- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
+
+## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
+
+🛠️ **Maintenance release**
+
+Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
+
+This is a maintenance release: no bug fixes, no new features, no breaking changes.
+
+## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
+
+✨ **New feature**
+
+`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
+
+## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
+
+✨ **New feature**
+
+`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
+
+## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
+
+⚡️ **Performance improvements**
+
+A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
+
+## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
+
+🐛 **Bug fixes**
+
+- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
+
+✨ **New features**
+
+- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
+- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
+
+These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
+
+💩 **Deprecations**
+
+- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
+
+`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
+
+- the `$base` parameter is required, it does not default to `10`
+- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
+
+## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
+
+**Improvements**
+
+- Safer conversion from `float` when using custom locales
+- **Much faster** `NativeCalculator` implementation 🚀
+
+You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
+
+## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
+
+**New method**
+
+`BigNumber::sum()` returns the sum of one or more numbers.
+
+## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
+
+**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
+
+Thanks @manowark 👍
+
+## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
+
+**New method**
+
+`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
+
+## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
+
+**New method**
+
+`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
+
+**New exception**
+
+`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
+
+## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
+
+**Performance update**
+
+- Further improvement of `toInt()` performance
+- `NativeCalculator` can now perform some multiplications more efficiently
+
+## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
+
+Performance optimization of `toInt()` methods.
+
+## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
+
+**Breaking changes**
+
+The following deprecated methods have been removed. Use the new method name instead:
+
+| Method removed | Replacement method |
+| --- | --- |
+| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
+| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
+
+---
+
+**New features**
+
+`BigInteger` has been augmented with 5 new methods for bitwise operations:
+
+| New method | Description |
+| --- | --- |
+| `and()` | performs a bitwise `AND` operation on two numbers |
+| `or()` | performs a bitwise `OR` operation on two numbers |
+| `xor()` | performs a bitwise `XOR` operation on two numbers |
+| `shiftedLeft()` | returns the number shifted left by a number of bits |
+| `shiftedRight()` | returns the number shifted right by a number of bits |
+
+Thanks to @DASPRiD 👍
+
+## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
+
+**New method:** `BigDecimal::hasNonZeroFractionalPart()`
+
+**Renamed/deprecated methods:**
+
+- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
+- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
+
+## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
+
+**Performance update**
+
+`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
+
+## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
+
+This is a maintenance release, no code has been changed.
+
+- When installed with `--no-dev`, the autoloader does not autoload tests anymore
+- Tests and other files unnecessary for production are excluded from the dist package
+
+This will help make installations more compact.
+
+## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
+
+Methods renamed:
+
+- `BigNumber:sign()` has been renamed to `getSign()`
+- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
+- `BigDecimal::scale()` has been renamed to `getScale()`
+- `BigDecimal::integral()` has been renamed to `getIntegral()`
+- `BigDecimal::fraction()` has been renamed to `getFraction()`
+- `BigRational::numerator()` has been renamed to `getNumerator()`
+- `BigRational::denominator()` has been renamed to `getDenominator()`
+
+Classes renamed:
+
+- `ArithmeticException` has been renamed to `MathException`
+
+## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
+
+The base class for all exceptions is now `MathException`.
+`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
+
+## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
+
+A number of methods have been renamed:
+
+- `BigNumber:sign()` is deprecated; use `getSign()` instead
+- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
+- `BigDecimal::scale()` is deprecated; use `getScale()` instead
+- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
+- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
+- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
+- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
+
+The old methods will be removed in version 0.7.0.
+
+## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
+
+- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
+- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
+- Method `BigNumber::toInteger()` has been renamed to `toInt()`
+
+## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
+
+`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
+The JSON output is always a string.
+
+## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
+
+This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
+
+## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
+
+The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
+
+## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
+
+**New method: `BigNumber::toScale()`**
+
+This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
+
+## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
+
+**New features**
+- Common `BigNumber` interface for all classes, with the following methods:
+ - `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
+ - `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
+ - `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
+ - `toInteger()` and `toFloat()` conversion methods to native types
+- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
+- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
+- New methods: `BigRational::quotient()` and `remainder()`
+- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
+- Factory methods `zero()`, `one()` and `ten()` available in all classes
+- Rounding mode reintroduced in `BigInteger::dividedBy()`
+
+This release also comes with many performance improvements.
+
+---
+
+**Breaking changes**
+- `BigInteger`:
+ - `getSign()` is renamed to `sign()`
+ - `toString()` is renamed to `toBase()`
+ - `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
+- `BigDecimal`:
+ - `getSign()` is renamed to `sign()`
+ - `getUnscaledValue()` is renamed to `unscaledValue()`
+ - `getScale()` is renamed to `scale()`
+ - `getIntegral()` is renamed to `integral()`
+ - `getFraction()` is renamed to `fraction()`
+ - `divideAndRemainder()` is renamed to `quotientAndRemainder()`
+ - `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
+ - `toBigInteger()` does not accept a `$roundingMode` parameter any more
+ - `toBigRational()` does not simplify the fraction any more; explicitly add `->simplified()` to get the previous behaviour
+- `BigRational`:
+ - `getSign()` is renamed to `sign()`
+ - `getNumerator()` is renamed to `numerator()`
+ - `getDenominator()` is renamed to `denominator()`
+ - `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
+- Miscellaneous:
+ - `ArithmeticException` is moved to an `Exception\` sub-namespace
+ - `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
+
+## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
+
+Backport of two bug fixes from the 0.5 branch:
+- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
+- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
+
+## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
+
+New method: `BigDecimal::stripTrailingZeros()`
+
+## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
+
+Introducing a `BigRational` class, to perform calculations on fractions of any size.
+
+## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
+
+Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
+
+`BigInteger::dividedBy()` now always returns the quotient of the division.
+
+## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
+
+Backport of two bug fixes from the 0.5 branch:
+
+- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
+- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
+
+## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
+
+New methods:
+- `BigInteger::remainder()` returns the remainder of a division only
+- `BigInteger::gcd()` returns the greatest common divisor of two numbers
+
+## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
+
+Fix `toString()` not handling negative numbers.
+
+## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
+
+`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
+- `-1` if the number is negative
+- `0` if the number is zero
+- `1` if the number is positive
+
+## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
+
+Minor performance improvements
+
+## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
+
+The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
+
+## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
+
+Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
+
+So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
+
+## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
+
+Added `BigDecimal::divideAndRemainder()`
+
+## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
+
+- `min()` and `max()` do not accept an `array` any more, but a variable number of parameters
+- **minimum PHP version is now 5.6**
+- continuous integration with PHP 7
+
+## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
+
+- Added `BigInteger::power()`
+- Added HHVM support
+
+## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
+
+First beta release.
+
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2013-present Benjamin Morel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+# Security Policy
+
+## Supported Versions
+
+Only the last two release streams are supported.
+
+| Version | Supported |
+| ------- | ------------------ |
+| 0.9.x | :white_check_mark: |
+| 0.8.x | :white_check_mark: |
+| < 0.8 | :x: |
+
+## Reporting a Vulnerability
+
+To report a security vulnerability, please use the
+[Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
--- /dev/null
+{
+ "name": "brick/math",
+ "description": "Arbitrary-precision arithmetic library",
+ "type": "library",
+ "keywords": [
+ "Brick",
+ "Math",
+ "Arbitrary-precision",
+ "Arithmetic",
+ "BigInteger",
+ "BigDecimal",
+ "BigRational",
+ "Bignum"
+ ],
+ "license": "MIT",
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "ext-json": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "php-coveralls/php-coveralls": "^2.2",
+ "vimeo/psalm": "4.9.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Brick\\Math\\Tests\\": "tests/"
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math;
+
+use Brick\Math\Exception\DivisionByZeroException;
+use Brick\Math\Exception\MathException;
+use Brick\Math\Exception\NegativeNumberException;
+use Brick\Math\Internal\Calculator;
+
+/**
+ * Immutable, arbitrary-precision signed decimal numbers.
+ *
+ * @psalm-immutable
+ */
+final class BigDecimal extends BigNumber
+{
+ /**
+ * The unscaled value of this decimal number.
+ *
+ * This is a string of digits with an optional leading minus sign.
+ * No leading zero must be present.
+ * No leading minus sign must be present if the value is 0.
+ *
+ * @var string
+ */
+ private $value;
+
+ /**
+ * The scale (number of digits after the decimal point) of this decimal number.
+ *
+ * This must be zero or more.
+ *
+ * @var int
+ */
+ private $scale;
+
+ /**
+ * Protected constructor. Use a factory method to obtain an instance.
+ *
+ * @param string $value The unscaled value, validated.
+ * @param int $scale The scale, validated.
+ */
+ protected function __construct(string $value, int $scale = 0)
+ {
+ $this->value = $value;
+ $this->scale = $scale;
+ }
+
+ /**
+ * Creates a BigDecimal of the given value.
+ *
+ * @param BigNumber|int|float|string $value
+ *
+ * @return BigDecimal
+ *
+ * @throws MathException If the value cannot be converted to a BigDecimal.
+ *
+ * @psalm-pure
+ */
+ public static function of($value) : BigNumber
+ {
+ 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.
+ *
+ * @return BigDecimal
+ *
+ * @throws \InvalidArgumentException If the scale is negative.
+ *
+ * @psalm-pure
+ */
+ public static function ofUnscaledValue($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.
+ *
+ * @return BigDecimal
+ *
+ * @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.
+ *
+ * @return BigDecimal
+ *
+ * @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.
+ *
+ * @return BigDecimal
+ *
+ * @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.
+ *
+ * @return BigDecimal The result.
+ *
+ * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
+ */
+ public function plus($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.
+ *
+ * @return BigDecimal The result.
+ *
+ * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
+ */
+ public function minus($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.
+ *
+ * @return BigDecimal The result.
+ *
+ * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
+ */
+ public function multipliedBy($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.
+ *
+ * @return BigDecimal
+ *
+ * @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($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.
+ *
+ * @return BigDecimal The result.
+ *
+ * @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($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`.
+ *
+ * @param int $exponent The exponent.
+ *
+ * @return BigDecimal The result.
+ *
+ * @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.
+ *
+ * @return BigDecimal The quotient.
+ *
+ * @throws MathException If the divisor is not a valid decimal number, or is zero.
+ */
+ public function quotient($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.
+ *
+ * @return BigDecimal The remainder.
+ *
+ * @throws MathException If the divisor is not a valid decimal number, or is zero.
+ */
+ public function remainder($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($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.
+ *
+ * @param int $scale
+ *
+ * @return BigDecimal
+ *
+ * @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.
+ *
+ * @param int $n
+ *
+ * @return BigDecimal
+ */
+ 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.
+ *
+ * @param int $n
+ *
+ * @return BigDecimal
+ */
+ 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.
+ *
+ * @return BigDecimal
+ */
+ 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.
+ *
+ * @return BigDecimal
+ */
+ public function abs() : BigDecimal
+ {
+ return $this->isNegative() ? $this->negated() : $this;
+ }
+
+ /**
+ * Returns the negated value of this number.
+ *
+ * @return BigDecimal
+ */
+ public function negated() : BigDecimal
+ {
+ return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compareTo($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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSign() : int
+ {
+ return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
+ }
+
+ /**
+ * @return BigInteger
+ */
+ public function getUnscaledValue() : BigInteger
+ {
+ return BigInteger::create($this->value);
+ }
+
+ /**
+ * @return int
+ */
+ public function getScale() : int
+ {
+ return $this->scale;
+ }
+
+ /**
+ * Returns a string representing the integral part of this decimal number.
+ *
+ * Example: `-123.456` => `-123`.
+ *
+ * @return string
+ */
+ 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` => ''.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @return bool
+ */
+ public function hasNonZeroFractionalPart() : bool
+ {
+ return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigInteger() : BigInteger
+ {
+ $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
+
+ return BigInteger::create($zeroScaleDecimal->value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigDecimal() : BigDecimal
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigRational() : BigRational
+ {
+ $numerator = BigInteger::create($this->value);
+ $denominator = BigInteger::create('1' . \str_repeat('0', $this->scale));
+
+ return BigRational::create($numerator, $denominator, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
+ {
+ if ($scale === $this->scale) {
+ return $this;
+ }
+
+ return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toInt() : int
+ {
+ return $this->toBigInteger()->toInt();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toFloat() : float
+ {
+ return (float) (string) $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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
+ *
+ * @return void
+ *
+ * @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
+ *
+ * @return string
+ */
+ 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
+ *
+ * @param string $value
+ *
+ * @return void
+ *
+ * @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.
+ *
+ * @param BigDecimal $x The first decimal number.
+ * @param BigDecimal $y The second decimal number.
+ *
+ * @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];
+ }
+
+ /**
+ * @param int $scale
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @return string
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math;
+
+use Brick\Math\Exception\DivisionByZeroException;
+use Brick\Math\Exception\IntegerOverflowException;
+use Brick\Math\Exception\MathException;
+use Brick\Math\Exception\NegativeNumberException;
+use Brick\Math\Exception\NumberFormatException;
+use Brick\Math\Internal\Calculator;
+
+/**
+ * An arbitrary-size integer.
+ *
+ * All methods accepting a number as a parameter accept either a BigInteger instance,
+ * an integer, or a string representing an arbitrary size integer.
+ *
+ * @psalm-immutable
+ */
+final class BigInteger extends BigNumber
+{
+ /**
+ * The value, as a string of digits with optional leading minus sign.
+ *
+ * No leading zeros must be present.
+ * No leading minus sign must be present if the number is zero.
+ *
+ * @var string
+ */
+ private $value;
+
+ /**
+ * Protected constructor. Use a factory method to obtain an instance.
+ *
+ * @param string $value A string of digits, with optional leading minus sign.
+ */
+ protected function __construct(string $value)
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Creates a BigInteger of the given value.
+ *
+ * @param BigNumber|int|float|string $value
+ *
+ * @return BigInteger
+ *
+ * @throws MathException If the value cannot be converted to a BigInteger.
+ *
+ * @psalm-pure
+ */
+ public static function of($value) : BigNumber
+ {
+ 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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @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 $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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @throws MathException If one of the parameters cannot be converted to a BigInteger,
+ * or `$min` is greater than `$max`.
+ */
+ public static function randomRange($min, $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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @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;
+ }
+
+ /**
+ * 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.
+ *
+ * @return BigInteger The result.
+ *
+ * @throws MathException If the number is not valid, or is not convertible to a BigInteger.
+ */
+ public function plus($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.
+ *
+ * @return BigInteger The result.
+ *
+ * @throws MathException If the number is not valid, or is not convertible to a BigInteger.
+ */
+ public function minus($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.
+ *
+ * @return BigInteger The result.
+ *
+ * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger.
+ */
+ public function multipliedBy($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.
+ *
+ * @return BigInteger The result.
+ *
+ * @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($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.
+ *
+ * @param int $exponent The exponent.
+ *
+ * @return BigInteger The result.
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @throws DivisionByZeroException If the divisor is zero.
+ */
+ public function quotient($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.
+ *
+ * @return BigInteger
+ *
+ * @throws DivisionByZeroException If the divisor is zero.
+ */
+ public function remainder($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($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.
+ *
+ * @return BigInteger
+ *
+ * @throws DivisionByZeroException If the divisor is zero.
+ */
+ public function mod($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.
+ *
+ * @param BigInteger $m
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ *
+ * @throws NegativeNumberException If any of the operands is negative.
+ * @throws DivisionByZeroException If the modulus is zero.
+ */
+ public function modPow($exp, $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.
+ *
+ * @return BigInteger
+ */
+ public function gcd($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.
+ *
+ * @return BigInteger
+ *
+ * @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.
+ *
+ * @return BigInteger
+ */
+ public function abs() : BigInteger
+ {
+ return $this->isNegative() ? $this->negated() : $this;
+ }
+
+ /**
+ * Returns the inverse of this number.
+ *
+ * @return BigInteger
+ */
+ 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.
+ *
+ * @return BigInteger
+ */
+ public function and($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.
+ *
+ * @return BigInteger
+ */
+ public function or($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.
+ *
+ * @return BigInteger
+ */
+ public function xor($that) : BigInteger
+ {
+ $that = BigInteger::of($that);
+
+ return new BigInteger(Calculator::get()->xor($this->value, $that->value));
+ }
+
+ /**
+ * Returns the bitwise-not of this BigInteger.
+ *
+ * @return BigInteger
+ */
+ public function not() : BigInteger
+ {
+ return $this->negated()->minus(1);
+ }
+
+ /**
+ * Returns the integer left shifted by a given number of bits.
+ *
+ * @param int $distance The distance to shift.
+ *
+ * @return BigInteger
+ */
+ 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.
+ *
+ * @param int $distance The distance to shift.
+ *
+ * @return BigInteger
+ */
+ 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))).
+ *
+ * @return int
+ */
+ 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.
+ *
+ * @return int
+ */
+ 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.
+ *
+ * @return bool
+ */
+ public function isEven() : bool
+ {
+ return \in_array($this->value[-1], ['0', '2', '4', '6', '8'], true);
+ }
+
+ /**
+ * Returns whether this number is odd.
+ *
+ * @return bool
+ */
+ 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<<n)) != 0).
+ *
+ * @param int $n The bit to test, 0-based.
+ *
+ * @return bool
+ *
+ * @throws \InvalidArgumentException If the bit to test is negative.
+ */
+ public function testBit(int $n) : bool
+ {
+ if ($n < 0) {
+ throw new \InvalidArgumentException('The bit to test cannot be negative.');
+ }
+
+ return $this->shiftedRight($n)->isOdd();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compareTo($that) : int
+ {
+ $that = BigNumber::of($that);
+
+ if ($that instanceof BigInteger) {
+ return Calculator::get()->cmp($this->value, $that->value);
+ }
+
+ return - $that->compareTo($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSign() : int
+ {
+ return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigInteger() : BigInteger
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigDecimal() : BigDecimal
+ {
+ return BigDecimal::create($this->value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigRational() : BigRational
+ {
+ return BigRational::create($this, BigInteger::one(), false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
+ {
+ return $this->toBigDecimal()->toScale($scale, $roundingMode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toInt() : int
+ {
+ $intValue = (int) $this->value;
+
+ if ($this->value !== (string) $intValue) {
+ throw IntegerOverflowException::toIntOverflow($this);
+ }
+
+ return $intValue;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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.
+ *
+ * @param int $base
+ *
+ * @return string
+ *
+ * @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.
+ *
+ * @return string
+ *
+ * @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.
+ *
+ * @return string
+ *
+ * @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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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
+ *
+ * @return void
+ *
+ * @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
+ *
+ * @return string
+ */
+ 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
+ *
+ * @param string $value
+ *
+ * @return void
+ *
+ * @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;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math;
+
+use Brick\Math\Exception\DivisionByZeroException;
+use Brick\Math\Exception\MathException;
+use Brick\Math\Exception\NumberFormatException;
+use Brick\Math\Exception\RoundingNecessaryException;
+
+/**
+ * Common interface for arbitrary-precision rational numbers.
+ *
+ * @psalm-immutable
+ */
+abstract class BigNumber implements \Serializable, \JsonSerializable
+{
+ /**
+ * The regular expression used to parse integer, decimal and rational numbers.
+ */
+ private const PARSE_REGEXP =
+ '/^' .
+ '(?<sign>[\-\+])?' .
+ '(?:' .
+ '(?:' .
+ '(?<integral>[0-9]+)?' .
+ '(?<point>\.)?' .
+ '(?<fractional>[0-9]+)?' .
+ '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
+ ')|(?:' .
+ '(?<numerator>[0-9]+)' .
+ '\/?' .
+ '(?<denominator>[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
+ *
+ * @param BigNumber|int|float|string $value
+ *
+ * @return BigNumber
+ *
+ * @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($value) : BigNumber
+ {
+ if ($value instanceof BigNumber) {
+ return $value;
+ }
+
+ if (\is_int($value)) {
+ return new BigInteger((string) $value);
+ }
+
+ /** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */
+ $value = \is_float($value) ? self::floatToString($value) : (string) $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 function(string $value) use ($matches) : ?string {
+ return isset($matches[$value]) && $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
+ *
+ * @param float $float
+ *
+ * @return string
+ *
+ * @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 protected constructors from sibling classes.
+ *
+ * @internal
+ *
+ * @param mixed ...$args The arguments to the constructor.
+ *
+ * @return static
+ *
+ * @psalm-pure
+ * @psalm-suppress TooManyArguments
+ * @psalm-suppress UnsafeInstantiation
+ */
+ protected static function create(... $args) : BigNumber
+ {
+ return new static(... $args);
+ }
+
+ /**
+ * 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.
+ *
+ * @return static The minimum value.
+ *
+ * @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(...$values) : BigNumber
+ {
+ $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.
+ *
+ * @return static The maximum value.
+ *
+ * @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(...$values) : BigNumber
+ {
+ $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.
+ *
+ * @return static The sum.
+ *
+ * @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 sum(...$values) : BigNumber
+ {
+ /** @var BigNumber|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...)
+ *
+ * @param BigNumber $a
+ * @param BigNumber $b
+ *
+ * @return BigNumber
+ *
+ * @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.
+ *
+ * @return string
+ *
+ * @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.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @return bool
+ */
+ public function isEqualTo($that) : bool
+ {
+ return $this->compareTo($that) === 0;
+ }
+
+ /**
+ * Checks if this number is strictly lower than the given one.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @return bool
+ */
+ public function isLessThan($that) : bool
+ {
+ return $this->compareTo($that) < 0;
+ }
+
+ /**
+ * Checks if this number is lower than or equal to the given one.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @return bool
+ */
+ public function isLessThanOrEqualTo($that) : bool
+ {
+ return $this->compareTo($that) <= 0;
+ }
+
+ /**
+ * Checks if this number is strictly greater than the given one.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @return bool
+ */
+ public function isGreaterThan($that) : bool
+ {
+ return $this->compareTo($that) > 0;
+ }
+
+ /**
+ * Checks if this number is greater than or equal to the given one.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @return bool
+ */
+ public function isGreaterThanOrEqualTo($that) : bool
+ {
+ return $this->compareTo($that) >= 0;
+ }
+
+ /**
+ * Checks if this number equals zero.
+ *
+ * @return bool
+ */
+ public function isZero() : bool
+ {
+ return $this->getSign() === 0;
+ }
+
+ /**
+ * Checks if this number is strictly negative.
+ *
+ * @return bool
+ */
+ public function isNegative() : bool
+ {
+ return $this->getSign() < 0;
+ }
+
+ /**
+ * Checks if this number is negative or zero.
+ *
+ * @return bool
+ */
+ public function isNegativeOrZero() : bool
+ {
+ return $this->getSign() <= 0;
+ }
+
+ /**
+ * Checks if this number is strictly positive.
+ *
+ * @return bool
+ */
+ public function isPositive() : bool
+ {
+ return $this->getSign() > 0;
+ }
+
+ /**
+ * Checks if this number is positive or zero.
+ *
+ * @return bool
+ */
+ 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.
+ *
+ * @param BigNumber|int|float|string $that
+ *
+ * @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($that) : int;
+
+ /**
+ * Converts this number to a BigInteger.
+ *
+ * @return BigInteger The converted number.
+ *
+ * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
+ */
+ abstract public function toBigInteger() : BigInteger;
+
+ /**
+ * Converts this number to a BigDecimal.
+ *
+ * @return BigDecimal The converted number.
+ *
+ * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
+ */
+ abstract public function toBigDecimal() : BigDecimal;
+
+ /**
+ * Converts this number to a BigRational.
+ *
+ * @return BigRational The converted number.
+ */
+ 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.
+ *
+ * @return BigDecimal
+ *
+ * @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.
+ *
+ * @return int The converted value.
+ *
+ * @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.
+ *
+ * @return float The converted value.
+ */
+ 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.
+ *
+ * @return string
+ */
+ abstract public function __toString() : string;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function jsonSerialize() : string
+ {
+ return $this->__toString();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math;
+
+use Brick\Math\Exception\DivisionByZeroException;
+use Brick\Math\Exception\MathException;
+use Brick\Math\Exception\NumberFormatException;
+use Brick\Math\Exception\RoundingNecessaryException;
+
+/**
+ * An arbitrarily large rational number.
+ *
+ * This class is immutable.
+ *
+ * @psalm-immutable
+ */
+final class BigRational extends BigNumber
+{
+ /**
+ * The numerator.
+ *
+ * @var BigInteger
+ */
+ private $numerator;
+
+ /**
+ * The denominator. Always strictly positive.
+ *
+ * @var BigInteger
+ */
+ private $denominator;
+
+ /**
+ * Protected constructor. Use a factory method to obtain an instance.
+ *
+ * @param BigInteger $numerator The numerator.
+ * @param BigInteger $denominator The denominator.
+ * @param bool $checkDenominator Whether to check the denominator for negative and zero.
+ *
+ * @throws DivisionByZeroException If the denominator is zero.
+ */
+ protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
+ {
+ if ($checkDenominator) {
+ if ($denominator->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.
+ *
+ * @param BigNumber|int|float|string $value
+ *
+ * @return BigRational
+ *
+ * @throws MathException If the value cannot be converted to a BigRational.
+ *
+ * @psalm-pure
+ */
+ public static function of($value) : BigNumber
+ {
+ 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.
+ *
+ * @return BigRational
+ *
+ * @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($numerator, $denominator) : BigRational
+ {
+ $numerator = BigInteger::of($numerator);
+ $denominator = BigInteger::of($denominator);
+
+ return new BigRational($numerator, $denominator, true);
+ }
+
+ /**
+ * Returns a BigRational representing zero.
+ *
+ * @return BigRational
+ *
+ * @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.
+ *
+ * @return BigRational
+ *
+ * @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.
+ *
+ * @return BigRational
+ *
+ * @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;
+ }
+
+ /**
+ * @return BigInteger
+ */
+ public function getNumerator() : BigInteger
+ {
+ return $this->numerator;
+ }
+
+ /**
+ * @return BigInteger
+ */
+ public function getDenominator() : BigInteger
+ {
+ return $this->denominator;
+ }
+
+ /**
+ * Returns the quotient of the division of the numerator by the denominator.
+ *
+ * @return BigInteger
+ */
+ public function quotient() : BigInteger
+ {
+ return $this->numerator->quotient($this->denominator);
+ }
+
+ /**
+ * Returns the remainder of the division of the numerator by the denominator.
+ *
+ * @return BigInteger
+ */
+ 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.
+ *
+ * @return BigRational The result.
+ *
+ * @throws MathException If the number is not valid.
+ */
+ public function plus($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.
+ *
+ * @return BigRational The result.
+ *
+ * @throws MathException If the number is not valid.
+ */
+ public function minus($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.
+ *
+ * @return BigRational The result.
+ *
+ * @throws MathException If the multiplier is not a valid number.
+ */
+ public function multipliedBy($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.
+ *
+ * @return BigRational The result.
+ *
+ * @throws MathException If the divisor is not a valid number, or is zero.
+ */
+ public function dividedBy($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.
+ *
+ * @param int $exponent The exponent.
+ *
+ * @return BigRational The result.
+ *
+ * @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.
+ *
+ * @return BigRational
+ *
+ * @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.
+ *
+ * @return BigRational
+ */
+ public function abs() : BigRational
+ {
+ return new BigRational($this->numerator->abs(), $this->denominator, false);
+ }
+
+ /**
+ * Returns the negated value of this BigRational.
+ *
+ * @return BigRational
+ */
+ public function negated() : BigRational
+ {
+ return new BigRational($this->numerator->negated(), $this->denominator, false);
+ }
+
+ /**
+ * Returns the simplified value of this BigRational.
+ *
+ * @return 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compareTo($that) : int
+ {
+ return $this->minus($that)->getSign();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSign() : int
+ {
+ return $this->numerator->getSign();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigDecimal() : BigDecimal
+ {
+ return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBigRational() : BigRational
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
+ {
+ return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toInt() : int
+ {
+ return $this->toBigInteger()->toInt();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toFloat() : float
+ {
+ return $this->numerator->toFloat() / $this->denominator->toFloat();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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
+ *
+ * @return void
+ *
+ * @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
+ *
+ * @return string
+ */
+ 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
+ *
+ * @param string $value
+ *
+ * @return void
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+/**
+ * Exception thrown when a division by zero occurs.
+ */
+class DivisionByZeroException extends MathException
+{
+ /**
+ * @return DivisionByZeroException
+ *
+ * @psalm-pure
+ */
+ public static function divisionByZero() : DivisionByZeroException
+ {
+ return new self('Division by zero.');
+ }
+
+ /**
+ * @return DivisionByZeroException
+ *
+ * @psalm-pure
+ */
+ public static function modulusMustNotBeZero() : DivisionByZeroException
+ {
+ return new self('The modulus must not be zero.');
+ }
+
+ /**
+ * @return DivisionByZeroException
+ *
+ * @psalm-pure
+ */
+ public static function denominatorMustNotBeZero() : DivisionByZeroException
+ {
+ return new self('The denominator of a rational number cannot be zero.');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+use Brick\Math\BigInteger;
+
+/**
+ * Exception thrown when an integer overflow occurs.
+ */
+class IntegerOverflowException extends MathException
+{
+ /**
+ * @param BigInteger $value
+ *
+ * @return IntegerOverflowException
+ *
+ * @psalm-pure
+ */
+ public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
+ {
+ $message = '%s is out of range %d to %d and cannot be represented as an integer.';
+
+ return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+/**
+ * Base class for all math exceptions.
+ *
+ * This class is abstract to ensure that only fine-grained exceptions are thrown throughout the code.
+ */
+class MathException extends \RuntimeException
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+/**
+ * Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
+ */
+class NegativeNumberException extends MathException
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+/**
+ * Exception thrown when attempting to create a number from a string with an invalid format.
+ */
+class NumberFormatException extends MathException
+{
+ /**
+ * @param string $char The failing character.
+ *
+ * @return NumberFormatException
+ *
+ * @psalm-pure
+ */
+ public static function charNotInAlphabet(string $char) : self
+ {
+ $ord = \ord($char);
+
+ if ($ord < 32 || $ord > 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));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Exception;
+
+/**
+ * Exception thrown when a number cannot be represented at the requested scale without rounding.
+ */
+class RoundingNecessaryException extends MathException
+{
+ /**
+ * @return RoundingNecessaryException
+ *
+ * @psalm-pure
+ */
+ public static function roundingNecessary() : RoundingNecessaryException
+ {
+ return new self('Rounding is necessary to represent the result of the operation at this scale.');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Internal;
+
+use Brick\Math\Exception\RoundingNecessaryException;
+use Brick\Math\RoundingMode;
+
+/**
+ * Performs basic operations on arbitrary size integers.
+ *
+ * Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
+ * without leading zero, and with an optional leading minus sign if the number is not zero.
+ *
+ * Any other parameter format will lead to undefined behaviour.
+ * All methods must return strings respecting this format, unless specified otherwise.
+ *
+ * @internal
+ *
+ * @psalm-immutable
+ */
+abstract class Calculator
+{
+ /**
+ * The maximum exponent value allowed for the pow() method.
+ */
+ public const MAX_POWER = 1000000;
+
+ /**
+ * The alphabet for converting from and to base 2 to 36, lowercase.
+ */
+ public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
+
+ /**
+ * The Calculator instance in use.
+ *
+ * @var Calculator|null
+ */
+ private static $instance;
+
+ /**
+ * Sets the Calculator instance to use.
+ *
+ * An instance is typically set only in unit tests: the autodetect is usually the best option.
+ *
+ * @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
+ *
+ * @return void
+ */
+ final public static function set(?Calculator $calculator) : void
+ {
+ self::$instance = $calculator;
+ }
+
+ /**
+ * Returns the Calculator instance to use.
+ *
+ * If none has been explicitly set, the fastest available implementation will be returned.
+ *
+ * @return Calculator
+ *
+ * @psalm-pure
+ * @psalm-suppress ImpureStaticProperty
+ */
+ final public static function get() : Calculator
+ {
+ if (self::$instance === null) {
+ /** @psalm-suppress ImpureMethodCall */
+ self::$instance = self::detect();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Returns the fastest available Calculator implementation.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return Calculator
+ */
+ private static function detect() : Calculator
+ {
+ if (\extension_loaded('gmp')) {
+ return new Calculator\GmpCalculator();
+ }
+
+ if (\extension_loaded('bcmath')) {
+ return new Calculator\BcMathCalculator();
+ }
+
+ return new Calculator\NativeCalculator();
+ }
+
+ /**
+ * Extracts the sign & digits of the operands.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
+ */
+ final protected function init(string $a, string $b) : array
+ {
+ return [
+ $aNeg = ($a[0] === '-'),
+ $bNeg = ($b[0] === '-'),
+
+ $aNeg ? \substr($a, 1) : $a,
+ $bNeg ? \substr($b, 1) : $b,
+ ];
+ }
+
+ /**
+ * Returns the absolute value of a number.
+ *
+ * @param string $n The number.
+ *
+ * @return string The absolute value.
+ */
+ final public function abs(string $n) : string
+ {
+ return ($n[0] === '-') ? \substr($n, 1) : $n;
+ }
+
+ /**
+ * Negates a number.
+ *
+ * @param string $n The number.
+ *
+ * @return string The negated value.
+ */
+ final public function neg(string $n) : string
+ {
+ if ($n === '0') {
+ return '0';
+ }
+
+ if ($n[0] === '-') {
+ return \substr($n, 1);
+ }
+
+ return '-' . $n;
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * @param string $a The first number.
+ * @param string $b The second number.
+ *
+ * @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number.
+ */
+ final public function cmp(string $a, string $b) : int
+ {
+ [$aNeg, $bNeg, $aDig, $bDig] = $this->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.
+ *
+ * @param string $a The augend.
+ * @param string $b The addend.
+ *
+ * @return string The sum.
+ */
+ abstract public function add(string $a, string $b) : string;
+
+ /**
+ * Subtracts two numbers.
+ *
+ * @param string $a The minuend.
+ * @param string $b The subtrahend.
+ *
+ * @return string The difference.
+ */
+ abstract public function sub(string $a, string $b) : string;
+
+ /**
+ * Multiplies two numbers.
+ *
+ * @param string $a The multiplicand.
+ * @param string $b The multiplier.
+ *
+ * @return string The product.
+ */
+ 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 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 $a
+ * @param string $b The modulus; must not be zero.
+ *
+ * @return string
+ */
+ 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 $x
+ * @param string $m The modulus; must not be negative or zero.
+ *
+ * @return string|null
+ */
+ 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);
+ }
+
+ $x = '0';
+ $y = '0';
+ $g = $this->gcdExtended($modVal, $m, $x, $y);
+
+ 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.
+ *
+ * @return string The power.
+ */
+ 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.
+ *
+ * @param string $a The first number.
+ * @param string $b The second number.
+ *
+ * @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));
+ }
+
+ private function gcdExtended(string $a, string $b, string &$x, string &$y) : string
+ {
+ if ($a === '0') {
+ $x = '0';
+ $y = '1';
+
+ return $b;
+ }
+
+ $x1 = '0';
+ $y1 = '0';
+
+ $gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1);
+
+ $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
+ $y = $x1;
+
+ return $gcd;
+ }
+
+ /**
+ * 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.
+ *
+ * @param string $n The number.
+ *
+ * @return string The square root.
+ */
+ 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.
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the rounding mode is invalid.
+ * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
+ */
+ 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.
+ *
+ * @param string $a
+ * @param string $b
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @param string $a
+ * @param string $b
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @param string $a
+ * @param string $b
+ *
+ * @return string
+ */
+ public function xor(string $a, string $b) : string
+ {
+ return $this->bitwise('xor', $a, $b);
+ }
+
+ /**
+ * Performs a bitwise operation on a decimal number.
+ *
+ * @param string $operator The operator to use, must be "and", "or" or "xor".
+ * @param string $a The left operand.
+ * @param string $b The right operand.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @return string
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Internal\Calculator;
+
+use Brick\Math\Internal\Calculator;
+
+/**
+ * Calculator implementation built around the bcmath library.
+ *
+ * @internal
+ *
+ * @psalm-immutable
+ */
+class BcMathCalculator extends Calculator
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function add(string $a, string $b) : string
+ {
+ return \bcadd($a, $b, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sub(string $a, string $b) : string
+ {
+ return \bcsub($a, $b, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mul(string $a, string $b) : string
+ {
+ return \bcmul($a, $b, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @psalm-suppress InvalidNullableReturnType
+ * @psalm-suppress NullableReturnStatement
+ */
+ public function divQ(string $a, string $b) : string
+ {
+ return \bcdiv($a, $b, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @psalm-suppress InvalidNullableReturnType
+ * @psalm-suppress NullableReturnStatement
+ */
+ public function divR(string $a, string $b) : string
+ {
+ if (version_compare(PHP_VERSION, '7.2') >= 0) {
+ return \bcmod($a, $b, 0);
+ }
+
+ return \bcmod($a, $b);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divQR(string $a, string $b) : array
+ {
+ $q = \bcdiv($a, $b, 0);
+
+ if (version_compare(PHP_VERSION, '7.2') >= 0) {
+ $r = \bcmod($a, $b, 0);
+ } else {
+ $r = \bcmod($a, $b);
+ }
+
+ assert($q !== null);
+ assert($r !== null);
+
+ return [$q, $r];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function pow(string $a, int $e) : string
+ {
+ return \bcpow($a, (string) $e, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @psalm-suppress InvalidNullableReturnType
+ * @psalm-suppress NullableReturnStatement
+ */
+ public function modPow(string $base, string $exp, string $mod) : string
+ {
+ return \bcpowmod($base, $exp, $mod, 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @psalm-suppress NullableReturnStatement
+ * @psalm-suppress InvalidNullableReturnType
+ */
+ public function sqrt(string $n) : string
+ {
+ return \bcsqrt($n, 0);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Internal\Calculator;
+
+use Brick\Math\Internal\Calculator;
+
+/**
+ * Calculator implementation built around the GMP library.
+ *
+ * @internal
+ *
+ * @psalm-immutable
+ */
+class GmpCalculator extends Calculator
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function add(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_add($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sub(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_sub($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mul(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_mul($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divQ(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_div_q($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divR(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_div_r($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divQR(string $a, string $b) : array
+ {
+ [$q, $r] = \gmp_div_qr($a, $b);
+
+ return [
+ \gmp_strval($q),
+ \gmp_strval($r)
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function pow(string $a, int $e) : string
+ {
+ return \gmp_strval(\gmp_pow($a, $e));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function modInverse(string $x, string $m) : ?string
+ {
+ $result = \gmp_invert($x, $m);
+
+ if ($result === false) {
+ return null;
+ }
+
+ return \gmp_strval($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function modPow(string $base, string $exp, string $mod) : string
+ {
+ return \gmp_strval(\gmp_powm($base, $exp, $mod));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gcd(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_gcd($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fromBase(string $number, int $base) : string
+ {
+ return \gmp_strval(\gmp_init($number, $base));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toBase(string $number, int $base) : string
+ {
+ return \gmp_strval($number, $base);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function and(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_and($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function or(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_or($a, $b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function xor(string $a, string $b) : string
+ {
+ return \gmp_strval(\gmp_xor($a, $b));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function sqrt(string $n) : string
+ {
+ return \gmp_strval(\gmp_sqrt($n));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math\Internal\Calculator;
+
+use Brick\Math\Internal\Calculator;
+
+/**
+ * Calculator implementation using only native PHP code.
+ *
+ * @internal
+ *
+ * @psalm-immutable
+ */
+class NativeCalculator extends Calculator
+{
+ /**
+ * The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
+ * For multiplication, this represents the max sum of the lengths of both operands.
+ *
+ * For addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
+ * Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
+ * 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
+ *
+ * @var int
+ */
+ private $maxDigits;
+
+ /**
+ * Class constructor.
+ *
+ * @codeCoverageIgnore
+ */
+ public function __construct()
+ {
+ switch (PHP_INT_SIZE) {
+ case 4:
+ $this->maxDigits = 9;
+ break;
+
+ case 8:
+ $this->maxDigits = 18;
+ break;
+
+ default:
+ throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sub(string $a, string $b) : string
+ {
+ return $this->add($a, $this->neg($b));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divQ(string $a, string $b) : string
+ {
+ return $this->divQR($a, $b)[0];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function divR(string $a, string $b): string
+ {
+ return $this->divQR($a, $b)[1];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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/
+ *
+ * {@inheritdoc}
+ */
+ 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
+ *
+ * {@inheritDoc}
+ */
+ 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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @return string
+ */
+ 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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @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.
+ *
+ * @param string $a The first operand.
+ * @param string $b The second operand.
+ *
+ * @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];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Brick\Math;
+
+/**
+ * Specifies a rounding behavior for numerical operations capable of discarding precision.
+ *
+ * Each rounding mode indicates how the least significant returned digit of a rounded result
+ * is to be calculated. If fewer digits are returned than the digits needed to represent the
+ * exact numerical result, the discarded digits will be referred to as the discarded fraction
+ * regardless the digits' contribution to the value of the number. In other words, considered
+ * as a numerical value, the discarded fraction could have an absolute value greater than one.
+ */
+final class RoundingMode
+{
+ /**
+ * Private constructor. This class is not instantiable.
+ *
+ * @codeCoverageIgnore
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Asserts that the requested operation has an exact result, hence no rounding is necessary.
+ *
+ * If this rounding mode is specified on an operation that yields a result that
+ * cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
+ */
+ public const UNNECESSARY = 0;
+
+ /**
+ * Rounds away from zero.
+ *
+ * Always increments the digit prior to a nonzero discarded fraction.
+ * Note that this rounding mode never decreases the magnitude of the calculated value.
+ */
+ public const UP = 1;
+
+ /**
+ * Rounds towards zero.
+ *
+ * Never increments the digit prior to a discarded fraction (i.e., truncates).
+ * Note that this rounding mode never increases the magnitude of the calculated value.
+ */
+ public const DOWN = 2;
+
+ /**
+ * Rounds towards positive infinity.
+ *
+ * If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
+ * Note that this rounding mode never decreases the calculated value.
+ */
+ public const CEILING = 3;
+
+ /**
+ * Rounds towards negative infinity.
+ *
+ * If the result is positive, behave as for DOWN; if negative, behave as for UP.
+ * Note that this rounding mode never increases the calculated value.
+ */
+ public const FLOOR = 4;
+
+ /**
+ * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
+ *
+ * Behaves as for UP if the discarded fraction is >= 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;
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array<string, array<string, int>>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array<string, list<string>>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list<string>
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array<string, array<string, list<string>>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list<string>
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array<string, string>
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array<string, bool>
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array<string, self>
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array<string, list<string>>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array<string, list<string>>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list<string>
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list<string>
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array<string, string> Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array<string, string> $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list<string>|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list<string>|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list<string>|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list<string>|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array<string, self>
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+ * @internal
+ */
+ private static $selfDir = null;
+
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return string
+ */
+ private static function getSelfDir()
+ {
+ if (self::$selfDir === null) {
+ self::$selfDir = strtr(__DIR__, '\\', '/');
+ }
+
+ return self::$selfDir;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+ $copiedLocalDir = false;
+
+ if (self::$canGetVendors) {
+ $selfDir = self::getSelfDir();
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\\', '/');
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
+ }
+ }
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array() && !$copiedLocalDir) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
--- /dev/null
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
--- /dev/null
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+ 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+ 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+);
--- /dev/null
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
+ 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
+ '51fcf4e06c07cc00c920b44bcd900e7a' => $vendorDir . '/thecodingmachine/safe/deprecated/apc.php',
+ '47f619d9197b36cf5ab70738d7743fe2' => $vendorDir . '/thecodingmachine/safe/deprecated/libevent.php',
+ 'ea6bb8a12ef9b68f6ada99058e530760' => $vendorDir . '/thecodingmachine/safe/deprecated/mssql.php',
+ '9a29089eb3ce41a446744c68a00f118c' => $vendorDir . '/thecodingmachine/safe/deprecated/stats.php',
+ '72243e5536b63e298acb6476f01f1aff' => $vendorDir . '/thecodingmachine/safe/lib/special_cases.php',
+ '3f648889e687f31c52f949ba8a9d0873' => $vendorDir . '/thecodingmachine/safe/generated/apache.php',
+ 'eeb4581d958421a4244aaa4167c6a575' => $vendorDir . '/thecodingmachine/safe/generated/apcu.php',
+ '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => $vendorDir . '/thecodingmachine/safe/generated/array.php',
+ '450b332a74a9a21e043c5e953485a791' => $vendorDir . '/thecodingmachine/safe/generated/bzip2.php',
+ '6e9b7954ecfd7cbb9ca239319d1acdb6' => $vendorDir . '/thecodingmachine/safe/generated/calendar.php',
+ '2c6d7e8bd2de9a272a9d4d43b0a4304a' => $vendorDir . '/thecodingmachine/safe/generated/classobj.php',
+ '0b8231c1ad0865447c988a4c16b4001f' => $vendorDir . '/thecodingmachine/safe/generated/com.php',
+ '7643a71fe1c3256058c8fee234cb86e5' => $vendorDir . '/thecodingmachine/safe/generated/cubrid.php',
+ '68e1365710575942efc1d55000032cee' => $vendorDir . '/thecodingmachine/safe/generated/curl.php',
+ '02fd26bca803106c5b942a7197c3ad8b' => $vendorDir . '/thecodingmachine/safe/generated/datetime.php',
+ 'f4817dcbd956cd221b1c31f6fbd5749c' => $vendorDir . '/thecodingmachine/safe/generated/dir.php',
+ '51c3f2d10ca61a70dbcea0e38d8e902d' => $vendorDir . '/thecodingmachine/safe/generated/eio.php',
+ '1d34f34327ca3e81535963016e3be2c3' => $vendorDir . '/thecodingmachine/safe/generated/errorfunc.php',
+ '4fd0ba2d3717b0424d474bebfdafa2b4' => $vendorDir . '/thecodingmachine/safe/generated/exec.php',
+ '98f4dae054bc7fb19c13be14935cbdd3' => $vendorDir . '/thecodingmachine/safe/generated/fileinfo.php',
+ '5530ae063ba88323eaf0a07904efdf85' => $vendorDir . '/thecodingmachine/safe/generated/filesystem.php',
+ '633f4f134975d70e97bddad83348e91a' => $vendorDir . '/thecodingmachine/safe/generated/filter.php',
+ 'fbd163fc68c5faf73d5ed4002ffd836d' => $vendorDir . '/thecodingmachine/safe/generated/fpm.php',
+ '21b511999d61411fab0692ff8795bbed' => $vendorDir . '/thecodingmachine/safe/generated/ftp.php',
+ '85fbd73fc92365cd90526b0ea03cae3a' => $vendorDir . '/thecodingmachine/safe/generated/funchand.php',
+ '51df9c146e0b7dcbdf358d8abd24dbdc' => $vendorDir . '/thecodingmachine/safe/generated/gmp.php',
+ '93bb7fe678d7dcfb1322f8e3475a48b0' => $vendorDir . '/thecodingmachine/safe/generated/gnupg.php',
+ 'c171ba99cf316379ff66468392bf4950' => $vendorDir . '/thecodingmachine/safe/generated/hash.php',
+ '5ab4aad4c28e468209fbfcceb2e5e6a5' => $vendorDir . '/thecodingmachine/safe/generated/ibase.php',
+ '4d57409c5e8e576b0c64c08d9d731cfb' => $vendorDir . '/thecodingmachine/safe/generated/ibmDb2.php',
+ 'eeb246d5403972a9d62106e4a4883496' => $vendorDir . '/thecodingmachine/safe/generated/iconv.php',
+ 'c28a05f498c01b810a714f7214b7a8da' => $vendorDir . '/thecodingmachine/safe/generated/image.php',
+ '8063cd92acdf00fd978b5599eb7cc142' => $vendorDir . '/thecodingmachine/safe/generated/imap.php',
+ '8bd26dbe768e9c9599edad7b198e5446' => $vendorDir . '/thecodingmachine/safe/generated/info.php',
+ '0c577fe603b029d4b65c84376b15dbd5' => $vendorDir . '/thecodingmachine/safe/generated/ingres-ii.php',
+ 'd4362910bde43c0f956b52527effd7d4' => $vendorDir . '/thecodingmachine/safe/generated/inotify.php',
+ '696ba49197d9b55f0428a12bb5a818e1' => $vendorDir . '/thecodingmachine/safe/generated/json.php',
+ '9818aaa99c8647c63f8ef62b7a368160' => $vendorDir . '/thecodingmachine/safe/generated/ldap.php',
+ 'bcf523ff2a195eb08e0fbb668ed784d0' => $vendorDir . '/thecodingmachine/safe/generated/libxml.php',
+ '68be68a9a8b95bb56cab6109ff03bc88' => $vendorDir . '/thecodingmachine/safe/generated/lzf.php',
+ 'bdca804bb0904ea9f53f328dfc0bb8a5' => $vendorDir . '/thecodingmachine/safe/generated/mailparse.php',
+ 'b0a3fcac3eaf55445796d6af26b89366' => $vendorDir . '/thecodingmachine/safe/generated/mbstring.php',
+ '98de16b8db03eb0cb4d318b4402215a6' => $vendorDir . '/thecodingmachine/safe/generated/misc.php',
+ 'c112440003b56e243b192c11fa9d836e' => $vendorDir . '/thecodingmachine/safe/generated/msql.php',
+ '7cefd81607cd21b8b3a15656eb6465f5' => $vendorDir . '/thecodingmachine/safe/generated/mysql.php',
+ 'aaf438b080089c6d0686679cd34aa72e' => $vendorDir . '/thecodingmachine/safe/generated/mysqli.php',
+ 'df0ef890e9afbf95f3924feb1c7a89f3' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndMs.php',
+ 'db595fee5972867e45c5327010d78735' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndQc.php',
+ 'cbac956836b72483dcff1ac39d5c0a0f' => $vendorDir . '/thecodingmachine/safe/generated/network.php',
+ '6c8f89dfbdc117d7871f572269363f25' => $vendorDir . '/thecodingmachine/safe/generated/oci8.php',
+ '169a669966a45c06bf55ed029122729b' => $vendorDir . '/thecodingmachine/safe/generated/opcache.php',
+ 'def61bf4fecd4d4bca7354919cd69302' => $vendorDir . '/thecodingmachine/safe/generated/openssl.php',
+ '26bb010649a6d32d4120181458aa6ef2' => $vendorDir . '/thecodingmachine/safe/generated/outcontrol.php',
+ '1212c201fe43c7492a085b2c71505e0f' => $vendorDir . '/thecodingmachine/safe/generated/password.php',
+ '002ebcb842e2c0d5b7f67fe64cc93158' => $vendorDir . '/thecodingmachine/safe/generated/pcntl.php',
+ '86df38612982dade72c7085ce7eca81f' => $vendorDir . '/thecodingmachine/safe/generated/pcre.php',
+ '1cacc3e65f82a473fbd5507c7ce4385d' => $vendorDir . '/thecodingmachine/safe/generated/pdf.php',
+ '1fc22f445c69ea8706e82fce301c0831' => $vendorDir . '/thecodingmachine/safe/generated/pgsql.php',
+ 'c70b42561584f7144bff38cd63c4eef3' => $vendorDir . '/thecodingmachine/safe/generated/posix.php',
+ '9923214639c32ca5173db03a177d3b63' => $vendorDir . '/thecodingmachine/safe/generated/ps.php',
+ '7e9c3f8eae2b5bf42205c4f1295cb7a7' => $vendorDir . '/thecodingmachine/safe/generated/pspell.php',
+ '91aa91f6245c349c2e2e88bd0025f199' => $vendorDir . '/thecodingmachine/safe/generated/readline.php',
+ 'd43773cacb9e5e8e897aa255e32007d1' => $vendorDir . '/thecodingmachine/safe/generated/rpminfo.php',
+ 'f053a3849e9e8383762b34b91db0320b' => $vendorDir . '/thecodingmachine/safe/generated/rrd.php',
+ '775b964f72f827a1bf87c65ab5b10800' => $vendorDir . '/thecodingmachine/safe/generated/sem.php',
+ '816428bd69c29ab5e1ed622af5dca0cd' => $vendorDir . '/thecodingmachine/safe/generated/session.php',
+ '5093e233bedbefaef0df262bfbab0a5c' => $vendorDir . '/thecodingmachine/safe/generated/shmop.php',
+ '01352920b0151f17e671266e44b52536' => $vendorDir . '/thecodingmachine/safe/generated/simplexml.php',
+ 'b080617b1d949683c2e37f8f01dc0e15' => $vendorDir . '/thecodingmachine/safe/generated/sockets.php',
+ '2708aa182ddcfe6ce27c96acaaa40f69' => $vendorDir . '/thecodingmachine/safe/generated/sodium.php',
+ 'f1b96cb260a5baeea9a7285cda82a1ec' => $vendorDir . '/thecodingmachine/safe/generated/solr.php',
+ '3fd8853757d0fe3557c179efb807afeb' => $vendorDir . '/thecodingmachine/safe/generated/spl.php',
+ '9312ce96a51c846913fcda5f186d58dd' => $vendorDir . '/thecodingmachine/safe/generated/sqlsrv.php',
+ 'd3eb383ad0b8b962b29dc4afd29d6715' => $vendorDir . '/thecodingmachine/safe/generated/ssdeep.php',
+ '42a09bc448f441a0b9f9367ea975c0bf' => $vendorDir . '/thecodingmachine/safe/generated/ssh2.php',
+ 'ef711077d356d1b33ca0b10b67b0be8f' => $vendorDir . '/thecodingmachine/safe/generated/stream.php',
+ '764b09f6df081cbb2807b97c6ace3866' => $vendorDir . '/thecodingmachine/safe/generated/strings.php',
+ 'ef241678769fee4a44aaa288f3b78aa1' => $vendorDir . '/thecodingmachine/safe/generated/swoole.php',
+ '0efc8f6778cba932b9e2a89e28de2452' => $vendorDir . '/thecodingmachine/safe/generated/uodbc.php',
+ 'd383d32907b98af53ee9208c62204fd0' => $vendorDir . '/thecodingmachine/safe/generated/uopz.php',
+ '2fd2e4060f7fe772660f002ce38f0b71' => $vendorDir . '/thecodingmachine/safe/generated/url.php',
+ '782249e03deebeaf57b9991ff5493aa0' => $vendorDir . '/thecodingmachine/safe/generated/var.php',
+ '344440cd1cd7200fdb4f12af0d3c587f' => $vendorDir . '/thecodingmachine/safe/generated/xdiff.php',
+ '3599f369219c658a5fb6c4fe66832f62' => $vendorDir . '/thecodingmachine/safe/generated/xml.php',
+ '7fcd313da9fae337051b091b3492c21b' => $vendorDir . '/thecodingmachine/safe/generated/xmlrpc.php',
+ 'd668c74cfa92d893b582356733d9a80e' => $vendorDir . '/thecodingmachine/safe/generated/yaml.php',
+ '4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php',
+ 'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php',
+ '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php',
+);
--- /dev/null
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);
--- /dev/null
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Webauthn\\MetadataService\\' => array($vendorDir . '/web-auth/metadata-service/src'),
+ 'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'),
+ 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+ 'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'),
+ 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
+ 'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
+ 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
+ 'League\\Uri\\' => array($vendorDir . '/league/uri/src', $vendorDir . '/league/uri-interfaces/src'),
+ 'FG\\' => array($vendorDir . '/fgrosse/phpasn1/lib'),
+ 'Cose\\' => array($vendorDir . '/web-auth/cose-lib/src'),
+ 'CBOR\\' => array($vendorDir . '/spomky-labs/cbor-php/src'),
+ 'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
+ 'Base64Url\\' => array($vendorDir . '/spomky-labs/base64url/src'),
+ 'Assert\\' => array($vendorDir . '/beberlei/assert/lib/Assert'),
+ 'App\\' => array($baseDir . '/include'),
+);
--- /dev/null
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInite54b3dc12d564adf08ae4c70663f0154
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ require __DIR__ . '/platform_check.php';
+
+ spl_autoload_register(array('ComposerAutoloaderInite54b3dc12d564adf08ae4c70663f0154', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+ spl_autoload_unregister(array('ComposerAutoloaderInite54b3dc12d564adf08ae4c70663f0154', 'loadClassLoader'));
+
+ require __DIR__ . '/autoload_static.php';
+ call_user_func(\Composer\Autoload\ComposerStaticInite54b3dc12d564adf08ae4c70663f0154::getInitializer($loader));
+
+ $loader->register(true);
+
+ $filesToLoad = \Composer\Autoload\ComposerStaticInite54b3dc12d564adf08ae4c70663f0154::$files;
+ $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+ require $file;
+ }
+ }, null, null);
+ foreach ($filesToLoad as $fileIdentifier => $file) {
+ $requireFile($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
--- /dev/null
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInite54b3dc12d564adf08ae4c70663f0154
+{
+ public static $files = array (
+ 'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
+ 'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
+ '51fcf4e06c07cc00c920b44bcd900e7a' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/apc.php',
+ '47f619d9197b36cf5ab70738d7743fe2' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/libevent.php',
+ 'ea6bb8a12ef9b68f6ada99058e530760' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/mssql.php',
+ '9a29089eb3ce41a446744c68a00f118c' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/stats.php',
+ '72243e5536b63e298acb6476f01f1aff' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/special_cases.php',
+ '3f648889e687f31c52f949ba8a9d0873' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apache.php',
+ 'eeb4581d958421a4244aaa4167c6a575' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apcu.php',
+ '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/array.php',
+ '450b332a74a9a21e043c5e953485a791' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/bzip2.php',
+ '6e9b7954ecfd7cbb9ca239319d1acdb6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/calendar.php',
+ '2c6d7e8bd2de9a272a9d4d43b0a4304a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/classobj.php',
+ '0b8231c1ad0865447c988a4c16b4001f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/com.php',
+ '7643a71fe1c3256058c8fee234cb86e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/cubrid.php',
+ '68e1365710575942efc1d55000032cee' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/curl.php',
+ '02fd26bca803106c5b942a7197c3ad8b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/datetime.php',
+ 'f4817dcbd956cd221b1c31f6fbd5749c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/dir.php',
+ '51c3f2d10ca61a70dbcea0e38d8e902d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/eio.php',
+ '1d34f34327ca3e81535963016e3be2c3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/errorfunc.php',
+ '4fd0ba2d3717b0424d474bebfdafa2b4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/exec.php',
+ '98f4dae054bc7fb19c13be14935cbdd3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fileinfo.php',
+ '5530ae063ba88323eaf0a07904efdf85' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filesystem.php',
+ '633f4f134975d70e97bddad83348e91a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filter.php',
+ 'fbd163fc68c5faf73d5ed4002ffd836d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fpm.php',
+ '21b511999d61411fab0692ff8795bbed' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ftp.php',
+ '85fbd73fc92365cd90526b0ea03cae3a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/funchand.php',
+ '51df9c146e0b7dcbdf358d8abd24dbdc' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gmp.php',
+ '93bb7fe678d7dcfb1322f8e3475a48b0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gnupg.php',
+ 'c171ba99cf316379ff66468392bf4950' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/hash.php',
+ '5ab4aad4c28e468209fbfcceb2e5e6a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibase.php',
+ '4d57409c5e8e576b0c64c08d9d731cfb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibmDb2.php',
+ 'eeb246d5403972a9d62106e4a4883496' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/iconv.php',
+ 'c28a05f498c01b810a714f7214b7a8da' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/image.php',
+ '8063cd92acdf00fd978b5599eb7cc142' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/imap.php',
+ '8bd26dbe768e9c9599edad7b198e5446' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/info.php',
+ '0c577fe603b029d4b65c84376b15dbd5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ingres-ii.php',
+ 'd4362910bde43c0f956b52527effd7d4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/inotify.php',
+ '696ba49197d9b55f0428a12bb5a818e1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/json.php',
+ '9818aaa99c8647c63f8ef62b7a368160' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ldap.php',
+ 'bcf523ff2a195eb08e0fbb668ed784d0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/libxml.php',
+ '68be68a9a8b95bb56cab6109ff03bc88' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/lzf.php',
+ 'bdca804bb0904ea9f53f328dfc0bb8a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mailparse.php',
+ 'b0a3fcac3eaf55445796d6af26b89366' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mbstring.php',
+ '98de16b8db03eb0cb4d318b4402215a6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/misc.php',
+ 'c112440003b56e243b192c11fa9d836e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/msql.php',
+ '7cefd81607cd21b8b3a15656eb6465f5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysql.php',
+ 'aaf438b080089c6d0686679cd34aa72e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqli.php',
+ 'df0ef890e9afbf95f3924feb1c7a89f3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndMs.php',
+ 'db595fee5972867e45c5327010d78735' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndQc.php',
+ 'cbac956836b72483dcff1ac39d5c0a0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/network.php',
+ '6c8f89dfbdc117d7871f572269363f25' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/oci8.php',
+ '169a669966a45c06bf55ed029122729b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/opcache.php',
+ 'def61bf4fecd4d4bca7354919cd69302' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/openssl.php',
+ '26bb010649a6d32d4120181458aa6ef2' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/outcontrol.php',
+ '1212c201fe43c7492a085b2c71505e0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/password.php',
+ '002ebcb842e2c0d5b7f67fe64cc93158' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcntl.php',
+ '86df38612982dade72c7085ce7eca81f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcre.php',
+ '1cacc3e65f82a473fbd5507c7ce4385d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pdf.php',
+ '1fc22f445c69ea8706e82fce301c0831' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pgsql.php',
+ 'c70b42561584f7144bff38cd63c4eef3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/posix.php',
+ '9923214639c32ca5173db03a177d3b63' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ps.php',
+ '7e9c3f8eae2b5bf42205c4f1295cb7a7' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pspell.php',
+ '91aa91f6245c349c2e2e88bd0025f199' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/readline.php',
+ 'd43773cacb9e5e8e897aa255e32007d1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rpminfo.php',
+ 'f053a3849e9e8383762b34b91db0320b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rrd.php',
+ '775b964f72f827a1bf87c65ab5b10800' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sem.php',
+ '816428bd69c29ab5e1ed622af5dca0cd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/session.php',
+ '5093e233bedbefaef0df262bfbab0a5c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/shmop.php',
+ '01352920b0151f17e671266e44b52536' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/simplexml.php',
+ 'b080617b1d949683c2e37f8f01dc0e15' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sockets.php',
+ '2708aa182ddcfe6ce27c96acaaa40f69' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sodium.php',
+ 'f1b96cb260a5baeea9a7285cda82a1ec' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/solr.php',
+ '3fd8853757d0fe3557c179efb807afeb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/spl.php',
+ '9312ce96a51c846913fcda5f186d58dd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sqlsrv.php',
+ 'd3eb383ad0b8b962b29dc4afd29d6715' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssdeep.php',
+ '42a09bc448f441a0b9f9367ea975c0bf' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssh2.php',
+ 'ef711077d356d1b33ca0b10b67b0be8f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/stream.php',
+ '764b09f6df081cbb2807b97c6ace3866' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/strings.php',
+ 'ef241678769fee4a44aaa288f3b78aa1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/swoole.php',
+ '0efc8f6778cba932b9e2a89e28de2452' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uodbc.php',
+ 'd383d32907b98af53ee9208c62204fd0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uopz.php',
+ '2fd2e4060f7fe772660f002ce38f0b71' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/url.php',
+ '782249e03deebeaf57b9991ff5493aa0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/var.php',
+ '344440cd1cd7200fdb4f12af0d3c587f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xdiff.php',
+ '3599f369219c658a5fb6c4fe66832f62' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xml.php',
+ '7fcd313da9fae337051b091b3492c21b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xmlrpc.php',
+ 'd668c74cfa92d893b582356733d9a80e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaml.php',
+ '4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php',
+ 'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php',
+ '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'W' =>
+ array (
+ 'Webauthn\\MetadataService\\' => 25,
+ 'Webauthn\\' => 9,
+ ),
+ 'S' =>
+ array (
+ 'Symfony\\Polyfill\\Php80\\' => 23,
+ 'Symfony\\Component\\Process\\' => 26,
+ 'Safe\\' => 5,
+ ),
+ 'R' =>
+ array (
+ 'Ramsey\\Uuid\\' => 12,
+ 'Ramsey\\Collection\\' => 18,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ 'Psr\\Http\\Client\\' => 16,
+ ),
+ 'L' =>
+ array (
+ 'League\\Uri\\' => 11,
+ ),
+ 'F' =>
+ array (
+ 'FG\\' => 3,
+ ),
+ 'C' =>
+ array (
+ 'Cose\\' => 5,
+ 'CBOR\\' => 5,
+ ),
+ 'B' =>
+ array (
+ 'Brick\\Math\\' => 11,
+ 'Base64Url\\' => 10,
+ ),
+ 'A' =>
+ array (
+ 'Assert\\' => 7,
+ 'App\\' => 4,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Webauthn\\MetadataService\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/web-auth/metadata-service/src',
+ ),
+ 'Webauthn\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src',
+ ),
+ 'Symfony\\Polyfill\\Php80\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
+ ),
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ 'Safe\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib',
+ 1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated',
+ 2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated',
+ ),
+ 'Ramsey\\Uuid\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ramsey/uuid/src',
+ ),
+ 'Ramsey\\Collection\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ramsey/collection/src',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-factory/src',
+ 1 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'Psr\\Http\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-client/src',
+ ),
+ 'League\\Uri\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/league/uri/src',
+ 1 => __DIR__ . '/..' . '/league/uri-interfaces/src',
+ ),
+ 'FG\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/fgrosse/phpasn1/lib',
+ ),
+ 'Cose\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/web-auth/cose-lib/src',
+ ),
+ 'CBOR\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spomky-labs/cbor-php/src',
+ ),
+ 'Brick\\Math\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/brick/math/src',
+ ),
+ 'Base64Url\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spomky-labs/base64url/src',
+ ),
+ 'Assert\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/beberlei/assert/lib/Assert',
+ ),
+ 'App\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/include',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+ 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+ 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInite54b3dc12d564adf08ae4c70663f0154::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInite54b3dc12d564adf08ae4c70663f0154::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInite54b3dc12d564adf08ae4c70663f0154::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
--- /dev/null
+{
+ "packages": [
+ {
+ "name": "beberlei/assert",
+ "version": "v3.3.3",
+ "version_normalized": "3.3.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/beberlei/assert.git",
+ "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd",
+ "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-simplexml": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "*",
+ "phpstan/phpstan": "*",
+ "phpunit/phpunit": ">=6.0.0",
+ "yoast/phpunit-polyfills": "^0.1.0"
+ },
+ "suggest": {
+ "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
+ },
+ "time": "2024-07-15T13:18:35+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "lib/Assert/functions.php"
+ ],
+ "psr-4": {
+ "Assert\\": "lib/Assert"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Richard Quadling",
+ "email": "rquadling@gmail.com",
+ "role": "Collaborator"
+ }
+ ],
+ "description": "Thin assertion library for input validation in business models.",
+ "keywords": [
+ "assert",
+ "assertion",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/beberlei/assert/issues",
+ "source": "https://github.com/beberlei/assert/tree/v3.3.3"
+ },
+ "install-path": "../beberlei/assert"
+ },
+ {
+ "name": "brick/math",
+ "version": "0.9.3",
+ "version_normalized": "0.9.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "vimeo/psalm": "4.9.2"
+ },
+ "time": "2021-08-15T20:50:18+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "brick",
+ "math"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/brick/math",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../brick/math"
+ },
+ {
+ "name": "fgrosse/phpasn1",
+ "version": "v2.5.0",
+ "version_normalized": "2.5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fgrosse/PHPASN1.git",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "~2.0",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "suggest": {
+ "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
+ "ext-curl": "For loading OID information from the web if they have not bee defined statically",
+ "ext-gmp": "GMP is the preferred extension for big integer calculations",
+ "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
+ },
+ "time": "2022-12-19T11:08:26+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "FG\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Friedrich Große",
+ "email": "friedrich.grosse@gmail.com",
+ "homepage": "https://github.com/FGrosse",
+ "role": "Author"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/FGrosse/PHPASN1/contributors"
+ }
+ ],
+ "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
+ "homepage": "https://github.com/FGrosse/PHPASN1",
+ "keywords": [
+ "DER",
+ "asn.1",
+ "asn1",
+ "ber",
+ "binary",
+ "decoding",
+ "encoding",
+ "x.509",
+ "x.690",
+ "x509",
+ "x690"
+ ],
+ "support": {
+ "issues": "https://github.com/fgrosse/PHPASN1/issues",
+ "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
+ },
+ "abandoned": true,
+ "install-path": "../fgrosse/phpasn1"
+ },
+ {
+ "name": "league/uri",
+ "version": "6.8.0",
+ "version_normalized": "6.8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri.git",
+ "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39",
+ "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "league/uri-interfaces": "^2.3",
+ "php": "^8.1",
+ "psr/http-message": "^1.0.1"
+ },
+ "conflict": {
+ "league/uri-schemes": "^1.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^v3.9.5",
+ "nyholm/psr7": "^1.5.1",
+ "php-http/psr7-integration-tests": "^1.1.1",
+ "phpbench/phpbench": "^1.2.6",
+ "phpstan/phpstan": "^1.8.5",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.1.1",
+ "phpstan/phpstan-strict-rules": "^1.4.3",
+ "phpunit/phpunit": "^9.5.24",
+ "psr/http-factory": "^1.0.1"
+ },
+ "suggest": {
+ "ext-fileinfo": "Needed to create Data URI from a filepath",
+ "ext-intl": "Needed to improve host validation",
+ "league/uri-components": "Needed to easily manipulate URI objects",
+ "psr/http-factory": "Needed to use the URI factory"
+ },
+ "time": "2022-09-13T19:58:47+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "URI manipulation library",
+ "homepage": "https://uri.thephpleague.com",
+ "keywords": [
+ "data-uri",
+ "file-uri",
+ "ftp",
+ "hostname",
+ "http",
+ "https",
+ "middleware",
+ "parse_str",
+ "parse_url",
+ "psr-7",
+ "query-string",
+ "querystring",
+ "rfc3986",
+ "rfc3987",
+ "rfc6570",
+ "uri",
+ "uri-template",
+ "url",
+ "ws"
+ ],
+ "support": {
+ "docs": "https://uri.thephpleague.com",
+ "forum": "https://thephpleague.slack.com",
+ "issues": "https://github.com/thephpleague/uri/issues",
+ "source": "https://github.com/thephpleague/uri/tree/6.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "install-path": "../league/uri"
+ },
+ {
+ "name": "league/uri-interfaces",
+ "version": "2.3.0",
+ "version_normalized": "2.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri-interfaces.git",
+ "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
+ "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.19",
+ "phpstan/phpstan": "^0.12.90",
+ "phpstan/phpstan-phpunit": "^0.12.19",
+ "phpstan/phpstan-strict-rules": "^0.12.9",
+ "phpunit/phpunit": "^8.5.15 || ^9.5"
+ },
+ "suggest": {
+ "ext-intl": "to use the IDNA feature",
+ "symfony/intl": "to use the IDNA feature via Symfony Polyfill"
+ },
+ "time": "2021-06-28T04:27:21+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "Common interface for URI representation",
+ "homepage": "http://github.com/thephpleague/uri-interfaces",
+ "keywords": [
+ "rfc3986",
+ "rfc3987",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/uri-interfaces/issues",
+ "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "install-path": "../league/uri-interfaces"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "version_normalized": "1.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "time": "2023-09-23T14:17:50+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "install-path": "../psr/http-client"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "time": "2024-04-15T12:06:14+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "install-path": "../psr/http-factory"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.1",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "time": "2023-04-04T09:50:52+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
+ },
+ "install-path": "../psr/http-message"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.4",
+ "version_normalized": "1.1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2021-05-03T11:20:27+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/1.1.4"
+ },
+ "install-path": "../psr/log"
+ },
+ {
+ "name": "ramsey/collection",
+ "version": "2.1.1",
+ "version_normalized": "2.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/collection.git",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "captainhook/plugin-composer": "^5.3",
+ "ergebnis/composer-normalize": "^2.45",
+ "fakerphp/faker": "^1.24",
+ "hamcrest/hamcrest-php": "^2.0",
+ "jangregor/phpstan-prophecy": "^2.1",
+ "mockery/mockery": "^1.6",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpspec/prophecy-phpunit": "^2.3",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5",
+ "ramsey/coding-standard": "^2.3",
+ "ramsey/conventional-commits": "^1.6",
+ "roave/security-advisories": "dev-latest"
+ },
+ "time": "2025-03-22T05:38:12+00:00",
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ },
+ "ramsey/conventional-commits": {
+ "configFile": "conventional-commits.json"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "A PHP library for representing and manipulating collections.",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/collection/issues",
+ "source": "https://github.com/ramsey/collection/tree/2.1.1"
+ },
+ "install-path": "../ramsey/collection"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "4.9.2",
+ "version_normalized": "4.9.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
+ "php": "^8.0",
+ "ramsey/collection": "^1.2 || ^2.0"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.25",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "ergebnis/composer-normalize": "^2.47",
+ "mockery/mockery": "^1.6",
+ "paragonie/random-lib": "^2",
+ "php-mock/php-mock": "^2.6",
+ "php-mock/php-mock-mockery": "^1.5",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpbench/phpbench": "^1.2.14",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "slevomat/coding-standard": "^8.18",
+ "squizlabs/php_codesniffer": "^3.13"
+ },
+ "suggest": {
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "time": "2025-12-14T04:43:48+00:00",
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/uuid/issues",
+ "source": "https://github.com/ramsey/uuid/tree/4.9.2"
+ },
+ "install-path": "../ramsey/uuid"
+ },
+ {
+ "name": "spomky-labs/base64url",
+ "version": "v2.0.4",
+ "version_normalized": "2.0.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/base64url.git",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.11|^0.12",
+ "phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
+ "phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
+ "phpstan/phpstan-phpunit": "^0.11|^0.12",
+ "phpstan/phpstan-strict-rules": "^0.11|^0.12"
+ },
+ "time": "2020-11-03T09:10:25+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Base64Url\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky-Labs/base64url/contributors"
+ }
+ ],
+ "description": "Base 64 URL Safe Encoding/Decoding PHP Library",
+ "homepage": "https://github.com/Spomky-Labs/base64url",
+ "keywords": [
+ "base64",
+ "rfc4648",
+ "safe",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/Spomky-Labs/base64url/issues",
+ "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../spomky-labs/base64url"
+ },
+ {
+ "name": "spomky-labs/cbor-php",
+ "version": "v2.1.0",
+ "version_normalized": "2.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/cbor-php.git",
+ "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/28e2712cfc0b48fae661a48ffc6896d7abe83684",
+ "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.15|^0.9.0",
+ "ext-mbstring": "*",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ekino/phpstan-banned-code": "^1.0",
+ "ext-json": "*",
+ "infection/infection": "^0.18|^0.25",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-beberlei-assert": "^1.0",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.12",
+ "roave/security-advisories": "dev-latest",
+ "symplify/easy-coding-standard": "^10.0"
+ },
+ "suggest": {
+ "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags",
+ "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance"
+ },
+ "time": "2021-12-13T12:46:26+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "CBOR\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors"
+ }
+ ],
+ "description": "CBOR Encoder/Decoder for PHP",
+ "keywords": [
+ "Concise Binary Object Representation",
+ "RFC7049",
+ "cbor"
+ ],
+ "support": {
+ "issues": "https://github.com/Spomky-Labs/cbor-php/issues",
+ "source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../spomky-labs/cbor-php"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.36.0",
+ "version_normalized": "1.36.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "time": "2026-04-10T16:19:22+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/polyfill-php80"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.4.51",
+ "version_normalized": "5.4.51.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+ "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "time": "2026-01-26T15:53:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v5.4.51"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/process"
+ },
+ {
+ "name": "thecodingmachine/safe",
+ "version": "v1.3.3",
+ "version_normalized": "1.3.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thecodingmachine/safe.git",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12",
+ "squizlabs/php_codesniffer": "^3.2",
+ "thecodingmachine/phpstan-strict-rules": "^0.12"
+ },
+ "time": "2020-10-28T17:51:34+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "deprecated/apc.php",
+ "deprecated/libevent.php",
+ "deprecated/mssql.php",
+ "deprecated/stats.php",
+ "lib/special_cases.php",
+ "generated/apache.php",
+ "generated/apcu.php",
+ "generated/array.php",
+ "generated/bzip2.php",
+ "generated/calendar.php",
+ "generated/classobj.php",
+ "generated/com.php",
+ "generated/cubrid.php",
+ "generated/curl.php",
+ "generated/datetime.php",
+ "generated/dir.php",
+ "generated/eio.php",
+ "generated/errorfunc.php",
+ "generated/exec.php",
+ "generated/fileinfo.php",
+ "generated/filesystem.php",
+ "generated/filter.php",
+ "generated/fpm.php",
+ "generated/ftp.php",
+ "generated/funchand.php",
+ "generated/gmp.php",
+ "generated/gnupg.php",
+ "generated/hash.php",
+ "generated/ibase.php",
+ "generated/ibmDb2.php",
+ "generated/iconv.php",
+ "generated/image.php",
+ "generated/imap.php",
+ "generated/info.php",
+ "generated/ingres-ii.php",
+ "generated/inotify.php",
+ "generated/json.php",
+ "generated/ldap.php",
+ "generated/libxml.php",
+ "generated/lzf.php",
+ "generated/mailparse.php",
+ "generated/mbstring.php",
+ "generated/misc.php",
+ "generated/msql.php",
+ "generated/mysql.php",
+ "generated/mysqli.php",
+ "generated/mysqlndMs.php",
+ "generated/mysqlndQc.php",
+ "generated/network.php",
+ "generated/oci8.php",
+ "generated/opcache.php",
+ "generated/openssl.php",
+ "generated/outcontrol.php",
+ "generated/password.php",
+ "generated/pcntl.php",
+ "generated/pcre.php",
+ "generated/pdf.php",
+ "generated/pgsql.php",
+ "generated/posix.php",
+ "generated/ps.php",
+ "generated/pspell.php",
+ "generated/readline.php",
+ "generated/rpminfo.php",
+ "generated/rrd.php",
+ "generated/sem.php",
+ "generated/session.php",
+ "generated/shmop.php",
+ "generated/simplexml.php",
+ "generated/sockets.php",
+ "generated/sodium.php",
+ "generated/solr.php",
+ "generated/spl.php",
+ "generated/sqlsrv.php",
+ "generated/ssdeep.php",
+ "generated/ssh2.php",
+ "generated/stream.php",
+ "generated/strings.php",
+ "generated/swoole.php",
+ "generated/uodbc.php",
+ "generated/uopz.php",
+ "generated/url.php",
+ "generated/var.php",
+ "generated/xdiff.php",
+ "generated/xml.php",
+ "generated/xmlrpc.php",
+ "generated/yaml.php",
+ "generated/yaz.php",
+ "generated/zip.php",
+ "generated/zlib.php"
+ ],
+ "psr-4": {
+ "Safe\\": [
+ "lib/",
+ "deprecated/",
+ "generated/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+ "support": {
+ "issues": "https://github.com/thecodingmachine/safe/issues",
+ "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
+ },
+ "install-path": "../thecodingmachine/safe"
+ },
+ {
+ "name": "web-auth/cose-lib",
+ "version": "v3.3.12",
+ "version_normalized": "3.3.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/cose-lib.git",
+ "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/efa6ec2ba4e840bc1316a493973c9916028afeeb",
+ "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "fgrosse/phpasn1": "^2.1",
+ "php": ">=7.2"
+ },
+ "time": "2021-12-04T12:13:35+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Cose\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/cose/contributors"
+ }
+ ],
+ "description": "CBOR Object Signing and Encryption (COSE) For PHP",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "COSE",
+ "RFC8152"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/cose-lib/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../web-auth/cose-lib"
+ },
+ {
+ "name": "web-auth/metadata-service",
+ "version": "v3.3.12",
+ "version_normalized": "3.3.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/webauthn-metadata-service.git",
+ "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
+ "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "league/uri": "^6.0",
+ "php": ">=7.2",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
+ "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources"
+ },
+ "time": "2021-11-21T11:14:31+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\MetadataService\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/metadata-service/contributors"
+ }
+ ],
+ "description": "Metadata Service for FIDO2/Webauthn",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "FIDO2",
+ "fido",
+ "webauthn"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-auth/webauthn-lib",
+ "install-path": "../web-auth/metadata-service"
+ },
+ {
+ "name": "web-auth/webauthn-lib",
+ "version": "v3.3.12",
+ "version_normalized": "3.3.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-auth/webauthn-lib.git",
+ "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
+ "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
+ "shasum": ""
+ },
+ "require": {
+ "beberlei/assert": "^3.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "fgrosse/phpasn1": "^2.1",
+ "php": ">=7.2",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0",
+ "psr/log": "^1.1",
+ "ramsey/uuid": "^3.8|^4.0",
+ "spomky-labs/base64url": "^2.0",
+ "spomky-labs/cbor-php": "^1.0|^2.0",
+ "symfony/process": "^3.0|^4.0|^5.0",
+ "thecodingmachine/safe": "^1.1",
+ "web-auth/cose-lib": "self.version",
+ "web-auth/metadata-service": "self.version"
+ },
+ "suggest": {
+ "psr/log-implementation": "Recommended to receive logs from the library",
+ "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support"
+ },
+ "time": "2022-02-18T07:13:44+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/webauthn-library/contributors"
+ }
+ ],
+ "description": "FIDO2/Webauthn Support For PHP",
+ "homepage": "https://github.com/web-auth",
+ "keywords": [
+ "FIDO2",
+ "fido",
+ "webauthn"
+ ],
+ "support": {
+ "source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../web-auth/webauthn-lib"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
--- /dev/null
+<?php return array(
+ 'root' => array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => 'd3bdbcb8db1c277dc2757e5eb51951ad975dd2b2',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => 'd3bdbcb8db1c277dc2757e5eb51951ad975dd2b2',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'beberlei/assert' => array(
+ 'pretty_version' => 'v3.3.3',
+ 'version' => '3.3.3.0',
+ 'reference' => 'b5fd8eacd8915a1b627b8bfc027803f1939734dd',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../beberlei/assert',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'brick/math' => array(
+ 'pretty_version' => '0.9.3',
+ 'version' => '0.9.3.0',
+ 'reference' => 'ca57d18f028f84f777b2168cd1911b0dee2343ae',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../brick/math',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'fgrosse/phpasn1' => array(
+ 'pretty_version' => 'v2.5.0',
+ 'version' => '2.5.0.0',
+ 'reference' => '42060ed45344789fb9f21f9f1864fc47b9e3507b',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../fgrosse/phpasn1',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'league/uri' => array(
+ 'pretty_version' => '6.8.0',
+ 'version' => '6.8.0.0',
+ 'reference' => 'a700b4656e4c54371b799ac61e300ab25a2d1d39',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../league/uri',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'league/uri-interfaces' => array(
+ 'pretty_version' => '2.3.0',
+ 'version' => '2.3.0.0',
+ 'reference' => '00e7e2943f76d8cb50c7dfdc2f6dee356e15e383',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../league/uri-interfaces',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/http-client' => array(
+ 'pretty_version' => '1.0.3',
+ 'version' => '1.0.3.0',
+ 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-client',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/http-factory' => array(
+ 'pretty_version' => '1.1.0',
+ 'version' => '1.1.0.0',
+ 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-factory',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/http-message' => array(
+ 'pretty_version' => '1.1',
+ 'version' => '1.1.0.0',
+ 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-message',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/log' => array(
+ 'pretty_version' => '1.1.4',
+ 'version' => '1.1.4.0',
+ 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/log',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ramsey/collection' => array(
+ 'pretty_version' => '2.1.1',
+ 'version' => '2.1.1.0',
+ 'reference' => '344572933ad0181accbf4ba763e85a0306a8c5e2',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ramsey/collection',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ramsey/uuid' => array(
+ 'pretty_version' => '4.9.2',
+ 'version' => '4.9.2.0',
+ 'reference' => '8429c78ca35a09f27565311b98101e2826affde0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ramsey/uuid',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'rhumsaa/uuid' => array(
+ 'dev_requirement' => false,
+ 'replaced' => array(
+ 0 => '4.9.2',
+ ),
+ ),
+ 'spomky-labs/base64url' => array(
+ 'pretty_version' => 'v2.0.4',
+ 'version' => '2.0.4.0',
+ 'reference' => '7752ce931ec285da4ed1f4c5aa27e45e097be61d',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../spomky-labs/base64url',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'spomky-labs/cbor-php' => array(
+ 'pretty_version' => 'v2.1.0',
+ 'version' => '2.1.0.0',
+ 'reference' => '28e2712cfc0b48fae661a48ffc6896d7abe83684',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../spomky-labs/cbor-php',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/polyfill-php80' => array(
+ 'pretty_version' => 'v1.36.0',
+ 'version' => '1.36.0.0',
+ 'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/polyfill-php80',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/process' => array(
+ 'pretty_version' => 'v5.4.51',
+ 'version' => '5.4.51.0',
+ 'reference' => '467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/process',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'thecodingmachine/safe' => array(
+ 'pretty_version' => 'v1.3.3',
+ 'version' => '1.3.3.0',
+ 'reference' => 'a8ab0876305a4cdaef31b2350fcb9811b5608dbc',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../thecodingmachine/safe',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'web-auth/cose-lib' => array(
+ 'pretty_version' => 'v3.3.12',
+ 'version' => '3.3.12.0',
+ 'reference' => 'efa6ec2ba4e840bc1316a493973c9916028afeeb',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../web-auth/cose-lib',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'web-auth/metadata-service' => array(
+ 'pretty_version' => 'v3.3.12',
+ 'version' => '3.3.12.0',
+ 'reference' => 'ef40d2b7b68c4964247d13fab52e2fa8dbd65246',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../web-auth/metadata-service',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'web-auth/webauthn-lib' => array(
+ 'pretty_version' => 'v3.3.12',
+ 'version' => '3.3.12.0',
+ 'reference' => '5ef9b21c8e9f8a817e524ac93290d08a9f065b33',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../web-auth/webauthn-lib',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
--- /dev/null
+<?php
+
+// platform_check.php @generated by Composer
+
+$issues = array();
+
+if (!(PHP_VERSION_ID >= 80100)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues)
+ );
+}
--- /dev/null
+#### v2.5.0 (2022-12)
+* Support PHP 8.2 [#99](https://github.com/fgrosse/PHPASN1/pull/99)
+* PHP 8 compatibility fix for DateTime::getLastErrors [#98](https://github.com/fgrosse/PHPASN1/pull/98)
+* Support more OIDs [#95](https://github.com/fgrosse/PHPASN1/pull/95)
+* FINAL RELEASE. Library is now no longer actively maintained and marked as archived on GitHub
+
+#### v2.4.0 (2021-12)
+* Drop support for PHP 7.0 [#89](https://github.com/fgrosse/PHPASN1/pull/89)
+
+#### v2.3.1 (2021-12)
+* Add `#[\ReturnTypeWillChange]` attributes for PHP 8.1 compatibility [#87](https://github.com/fgrosse/PHPASN1/pull/87)
+
+#### v2.3.0 (2021-04)
+* Allow creating an unsigned CSR and adding the signature later [#82](https://github.com/fgrosse/PHPASN1/pull/82)
+
+#### v2.2.0 (2020-08)
+* support polyfills for bcmath and gmp, and add a composer.json
+ suggestion for the `phpseclib/bcmath_polyfill` for servers unable
+ to install PHP the gmp or bcmath extensions.
+
+#### v.2.1.1 & &v.2.0.2 (2018-12)
+* add stricter validation around some structures, highlighed
+ by wycheproof test suite
+
+#### v.2.1.0 (2018-03)
+* add support for `bcmath` extension (making `gmp` optional) [#68](https://github.com/fgrosse/PHPASN1/pull/68)
+
+#### v.2.0.1 & v.1.5.3 (2017-12)
+* add .gitattributes file to prevent examples and tests to be installed via composer when --prefer-dist was set
+
+#### v.2.0.0 (2017-08)
+* rename `FG\ASN1\Object` to `FG\ASN1\ASNObject` because `Object` is a special class name in the next major PHP release
+ - when you upgrade you have to adapt all corresponding `use` and `extends` statements as well as type hints and all
+ usages of `Object::fromBinary(…)`.
+* generally drop PHP 5.6 support
+
+#### v.1.5.2 (2016-10-29)
+* allow empty octet strings
+
+#### v.1.5.1 (2015-10-02)
+* add keywords to composer.json (this is a version on its own so the keywords are found on a stable version at packagist.org)
+
+#### v.1.5.0 (2015-10-30)
+* fix a bug that would prevent you from decoding context specific tags on multiple objects [#57](https://github.com/fgrosse/PHPASN1/issues/57)
+ - `ExplicitlyTaggedObject::__construct` does now accept multiple objects to be tagged with a single tag
+ - `ExplicitlyTaggedObject::getContent` will now always return an array (even if only one object is tagged)
+
+#### v.1.4.2 (2015-09-29)
+* fix a bug that would prevent you from decoding empty tagged objects [#57](https://github.com/fgrosse/PHPASN1/issues/57)
+
+#### v.1.4.1
+* improve exception messages and general error handling [#55](https ://github.com/fgrosse/PHPASN1/pull/55)
+
+#### v.1.4.0
+* **require PHP 5.6**
+* support big integers (closes #1 and #37)
+* enforce one code style via [styleci.io][9]
+* track code coverage via [coveralls.io][10]
+* replace obsolete `FG\ASN1\Exception\GeneralException` with `\Exception`
+* `Construct` (`Sequence`, `Set`) does now implement `ArrayAccess`, `Countable` and `Iterator` so its easier to use
+* add [`TemplateParser`][11]
--- /dev/null
+Copyright (c) 2012-2015 Friedrich Große <friedrich.grosse@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+PHPASN1
+=======
+
+[](https://github.com/fgrosse/PHPASN1/actions/workflows/phpunit.yml)
+[](https://travis-ci.org/fgrosse/PHPASN1)
+[](https://coveralls.io/github/fgrosse/PHPASN1?branch=master)
+
+[](https://packagist.org/packages/fgrosse/phpasn1)
+[](https://packagist.org/packages/fgrosse/phpasn1)
+[](https://packagist.org/packages/fgrosse/phpasn1)
+[](https://packagist.org/packages/fgrosse/phpasn1)
+
+---
+
+<h2><span style="color:red">Notice: This library is no longer actively maintained!</span></h2>
+If you are currently using PHPASN1, this might not be an immediate problem for you, since this library was always rather stable.
+
+However, you are advised to migrate to alternative packages to ensure that your applications remain functional also with newer PHP versions.
+
+---
+
+A PHP Framework that allows you to encode and decode arbitrary [ASN.1][3] structures
+using the [ITU-T X.690 Encoding Rules][4].
+This encoding is very frequently used in [X.509 PKI environments][5] or the communication between heterogeneous computer systems.
+
+The API allows you to encode ASN.1 structures to create binary data such as certificate
+signing requests (CSR), X.509 certificates or certificate revocation lists (CRL).
+PHPASN1 can also read [BER encoded][6] binary data into separate PHP objects that can be manipulated by the user and reencoded afterwards.
+
+The **changelog** can now be found at [CHANGELOG.md](CHANGELOG.md).
+
+## Dependencies
+
+PHPASN1 requires at least `PHP 7.0` and either the `gmp` or `bcmath` extension.
+Support for older PHP versions (i.e. PHP 5.6) was dropped starting with `v2.0`.
+If you must use an outdated PHP version consider using [PHPASN v1.5][13].
+
+For the loading of object identifier names directly from the web [curl][7] is used.
+
+## Installation
+
+The preferred way to install this library is to rely on [Composer][2]:
+
+```bash
+$ composer require fgrosse/phpasn1
+```
+
+## Usage
+
+### Encoding ASN.1 Structures
+
+PHPASN1 offers you a class for each of the implemented ASN.1 universal types.
+The constructors should be pretty self explanatory so you should have no big trouble getting started.
+All data will be encoded using [DER encoding][8]
+
+```php
+use FG\ASN1\OID;
+use FG\ASN1\Universal\Integer;
+use FG\ASN1\Universal\Boolean;
+use FG\ASN1\Universal\Enumerated;
+use FG\ASN1\Universal\IA5String;
+use FG\ASN1\Universal\ObjectIdentifier;
+use FG\ASN1\Universal\PrintableString;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\Set;
+use FG\ASN1\Universal\NullObject;
+
+$integer = new Integer(123456);
+$boolean = new Boolean(true);
+$enum = new Enumerated(1);
+$ia5String = new IA5String('Hello world');
+
+$asnNull = new NullObject();
+$objectIdentifier1 = new ObjectIdentifier('1.2.250.1.16.9');
+$objectIdentifier2 = new ObjectIdentifier(OID::RSA_ENCRYPTION);
+$printableString = new PrintableString('Foo bar');
+
+$sequence = new Sequence($integer, $boolean, $enum, $ia5String);
+$set = new Set($sequence, $asnNull, $objectIdentifier1, $objectIdentifier2, $printableString);
+
+$myBinary = $sequence->getBinary();
+$myBinary .= $set->getBinary();
+
+echo base64_encode($myBinary);
+```
+
+
+### Decoding binary data
+
+Decoding BER encoded binary data is just as easy as encoding it:
+
+```php
+use FG\ASN1\ASNObject;
+
+$base64String = ...
+$binaryData = base64_decode($base64String);
+$asnObject = ASNObject::fromBinary($binaryData);
+
+
+// do stuff
+```
+
+If you already know exactly how your expected data should look like you can use the `FG\ASN1\TemplateParser`:
+
+```php
+use FG\ASN1\TemplateParser;
+
+// first define your template
+$template = [
+ Identifier::SEQUENCE => [
+ Identifier::SET => [
+ Identifier::OBJECT_IDENTIFIER,
+ Identifier::SEQUENCE => [
+ Identifier::INTEGER,
+ Identifier::BITSTRING,
+ ]
+ ]
+ ]
+];
+
+// if your binary data is not matching the template you provided this will throw an `\Exception`:
+$parser = new TemplateParser();
+$object = $parser->parseBinary($data, $template);
+
+// there is also a convenience function if you parse binary data from base64:
+$object = $parser->parseBase64($data, $template);
+```
+
+You can use this function to make sure your data has exactly the format you are expecting.
+
+### Navigating decoded data
+
+All constructed classes (i.e. `Sequence` and `Set`) can be navigated by array access or using an iterator.
+You can find examples
+[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/Universal/SequenceTest.php#L148-148),
+[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/Universal/SequenceTest.php#L121) and
+[here](https://github.com/fgrosse/PHPASN1/blob/f6442cadda9d36f3518c737e32f28300a588b777/tests/ASN1/TemplateParserTest.php#L45).
+
+
+### Give me more examples!
+
+To see some example usage of the API classes or some generated output check out the [examples](https://github.com/fgrosse/PHPASN1/tree/master/examples).
+
+
+### How do I contribute?
+
+This project is no longer maintained and thus does not accept any new contributions.
+
+### Thanks
+
+To [all contributors][1] so far!
+
+## License
+
+This library is distributed under the [MIT License](LICENSE).
+
+[1]: https://github.com/fgrosse/PHPASN1/graphs/contributors
+[2]: https://getcomposer.org/
+[3]: http://www.itu.int/ITU-T/asn1/
+[4]: http://www.itu.int/ITU-T/recommendations/rec.aspx?rec=x.690
+[5]: http://en.wikipedia.org/wiki/X.509
+[6]: http://en.wikipedia.org/wiki/X.690#BER_encoding
+[7]: http://php.net/manual/en/book.curl.php
+[8]: http://en.wikipedia.org/wiki/X.690#DER_encoding
+[9]: https://styleci.io
+[10]: https://coveralls.io/github/fgrosse/PHPASN1
+[11]: https://github.com/fgrosse/PHPASN1/blob/master/tests/ASN1/TemplateParserTest.php#L16
+[12]: https://groups.google.com/d/forum/phpasn1
+[13]: https://packagist.org/packages/fgrosse/phpasn1#1.5.2
--- /dev/null
+{
+ "name": "fgrosse/phpasn1",
+ "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
+ "type": "library",
+ "homepage": "https://github.com/FGrosse/PHPASN1",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Friedrich Große",
+ "email": "friedrich.grosse@gmail.com",
+ "homepage": "https://github.com/FGrosse",
+ "role": "Author"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/FGrosse/PHPASN1/contributors"
+ }
+ ],
+ "keywords": [ "x690", "x.690", "x.509", "x509", "asn1", "asn.1", "ber", "der", "binary", "encoding", "decoding" ],
+
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+ "php-coveralls/php-coveralls": "~2.0"
+ },
+ "suggest": {
+ "ext-gmp": "GMP is the preferred extension for big integer calculations",
+ "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
+ "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available",
+ "ext-curl": "For loading OID information from the web if they have not bee defined statically"
+ },
+ "autoload": {
+ "psr-4": {
+ "FG\\": "lib/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "FG\\Test\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\Boolean;
+use FG\ASN1\Universal\Enumerated;
+use FG\ASN1\Universal\GeneralizedTime;
+use FG\ASN1\Universal\Integer;
+use FG\ASN1\Universal\NullObject;
+use FG\ASN1\Universal\ObjectIdentifier;
+use FG\ASN1\Universal\RelativeObjectIdentifier;
+use FG\ASN1\Universal\OctetString;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\Set;
+use FG\ASN1\Universal\UTCTime;
+use FG\ASN1\Universal\IA5String;
+use FG\ASN1\Universal\PrintableString;
+use FG\ASN1\Universal\NumericString;
+use FG\ASN1\Universal\UTF8String;
+use FG\ASN1\Universal\UniversalString;
+use FG\ASN1\Universal\CharacterString;
+use FG\ASN1\Universal\GeneralString;
+use FG\ASN1\Universal\VisibleString;
+use FG\ASN1\Universal\GraphicString;
+use FG\ASN1\Universal\BMPString;
+use FG\ASN1\Universal\T61String;
+use FG\ASN1\Universal\ObjectDescriptor;
+use FG\Utility\BigInteger;
+use LogicException;
+
+/**
+ * Class ASNObject is the base class for all concrete ASN.1 objects.
+ */
+abstract class ASNObject implements Parsable
+{
+ private $contentLength;
+ private $nrOfLengthOctets;
+
+ /**
+ * Must return the number of octets of the content part.
+ *
+ * @return int
+ */
+ abstract protected function calculateContentLength();
+
+ /**
+ * Encode the object using DER encoding.
+ *
+ * @see http://en.wikipedia.org/wiki/X.690#DER_encoding
+ *
+ * @return string the binary representation of an objects value
+ */
+ abstract protected function getEncodedValue();
+
+ /**
+ * Return the content of this object in a non encoded form.
+ * This can be used to print the value in human readable form.
+ *
+ * @return mixed
+ */
+ abstract public function getContent();
+
+ /**
+ * Return the object type octet.
+ * This should use the class constants of Identifier.
+ *
+ * @see Identifier
+ *
+ * @return int
+ */
+ abstract public function getType();
+
+ /**
+ * Returns all identifier octets. If an inheriting class models a tag with
+ * the long form identifier format, it MUST reimplement this method to
+ * return all octets of the identifier.
+ *
+ * @throws LogicException If the identifier format is long form
+ *
+ * @return string Identifier as a set of octets
+ */
+ public function getIdentifier()
+ {
+ $firstOctet = $this->getType();
+
+ if (Identifier::isLongForm($firstOctet)) {
+ throw new LogicException(sprintf('Identifier of %s uses the long form and must therefor override "ASNObject::getIdentifier()".', get_class($this)));
+ }
+
+ return chr($firstOctet);
+ }
+
+ /**
+ * Encode this object using DER encoding.
+ *
+ * @return string the full binary representation of the complete object
+ */
+ public function getBinary()
+ {
+ $result = $this->getIdentifier();
+ $result .= $this->createLengthPart();
+ $result .= $this->getEncodedValue();
+
+ return $result;
+ }
+
+ private function createLengthPart()
+ {
+ $contentLength = $this->getContentLength();
+ $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength);
+
+ if ($nrOfLengthOctets == 1) {
+ return chr($contentLength);
+ } else {
+ // the first length octet determines the number subsequent length octets
+ $lengthOctets = chr(0x80 | ($nrOfLengthOctets - 1));
+ for ($shiftLength = 8 * ($nrOfLengthOctets - 2); $shiftLength >= 0; $shiftLength -= 8) {
+ $lengthOctets .= chr($contentLength >> $shiftLength);
+ }
+
+ return $lengthOctets;
+ }
+ }
+
+ protected function getNumberOfLengthOctets($contentLength = null)
+ {
+ if (!isset($this->nrOfLengthOctets)) {
+ if ($contentLength == null) {
+ $contentLength = $this->getContentLength();
+ }
+
+ $this->nrOfLengthOctets = 1;
+ if ($contentLength > 127) {
+ do { // long form
+ $this->nrOfLengthOctets++;
+ $contentLength = $contentLength >> 8;
+ } while ($contentLength > 0);
+ }
+ }
+
+ return $this->nrOfLengthOctets;
+ }
+
+ protected function getContentLength()
+ {
+ if (!isset($this->contentLength)) {
+ $this->contentLength = $this->calculateContentLength();
+ }
+
+ return $this->contentLength;
+ }
+
+ protected function setContentLength($newContentLength)
+ {
+ $this->contentLength = $newContentLength;
+ $this->getNumberOfLengthOctets($newContentLength);
+ }
+
+ /**
+ * Returns the length of the whole object (including the identifier and length octets).
+ */
+ public function getObjectLength()
+ {
+ $nrOfIdentifierOctets = strlen($this->getIdentifier());
+ $contentLength = $this->getContentLength();
+ $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength);
+
+ return $nrOfIdentifierOctets + $nrOfLengthOctets + $contentLength;
+ }
+
+ public function __toString()
+ {
+ return $this->getContent();
+ }
+
+ /**
+ * Returns the name of the ASN.1 Type of this object.
+ *
+ * @see Identifier::getName()
+ */
+ public function getTypeName()
+ {
+ return Identifier::getName($this->getType());
+ }
+
+ /**
+ * @param string $binaryData
+ * @param int $offsetIndex
+ *
+ * @throws ParserException
+ *
+ * @return \FG\ASN1\ASNObject
+ */
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ if (strlen($binaryData) <= $offsetIndex) {
+ throw new ParserException('Can not parse binary from data: Offset index larger than input size', $offsetIndex);
+ }
+
+ $identifierOctet = ord($binaryData[$offsetIndex]);
+ if (Identifier::isContextSpecificClass($identifierOctet) && Identifier::isConstructed($identifierOctet)) {
+ return ExplicitlyTaggedObject::fromBinary($binaryData, $offsetIndex);
+ }
+
+ switch ($identifierOctet) {
+ case Identifier::BITSTRING:
+ return BitString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::BOOLEAN:
+ return Boolean::fromBinary($binaryData, $offsetIndex);
+ case Identifier::ENUMERATED:
+ return Enumerated::fromBinary($binaryData, $offsetIndex);
+ case Identifier::INTEGER:
+ return Integer::fromBinary($binaryData, $offsetIndex);
+ case Identifier::NULL:
+ return NullObject::fromBinary($binaryData, $offsetIndex);
+ case Identifier::OBJECT_IDENTIFIER:
+ return ObjectIdentifier::fromBinary($binaryData, $offsetIndex);
+ case Identifier::RELATIVE_OID:
+ return RelativeObjectIdentifier::fromBinary($binaryData, $offsetIndex);
+ case Identifier::OCTETSTRING:
+ return OctetString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::SEQUENCE:
+ return Sequence::fromBinary($binaryData, $offsetIndex);
+ case Identifier::SET:
+ return Set::fromBinary($binaryData, $offsetIndex);
+ case Identifier::UTC_TIME:
+ return UTCTime::fromBinary($binaryData, $offsetIndex);
+ case Identifier::GENERALIZED_TIME:
+ return GeneralizedTime::fromBinary($binaryData, $offsetIndex);
+ case Identifier::IA5_STRING:
+ return IA5String::fromBinary($binaryData, $offsetIndex);
+ case Identifier::PRINTABLE_STRING:
+ return PrintableString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::NUMERIC_STRING:
+ return NumericString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::UTF8_STRING:
+ return UTF8String::fromBinary($binaryData, $offsetIndex);
+ case Identifier::UNIVERSAL_STRING:
+ return UniversalString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::CHARACTER_STRING:
+ return CharacterString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::GENERAL_STRING:
+ return GeneralString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::VISIBLE_STRING:
+ return VisibleString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::GRAPHIC_STRING:
+ return GraphicString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::BMP_STRING:
+ return BMPString::fromBinary($binaryData, $offsetIndex);
+ case Identifier::T61_STRING:
+ return T61String::fromBinary($binaryData, $offsetIndex);
+ case Identifier::OBJECT_DESCRIPTOR:
+ return ObjectDescriptor::fromBinary($binaryData, $offsetIndex);
+ default:
+ // At this point the identifier may be >1 byte.
+ if (Identifier::isConstructed($identifierOctet)) {
+ return new UnknownConstructedObject($binaryData, $offsetIndex);
+ } else {
+ $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex);
+ $lengthOfUnknownObject = self::parseContentLength($binaryData, $offsetIndex);
+ $offsetIndex += $lengthOfUnknownObject;
+
+ return new UnknownObject($identifier, $lengthOfUnknownObject);
+ }
+ }
+ }
+
+ protected static function parseIdentifier($identifierOctet, $expectedIdentifier, $offsetForExceptionHandling)
+ {
+ if (is_string($identifierOctet) || is_numeric($identifierOctet) == false) {
+ $identifierOctet = ord($identifierOctet);
+ }
+
+ if ($identifierOctet != $expectedIdentifier) {
+ $message = 'Can not create an '.Identifier::getName($expectedIdentifier).' from an '.Identifier::getName($identifierOctet);
+ throw new ParserException($message, $offsetForExceptionHandling);
+ }
+ }
+
+ protected static function parseBinaryIdentifier($binaryData, &$offsetIndex)
+ {
+ if (strlen($binaryData) <= $offsetIndex) {
+ throw new ParserException('Can not parse identifier from data: Offset index larger than input size', $offsetIndex);
+ }
+
+ $identifier = $binaryData[$offsetIndex++];
+
+ if (Identifier::isLongForm(ord($identifier)) == false) {
+ return $identifier;
+ }
+
+ while (true) {
+ if (strlen($binaryData) <= $offsetIndex) {
+ throw new ParserException('Can not parse identifier (long form) from data: Offset index larger than input size', $offsetIndex);
+ }
+ $nextOctet = $binaryData[$offsetIndex++];
+ $identifier .= $nextOctet;
+
+ if ((ord($nextOctet) & 0x80) === 0) {
+ // the most significant bit is 0 to we have reached the end of the identifier
+ break;
+ }
+ }
+
+ return $identifier;
+ }
+
+ protected static function parseContentLength(&$binaryData, &$offsetIndex, $minimumLength = 0)
+ {
+ if (strlen($binaryData) <= $offsetIndex) {
+ throw new ParserException('Can not parse content length from data: Offset index larger than input size', $offsetIndex);
+ }
+
+ $contentLength = ord($binaryData[$offsetIndex++]);
+ if (($contentLength & 0x80) != 0) {
+ // bit 8 is set -> this is the long form
+ $nrOfLengthOctets = $contentLength & 0x7F;
+ $contentLength = BigInteger::create(0x00);
+ for ($i = 0; $i < $nrOfLengthOctets; $i++) {
+ if (strlen($binaryData) <= $offsetIndex) {
+ throw new ParserException('Can not parse content length (long form) from data: Offset index larger than input size', $offsetIndex);
+ }
+ $contentLength = $contentLength->shiftLeft(8)->add(ord($binaryData[$offsetIndex++]));
+ }
+
+ if ($contentLength->compare(PHP_INT_MAX) > 0) {
+ throw new ParserException("Can not parse content length from data: length > maximum integer", $offsetIndex);
+ }
+
+ $contentLength = $contentLength->toInteger();
+ }
+
+ if ($contentLength < $minimumLength) {
+ throw new ParserException('A '.get_called_class()." should have a content length of at least {$minimumLength}. Extracted length was {$contentLength}", $offsetIndex);
+ }
+
+ $lenDataRemaining = strlen($binaryData) - $offsetIndex;
+
+ if ($lenDataRemaining < $contentLength) {
+ throw new ParserException("Content length {$contentLength} exceeds remaining data length {$lenDataRemaining}", $offsetIndex);
+ }
+
+ return $contentLength;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use Exception;
+
+abstract class AbstractString extends ASNObject implements Parsable
+{
+ /** @var string */
+ protected $value;
+ private $checkStringForIllegalChars = true;
+ private $allowedCharacters = [];
+
+ /**
+ * The abstract base class for ASN.1 classes which represent some string of character.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ protected function allowCharacter($character)
+ {
+ $this->allowedCharacters[] = $character;
+ }
+
+ protected function allowCharacters(...$characters)
+ {
+ foreach ($characters as $character) {
+ $this->allowedCharacters[] = $character;
+ }
+ }
+
+ protected function allowNumbers()
+ {
+ foreach (range('0', '9') as $char) {
+ $this->allowedCharacters[] = (string) $char;
+ }
+ }
+
+ protected function allowAllLetters()
+ {
+ $this->allowSmallLetters();
+ $this->allowCapitalLetters();
+ }
+
+ protected function allowSmallLetters()
+ {
+ foreach (range('a', 'z') as $char) {
+ $this->allowedCharacters[] = $char;
+ }
+ }
+
+ protected function allowCapitalLetters()
+ {
+ foreach (range('A', 'Z') as $char) {
+ $this->allowedCharacters[] = $char;
+ }
+ }
+
+ protected function allowSpaces()
+ {
+ $this->allowedCharacters[] = ' ';
+ }
+
+ protected function allowAll()
+ {
+ $this->checkStringForIllegalChars = false;
+ }
+
+ protected function calculateContentLength()
+ {
+ return strlen($this->value);
+ }
+
+ protected function getEncodedValue()
+ {
+ if ($this->checkStringForIllegalChars) {
+ $this->checkString();
+ }
+
+ return $this->value;
+ }
+
+ protected function checkString()
+ {
+ $stringLength = $this->getContentLength();
+ for ($i = 0; $i < $stringLength; $i++) {
+ if (in_array($this->value[$i], $this->allowedCharacters) == false) {
+ $typeName = Identifier::getName($this->getType());
+ throw new Exception("Could not create a {$typeName} from the character sequence '{$this->value}'.");
+ }
+ }
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ $parsedObject = new static('');
+
+ self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+ $string = substr($binaryData, $offsetIndex, $contentLength);
+ $offsetIndex += $contentLength;
+
+ $parsedObject->value = $string;
+ $parsedObject->setContentLength($contentLength);
+ return $parsedObject;
+ }
+
+ public static function isValid($string)
+ {
+ $testObject = new static($string);
+ try {
+ $testObject->checkString();
+
+ return true;
+ } catch (Exception $exception) {
+ return false;
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use DateInterval;
+use DateTime;
+use DateTimeZone;
+use Exception;
+
+abstract class AbstractTime extends ASNObject
+{
+ /** @var DateTime */
+ protected $value;
+
+ public function __construct($dateTime = null, $dateTimeZone = 'UTC')
+ {
+ if ($dateTime == null || is_string($dateTime)) {
+ $timeZone = new DateTimeZone($dateTimeZone);
+ $dateTimeObject = new DateTime($dateTime, $timeZone);
+ if ($dateTimeObject == false) {
+ $errorMessage = $this->getLastDateTimeErrors();
+ $className = Identifier::getName($this->getType());
+ throw new Exception(sprintf("Could not create %s from date time string '%s': %s", $className, $dateTime, $errorMessage));
+ }
+ $dateTime = $dateTimeObject;
+ } elseif (!$dateTime instanceof DateTime) {
+ throw new Exception('Invalid first argument for some instance of AbstractTime constructor');
+ }
+
+ $this->value = $dateTime;
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ protected function getLastDateTimeErrors()
+ {
+ $messages = '';
+ $lastErrors = DateTime::getLastErrors() ?: ['errors' => []];
+ foreach ($lastErrors['errors'] as $errorMessage) {
+ $messages .= "{$errorMessage}, ";
+ }
+
+ return substr($messages, 0, -2);
+ }
+
+ public function __toString()
+ {
+ return $this->value->format("Y-m-d\tH:i:s");
+ }
+
+ protected static function extractTimeZoneData(&$binaryData, &$offsetIndex, DateTime $dateTime)
+ {
+ $sign = $binaryData[$offsetIndex++];
+ $timeOffsetHours = intval(substr($binaryData, $offsetIndex, 2));
+ $timeOffsetMinutes = intval(substr($binaryData, $offsetIndex + 2, 2));
+ $offsetIndex += 4;
+
+ $interval = new DateInterval("PT{$timeOffsetHours}H{$timeOffsetMinutes}M");
+ if ($sign == '+') {
+ $dateTime->sub($interval);
+ } else {
+ $dateTime->add($interval);
+ }
+
+ return $dateTime;
+ }
+}
--- /dev/null
+<?php
+
+namespace FG\ASN1;
+
+use FG\Utility\BigInteger;
+use InvalidArgumentException;
+
+/**
+ * A base-128 decoder.
+ */
+class Base128
+{
+ /**
+ * @param int $value
+ *
+ * @return string
+ */
+ public static function encode($value)
+ {
+ $value = BigInteger::create($value);
+ $octets = chr($value->modulus(0x80)->toInteger());
+
+ $value = $value->shiftRight(7);
+ while ($value->compare(0) > 0) {
+ $octets .= chr(0x80 | $value->modulus(0x80)->toInteger());
+ $value = $value->shiftRight(7);
+ }
+
+ return strrev($octets);
+ }
+
+ /**
+ * @param string $octets
+ *
+ * @throws InvalidArgumentException if the given octets represent a malformed base-128 value or the decoded value would exceed the the maximum integer length
+ *
+ * @return int
+ */
+ public static function decode($octets)
+ {
+ $bitsPerOctet = 7;
+ $value = BigInteger::create(0);
+ $i = 0;
+
+ while (true) {
+ if (!isset($octets[$i])) {
+ throw new InvalidArgumentException(sprintf('Malformed base-128 encoded value (0x%s).', strtoupper(bin2hex($octets)) ?: '0'));
+ }
+
+ $octet = ord($octets[$i++]);
+
+ $l1 = $value->shiftLeft($bitsPerOctet);
+ $r1 = $octet & 0x7f;
+ $value = $l1->add($r1);
+
+ if (0 === ($octet & 0x80)) {
+ break;
+ }
+ }
+
+ return (string)$value;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Composite;
+
+use FG\ASN1\ASNObject;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\ObjectIdentifier;
+
+class AttributeTypeAndValue extends Sequence
+{
+ /**
+ * @param ObjectIdentifier|string $objIdentifier
+ * @param \FG\ASN1\ASNObject $value
+ */
+ public function __construct($objIdentifier, ASNObject $value)
+ {
+ if ($objIdentifier instanceof ObjectIdentifier == false) {
+ $objIdentifier = new ObjectIdentifier($objIdentifier);
+ }
+ parent::__construct($objIdentifier, $value);
+ }
+
+ public function __toString()
+ {
+ return $this->children[0].': '.$this->children[1];
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Composite;
+
+use FG\ASN1\Universal\PrintableString;
+use FG\ASN1\Universal\IA5String;
+use FG\ASN1\Universal\UTF8String;
+
+class RDNString extends RelativeDistinguishedName
+{
+ /**
+ * @param string|\FG\ASN1\Universal\ObjectIdentifier $objectIdentifierString
+ * @param string|\FG\ASN1\ASNObject $value
+ */
+ public function __construct($objectIdentifierString, $value)
+ {
+ if (PrintableString::isValid($value)) {
+ $value = new PrintableString($value);
+ } else {
+ if (IA5String::isValid($value)) {
+ $value = new IA5String($value);
+ } else {
+ $value = new UTF8String($value);
+ }
+ }
+
+ parent::__construct($objectIdentifierString, $value);
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Composite;
+
+use FG\ASN1\Exception\NotImplementedException;
+use FG\ASN1\ASNObject;
+use FG\ASN1\Universal\Set;
+
+class RelativeDistinguishedName extends Set
+{
+ /**
+ * @param string|\FG\ASN1\Universal\ObjectIdentifier $objIdentifierString
+ * @param \FG\ASN1\ASNObject $value
+ */
+ public function __construct($objIdentifierString, ASNObject $value)
+ {
+ // TODO: This does only support one element in the RelativeDistinguishedName Set but it it is defined as follows:
+ // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+ parent::__construct(new AttributeTypeAndValue($objIdentifierString, $value));
+ }
+
+ public function getContent()
+ {
+ /** @var \FG\ASN1\ASNObject $firstObject */
+ $firstObject = $this->children[0];
+ return $firstObject->__toString();
+ }
+
+ /**
+ * At the current version this code can not work since the implementation of Construct requires
+ * the class to support a constructor without arguments.
+ *
+ * @deprecated this function is not yet implemented! Feel free to submit a pull request on github
+ * @param string $binaryData
+ * @param int $offsetIndex
+ * @throws NotImplementedException
+ */
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ throw new NotImplementedException();
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use FG\ASN1\Exception\ParserException;
+use Iterator;
+
+abstract class Construct extends ASNObject implements Countable, ArrayAccess, Iterator, Parsable
+{
+ /** @var \FG\ASN1\ASNObject[] */
+ protected $children;
+ private $iteratorPosition;
+
+ /**
+ * @param \FG\ASN1\ASNObject[] $children the variadic type hint is commented due to https://github.com/facebook/hhvm/issues/4858
+ */
+ public function __construct(/* HH_FIXME[4858]: variadic + strict */ ...$children)
+ {
+ $this->children = $children;
+ $this->iteratorPosition = 0;
+ }
+
+ public function getContent()
+ {
+ return $this->children;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->iteratorPosition = 0;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->children[$this->iteratorPosition];
+ }
+
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->iteratorPosition;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function next()
+ {
+ $this->iteratorPosition++;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function valid()
+ {
+ return isset($this->children[$this->iteratorPosition]);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->children);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->children[$offset];
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ if ($offset === null) {
+ $offset = count($this->children);
+ }
+
+ $this->children[$offset] = $value;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ unset($this->children[$offset]);
+ }
+
+ protected function calculateContentLength()
+ {
+ $length = 0;
+ foreach ($this->children as $component) {
+ $length += $component->getObjectLength();
+ }
+
+ return $length;
+ }
+
+ protected function getEncodedValue()
+ {
+ $result = '';
+ foreach ($this->children as $component) {
+ $result .= $component->getBinary();
+ }
+
+ return $result;
+ }
+
+ public function addChild(ASNObject $child)
+ {
+ $this->children[] = $child;
+ }
+
+ public function addChildren(array $children)
+ {
+ foreach ($children as $child) {
+ $this->addChild($child);
+ }
+ }
+
+ public function __toString()
+ {
+ $nrOfChildren = $this->getNumberOfChildren();
+ $childString = $nrOfChildren == 1 ? 'child' : 'children';
+
+ return "[{$nrOfChildren} {$childString}]";
+ }
+
+ public function getNumberOfChildren()
+ {
+ return count($this->children);
+ }
+
+ /**
+ * @return \FG\ASN1\ASNObject[]
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * @return \FG\ASN1\ASNObject
+ */
+ public function getFirstChild()
+ {
+ return $this->children[0];
+ }
+
+ /**
+ * @param string $binaryData
+ * @param int $offsetIndex
+ *
+ * @throws Exception\ParserException
+ *
+ * @return Construct|static
+ */
+ #[\ReturnTypeWillChange]
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ $parsedObject = new static();
+ self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+ $startIndex = $offsetIndex;
+
+ $children = [];
+ $octetsToRead = $contentLength;
+ while ($octetsToRead > 0) {
+ $newChild = ASNObject::fromBinary($binaryData, $offsetIndex);
+ $octetsToRead -= $newChild->getObjectLength();
+ $children[] = $newChild;
+ }
+
+ if ($octetsToRead !== 0) {
+ throw new ParserException("Sequence length incorrect", $startIndex);
+ }
+
+ $parsedObject->addChildren($children);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count($mode = COUNT_NORMAL)
+ {
+ return count($this->children, $mode);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->children);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Exception;
+
+class NotImplementedException extends \Exception
+{
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Exception;
+
+class ParserException extends \Exception
+{
+ private $errorMessage;
+ private $offset;
+
+ public function __construct($errorMessage, $offset)
+ {
+ $this->errorMessage = $errorMessage;
+ $this->offset = $offset;
+ parent::__construct("ASN.1 Parser Exception at offset {$this->offset}: {$this->errorMessage}");
+ }
+
+ public function getOffset()
+ {
+ return $this->offset;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use FG\ASN1\Exception\ParserException;
+
+/**
+ * Class ExplicitlyTaggedObject decorate an inner object with an additional tag that gives information about
+ * its context specific meaning.
+ *
+ * Explanation taken from A Layman's Guide to a Subset of ASN.1, BER, and DER:
+ * >>> An RSA Laboratories Technical Note
+ * >>> Burton S. Kaliski Jr.
+ * >>> Revised November 1, 1993
+ *
+ * [...]
+ * Explicitly tagged types are derived from other types by adding an outer tag to the underlying type.
+ * In effect, explicitly tagged types are structured types consisting of one component, the underlying type.
+ * Explicit tagging is denoted by the ASN.1 keywords [class number] EXPLICIT (see Section 5.2).
+ * [...]
+ *
+ * @see http://luca.ntop.org/Teaching/Appunti/asn1.html
+ */
+class ExplicitlyTaggedObject extends ASNObject
+{
+ /** @var \FG\ASN1\ASNObject[] */
+ private $decoratedObjects;
+ private $tag;
+
+ /**
+ * @param int $tag
+ * @param \FG\ASN1\ASNObject $objects,...
+ */
+ public function __construct($tag, /* HH_FIXME[4858]: variadic + strict */ ...$objects)
+ {
+ $this->tag = $tag;
+ $this->decoratedObjects = $objects;
+ }
+
+ protected function calculateContentLength()
+ {
+ $length = 0;
+ foreach ($this->decoratedObjects as $object) {
+ $length += $object->getObjectLength();
+ }
+
+ return $length;
+ }
+
+ protected function getEncodedValue()
+ {
+ $encoded = '';
+ foreach ($this->decoratedObjects as $object) {
+ $encoded .= $object->getBinary();
+ }
+
+ return $encoded;
+ }
+
+ public function getContent()
+ {
+ return $this->decoratedObjects;
+ }
+
+ public function __toString()
+ {
+ switch ($length = count($this->decoratedObjects)) {
+ case 0:
+ return "Context specific empty object with tag [{$this->tag}]";
+ case 1:
+ $decoratedType = Identifier::getShortName($this->decoratedObjects[0]->getType());
+ return "Context specific $decoratedType with tag [{$this->tag}]";
+ default:
+ return "$length context specific objects with tag [{$this->tag}]";
+ }
+ }
+
+ public function getType()
+ {
+ return ord($this->getIdentifier());
+ }
+
+ public function getIdentifier()
+ {
+ $identifier = Identifier::create(Identifier::CLASS_CONTEXT_SPECIFIC, true, $this->tag);
+
+ return is_int($identifier) ? chr($identifier) : $identifier;
+ }
+
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex);
+ $firstIdentifierOctet = ord($identifier);
+ assert(Identifier::isContextSpecificClass($firstIdentifierOctet), 'identifier octet should indicate context specific class');
+ assert(Identifier::isConstructed($firstIdentifierOctet), 'identifier octet should indicate constructed object');
+ $tag = Identifier::getTagNumber($identifier);
+
+ $totalContentLength = self::parseContentLength($binaryData, $offsetIndex);
+ $remainingContentLength = $totalContentLength;
+
+ $offsetIndexOfDecoratedObject = $offsetIndex;
+ $decoratedObjects = [];
+
+ while ($remainingContentLength > 0) {
+ $nextObject = ASNObject::fromBinary($binaryData, $offsetIndex);
+ $remainingContentLength -= $nextObject->getObjectLength();
+ $decoratedObjects[] = $nextObject;
+ }
+
+ if ($remainingContentLength != 0) {
+ throw new ParserException("Context-Specific explicitly tagged object [$tag] starting at offset $offsetIndexOfDecoratedObject specifies a length of $totalContentLength octets but $remainingContentLength remain after parsing the content", $offsetIndexOfDecoratedObject);
+ }
+
+ $parsedObject = new self($tag, ...$decoratedObjects);
+ $parsedObject->setContentLength($totalContentLength);
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use Exception;
+
+/**
+ * The Identifier encodes the ASN.1 tag (class and number) of the type of a data value.
+ *
+ * Every identifier whose number is in the range 0 to 30 has the following structure:
+ *
+ * Bits: 8 7 6 5 4 3 2 1
+ * | Class | P/C | Tag number |
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Bits 8 and 7 define the class of this type ( Universal, Application, Context-specific or Private).
+ * Bit 6 encoded whether this type is primitive or constructed
+ * The remaining bits 5 - 1 encode the tag number
+ */
+class Identifier
+{
+ const CLASS_UNIVERSAL = 0x00;
+ const CLASS_APPLICATION = 0x01;
+ const CLASS_CONTEXT_SPECIFIC = 0x02;
+ const CLASS_PRIVATE = 0x03;
+
+ const EOC = 0x00; // unsupported for now
+ const BOOLEAN = 0x01;
+ const INTEGER = 0x02;
+ const BITSTRING = 0x03;
+ const OCTETSTRING = 0x04;
+ const NULL = 0x05;
+ const OBJECT_IDENTIFIER = 0x06;
+ const OBJECT_DESCRIPTOR = 0x07;
+ const EXTERNAL = 0x08; // unsupported for now
+ const REAL = 0x09; // unsupported for now
+ const ENUMERATED = 0x0A;
+ const EMBEDDED_PDV = 0x0B; // unsupported for now
+ const UTF8_STRING = 0x0C;
+ const RELATIVE_OID = 0x0D;
+ // value 0x0E and 0x0F are reserved for future use
+
+ const SEQUENCE = 0x30;
+ const SET = 0x31;
+ const NUMERIC_STRING = 0x12;
+ const PRINTABLE_STRING = 0x13;
+ const T61_STRING = 0x14; // sometimes referred to as TeletextString
+ const VIDEOTEXT_STRING = 0x15;
+ const IA5_STRING = 0x16;
+ const UTC_TIME = 0x17;
+ const GENERALIZED_TIME = 0x18;
+ const GRAPHIC_STRING = 0x19;
+ const VISIBLE_STRING = 0x1A;
+ const GENERAL_STRING = 0x1B;
+ const UNIVERSAL_STRING = 0x1C;
+ const CHARACTER_STRING = 0x1D; // Unrestricted character type
+ const BMP_STRING = 0x1E;
+
+ const LONG_FORM = 0x1F;
+ const IS_CONSTRUCTED = 0x20;
+
+ /**
+ * Creates an identifier. Short form identifiers are returned as integers
+ * for BC, long form identifiers will be returned as a string of octets.
+ *
+ * @param int $class
+ * @param bool $isConstructed
+ * @param int $tagNumber
+ *
+ * @throws Exception if the given arguments are invalid
+ *
+ * @return int|string
+ */
+ public static function create($class, $isConstructed, $tagNumber)
+ {
+ if (!is_numeric($class) || $class < self::CLASS_UNIVERSAL || $class > self::CLASS_PRIVATE) {
+ throw new Exception(sprintf('Invalid class %d given', $class));
+ }
+
+ if (!is_bool($isConstructed)) {
+ throw new Exception("\$isConstructed must be a boolean value ($isConstructed given)");
+ }
+
+ $tagNumber = self::makeNumeric($tagNumber);
+ if ($tagNumber < 0) {
+ throw new Exception(sprintf('Invalid $tagNumber %d given. You can only use positive integers.', $tagNumber));
+ }
+
+ if ($tagNumber < self::LONG_FORM) {
+ return ($class << 6) | ($isConstructed << 5) | $tagNumber;
+ }
+
+ $firstOctet = ($class << 6) | ($isConstructed << 5) | self::LONG_FORM;
+
+ // Tag numbers formatted in long form are base-128 encoded. See X.609#8.1.2.4
+ return chr($firstOctet).Base128::encode($tagNumber);
+ }
+
+ public static function isConstructed($identifierOctet)
+ {
+ return ($identifierOctet & self::IS_CONSTRUCTED) === self::IS_CONSTRUCTED;
+ }
+
+ public static function isLongForm($identifierOctet)
+ {
+ return ($identifierOctet & self::LONG_FORM) === self::LONG_FORM;
+ }
+
+ /**
+ * Return the name of the mapped ASN.1 type with a preceding "ASN.1 ".
+ *
+ * Example: ASN.1 Octet String
+ *
+ * @see Identifier::getShortName()
+ *
+ * @param int|string $identifier
+ *
+ * @return string
+ */
+ public static function getName($identifier)
+ {
+ $identifierOctet = self::makeNumeric($identifier);
+
+ $typeName = static::getShortName($identifier);
+
+ if (($identifierOctet & self::LONG_FORM) < self::LONG_FORM) {
+ $typeName = "ASN.1 {$typeName}";
+ }
+
+ return $typeName;
+ }
+
+ /**
+ * Return the short version of the type name.
+ *
+ * If the given identifier octet can be mapped to a known universal type this will
+ * return its name. Else Identifier::getClassDescription() is used to retrieve
+ * information about the identifier.
+ *
+ * @see Identifier::getName()
+ * @see Identifier::getClassDescription()
+ *
+ * @param int|string $identifier
+ *
+ * @return string
+ */
+ public static function getShortName($identifier)
+ {
+ $identifierOctet = self::makeNumeric($identifier);
+
+ switch ($identifierOctet) {
+ case self::EOC:
+ return 'End-of-contents octet';
+ case self::BOOLEAN:
+ return 'Boolean';
+ case self::INTEGER:
+ return 'Integer';
+ case self::BITSTRING:
+ return 'Bit String';
+ case self::OCTETSTRING:
+ return 'Octet String';
+ case self::NULL:
+ return 'NULL';
+ case self::OBJECT_IDENTIFIER:
+ return 'Object Identifier';
+ case self::OBJECT_DESCRIPTOR:
+ return 'Object Descriptor';
+ case self::EXTERNAL:
+ return 'External Type';
+ case self::REAL:
+ return 'Real';
+ case self::ENUMERATED:
+ return 'Enumerated';
+ case self::EMBEDDED_PDV:
+ return 'Embedded PDV';
+ case self::UTF8_STRING:
+ return 'UTF8 String';
+ case self::RELATIVE_OID:
+ return 'Relative OID';
+ case self::SEQUENCE:
+ return 'Sequence';
+ case self::SET:
+ return 'Set';
+ case self::NUMERIC_STRING:
+ return 'Numeric String';
+ case self::PRINTABLE_STRING:
+ return 'Printable String';
+ case self::T61_STRING:
+ return 'T61 String';
+ case self::VIDEOTEXT_STRING:
+ return 'Videotext String';
+ case self::IA5_STRING:
+ return 'IA5 String';
+ case self::UTC_TIME:
+ return 'UTC Time';
+ case self::GENERALIZED_TIME:
+ return 'Generalized Time';
+ case self::GRAPHIC_STRING:
+ return 'Graphic String';
+ case self::VISIBLE_STRING:
+ return 'Visible String';
+ case self::GENERAL_STRING:
+ return 'General String';
+ case self::UNIVERSAL_STRING:
+ return 'Universal String';
+ case self::CHARACTER_STRING:
+ return 'Character String';
+ case self::BMP_STRING:
+ return 'BMP String';
+
+ case 0x0E:
+ return 'RESERVED (0x0E)';
+ case 0x0F:
+ return 'RESERVED (0x0F)';
+
+ case self::LONG_FORM:
+ default:
+ $classDescription = self::getClassDescription($identifier);
+
+ if (is_int($identifier)) {
+ $identifier = chr($identifier);
+ }
+
+ return "$classDescription (0x".strtoupper(bin2hex($identifier)).')';
+ }
+ }
+
+ /**
+ * Returns a textual description of the information encoded in a given identifier octet.
+ *
+ * The first three (most significant) bytes are evaluated to determine if this is a
+ * constructed or primitive type and if it is either universal, application, context-specific or
+ * private.
+ *
+ * Example:
+ * Constructed context-specific
+ * Primitive universal
+ *
+ * @param int|string $identifier
+ *
+ * @return string
+ */
+ public static function getClassDescription($identifier)
+ {
+ $identifierOctet = self::makeNumeric($identifier);
+
+ if (self::isConstructed($identifierOctet)) {
+ $classDescription = 'Constructed ';
+ } else {
+ $classDescription = 'Primitive ';
+ }
+ $classBits = $identifierOctet >> 6;
+ switch ($classBits) {
+ case self::CLASS_UNIVERSAL:
+ $classDescription .= 'universal';
+ break;
+ case self::CLASS_APPLICATION:
+ $classDescription .= 'application';
+ break;
+ case self::CLASS_CONTEXT_SPECIFIC:
+ $tagNumber = self::getTagNumber($identifier);
+ $classDescription = "[$tagNumber] Context-specific";
+ break;
+ case self::CLASS_PRIVATE:
+ $classDescription .= 'private';
+ break;
+
+ default:
+ return "INVALID IDENTIFIER OCTET: {$identifierOctet}";
+ }
+
+ return $classDescription;
+ }
+
+ /**
+ * @param int|string $identifier
+ *
+ * @return int
+ */
+ public static function getTagNumber($identifier)
+ {
+ $firstOctet = self::makeNumeric($identifier);
+ $tagNumber = $firstOctet & self::LONG_FORM;
+
+ if ($tagNumber < self::LONG_FORM) {
+ return $tagNumber;
+ }
+
+ if (is_numeric($identifier)) {
+ $identifier = chr($identifier);
+ }
+ return Base128::decode(substr($identifier, 1));
+ }
+
+ public static function isUniversalClass($identifier)
+ {
+ $identifier = self::makeNumeric($identifier);
+
+ return $identifier >> 6 == self::CLASS_UNIVERSAL;
+ }
+
+ public static function isApplicationClass($identifier)
+ {
+ $identifier = self::makeNumeric($identifier);
+
+ return $identifier >> 6 == self::CLASS_APPLICATION;
+ }
+
+ public static function isContextSpecificClass($identifier)
+ {
+ $identifier = self::makeNumeric($identifier);
+
+ return $identifier >> 6 == self::CLASS_CONTEXT_SPECIFIC;
+ }
+
+ public static function isPrivateClass($identifier)
+ {
+ $identifier = self::makeNumeric($identifier);
+
+ return $identifier >> 6 == self::CLASS_PRIVATE;
+ }
+
+ private static function makeNumeric($identifierOctet)
+ {
+ if (!is_numeric($identifierOctet)) {
+ return ord($identifierOctet);
+ } else {
+ return $identifierOctet;
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+class OID
+{
+ const RSA_ENCRYPTION = '1.2.840.113549.1.1.1';
+ const MD5_WITH_RSA_ENCRYPTION = '1.2.840.113549.1.1.4';
+ const SHA1_WITH_RSA_SIGNATURE = '1.2.840.113549.1.1.5';
+ const SHA256_WITH_RSA_SIGNATURE = '1.2.840.113549.1.1.11';
+ const PKCS9_EMAIL = '1.2.840.113549.1.9.1';
+ const PKCS9_UNSTRUCTURED_NAME = '1.2.840.113549.1.9.2';
+ const PKCS9_CONTENT_TYPE = '1.2.840.113549.1.9.3';
+ const PKCS9_MESSAGE_DIGEST = '1.2.840.113549.1.9.4';
+ const PKCS9_SIGNING_TIME = '1.2.840.113549.1.9.5';
+ const PKCS9_EXTENSION_REQUEST = '1.2.840.113549.1.9.14';
+
+ // certificate extension identifier
+ const CERT_EXT_SUBJECT_DIRECTORY_ATTR = '2.5.29.9';
+ const CERT_EXT_SUBJECT_KEY_IDENTIFIER = '2.5.29.14';
+ const CERT_EXT_KEY_USAGE = '2.5.29.15';
+ const CERT_EXT_PRIVATE_KEY_USAGE_PERIOD = '2.5.29.16';
+ const CERT_EXT_SUBJECT_ALT_NAME = '2.5.29.17';
+ const CERT_EXT_ISSUER_ALT_NAME = '2.5.29.18';
+ const CERT_EXT_BASIC_CONSTRAINTS = '2.5.29.19';
+ const CERT_EXT_CRL_NUMBER = '2.5.29.20';
+ const CERT_EXT_REASON_CODE = '2.5.29.21';
+ const CERT_EXT_INVALIDITY_DATE = '2.5.29.24';
+ const CERT_EXT_DELTA_CRL_INDICATOR = '2.5.29.27';
+ const CERT_EXT_ISSUING_DIST_POINT = '2.5.29.28';
+ const CERT_EXT_CERT_ISSUER = '2.5.29.29';
+ const CERT_EXT_NAME_CONSTRAINTS = '2.5.29.30';
+ const CERT_EXT_CRL_DISTRIBUTION_POINTS = '2.5.29.31';
+ const CERT_EXT_CERT_POLICIES = '2.5.29.32';
+ const CERT_EXT_AUTHORITY_KEY_IDENTIFIER = '2.5.29.35';
+ const CERT_EXT_EXTENDED_KEY_USAGE = '2.5.29.37';
+
+ // standard certificate files
+ const COMMON_NAME = '2.5.4.3';
+ const SURNAME = '2.5.4.4';
+ const SERIAL_NUMBER = '2.5.4.5';
+ const COUNTRY_NAME = '2.5.4.6';
+ const LOCALITY_NAME = '2.5.4.7';
+ const STATE_OR_PROVINCE_NAME = '2.5.4.8';
+ const STREET_ADDRESS = '2.5.4.9';
+ const ORGANIZATION_NAME = '2.5.4.10';
+ const OU_NAME = '2.5.4.11';
+ const TITLE = '2.5.4.12';
+ const DESCRIPTION = '2.5.4.13';
+ const POSTAL_ADDRESS = '2.5.4.16';
+ const POSTAL_CODE = '2.5.4.17';
+ const AUTHORITY_REVOCATION_LIST = '2.5.4.38';
+
+ const AUTHORITY_INFORMATION_ACCESS = '1.3.6.1.5.5.7.1.1';
+
+ /**
+ * Returns the name of the given object identifier.
+ *
+ * Some OIDs are saved as class constants in this class.
+ * If the wanted oidString is not among them, this method will
+ * query http://oid-info.com for the right name.
+ * This behavior can be suppressed by setting the second method parameter to false.
+ *
+ * @param string $oidString
+ * @param bool $loadFromWeb
+ *
+ * @see self::loadFromWeb($oidString)
+ *
+ * @return string
+ */
+ public static function getName($oidString, $loadFromWeb = true)
+ {
+ $oids = [
+ '1.2' => 'ISO Member Body',
+ '1.3' => 'org',
+ '1.3.6.1.5.5.8.1.1' => 'hmac-md5',
+ '1.3.6.1.5.5.8.1.2' => 'hmac-sha1',
+ '1.3.132' => 'certicom-arc',
+ '2.23' => 'International Organizations',
+ '2.23.43' => 'wap',
+ '2.23.43.1' => 'wap-wsg',
+ '2.5.1.5' => 'Selected Attribute Types',
+ '2.5.1.5.55' => 'clearance',
+ '1.2.840' => 'ISO US Member Body',
+ '1.2.840.10040' => 'X9.57',
+ '1.2.840.10040.4' => 'X9.57 CM ?',
+ '1.2.840.10040.4.1' => 'dsaEncryption',
+ '1.2.840.10040.4.3' => 'dsaWithSHA1',
+ '1.2.840.10045' => 'ANSI X9.62',
+ '1.2.840.10045.1' => 'X9-62_id-fieldType',
+ '1.2.840.10045.1.1' => 'X9-62_prime-field',
+ '1.2.840.10045.1.2' => 'X9-62_characteristic-two-field',
+ '1.2.840.10045.1.2.3' => 'X9-62_id-characteristic-two-basis',
+ '1.2.840.10045.1.2.3.1' => 'X9-62_onBasis',
+ '1.2.840.10045.1.2.3.2' => 'X9-62_tpBasis',
+ '1.2.840.10045.1.2.3.3' => 'X9-62_ppBasis',
+ '1.2.840.10045.2' => 'X9-62_id-publicKeyType',
+ '1.2.840.10045.2.1' => 'X9-62_id-ecPublicKey',
+ '1.2.840.10045.3' => 'X9-62_ellipticCurve',
+ '1.2.840.10045.3.0' => 'X9-62_c-TwoCurve',
+ '1.2.840.10045.3.0.1' => 'X9-62_c2pnb163v1',
+ '1.2.840.10045.3.0.2' => 'X9-62_c2pnb163v2',
+ '1.2.840.10045.3.0.3' => 'X9-62_c2pnb163v3',
+ '1.2.840.10045.3.0.4' => 'X9-62_c2pnb176v1',
+ '1.2.840.10045.3.0.5' => 'X9-62_c2tnb191v1',
+ '1.2.840.10045.3.0.6' => 'X9-62_c2tnb191v2',
+ '1.2.840.10045.3.0.7' => 'X9-62_c2tnb191v3',
+ '1.2.840.10045.3.0.8' => 'X9-62_c2onb191v4',
+ '1.2.840.10045.3.0.9' => 'X9-62_c2onb191v5',
+ '1.2.840.10045.3.0.10' => 'X9-62_c2pnb208w1',
+ '1.2.840.10045.3.0.11' => 'X9-62_c2tnb239v1',
+ '1.2.840.10045.3.0.12' => 'X9-62_c2tnb239v2',
+ '1.2.840.10045.3.0.13' => 'X9-62_c2tnb239v3',
+ '1.2.840.10045.3.0.14' => 'X9-62_c2onb239v4',
+ '1.2.840.10045.3.0.15' => 'X9-62_c2onb239v5',
+ '1.2.840.10045.3.0.16' => 'X9-62_c2pnb272w1',
+ '1.2.840.10045.3.0.17' => 'X9-62_c2pnb304w1',
+ '1.2.840.10045.3.0.18' => 'X9-62_c2tnb359v1',
+ '1.2.840.10045.3.0.19' => 'X9-62_c2pnb368w1',
+ '1.2.840.10045.3.0.20' => 'X9-62_c2tnb431r1',
+ '1.2.840.10045.3.1' => 'X9-62_primeCurve',
+ '1.2.840.10045.3.1.1' => 'X9-62_prime192v1',
+ '1.2.840.10045.3.1.2' => 'X9-62_prime192v2',
+ '1.2.840.10045.3.1.3' => 'X9-62_prime192v3',
+ '1.2.840.10045.3.1.4' => 'X9-62_prime239v1',
+ '1.2.840.10045.3.1.5' => 'X9-62_prime239v2',
+ '1.2.840.10045.3.1.6' => 'X9-62_prime239v3',
+ '1.2.840.10045.3.1.7' => 'X9-62_prime256v1',
+ '1.2.840.10045.4' => 'X9-62_id-ecSigType',
+ '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
+ '1.2.840.10045.4.2' => 'ecdsa-with-Recommended',
+ '1.2.840.10045.4.3' => 'ecdsa-with-Specified',
+ '1.2.840.10045.4.3.1' => 'ecdsa-with-SHA224',
+ '1.2.840.10045.4.3.2' => 'ecdsa-with-SHA256',
+ '1.2.840.10045.4.3.3' => 'ecdsa-with-SHA384',
+ '1.2.840.10045.4.3.4' => 'ecdsa-with-SHA512',
+ '1.3.132.0' => 'secg_ellipticCurve',
+ '2.23.43.1.4' => 'wap-wsg-idm-ecid',
+ '2.23.43.1.4.1' => 'wap-wsg-idm-ecid-wtls1',
+ '2.23.43.1.4.3' => 'wap-wsg-idm-ecid-wtls3',
+ '2.23.43.1.4.4' => 'wap-wsg-idm-ecid-wtls4',
+ '2.23.43.1.4.5' => 'wap-wsg-idm-ecid-wtls5',
+ '2.23.43.1.4.6' => 'wap-wsg-idm-ecid-wtls6',
+ '2.23.43.1.4.7' => 'wap-wsg-idm-ecid-wtls7',
+ '2.23.43.1.4.8' => 'wap-wsg-idm-ecid-wtls8',
+ '2.23.43.1.4.9' => 'wap-wsg-idm-ecid-wtls9',
+ '2.23.43.1.4.10' => 'wap-wsg-idm-ecid-wtls10',
+ '2.23.43.1.4.11' => 'wap-wsg-idm-ecid-wtls11',
+ '2.23.43.1.4.12' => 'wap-wsg-idm-ecid-wtls12',
+ '1.2.840.113533.7.66.10' => 'cast5-cbc',
+ '1.2.840.113533.7.66.12' => 'pbeWithMD5AndCast5CBC',
+ '1.2.840.113533.7.66.13' => 'password based MAC',
+ '1.2.840.113533.7.66.30' => 'Diffie-Hellman based MAC',
+ '1.2.840.113549' => 'RSA Data Security, Inc.',
+ '1.2.840.113549.1' => 'RSA Data Security, Inc. PKCS',
+ '1.2.840.113549.1.1' => 'pkcs1',
+ '1.2.840.113549.1.1.1' => 'rsaEncryption',
+ '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
+ '1.2.840.113549.1.1.3' => 'md4WithRSAEncryption',
+ '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
+ '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
+ '1.2.840.113549.1.1.7' => 'rsaesOaep',
+ '1.2.840.113549.1.1.8' => 'mgf1',
+ '1.2.840.113549.1.1.9' => 'pSpecified',
+ '1.2.840.113549.1.1.10' => 'rsassaPss',
+ '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
+ '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
+ '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
+ '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
+ '1.2.840.113549.1.3' => 'pkcs3',
+ '1.2.840.113549.1.3.1' => 'dhKeyAgreement',
+ '1.2.840.113549.1.5' => 'pkcs5',
+ '1.2.840.113549.1.5.1' => 'pbeWithMD2AndDES-CBC',
+ '1.2.840.113549.1.5.3' => 'pbeWithMD5AndDES-CBC',
+ '1.2.840.113549.1.5.4' => 'pbeWithMD2AndRC2-CBC',
+ '1.2.840.113549.1.5.6' => 'pbeWithMD5AndRC2-CBC',
+ '1.2.840.113549.1.5.10' => 'pbeWithSHA1AndDES-CBC',
+ '1.2.840.113549.1.5.11' => 'pbeWithSHA1AndRC2-CBC',
+ '1.2.840.113549.1.5.12' => 'PBKDF2',
+ '1.2.840.113549.1.5.13' => 'PBES2',
+ '1.2.840.113549.1.5.14' => 'PBMAC1',
+ '1.2.840.113549.1.7' => 'pkcs7',
+ '1.2.840.113549.1.7.1' => 'pkcs7-data',
+ '1.2.840.113549.1.7.2' => 'pkcs7-signedData',
+ '1.2.840.113549.1.7.3' => 'pkcs7-envelopedData',
+ '1.2.840.113549.1.7.4' => 'pkcs7-signedAndEnvelopedData',
+ '1.2.840.113549.1.7.5' => 'pkcs7-digestData',
+ '1.2.840.113549.1.7.6' => 'pkcs7-encryptedData',
+ '1.2.840.113549.1.9' => 'pkcs9',
+ '1.2.840.113549.1.9.1' => 'emailAddress',
+ '1.2.840.113549.1.9.2' => 'unstructuredName',
+ '1.2.840.113549.1.9.3' => 'contentType',
+ '1.2.840.113549.1.9.4' => 'messageDigest',
+ '1.2.840.113549.1.9.5' => 'signingTime',
+ '1.2.840.113549.1.9.6' => 'countersignature',
+ '1.2.840.113549.1.9.7' => 'challengePassword',
+ '1.2.840.113549.1.9.8' => 'unstructuredAddress',
+ '1.2.840.113549.1.9.9' => 'extendedCertificateAttributes',
+ '1.2.840.113549.1.9.14' => 'Extension Request',
+ '1.2.840.113549.1.9.15' => 'S/MIME Capabilities',
+ '1.2.840.113549.1.9.16' => 'S/MIME',
+ '1.2.840.113549.1.9.16.0' => 'id-smime-mod',
+ '1.2.840.113549.1.9.16.1' => 'id-smime-ct',
+ '1.2.840.113549.1.9.16.2' => 'id-smime-aa',
+ '1.2.840.113549.1.9.16.3' => 'id-smime-alg',
+ '1.2.840.113549.1.9.16.4' => 'id-smime-cd',
+ '1.2.840.113549.1.9.16.5' => 'id-smime-spq',
+ '1.2.840.113549.1.9.16.6' => 'id-smime-cti',
+ '1.2.840.113549.1.9.16.0.1' => 'id-smime-mod-cms',
+ '1.2.840.113549.1.9.16.0.2' => 'id-smime-mod-ess',
+ '1.2.840.113549.1.9.16.0.3' => 'id-smime-mod-oid',
+ '1.2.840.113549.1.9.16.0.4' => 'id-smime-mod-msg-v3',
+ '1.2.840.113549.1.9.16.0.5' => 'id-smime-mod-ets-eSignature-88',
+ '1.2.840.113549.1.9.16.0.6' => 'id-smime-mod-ets-eSignature-97',
+ '1.2.840.113549.1.9.16.0.7' => 'id-smime-mod-ets-eSigPolicy-88',
+ '1.2.840.113549.1.9.16.0.8' => 'id-smime-mod-ets-eSigPolicy-97',
+ '1.2.840.113549.1.9.16.1.1' => 'id-smime-ct-receipt',
+ '1.2.840.113549.1.9.16.1.2' => 'id-smime-ct-authData',
+ '1.2.840.113549.1.9.16.1.3' => 'id-smime-ct-publishCert',
+ '1.2.840.113549.1.9.16.1.4' => 'id-smime-ct-TSTInfo',
+ '1.2.840.113549.1.9.16.1.5' => 'id-smime-ct-TDTInfo',
+ '1.2.840.113549.1.9.16.1.6' => 'id-smime-ct-contentInfo',
+ '1.2.840.113549.1.9.16.1.7' => 'id-smime-ct-DVCSRequestData',
+ '1.2.840.113549.1.9.16.1.8' => 'id-smime-ct-DVCSResponseData',
+ '1.2.840.113549.1.9.16.1.9' => 'id-smime-ct-compressedData',
+ '1.2.840.113549.1.9.16.1.27' => 'id-ct-asciiTextWithCRLF',
+ '1.2.840.113549.1.9.16.2.1' => 'id-smime-aa-receiptRequest',
+ '1.2.840.113549.1.9.16.2.2' => 'id-smime-aa-securityLabel',
+ '1.2.840.113549.1.9.16.2.3' => 'id-smime-aa-mlExpandHistory',
+ '1.2.840.113549.1.9.16.2.4' => 'id-smime-aa-contentHint',
+ '1.2.840.113549.1.9.16.2.5' => 'id-smime-aa-msgSigDigest',
+ '1.2.840.113549.1.9.16.2.6' => 'id-smime-aa-encapContentType',
+ '1.2.840.113549.1.9.16.2.7' => 'id-smime-aa-contentIdentifier',
+ '1.2.840.113549.1.9.16.2.8' => 'id-smime-aa-macValue',
+ '1.2.840.113549.1.9.16.2.9' => 'id-smime-aa-equivalentLabels',
+ '1.2.840.113549.1.9.16.2.10' => 'id-smime-aa-contentReference',
+ '1.2.840.113549.1.9.16.2.11' => 'id-smime-aa-encrypKeyPref',
+ '1.2.840.113549.1.9.16.2.12' => 'id-smime-aa-signingCertificate',
+ '1.2.840.113549.1.9.16.2.13' => 'id-smime-aa-smimeEncryptCerts',
+ '1.2.840.113549.1.9.16.2.14' => 'id-smime-aa-timeStampToken',
+ '1.2.840.113549.1.9.16.2.15' => 'id-smime-aa-ets-sigPolicyId',
+ '1.2.840.113549.1.9.16.2.16' => 'id-smime-aa-ets-commitmentType',
+ '1.2.840.113549.1.9.16.2.17' => 'id-smime-aa-ets-signerLocation',
+ '1.2.840.113549.1.9.16.2.18' => 'id-smime-aa-ets-signerAttr',
+ '1.2.840.113549.1.9.16.2.19' => 'id-smime-aa-ets-otherSigCert',
+ '1.2.840.113549.1.9.16.2.20' => 'id-smime-aa-ets-contentTimestamp',
+ '1.2.840.113549.1.9.16.2.21' => 'id-smime-aa-ets-CertificateRefs',
+ '1.2.840.113549.1.9.16.2.22' => 'id-smime-aa-ets-RevocationRefs',
+ '1.2.840.113549.1.9.16.2.23' => 'id-smime-aa-ets-certValues',
+ '1.2.840.113549.1.9.16.2.24' => 'id-smime-aa-ets-revocationValues',
+ '1.2.840.113549.1.9.16.2.25' => 'id-smime-aa-ets-escTimeStamp',
+ '1.2.840.113549.1.9.16.2.26' => 'id-smime-aa-ets-certCRLTimestamp',
+ '1.2.840.113549.1.9.16.2.27' => 'id-smime-aa-ets-archiveTimeStamp',
+ '1.2.840.113549.1.9.16.2.28' => 'id-smime-aa-signatureType',
+ '1.2.840.113549.1.9.16.2.29' => 'id-smime-aa-dvcs-dvc',
+ '1.2.840.113549.1.9.16.3.1' => 'id-smime-alg-ESDHwith3DES',
+ '1.2.840.113549.1.9.16.3.2' => 'id-smime-alg-ESDHwithRC2',
+ '1.2.840.113549.1.9.16.3.3' => 'id-smime-alg-3DESwrap',
+ '1.2.840.113549.1.9.16.3.4' => 'id-smime-alg-RC2wrap',
+ '1.2.840.113549.1.9.16.3.5' => 'id-smime-alg-ESDH',
+ '1.2.840.113549.1.9.16.3.6' => 'id-smime-alg-CMS3DESwrap',
+ '1.2.840.113549.1.9.16.3.7' => 'id-smime-alg-CMSRC2wrap',
+ '1.2.840.113549.1.9.16.3.9' => 'id-alg-PWRI-KEK',
+ '1.2.840.113549.1.9.16.4.1' => 'id-smime-cd-ldap',
+ '1.2.840.113549.1.9.16.5.1' => 'id-smime-spq-ets-sqt-uri',
+ '1.2.840.113549.1.9.16.5.2' => 'id-smime-spq-ets-sqt-unotice',
+ '1.2.840.113549.1.9.16.6.1' => 'id-smime-cti-ets-proofOfOrigin',
+ '1.2.840.113549.1.9.16.6.2' => 'id-smime-cti-ets-proofOfReceipt',
+ '1.2.840.113549.1.9.16.6.3' => 'id-smime-cti-ets-proofOfDelivery',
+ '1.2.840.113549.1.9.16.6.4' => 'id-smime-cti-ets-proofOfSender',
+ '1.2.840.113549.1.9.16.6.5' => 'id-smime-cti-ets-proofOfApproval',
+ '1.2.840.113549.1.9.16.6.6' => 'id-smime-cti-ets-proofOfCreation',
+ '1.2.840.113549.1.9.20' => 'friendlyName',
+ '1.2.840.113549.1.9.21' => 'localKeyID',
+ '1.3.6.1.4.1.311.17.1' => 'Microsoft CSP Name',
+ '1.3.6.1.4.1.311.17.2' => 'Microsoft Local Key set',
+ '1.2.840.113549.1.9.22' => 'certTypes',
+ '1.2.840.113549.1.9.22.1' => 'x509Certificate',
+ '1.2.840.113549.1.9.22.2' => 'sdsiCertificate',
+
+ '1.2.840.113549.1.9.23' => 'crlTypes',
+ '1.2.840.113549.1.9.23.1' => 'x509Crl',
+ '1.2.840.113549.1.12' => 'pkcs12',
+ '1.2.840.113549.1.12.1' => 'pkcs12-pbeids',
+ '1.2.840.113549.1.12.1.1' => 'pbeWithSHA1And128BitRC4',
+ '1.2.840.113549.1.12.1.2' => 'pbeWithSHA1And40BitRC4',
+ '1.2.840.113549.1.12.1.3' => 'pbeWithSHA1And3-KeyTripleDES-CBC',
+ '1.2.840.113549.1.12.1.4' => 'pbeWithSHA1And2-KeyTripleDES-CBC',
+ '1.2.840.113549.1.12.1.5' => 'pbeWithSHA1And128BitRC2-CBC',
+ '1.2.840.113549.1.12.1.6' => 'pbeWithSHA1And40BitRC2-CBC',
+ '1.2.840.113549.1.12.10' => 'pkcs12-Version1',
+ '1.2.840.113549.1.12.10.1' => 'pkcs12-BagIds',
+ '1.2.840.113549.1.12.10.1.1' => 'keyBag',
+ '1.2.840.113549.1.12.10.1.2' => 'pkcs8ShroudedKeyBag',
+ '1.2.840.113549.1.12.10.1.3' => 'certBag',
+ '1.2.840.113549.1.12.10.1.4' => 'crlBag',
+ '1.2.840.113549.1.12.10.1.5' => 'secretBag',
+ '1.2.840.113549.1.12.10.1.6' => 'safeContentsBag',
+ '1.2.840.113549.2.2' => 'md2',
+ '1.2.840.113549.2.4' => 'md4',
+ '1.2.840.113549.2.5' => 'md5',
+ '1.2.840.113549.2.6' => 'hmacWithMD5',
+ '1.2.840.113549.2.7' => 'hmacWithSHA1',
+ '1.2.840.113549.2.8' => 'hmacWithSHA224',
+ '1.2.840.113549.2.9' => 'hmacWithSHA256',
+ '1.2.840.113549.2.10' => 'hmacWithSHA384',
+ '1.2.840.113549.2.11' => 'hmacWithSHA512',
+ '1.2.840.113549.3.2' => 'rc2-cbc',
+ '1.2.840.113549.3.4' => 'rc4',
+ '1.2.840.113549.3.7' => 'des-ede3-cbc',
+ '1.2.840.113549.3.8' => 'rc5-cbc',
+ '1.3.6.1.4.1.311.2.1.14' => 'Microsoft Extension Request',
+ '1.3.6.1.4.1.311.2.1.21' => 'Microsoft Individual Code Signing',
+ '1.3.6.1.4.1.311.2.1.22' => 'Microsoft Commercial Code Signing',
+ '1.3.6.1.4.1.311.10.3.1' => 'Microsoft Trust List Signing',
+ '1.3.6.1.4.1.311.10.3.3' => 'Microsoft Server Gated Crypto',
+ '1.3.6.1.4.1.311.10.3.4' => 'Microsoft Encrypted File System',
+ '1.3.6.1.4.1.311.20.2.2' => 'Microsoft Smartcardlogin',
+ '1.3.6.1.4.1.311.20.2.3' => 'Microsoft Universal Principal Name',
+ '1.3.6.1.4.1.188.7.1.1.2' => 'idea-cbc',
+ '1.3.6.1.4.1.3029.1.2' => 'bf-cbc',
+ '1.3.6.1.5.5.7' => 'PKIX',
+ '1.3.6.1.5.5.7.0' => 'id-pkix-mod',
+ '1.3.6.1.5.5.7.1' => 'id-pe',
+ '1.3.6.1.5.5.7.2' => 'id-qt',
+ '1.3.6.1.5.5.7.3' => 'id-kp',
+ '1.3.6.1.5.5.7.4' => 'id-it',
+ '1.3.6.1.5.5.7.5' => 'id-pkip',
+ '1.3.6.1.5.5.7.6' => 'id-alg',
+ '1.3.6.1.5.5.7.7' => 'id-cmc',
+ '1.3.6.1.5.5.7.8' => 'id-on',
+ '1.3.6.1.5.5.7.9' => 'id-pda',
+ '1.3.6.1.5.5.7.10' => 'id-aca',
+ '1.3.6.1.5.5.7.11' => 'id-qcs',
+ '1.3.6.1.5.5.7.12' => 'id-cct',
+ '1.3.6.1.5.5.7.21' => 'id-ppl',
+ '1.3.6.1.5.5.7.48' => 'id-ad',
+ '1.3.6.1.5.5.7.0.1' => 'id-pkix1-explicit-88',
+ '1.3.6.1.5.5.7.0.2' => 'id-pkix1-implicit-88',
+ '1.3.6.1.5.5.7.0.3' => 'id-pkix1-explicit-93',
+ '1.3.6.1.5.5.7.0.4' => 'id-pkix1-implicit-93',
+ '1.3.6.1.5.5.7.0.5' => 'id-mod-crmf',
+ '1.3.6.1.5.5.7.0.6' => 'id-mod-cmc',
+ '1.3.6.1.5.5.7.0.7' => 'id-mod-kea-profile-88',
+ '1.3.6.1.5.5.7.0.8' => 'id-mod-kea-profile-93',
+ '1.3.6.1.5.5.7.0.9' => 'id-mod-cmp',
+ '1.3.6.1.5.5.7.0.10' => 'id-mod-qualified-cert-88',
+ '1.3.6.1.5.5.7.0.11' => 'id-mod-qualified-cert-93',
+ '1.3.6.1.5.5.7.0.12' => 'id-mod-attribute-cert',
+ '1.3.6.1.5.5.7.0.13' => 'id-mod-timestamp-protocol',
+ '1.3.6.1.5.5.7.0.14' => 'id-mod-ocsp',
+ '1.3.6.1.5.5.7.0.15' => 'id-mod-dvcs',
+ '1.3.6.1.5.5.7.0.16' => 'id-mod-cmp2000',
+ '1.3.6.1.5.5.7.1.1' => 'Authority Information Access',
+ '1.3.6.1.5.5.7.1.2' => 'Biometric Info',
+ '1.3.6.1.5.5.7.1.3' => 'qcStatements',
+ '1.3.6.1.5.5.7.1.4' => 'ac-auditEntity',
+ '1.3.6.1.5.5.7.1.5' => 'ac-targeting',
+ '1.3.6.1.5.5.7.1.6' => 'aaControls',
+ '1.3.6.1.5.5.7.1.7' => 'sbgp-ipAddrBlock',
+ '1.3.6.1.5.5.7.1.8' => 'sbgp-autonomousSysNum',
+ '1.3.6.1.5.5.7.1.9' => 'sbgp-routerIdentifier',
+ '1.3.6.1.5.5.7.1.10' => 'ac-proxying',
+ '1.3.6.1.5.5.7.1.11' => 'Subject Information Access',
+ '1.3.6.1.5.5.7.1.14' => 'Proxy Certificate Information',
+ '1.3.6.1.5.5.7.2.1' => 'Policy Qualifier CPS',
+ '1.3.6.1.5.5.7.2.2' => 'Policy Qualifier User Notice',
+ '1.3.6.1.5.5.7.2.3' => 'textNotice',
+ '1.3.6.1.5.5.7.3.1' => 'TLS Web Server Authentication',
+ '1.3.6.1.5.5.7.3.2' => 'TLS Web Client Authentication',
+ '1.3.6.1.5.5.7.3.3' => 'Code Signing',
+ '1.3.6.1.5.5.7.3.4' => 'E-mail Protection',
+ '1.3.6.1.5.5.7.3.5' => 'IPSec End System',
+ '1.3.6.1.5.5.7.3.6' => 'IPSec Tunnel',
+ '1.3.6.1.5.5.7.3.7' => 'IPSec User',
+ '1.3.6.1.5.5.7.3.8' => 'Time Stamping',
+ '1.3.6.1.5.5.7.3.9' => 'OCSP Signing',
+ '1.3.6.1.5.5.7.3.10' => 'dvcs',
+ '1.3.6.1.5.5.7.4.1' => 'id-it-caProtEncCert',
+ '1.3.6.1.5.5.7.4.2' => 'id-it-signKeyPairTypes',
+ '1.3.6.1.5.5.7.4.3' => 'id-it-encKeyPairTypes',
+ '1.3.6.1.5.5.7.4.4' => 'id-it-preferredSymmAlg',
+ '1.3.6.1.5.5.7.4.5' => 'id-it-caKeyUpdateInfo',
+ '1.3.6.1.5.5.7.4.6' => 'id-it-currentCRL',
+ '1.3.6.1.5.5.7.4.7' => 'id-it-unsupportedOIDs',
+ '1.3.6.1.5.5.7.4.8' => 'id-it-subscriptionRequest',
+ '1.3.6.1.5.5.7.4.9' => 'id-it-subscriptionResponse',
+ '1.3.6.1.5.5.7.4.10' => 'id-it-keyPairParamReq',
+ '1.3.6.1.5.5.7.4.11' => 'id-it-keyPairParamRep',
+ '1.3.6.1.5.5.7.4.12' => 'id-it-revPassphrase',
+ '1.3.6.1.5.5.7.4.13' => 'id-it-implicitConfirm',
+ '1.3.6.1.5.5.7.4.14' => 'id-it-confirmWaitTime',
+ '1.3.6.1.5.5.7.4.15' => 'id-it-origPKIMessage',
+ '1.3.6.1.5.5.7.4.16' => 'id-it-suppLangTags',
+ '1.3.6.1.5.5.7.5.1' => 'id-regCtrl',
+ '1.3.6.1.5.5.7.5.2' => 'id-regInfo',
+ '1.3.6.1.5.5.7.5.1.1' => 'id-regCtrl-regToken',
+ '1.3.6.1.5.5.7.5.1.2' => 'id-regCtrl-authenticator',
+ '1.3.6.1.5.5.7.5.1.3' => 'id-regCtrl-pkiPublicationInfo',
+ '1.3.6.1.5.5.7.5.1.4' => 'id-regCtrl-pkiArchiveOptions',
+ '1.3.6.1.5.5.7.5.1.5' => 'id-regCtrl-oldCertID',
+ '1.3.6.1.5.5.7.5.1.6' => 'id-regCtrl-protocolEncrKey',
+ '1.3.6.1.5.5.7.5.2.1' => 'id-regInfo-utf8Pairs',
+ '1.3.6.1.5.5.7.5.2.2' => 'id-regInfo-certReq',
+ '1.3.6.1.5.5.7.6.1' => 'id-alg-des40',
+ '1.3.6.1.5.5.7.6.2' => 'id-alg-noSignature',
+ '1.3.6.1.5.5.7.6.3' => 'id-alg-dh-sig-hmac-sha1',
+ '1.3.6.1.5.5.7.6.4' => 'id-alg-dh-pop',
+ '1.3.6.1.5.5.7.7.1' => 'id-cmc-statusInfo',
+ '1.3.6.1.5.5.7.7.2' => 'id-cmc-identification',
+ '1.3.6.1.5.5.7.7.3' => 'id-cmc-identityProof',
+ '1.3.6.1.5.5.7.7.4' => 'id-cmc-dataReturn',
+ '1.3.6.1.5.5.7.7.5' => 'id-cmc-transactionId',
+ '1.3.6.1.5.5.7.7.6' => 'id-cmc-senderNonce',
+ '1.3.6.1.5.5.7.7.7' => 'id-cmc-recipientNonce',
+ '1.3.6.1.5.5.7.7.8' => 'id-cmc-addExtensions',
+ '1.3.6.1.5.5.7.7.9' => 'id-cmc-encryptedPOP',
+ '1.3.6.1.5.5.7.7.10' => 'id-cmc-decryptedPOP',
+ '1.3.6.1.5.5.7.7.11' => 'id-cmc-lraPOPWitness',
+ '1.3.6.1.5.5.7.7.15' => 'id-cmc-getCert',
+ '1.3.6.1.5.5.7.7.16' => 'id-cmc-getCRL',
+ '1.3.6.1.5.5.7.7.17' => 'id-cmc-revokeRequest',
+ '1.3.6.1.5.5.7.7.18' => 'id-cmc-regInfo',
+ '1.3.6.1.5.5.7.7.19' => 'id-cmc-responseInfo',
+ '1.3.6.1.5.5.7.7.21' => 'id-cmc-queryPending',
+ '1.3.6.1.5.5.7.7.22' => 'id-cmc-popLinkRandom',
+ '1.3.6.1.5.5.7.7.23' => 'id-cmc-popLinkWitness',
+ '1.3.6.1.5.5.7.7.24' => 'id-cmc-confirmCertAcceptance',
+ '1.3.6.1.5.5.7.8.1' => 'id-on-personalData',
+ '1.3.6.1.5.5.7.8.3' => 'Permanent Identifier',
+ '1.3.6.1.5.5.7.9.1' => 'id-pda-dateOfBirth',
+ '1.3.6.1.5.5.7.9.2' => 'id-pda-placeOfBirth',
+ '1.3.6.1.5.5.7.9.3' => 'id-pda-gender',
+ '1.3.6.1.5.5.7.9.4' => 'id-pda-countryOfCitizenship',
+ '1.3.6.1.5.5.7.9.5' => 'id-pda-countryOfResidence',
+ '1.3.6.1.5.5.7.10.1' => 'id-aca-authenticationInfo',
+ '1.3.6.1.5.5.7.10.2' => 'id-aca-accessIdentity',
+ '1.3.6.1.5.5.7.10.3' => 'id-aca-chargingIdentity',
+ '1.3.6.1.5.5.7.10.4' => 'id-aca-group',
+ '1.3.6.1.5.5.7.10.5' => 'id-aca-role',
+ '1.3.6.1.5.5.7.10.6' => 'id-aca-encAttrs',
+ '1.3.6.1.5.5.7.11.1' => 'id-qcs-pkixQCSyntax-v1',
+ '1.3.6.1.5.5.7.12.1' => 'id-cct-crs',
+ '1.3.6.1.5.5.7.12.2' => 'id-cct-PKIData',
+ '1.3.6.1.5.5.7.12.3' => 'id-cct-PKIResponse',
+ '1.3.6.1.5.5.7.21.0' => 'Any language',
+ '1.3.6.1.5.5.7.21.1' => 'Inherit all',
+ '1.3.6.1.5.5.7.21.2' => 'Independent',
+ '1.3.6.1.5.5.7.48.1' => 'OCSP',
+ '1.3.6.1.5.5.7.48.2' => 'CA Issuers',
+ '1.3.6.1.5.5.7.48.3' => 'AD Time Stamping',
+ '1.3.6.1.5.5.7.48.4' => 'ad dvcs',
+ '1.3.6.1.5.5.7.48.5' => 'CA Repository',
+ '1.3.6.1.5.5.7.48.1.1' => 'Basic OCSP Response',
+ '1.3.6.1.5.5.7.48.1.2' => 'OCSP Nonce',
+ '1.3.6.1.5.5.7.48.1.3' => 'OCSP CRL ID',
+ '1.3.6.1.5.5.7.48.1.4' => 'Acceptable OCSP Responses',
+ '1.3.6.1.5.5.7.48.1.5' => 'OCSP No Check',
+ '1.3.6.1.5.5.7.48.1.6' => 'OCSP Archive Cutoff',
+ '1.3.6.1.5.5.7.48.1.7' => 'OCSP Service Locator',
+ '1.3.6.1.5.5.7.48.1.8' => 'Extended OCSP Status',
+ '1.3.6.1.5.5.7.48.1.9' => 'id-pkix-OCSP_valid',
+ '1.3.6.1.5.5.7.48.1.10' => 'id-pkix-OCSP_path',
+ '1.3.6.1.5.5.7.48.1.11' => 'Trust Root',
+ '1.3.14.3.2' => 'algorithm',
+ '1.3.14.3.2.3' => 'md5WithRSA',
+ '1.3.14.3.2.6' => 'des-ecb',
+ '1.3.14.3.2.7' => 'des-cbc',
+ '1.3.14.3.2.8' => 'des-ofb',
+ '1.3.14.3.2.9' => 'des-cfb',
+ '1.3.14.3.2.11' => 'rsaSignature',
+ '1.3.14.3.2.12' => 'dsaEncryption-old',
+ '1.3.14.3.2.13' => 'dsaWithSHA',
+ '1.3.14.3.2.15' => 'shaWithRSAEncryption',
+ '1.3.14.3.2.17' => 'des-ede',
+ '1.3.14.3.2.18' => 'sha',
+ '1.3.14.3.2.26' => 'sha1',
+ '1.3.14.3.2.27' => 'dsaWithSHA1-old',
+ '1.3.14.3.2.29' => 'sha1WithRSA',
+ '1.3.36.3.2.1' => 'ripemd160',
+ '1.3.36.3.3.1.2' => 'ripemd160WithRSA',
+ '1.3.101.1.4.1' => 'Strong Extranet ID',
+ '2.5' => 'directory services (X.500)',
+ '2.5.4' => 'X509',
+ '2.5.4.3' => 'commonName',
+ '2.5.4.4' => 'surname',
+ '2.5.4.5' => 'serialNumber',
+ '2.5.4.6' => 'countryName',
+ '2.5.4.7' => 'localityName',
+ '2.5.4.8' => 'stateOrProvinceName',
+ '2.5.4.9' => 'streetAddress',
+ '2.5.4.10' => 'organizationName',
+ '2.5.4.11' => 'organizationalUnitName',
+ '2.5.4.12' => 'title',
+ '2.5.4.13' => 'description',
+ '2.5.4.14' => 'searchGuide',
+ '2.5.4.15' => 'businessCategory',
+ '2.5.4.16' => 'postalAddress',
+ '2.5.4.17' => 'postalCode',
+ '2.5.4.18' => 'postOfficeBox',
+ '2.5.4.19' => 'physicalDeliveryOfficeName',
+ '2.5.4.20' => 'telephoneNumber',
+ '2.5.4.21' => 'telexNumber',
+ '2.5.4.22' => 'teletexTerminalIdentifier',
+ '2.5.4.23' => 'facsimileTelephoneNumber',
+ '2.5.4.24' => 'x121Address',
+ '2.5.4.25' => 'internationaliSDNNumber',
+ '2.5.4.26' => 'registeredAddress',
+ '2.5.4.27' => 'destinationIndicator',
+ '2.5.4.28' => 'preferredDeliveryMethod',
+ '2.5.4.29' => 'presentationAddress',
+ '2.5.4.30' => 'supportedApplicationContext',
+ '2.5.4.31' => 'member',
+ '2.5.4.32' => 'owner',
+ '2.5.4.33' => 'roleOccupant',
+ '2.5.4.34' => 'seeAlso',
+ '2.5.4.35' => 'userPassword',
+ '2.5.4.36' => 'userCertificate',
+ '2.5.4.37' => 'cACertificate',
+ '2.5.4.38' => 'authorityRevocationList',
+ '2.5.4.39' => 'certificateRevocationList',
+ '2.5.4.40' => 'crossCertificatePair',
+ '2.5.4.41' => 'name',
+ '2.5.4.42' => 'givenName',
+ '2.5.4.43' => 'initials',
+ '2.5.4.44' => 'generationQualifier',
+ '2.5.4.45' => 'x500UniqueIdentifier',
+ '2.5.4.46' => 'dnQualifier',
+ '2.5.4.47' => 'enhancedSearchGuide',
+ '2.5.4.48' => 'protocolInformation',
+ '2.5.4.49' => 'distinguishedName',
+ '2.5.4.50' => 'uniqueMember',
+ '2.5.4.51' => 'houseIdentifier',
+ '2.5.4.52' => 'supportedAlgorithms',
+ '2.5.4.53' => 'deltaRevocationList',
+ '2.5.4.54' => 'dmdName',
+ '2.5.4.65' => 'pseudonym',
+ '2.5.4.72' => 'role',
+ '2.5.8' => 'directory services - algorithms',
+ '2.5.8.1.1' => 'rsa',
+ '2.5.8.3.100' => 'mdc2WithRSA',
+ '2.5.8.3.101' => 'mdc2',
+ '2.5.29' => 'id-ce',
+ '2.5.29.9' => 'X509v3 Subject Directory Attributes',
+ '2.5.29.14' => 'X509v3 Subject Key Identifier',
+ '2.5.29.15' => 'X509v3 Key Usage',
+ '2.5.29.16' => 'X509v3 Private Key Usage Period',
+ '2.5.29.17' => 'X509v3 Subject Alternative Name',
+ '2.5.29.18' => 'X509v3 Issuer Alternative Name',
+ '2.5.29.19' => 'X509v3 Basic Constraints',
+ '2.5.29.20' => 'X509v3 CRL Number',
+ '2.5.29.21' => 'X509v3 CRL Reason Code',
+ '2.5.29.24' => 'Invalidity Date',
+ '2.5.29.27' => 'X509v3 Delta CRL Indicator',
+ '2.5.29.28' => 'X509v3 Issuing Distribution Point',
+ '2.5.29.29' => 'X509v3 Certificate Issuer',
+ '2.5.29.30' => 'X509v3 Name Constraints',
+ '2.5.29.31' => 'X509v3 CRL Distribution Points',
+ '2.5.29.32' => 'X509v3 Certificate Policies',
+ '2.5.29.32.0' => 'X509v3 Any Policy',
+ '2.5.29.33' => 'X509v3 Policy Mappings',
+ '2.5.29.35' => 'X509v3 Authority Key Identifier',
+ '2.5.29.36' => 'X509v3 Policy Constraints',
+ '2.5.29.37' => 'X509v3 Extended Key Usage',
+ '2.5.29.46' => 'X509v3 Freshest CRL',
+ '2.5.29.54' => 'X509v3 Inhibit Any Policy',
+ '2.5.29.55' => 'X509v3 AC Targeting',
+ '2.5.29.56' => 'X509v3 No Revocation Available',
+ '2.5.29.37.0' => 'Any Extended Key Usage',
+ '2.16.840.1.113730' => 'Netscape Communications Corp.',
+ '2.16.840.1.113730.1' => 'Netscape Certificate Extension',
+ '2.16.840.1.113730.2' => 'Netscape Data Type',
+ '2.16.840.1.113730.1.1' => 'Netscape Cert Type',
+ '2.16.840.1.113730.1.2' => 'Netscape Base Url',
+ '2.16.840.1.113730.1.3' => 'Netscape Revocation Url',
+ '2.16.840.1.113730.1.4' => 'Netscape CA Revocation Url',
+ '2.16.840.1.113730.1.7' => 'Netscape Renewal Url',
+ '2.16.840.1.113730.1.8' => 'Netscape CA Policy Url',
+ '2.16.840.1.113730.1.12' => 'Netscape SSL Server Name',
+ '2.16.840.1.113730.1.13' => 'Netscape Comment',
+ '2.16.840.1.113730.2.5' => 'Netscape Certificate Sequence',
+ '2.16.840.1.113730.4.1' => 'Netscape Server Gated Crypto',
+ '1.3.6' => 'dod',
+ '1.3.6.1' => 'iana',
+ '1.3.6.1.1' => 'Directory',
+ '1.3.6.1.2' => 'Management',
+ '1.3.6.1.3' => 'Experimental',
+ '1.3.6.1.4' => 'Private',
+ '1.3.6.1.5' => 'Security',
+ '1.3.6.1.6' => 'SNMPv2',
+ '1.3.6.1.7' => 'Mail',
+ '1.3.6.1.4.1' => 'Enterprises',
+ '1.3.6.1.4.1.1466.344' => 'dcObject',
+ '1.2.840.113549.1.9.16.3.8' => 'zlib compression',
+ '2.16.840.1.101.3' => 'csor',
+ '2.16.840.1.101.3.4' => 'nistAlgorithms',
+ '2.16.840.1.101.3.4.1' => 'aes',
+ '2.16.840.1.101.3.4.1.1' => 'aes-128-ecb',
+ '2.16.840.1.101.3.4.1.2' => 'aes-128-cbc',
+ '2.16.840.1.101.3.4.1.3' => 'aes-128-ofb',
+ '2.16.840.1.101.3.4.1.4' => 'aes-128-cfb',
+ '2.16.840.1.101.3.4.1.5' => 'id-aes128-wrap',
+ '2.16.840.1.101.3.4.1.6' => 'aes-128-gcm',
+ '2.16.840.1.101.3.4.1.7' => 'aes-128-ccm',
+ '2.16.840.1.101.3.4.1.8' => 'id-aes128-wrap-pad',
+ '2.16.840.1.101.3.4.1.21' => 'aes-192-ecb',
+ '2.16.840.1.101.3.4.1.22' => 'aes-192-cbc',
+ '2.16.840.1.101.3.4.1.23' => 'aes-192-ofb',
+ '2.16.840.1.101.3.4.1.24' => 'aes-192-cfb',
+ '2.16.840.1.101.3.4.1.25' => 'id-aes192-wrap',
+ '2.16.840.1.101.3.4.1.26' => 'aes-192-gcm',
+ '2.16.840.1.101.3.4.1.27' => 'aes-192-ccm',
+ '2.16.840.1.101.3.4.1.28' => 'id-aes192-wrap-pad',
+ '2.16.840.1.101.3.4.1.41' => 'aes-256-ecb',
+ '2.16.840.1.101.3.4.1.42' => 'aes-256-cbc',
+ '2.16.840.1.101.3.4.1.43' => 'aes-256-ofb',
+ '2.16.840.1.101.3.4.1.44' => 'aes-256-cfb',
+ '2.16.840.1.101.3.4.1.45' => 'id-aes256-wrap',
+ '2.16.840.1.101.3.4.1.46' => 'aes-256-gcm',
+ '2.16.840.1.101.3.4.1.47' => 'aes-256-ccm',
+ '2.16.840.1.101.3.4.1.48' => 'id-aes256-wrap-pad',
+ '2.16.840.1.101.3.4.2' => 'nist_hashalgs',
+ '2.16.840.1.101.3.4.2.1' => 'sha256',
+ '2.16.840.1.101.3.4.2.2' => 'sha384',
+ '2.16.840.1.101.3.4.2.3' => 'sha512',
+ '2.16.840.1.101.3.4.2.4' => 'sha224',
+ '2.16.840.1.101.3.4.3' => 'dsa_with_sha2',
+ '2.16.840.1.101.3.4.3.1' => 'dsa_with_SHA224',
+ '2.16.840.1.101.3.4.3.2' => 'dsa_with_SHA256',
+ '2.5.29.23' => 'Hold Instruction Code',
+ '0.9' => 'data',
+ '0.9.2342' => 'pss',
+ '0.9.2342.19200300' => 'ucl',
+ '0.9.2342.19200300.100' => 'pilot',
+ '0.9.2342.19200300.100.1' => 'pilotAttributeType',
+ '0.9.2342.19200300.100.3' => 'pilotAttributeSyntax',
+ '0.9.2342.19200300.100.4' => 'pilotObjectClass',
+ '0.9.2342.19200300.100.10' => 'pilotGroups',
+ '2.23.42' => 'Secure Electronic Transactions',
+ '2.23.42.0' => 'content types',
+ '2.23.42.1' => 'message extensions',
+ '2.23.42.3' => 'set-attr',
+ '2.23.42.5' => 'set-policy',
+ '2.23.42.7' => 'certificate extensions',
+ '2.23.42.8' => 'set-brand',
+ '2.23.42.0.0' => 'setct-PANData',
+ '2.23.42.0.1' => 'setct-PANToken',
+ '2.23.42.0.2' => 'setct-PANOnly',
+ '2.23.42.0.3' => 'setct-OIData',
+ '2.23.42.0.4' => 'setct-PI',
+ '2.23.42.0.5' => 'setct-PIData',
+ '2.23.42.0.6' => 'setct-PIDataUnsigned',
+ '2.23.42.0.7' => 'setct-HODInput',
+ '2.23.42.0.8' => 'setct-AuthResBaggage',
+ '2.23.42.0.9' => 'setct-AuthRevReqBaggage',
+ '2.23.42.0.10' => 'setct-AuthRevResBaggage',
+ '2.23.42.0.11' => 'setct-CapTokenSeq',
+ '2.23.42.0.12' => 'setct-PInitResData',
+ '2.23.42.0.13' => 'setct-PI-TBS',
+ '2.23.42.0.14' => 'setct-PResData',
+ '2.23.42.0.16' => 'setct-AuthReqTBS',
+ '2.23.42.0.17' => 'setct-AuthResTBS',
+ '2.23.42.0.18' => 'setct-AuthResTBSX',
+ '2.23.42.0.19' => 'setct-AuthTokenTBS',
+ '2.23.42.0.20' => 'setct-CapTokenData',
+ '2.23.42.0.21' => 'setct-CapTokenTBS',
+ '2.23.42.0.22' => 'setct-AcqCardCodeMsg',
+ '2.23.42.0.23' => 'setct-AuthRevReqTBS',
+ '2.23.42.0.24' => 'setct-AuthRevResData',
+ '2.23.42.0.25' => 'setct-AuthRevResTBS',
+ '2.23.42.0.26' => 'setct-CapReqTBS',
+ '2.23.42.0.27' => 'setct-CapReqTBSX',
+ '2.23.42.0.28' => 'setct-CapResData',
+ '2.23.42.0.29' => 'setct-CapRevReqTBS',
+ '2.23.42.0.30' => 'setct-CapRevReqTBSX',
+ '2.23.42.0.31' => 'setct-CapRevResData',
+ '2.23.42.0.32' => 'setct-CredReqTBS',
+ '2.23.42.0.33' => 'setct-CredReqTBSX',
+ '2.23.42.0.34' => 'setct-CredResData',
+ '2.23.42.0.35' => 'setct-CredRevReqTBS',
+ '2.23.42.0.36' => 'setct-CredRevReqTBSX',
+ '2.23.42.0.37' => 'setct-CredRevResData',
+ '2.23.42.0.38' => 'setct-PCertReqData',
+ '2.23.42.0.39' => 'setct-PCertResTBS',
+ '2.23.42.0.40' => 'setct-BatchAdminReqData',
+ '2.23.42.0.41' => 'setct-BatchAdminResData',
+ '2.23.42.0.42' => 'setct-CardCInitResTBS',
+ '2.23.42.0.43' => 'setct-MeAqCInitResTBS',
+ '2.23.42.0.44' => 'setct-RegFormResTBS',
+ '2.23.42.0.45' => 'setct-CertReqData',
+ '2.23.42.0.46' => 'setct-CertReqTBS',
+ '2.23.42.0.47' => 'setct-CertResData',
+ '2.23.42.0.48' => 'setct-CertInqReqTBS',
+ '2.23.42.0.49' => 'setct-ErrorTBS',
+ '2.23.42.0.50' => 'setct-PIDualSignedTBE',
+ '2.23.42.0.51' => 'setct-PIUnsignedTBE',
+ '2.23.42.0.52' => 'setct-AuthReqTBE',
+ '2.23.42.0.53' => 'setct-AuthResTBE',
+ '2.23.42.0.54' => 'setct-AuthResTBEX',
+ '2.23.42.0.55' => 'setct-AuthTokenTBE',
+ '2.23.42.0.56' => 'setct-CapTokenTBE',
+ '2.23.42.0.57' => 'setct-CapTokenTBEX',
+ '2.23.42.0.58' => 'setct-AcqCardCodeMsgTBE',
+ '2.23.42.0.59' => 'setct-AuthRevReqTBE',
+ '2.23.42.0.60' => 'setct-AuthRevResTBE',
+ '2.23.42.0.61' => 'setct-AuthRevResTBEB',
+ '2.23.42.0.62' => 'setct-CapReqTBE',
+ '2.23.42.0.63' => 'setct-CapReqTBEX',
+ '2.23.42.0.64' => 'setct-CapResTBE',
+ '2.23.42.0.65' => 'setct-CapRevReqTBE',
+ '2.23.42.0.66' => 'setct-CapRevReqTBEX',
+ '2.23.42.0.67' => 'setct-CapRevResTBE',
+ '2.23.42.0.68' => 'setct-CredReqTBE',
+ '2.23.42.0.69' => 'setct-CredReqTBEX',
+ '2.23.42.0.70' => 'setct-CredResTBE',
+ '2.23.42.0.71' => 'setct-CredRevReqTBE',
+ '2.23.42.0.72' => 'setct-CredRevReqTBEX',
+ '2.23.42.0.73' => 'setct-CredRevResTBE',
+ '2.23.42.0.74' => 'setct-BatchAdminReqTBE',
+ '2.23.42.0.75' => 'setct-BatchAdminResTBE',
+ '2.23.42.0.76' => 'setct-RegFormReqTBE',
+ '2.23.42.0.77' => 'setct-CertReqTBE',
+ '2.23.42.0.78' => 'setct-CertReqTBEX',
+ '2.23.42.0.79' => 'setct-CertResTBE',
+ '2.23.42.0.80' => 'setct-CRLNotificationTBS',
+ '2.23.42.0.81' => 'setct-CRLNotificationResTBS',
+ '2.23.42.0.82' => 'setct-BCIDistributionTBS',
+ '2.23.42.1.1' => 'generic cryptogram',
+ '2.23.42.1.3' => 'merchant initiated auth',
+ '2.23.42.1.4' => 'setext-pinSecure',
+ '2.23.42.1.5' => 'setext-pinAny',
+ '2.23.42.1.7' => 'setext-track2',
+ '2.23.42.1.8' => 'additional verification',
+ '2.23.42.5.0' => 'set-policy-root',
+ '2.23.42.7.0' => 'setCext-hashedRoot',
+ '2.23.42.7.1' => 'setCext-certType',
+ '2.23.42.7.2' => 'setCext-merchData',
+ '2.23.42.7.3' => 'setCext-cCertRequired',
+ '2.23.42.7.4' => 'setCext-tunneling',
+ '2.23.42.7.5' => 'setCext-setExt',
+ '2.23.42.7.6' => 'setCext-setQualf',
+ '2.23.42.7.7' => 'setCext-PGWYcapabilities',
+ '2.23.42.7.8' => 'setCext-TokenIdentifier',
+ '2.23.42.7.9' => 'setCext-Track2Data',
+ '2.23.42.7.10' => 'setCext-TokenType',
+ '2.23.42.7.11' => 'setCext-IssuerCapabilities',
+ '2.23.42.3.0' => 'setAttr-Cert',
+ '2.23.42.3.1' => 'payment gateway capabilities',
+ '2.23.42.3.2' => 'setAttr-TokenType',
+ '2.23.42.3.3' => 'issuer capabilities',
+ '2.23.42.3.0.0' => 'set-rootKeyThumb',
+ '2.23.42.3.0.1' => 'set-addPolicy',
+ '2.23.42.3.2.1' => 'setAttr-Token-EMV',
+ '2.23.42.3.2.2' => 'setAttr-Token-B0Prime',
+ '2.23.42.3.3.3' => 'setAttr-IssCap-CVM',
+ '2.23.42.3.3.4' => 'setAttr-IssCap-T2',
+ '2.23.42.3.3.5' => 'setAttr-IssCap-Sig',
+ '2.23.42.3.3.3.1' => 'generate cryptogram',
+ '2.23.42.3.3.4.1' => 'encrypted track 2',
+ '2.23.42.3.3.4.2' => 'cleartext track 2',
+ '2.23.42.3.3.5.1' => 'ICC or token signature',
+ '2.23.42.3.3.5.2' => 'secure device signature',
+ '2.23.42.8.1' => 'set-brand-IATA-ATA',
+ '2.23.42.8.30' => 'set-brand-Diners',
+ '2.23.42.8.34' => 'set-brand-AmericanExpress',
+ '2.23.42.8.35' => 'set-brand-JCB',
+ '2.23.42.8.4' => 'set-brand-Visa',
+ '2.23.42.8.5' => 'set-brand-MasterCard',
+ '2.23.42.8.6011' => 'set-brand-Novus',
+ '1.2.840.113549.3.10' => 'des-cdmf',
+ '1.2.840.113549.1.1.6' => 'rsaOAEPEncryptionSET',
+ '1.0.10118.3.0.55' => 'whirlpool',
+ '1.2.643.2.2' => 'cryptopro',
+ '1.2.643.2.9' => 'cryptocom',
+ '1.2.643.2.2.3' => 'GOST R 34.11-94 with GOST R 34.10-2001',
+ '1.2.643.2.2.4' => 'GOST R 34.11-94 with GOST R 34.10-94',
+ '1.2.643.2.2.9' => 'GOST R 34.11-94',
+ '1.2.643.2.2.10' => 'HMAC GOST 34.11-94',
+ '1.2.643.2.2.19' => 'GOST R 34.10-2001',
+ '1.2.643.2.2.20' => 'GOST R 34.10-94',
+ '1.2.643.2.2.21' => 'GOST 28147-89',
+ '1.2.643.2.2.22' => 'GOST 28147-89 MAC',
+ '1.2.643.2.2.23' => 'GOST R 34.11-94 PRF',
+ '1.2.643.2.2.98' => 'GOST R 34.10-2001 DH',
+ '1.2.643.2.2.99' => 'GOST R 34.10-94 DH',
+ '1.2.643.2.2.14.1' => 'id-Gost28147-89-CryptoPro-KeyMeshing',
+ '1.2.643.2.2.14.0' => 'id-Gost28147-89-None-KeyMeshing',
+ '1.2.643.2.2.30.0' => 'id-GostR3411-94-TestParamSet',
+ '1.2.643.2.2.30.1' => 'id-GostR3411-94-CryptoProParamSet',
+ '1.2.643.2.2.31.0' => 'id-Gost28147-89-TestParamSet',
+ '1.2.643.2.2.31.1' => 'id-Gost28147-89-CryptoPro-A-ParamSet',
+ '1.2.643.2.2.31.2' => 'id-Gost28147-89-CryptoPro-B-ParamSet',
+ '1.2.643.2.2.31.3' => 'id-Gost28147-89-CryptoPro-C-ParamSet',
+ '1.2.643.2.2.31.4' => 'id-Gost28147-89-CryptoPro-D-ParamSet',
+ '1.2.643.2.2.31.5' => 'id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet',
+ '1.2.643.2.2.31.6' => 'id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet',
+ '1.2.643.2.2.31.7' => 'id-Gost28147-89-CryptoPro-RIC-1-ParamSet',
+ '1.2.643.2.2.32.0' => 'id-GostR3410-94-TestParamSet',
+ '1.2.643.2.2.32.2' => 'id-GostR3410-94-CryptoPro-A-ParamSet',
+ '1.2.643.2.2.32.3' => 'id-GostR3410-94-CryptoPro-B-ParamSet',
+ '1.2.643.2.2.32.4' => 'id-GostR3410-94-CryptoPro-C-ParamSet',
+ '1.2.643.2.2.32.5' => 'id-GostR3410-94-CryptoPro-D-ParamSet',
+ '1.2.643.2.2.33.1' => 'id-GostR3410-94-CryptoPro-XchA-ParamSet',
+ '1.2.643.2.2.33.2' => 'id-GostR3410-94-CryptoPro-XchB-ParamSet',
+ '1.2.643.2.2.33.3' => 'id-GostR3410-94-CryptoPro-XchC-ParamSet',
+ '1.2.643.2.2.35.0' => 'id-GostR3410-2001-TestParamSet',
+ '1.2.643.2.2.35.1' => 'id-GostR3410-2001-CryptoPro-A-ParamSet',
+ '1.2.643.2.2.35.2' => 'id-GostR3410-2001-CryptoPro-B-ParamSet',
+ '1.2.643.2.2.35.3' => 'id-GostR3410-2001-CryptoPro-C-ParamSet',
+ '1.2.643.2.2.36.0' => 'id-GostR3410-2001-CryptoPro-XchA-ParamSet',
+ '1.2.643.2.2.36.1' => 'id-GostR3410-2001-CryptoPro-XchB-ParamSet',
+ '1.2.643.2.2.20.1' => 'id-GostR3410-94-a',
+ '1.2.643.2.2.20.2' => 'id-GostR3410-94-aBis',
+ '1.2.643.2.2.20.3' => 'id-GostR3410-94-b',
+ '1.2.643.2.2.20.4' => 'id-GostR3410-94-bBis',
+ '1.2.643.2.9.1.6.1' => 'GOST 28147-89 Cryptocom ParamSet',
+ '1.2.643.2.9.1.5.3' => 'GOST 34.10-94 Cryptocom',
+ '1.2.643.2.9.1.5.4' => 'GOST 34.10-2001 Cryptocom',
+ '1.2.643.2.9.1.3.3' => 'GOST R 34.11-94 with GOST R 34.10-94 Cryptocom',
+ '1.2.643.2.9.1.3.4' => 'GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom',
+ '1.2.643.2.9.1.8.1' => 'GOST R 3410-2001 Parameter Set Cryptocom',
+ '1.2.392.200011.61.1.1.1.2' => 'camellia-128-cbc',
+ '1.2.392.200011.61.1.1.1.3' => 'camellia-192-cbc',
+ '1.2.392.200011.61.1.1.1.4' => 'camellia-256-cbc',
+ '1.2.392.200011.61.1.1.3.2' => 'id-camellia128-wrap',
+ '1.2.392.200011.61.1.1.3.3' => 'id-camellia192-wrap',
+ '1.2.392.200011.61.1.1.3.4' => 'id-camellia256-wrap',
+ '0.3.4401.5' => 'ntt-ds',
+ '0.3.4401.5.3.1.9' => 'camellia',
+ '0.3.4401.5.3.1.9.1' => 'camellia-128-ecb',
+ '0.3.4401.5.3.1.9.3' => 'camellia-128-ofb',
+ '0.3.4401.5.3.1.9.4' => 'camellia-128-cfb',
+ '0.3.4401.5.3.1.9.6' => 'camellia-128-gcm',
+ '0.3.4401.5.3.1.9.7' => 'camellia-128-ccm',
+ '0.3.4401.5.3.1.9.9' => 'camellia-128-ctr',
+ '0.3.4401.5.3.1.9.10' => 'camellia-128-cmac',
+ '0.3.4401.5.3.1.9.21' => 'camellia-192-ecb',
+ '0.3.4401.5.3.1.9.23' => 'camellia-192-ofb',
+ '0.3.4401.5.3.1.9.24' => 'camellia-192-cfb',
+ '0.3.4401.5.3.1.9.26' => 'camellia-192-gcm',
+ '0.3.4401.5.3.1.9.27' => 'camellia-192-ccm',
+ '0.3.4401.5.3.1.9.29' => 'camellia-192-ctr',
+ '0.3.4401.5.3.1.9.30' => 'camellia-192-cmac',
+ '0.3.4401.5.3.1.9.41' => 'camellia-256-ecb',
+ '0.3.4401.5.3.1.9.43' => 'camellia-256-ofb',
+ '0.3.4401.5.3.1.9.44' => 'camellia-256-cfb',
+ '0.3.4401.5.3.1.9.46' => 'camellia-256-gcm',
+ '0.3.4401.5.3.1.9.47' => 'camellia-256-ccm',
+ '0.3.4401.5.3.1.9.49' => 'camellia-256-ctr',
+ '0.3.4401.5.3.1.9.50' => 'camellia-256-cmac',
+ '1.2.410.200004' => 'kisa',
+ '1.2.410.200004.1.3' => 'seed-ecb',
+ '1.2.410.200004.1.4' => 'seed-cbc',
+ '1.2.410.200004.1.5' => 'seed-cfb',
+ '1.2.410.200004.1.6' => 'seed-ofb',
+ '1.2.840.10046.2.1' => 'X9.42 DH',
+ '1.3.36.3.3.2.8.1.1.1' => 'brainpoolP160r1',
+ '1.3.36.3.3.2.8.1.1.2' => 'brainpoolP160t1',
+ '1.3.36.3.3.2.8.1.1.3' => 'brainpoolP192r1',
+ '1.3.36.3.3.2.8.1.1.4' => 'brainpoolP192t1',
+ '1.3.36.3.3.2.8.1.1.5' => 'brainpoolP224r1',
+ '1.3.36.3.3.2.8.1.1.6' => 'brainpoolP224t1',
+ '1.3.36.3.3.2.8.1.1.7' => 'brainpoolP256r1',
+ '1.3.36.3.3.2.8.1.1.8' => 'brainpoolP256t1',
+ '1.3.36.3.3.2.8.1.1.9' => 'brainpoolP320r1',
+ '1.3.36.3.3.2.8.1.1.10' => 'brainpoolP320t1',
+ '1.3.36.3.3.2.8.1.1.11' => 'brainpoolP384r1',
+ '1.3.36.3.3.2.8.1.1.12' => 'brainpoolP384t1',
+ '1.3.36.3.3.2.8.1.1.13' => 'brainpoolP512r1',
+ '1.3.36.3.3.2.8.1.1.14' => 'brainpoolP512t1',
+ '1.3.133.16.840.63.0' => 'x9-63-scheme',
+ '1.3.132.1' => 'secg-scheme',
+ '1.3.133.16.840.63.0.2' => 'dhSinglePass-stdDH-sha1kdf-scheme',
+ '1.3.132.1.11.0' => 'dhSinglePass-stdDH-sha224kdf-scheme',
+ '1.3.132.1.11.1' => 'dhSinglePass-stdDH-sha256kdf-scheme',
+ '1.3.132.1.11.2' => 'dhSinglePass-stdDH-sha384kdf-scheme',
+ '1.3.132.1.11.3' => 'dhSinglePass-stdDH-sha512kdf-scheme',
+ '1.3.133.16.840.63.0.3' => 'dhSinglePass-cofactorDH-sha1kdf-scheme',
+ '1.3.132.1.14.0' => 'dhSinglePass-cofactorDH-sha224kdf-scheme',
+ '1.3.132.1.14.1' => 'dhSinglePass-cofactorDH-sha256kdf-scheme',
+ '1.3.132.1.14.2' => 'dhSinglePass-cofactorDH-sha384kdf-scheme',
+ '1.3.132.1.14.3' => 'dhSinglePass-cofactorDH-sha512kdf-scheme',
+ '1.3.6.1.4.1.11129.2.4.2' => 'CT Precertificate SCTs',
+ '1.3.6.1.4.1.11129.2.4.3' => 'CT Precertificate Poison',
+ '1.3.6.1.4.1.11129.2.4.4' => 'CT Precertificate Signer',
+ '1.3.6.1.4.1.11129.2.4.5' => 'CT Certificate SCTs',
+ '1.3.6.1.4.1.311.60.2.1.1' => 'jurisdictionLocalityName',
+ '1.3.6.1.4.1.311.60.2.1.2' => 'jurisdictionStateOrProvinceName',
+ '1.3.6.1.4.1.311.60.2.1.3' => 'jurisdictionCountryName',
+ '1.3.6.1.4.1.11591.4.11' => 'id-scrypt',
+ ];
+
+ if (array_key_exists($oidString, $oids)) {
+ return $oids[$oidString];
+ }
+
+ switch ($oidString) {
+ case self::RSA_ENCRYPTION:
+ return 'RSA Encryption';
+ case self::MD5_WITH_RSA_ENCRYPTION:
+ return 'MD5 with RSA Encryption';
+ case self::SHA1_WITH_RSA_SIGNATURE:
+ return 'SHA-1 with RSA Signature';
+
+ case self::PKCS9_EMAIL:
+ return 'PKCS #9 Email Address';
+ case self::PKCS9_UNSTRUCTURED_NAME:
+ return 'PKCS #9 Unstructured Name';
+ case self::PKCS9_CONTENT_TYPE:
+ return 'PKCS #9 Content Type';
+ case self::PKCS9_MESSAGE_DIGEST:
+ return 'PKCS #9 Message Digest';
+ case self::PKCS9_SIGNING_TIME:
+ return 'PKCS #9 Signing Time';
+
+ case self::COMMON_NAME:
+ return 'Common Name';
+ case self::SURNAME:
+ return 'Surname';
+ case self::SERIAL_NUMBER:
+ return 'Serial Number';
+ case self::COUNTRY_NAME:
+ return 'Country Name';
+ case self::LOCALITY_NAME:
+ return 'Locality Name';
+ case self::STATE_OR_PROVINCE_NAME:
+ return 'State or Province Name';
+ case self::STREET_ADDRESS:
+ return 'Street Address';
+ case self::ORGANIZATION_NAME:
+ return 'Organization Name';
+ case self::OU_NAME:
+ return 'Organization Unit Name';
+ case self::TITLE:
+ return 'Title';
+ case self::DESCRIPTION:
+ return 'Description';
+ case self::POSTAL_ADDRESS:
+ return 'Postal Address';
+ case self::POSTAL_CODE:
+ return 'Postal Code';
+ case self::AUTHORITY_REVOCATION_LIST:
+ return 'Authority Revocation List';
+
+ case self::CERT_EXT_SUBJECT_DIRECTORY_ATTR:
+ return 'Subject directory attributes';
+ case self::CERT_EXT_SUBJECT_KEY_IDENTIFIER:
+ return 'Subject key identifier';
+ case self::CERT_EXT_KEY_USAGE:
+ return 'Key usage certificate extension';
+ case self::CERT_EXT_PRIVATE_KEY_USAGE_PERIOD:
+ return 'Private key usage';
+ case self::CERT_EXT_SUBJECT_ALT_NAME:
+ return 'Subject alternative name (SAN)';
+ case self::CERT_EXT_ISSUER_ALT_NAME:
+ return 'Issuer alternative name';
+ case self::CERT_EXT_BASIC_CONSTRAINTS:
+ return 'Basic constraints';
+ case self::CERT_EXT_CRL_NUMBER:
+ return 'CRL number';
+ case self::CERT_EXT_REASON_CODE:
+ return 'Reason code';
+ case self::CERT_EXT_INVALIDITY_DATE:
+ return 'Invalidity code';
+ case self::CERT_EXT_DELTA_CRL_INDICATOR:
+ return 'Delta CRL indicator';
+ case self::CERT_EXT_ISSUING_DIST_POINT:
+ return 'Issuing distribution point';
+ case self::CERT_EXT_CERT_ISSUER:
+ return 'Certificate issuer';
+ case self::CERT_EXT_NAME_CONSTRAINTS:
+ return 'Name constraints';
+ case self::CERT_EXT_CRL_DISTRIBUTION_POINTS:
+ return 'CRL distribution points';
+ case self::CERT_EXT_CERT_POLICIES:
+ return 'Certificate policies ';
+ case self::CERT_EXT_AUTHORITY_KEY_IDENTIFIER:
+ return 'Authority key identifier';
+ case self::CERT_EXT_EXTENDED_KEY_USAGE:
+ return 'Extended key usage';
+ case self::AUTHORITY_INFORMATION_ACCESS:
+ return 'Certificate Authority Information Access (AIA)';
+
+ default:
+ if ($loadFromWeb) {
+ return self::loadFromWeb($oidString);
+ } else {
+ return $oidString;
+ }
+ }
+ }
+
+ public static function loadFromWeb($oidString)
+ {
+ $ch = curl_init("http://oid-info.com/get/{$oidString}");
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+
+ $contents = curl_exec($ch);
+ curl_close($ch);
+
+ // This pattern needs to be updated as soon as the website layout of oid-info.com changes
+ preg_match_all('#<tt>(.+)\(\d+\)</tt>#si', $contents, $oidName);
+
+ if (empty($oidName[1])) {
+ return "{$oidString} (unknown)";
+ }
+
+ $oidName = ucfirst(strtolower(preg_replace('/([A-Z][a-z])/', ' $1', $oidName[1][0])));
+ $oidName = str_replace('-', ' ', $oidName);
+
+ return "{$oidName} ({$oidString})";
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use FG\ASN1\Exception\ParserException;
+
+/**
+ * The Parsable interface describes classes that can be parsed from their binary DER representation.
+ */
+interface Parsable
+{
+ /**
+ * Parse an instance of this class from its binary DER encoded representation.
+ *
+ * @param string $binaryData
+ * @param int $offsetIndex the offset at which parsing of the $binaryData is started. This parameter ill be modified
+ * to contain the offset index of the next object after this object has been parsed
+ *
+ * @throws ParserException if the given binary data is either invalid or not currently supported
+ *
+ * @return static
+ */
+ public static function fromBinary(&$binaryData, &$offsetIndex = null);
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+use Exception;
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\Universal\Sequence;
+
+class TemplateParser
+{
+ /**
+ * @param string $data
+ * @param array $template
+ * @return \FG\ASN1\ASNObject|Sequence
+ * @throws ParserException if there was an issue parsing
+ */
+ public function parseBase64($data, array $template)
+ {
+ // TODO test with invalid data
+ return $this->parseBinary(base64_decode($data), $template);
+ }
+
+ /**
+ * @param string $binary
+ * @param array $template
+ * @return \FG\ASN1\ASNObject|Sequence
+ * @throws ParserException if there was an issue parsing
+ */
+ public function parseBinary($binary, array $template)
+ {
+ $parsedObject = ASNObject::fromBinary($binary);
+
+ foreach ($template as $key => $value) {
+ $this->validate($parsedObject, $key, $value);
+ }
+
+ return $parsedObject;
+ }
+
+ private function validate(ASNObject $object, $key, $value)
+ {
+ if (is_array($value)) {
+ $this->assertTypeId($key, $object);
+
+ /* @var Construct $object */
+ foreach ($value as $key => $child) {
+ $this->validate($object->current(), $key, $child);
+ $object->next();
+ }
+ } else {
+ $this->assertTypeId($value, $object);
+ }
+ }
+
+ private function assertTypeId($expectedTypeId, ASNObject $object)
+ {
+ $actualType = $object->getType();
+ if ($expectedTypeId != $actualType) {
+ throw new Exception("Expected type ($expectedTypeId) does not match actual type ($actualType");
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class BMPString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 BMP String.
+ *
+ * BMPString is a subtype of UniversalString that has its own
+ * unique tag and contains only the characters in the
+ * Basic Multilingual Plane (those corresponding to the first
+ * 64K-2 cells, less cells whose encoding is used to address
+ * characters outside the Basic Multilingual Plane) of ISO/IEC 10646-1.
+ *
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::BMP_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use Exception;
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+
+class BitString extends OctetString implements Parsable
+{
+ private $nrOfUnusedBits;
+
+ /**
+ * Creates a new ASN.1 BitString object.
+ *
+ * @param string|int $value Either the hexadecimal value as a string (spaces are allowed - leading 0x is optional) or a numeric value
+ * @param int $nrOfUnusedBits the number of unused bits in the last octet [optional].
+ *
+ * @throws Exception if the second parameter is no positive numeric value
+ */
+ public function __construct($value, $nrOfUnusedBits = 0)
+ {
+ parent::__construct($value);
+
+ if (!is_numeric($nrOfUnusedBits) || $nrOfUnusedBits < 0) {
+ throw new Exception('BitString: second parameter needs to be a positive number (or zero)!');
+ }
+
+ $this->nrOfUnusedBits = $nrOfUnusedBits;
+ }
+
+ public function getType()
+ {
+ return Identifier::BITSTRING;
+ }
+
+ protected function calculateContentLength()
+ {
+ // add one to the length for the first octet which encodes the number of unused bits in the last octet
+ return parent::calculateContentLength() + 1;
+ }
+
+ protected function getEncodedValue()
+ {
+ // the first octet determines the number of unused bits
+ $nrOfUnusedBitsOctet = chr($this->nrOfUnusedBits);
+ $actualContent = parent::getEncodedValue();
+
+ return $nrOfUnusedBitsOctet.$actualContent;
+ }
+
+ public function getNumberOfUnusedBits()
+ {
+ return $this->nrOfUnusedBits;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::BITSTRING, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, 2);
+
+ $nrOfUnusedBits = ord($binaryData[$offsetIndex]);
+ $value = substr($binaryData, $offsetIndex + 1, $contentLength - 1);
+
+ if ($nrOfUnusedBits > 7 || // no less than 1 used, otherwise non-minimal
+ ($contentLength - 1) == 1 && $nrOfUnusedBits > 0 || // content length only 1, no
+ (ord($value[strlen($value)-1])&((1<<$nrOfUnusedBits)-1)) != 0 // unused bits set
+ ) {
+ throw new ParserException("Can not parse bit string with invalid padding", $offsetIndex);
+ }
+
+ $offsetIndex += $contentLength;
+
+ $parsedObject = new self(bin2hex($value), $nrOfUnusedBits);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+class Boolean extends ASNObject implements Parsable
+{
+ private $value;
+
+ /**
+ * @param bool $value
+ */
+ public function __construct($value)
+ {
+ $this->value = $value;
+ }
+
+ public function getType()
+ {
+ return Identifier::BOOLEAN;
+ }
+
+ protected function calculateContentLength()
+ {
+ return 1;
+ }
+
+ protected function getEncodedValue()
+ {
+ if ($this->value == false) {
+ return chr(0x00);
+ } else {
+ return chr(0xFF);
+ }
+ }
+
+ public function getContent()
+ {
+ if ($this->value == true) {
+ return 'TRUE';
+ } else {
+ return 'FALSE';
+ }
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::BOOLEAN, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ if ($contentLength != 1) {
+ throw new ParserException("An ASN.1 Boolean should not have a length other than one. Extracted length was {$contentLength}", $offsetIndex);
+ }
+
+ $value = ord($binaryData[$offsetIndex++]);
+ $booleanValue = $value == 0xFF ? true : false;
+
+ $parsedObject = new self($booleanValue);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class CharacterString extends AbstractString
+{
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::CHARACTER_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\Identifier;
+
+class Enumerated extends Integer
+{
+ public function getType()
+ {
+ return Identifier::ENUMERATED;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class GeneralString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 GeneralString.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::GENERAL_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractTime;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+/**
+ * This ASN.1 universal type contains date and time information according to ISO 8601.
+ *
+ * The type consists of values representing:
+ * a) a calendar date, as defined in ISO 8601; and
+ * b) a time of day, to any of the precisions defined in ISO 8601, except for the hours value 24 which shall not be used; and
+ * c) the local time differential factor as defined in ISO 8601.
+ *
+ * Decoding of this type will accept the Basic Encoding Rules (BER)
+ * The encoding will comply with the Distinguished Encoding Rules (DER).
+ */
+class GeneralizedTime extends AbstractTime implements Parsable
+{
+ private $microseconds;
+
+ public function __construct($dateTime = null, $dateTimeZone = 'UTC')
+ {
+ parent::__construct($dateTime, $dateTimeZone);
+ $this->microseconds = $this->value->format('u');
+ if ($this->containsFractionalSecondsElement()) {
+ // DER requires us to remove trailing zeros
+ $this->microseconds = preg_replace('/([1-9]+)0+$/', '$1', $this->microseconds);
+ }
+ }
+
+ public function getType()
+ {
+ return Identifier::GENERALIZED_TIME;
+ }
+
+ protected function calculateContentLength()
+ {
+ $contentSize = 15; // YYYYMMDDHHmmSSZ
+
+ if ($this->containsFractionalSecondsElement()) {
+ $contentSize += 1 + strlen($this->microseconds);
+ }
+
+ return $contentSize;
+ }
+
+ public function containsFractionalSecondsElement()
+ {
+ return intval($this->microseconds) > 0;
+ }
+
+ protected function getEncodedValue()
+ {
+ $encodedContent = $this->value->format('YmdHis');
+ if ($this->containsFractionalSecondsElement()) {
+ $encodedContent .= ".{$this->microseconds}";
+ }
+
+ return $encodedContent.'Z';
+ }
+
+ public function __toString()
+ {
+ if ($this->containsFractionalSecondsElement()) {
+ return $this->value->format("Y-m-d\tH:i:s.uP");
+ } else {
+ return $this->value->format("Y-m-d\tH:i:sP");
+ }
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::GENERALIZED_TIME, $offsetIndex++);
+ $lengthOfMinimumTimeString = 14; // YYYYMMDDHHmmSS
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, $lengthOfMinimumTimeString);
+ $maximumBytesToRead = $contentLength;
+
+ $format = 'YmdGis';
+ $content = substr($binaryData, $offsetIndex, $contentLength);
+ $dateTimeString = substr($content, 0, $lengthOfMinimumTimeString);
+ $offsetIndex += $lengthOfMinimumTimeString;
+ $maximumBytesToRead -= $lengthOfMinimumTimeString;
+
+ if ($contentLength == $lengthOfMinimumTimeString) {
+ $localTimeZone = new \DateTimeZone(date_default_timezone_get());
+ $dateTime = \DateTime::createFromFormat($format, $dateTimeString, $localTimeZone);
+ } else {
+ if ($binaryData[$offsetIndex] == '.') {
+ $maximumBytesToRead--; // account for the '.'
+ $nrOfFractionalSecondElements = 1; // account for the '.'
+
+ while ($maximumBytesToRead > 0
+ && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '+'
+ && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '-'
+ && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != 'Z') {
+ $nrOfFractionalSecondElements++;
+ $maximumBytesToRead--;
+ }
+
+ $dateTimeString .= substr($binaryData, $offsetIndex, $nrOfFractionalSecondElements);
+ $offsetIndex += $nrOfFractionalSecondElements;
+ $format .= '.u';
+ }
+
+ $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC'));
+
+ if ($maximumBytesToRead > 0) {
+ if ($binaryData[$offsetIndex] == '+'
+ || $binaryData[$offsetIndex] == '-') {
+ $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime);
+ } elseif ($binaryData[$offsetIndex++] != 'Z') {
+ throw new ParserException('Invalid ISO 8601 Time String', $offsetIndex);
+ }
+ }
+ }
+
+ $parsedObject = new self($dateTime);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class GraphicString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 Graphic String.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::GRAPHIC_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+/**
+ * The International Alphabet No.5 (IA5) references the encoding of the ASCII characters.
+ *
+ * Each character in the data is encoded as 1 byte.
+ */
+class IA5String extends AbstractString
+{
+ public function __construct($string)
+ {
+ parent::__construct($string);
+ for ($i = 1; $i < 128; $i++) {
+ $this->allowCharacter(chr($i));
+ }
+ }
+
+ public function getType()
+ {
+ return Identifier::IA5_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use Exception;
+use FG\Utility\BigInteger;
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+
+class Integer extends ASNObject implements Parsable
+{
+ /** @var int */
+ private $value;
+
+ /**
+ * @param int $value
+ *
+ * @throws Exception if the value is not numeric
+ */
+ public function __construct($value)
+ {
+ if (is_numeric($value) == false) {
+ throw new Exception("Invalid VALUE [{$value}] for ASN1_INTEGER");
+ }
+ $this->value = $value;
+ }
+
+ public function getType()
+ {
+ return Identifier::INTEGER;
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ protected function calculateContentLength()
+ {
+ return strlen($this->getEncodedValue());
+ }
+
+ protected function getEncodedValue()
+ {
+ $value = BigInteger::create($this->value, 10);
+ $negative = $value->compare(0) < 0;
+ if ($negative) {
+ $value = $value->absoluteValue();
+ $limit = 0x80;
+ } else {
+ $limit = 0x7f;
+ }
+
+ $mod = 0xff+1;
+ $values = [];
+ while($value->compare($limit) > 0) {
+ $values[] = $value->modulus($mod)->toInteger();
+ $value = $value->shiftRight(8);
+ }
+
+ $values[] = $value->modulus($mod)->toInteger();
+ $numValues = count($values);
+
+ if ($negative) {
+ for ($i = 0; $i < $numValues; $i++) {
+ $values[$i] = 0xff - $values[$i];
+ }
+ for ($i = 0; $i < $numValues; $i++) {
+ $values[$i] += 1;
+ if ($values[$i] <= 0xff) {
+ break;
+ }
+ assert($i != $numValues - 1);
+ $values[$i] = 0;
+ }
+ if ($values[$numValues - 1] == 0x7f) {
+ $values[] = 0xff;
+ }
+ }
+ $values = array_reverse($values);
+ $r = pack("C*", ...$values);
+ return $r;
+ }
+
+ private static function ensureMinimalEncoding($binaryData, $offsetIndex)
+ {
+ // All the first nine bits cannot equal 0 or 1, which would
+ // be non-minimal encoding for positive and negative integers respectively
+ if ((ord($binaryData[$offsetIndex]) == 0x00 && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0) ||
+ (ord($binaryData[$offsetIndex]) == 0xff && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0x80)) {
+ throw new ParserException("Integer not minimally encoded", $offsetIndex);
+ }
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ $parsedObject = new static(0);
+ self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1);
+
+ if ($contentLength > 1) {
+ self::ensureMinimalEncoding($binaryData, $offsetIndex);
+ }
+ $isNegative = (ord($binaryData[$offsetIndex]) & 0x80) != 0x00;
+ $number = BigInteger::create(ord($binaryData[$offsetIndex++]) & 0x7F);
+
+ for ($i = 0; $i < $contentLength - 1; $i++) {
+ $number = $number->multiply(0x100)->add(ord($binaryData[$offsetIndex++]));
+ }
+
+ if ($isNegative) {
+ $number = $number->subtract(BigInteger::create(2)->toPower(8 * $contentLength - 1));
+ }
+
+ $parsedObject = new static((string)$number);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+class NullObject extends ASNObject implements Parsable
+{
+ public function getType()
+ {
+ return Identifier::NULL;
+ }
+
+ protected function calculateContentLength()
+ {
+ return 0;
+ }
+
+ protected function getEncodedValue()
+ {
+ return null;
+ }
+
+ public function getContent()
+ {
+ return 'NULL';
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::NULL, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ if ($contentLength != 0) {
+ throw new ParserException("An ASN.1 Null should not have a length other than zero. Extracted length was {$contentLength}", $offsetIndex);
+ }
+
+ $parsedObject = new self();
+ $parsedObject->setContentLength(0);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class NumericString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 NumericString.
+ *
+ * The following characters are permitted:
+ * Digits 0,1, ... 9
+ * SPACE (space)
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowNumbers();
+ $this->allowSpaces();
+ }
+
+ public function getType()
+ {
+ return Identifier::NUMERIC_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\Identifier;
+
+class ObjectDescriptor extends GraphicString
+{
+ public function __construct($objectDescription)
+ {
+ parent::__construct($objectDescription);
+ }
+
+ public function getType()
+ {
+ return Identifier::OBJECT_DESCRIPTOR;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use Exception;
+use FG\ASN1\Base128;
+use FG\ASN1\OID;
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+class ObjectIdentifier extends ASNObject implements Parsable
+{
+ protected $subIdentifiers;
+ protected $value;
+
+ public function __construct($value)
+ {
+ $this->subIdentifiers = explode('.', $value);
+ $nrOfSubIdentifiers = count($this->subIdentifiers);
+
+ for ($i = 0; $i < $nrOfSubIdentifiers; $i++) {
+ if (is_numeric($this->subIdentifiers[$i])) {
+ // enforce the integer type
+ $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]);
+ } else {
+ throw new Exception("[{$value}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!');
+ }
+ }
+
+ // Merge the first to arcs of the OID registration tree (per ASN definition!)
+ if ($nrOfSubIdentifiers >= 2) {
+ $this->subIdentifiers[1] = ($this->subIdentifiers[0] * 40) + $this->subIdentifiers[1];
+ unset($this->subIdentifiers[0]);
+ }
+
+ $this->value = $value;
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ public function getType()
+ {
+ return Identifier::OBJECT_IDENTIFIER;
+ }
+
+ protected function calculateContentLength()
+ {
+ $length = 0;
+ foreach ($this->subIdentifiers as $subIdentifier) {
+ do {
+ $subIdentifier = $subIdentifier >> 7;
+ $length++;
+ } while ($subIdentifier > 0);
+ }
+
+ return $length;
+ }
+
+ protected function getEncodedValue()
+ {
+ $encodedValue = '';
+ foreach ($this->subIdentifiers as $subIdentifier) {
+ $encodedValue .= Base128::encode($subIdentifier);
+ }
+
+ return $encodedValue;
+ }
+
+ public function __toString()
+ {
+ return OID::getName($this->value);
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::OBJECT_IDENTIFIER, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1);
+
+ $firstOctet = ord($binaryData[$offsetIndex++]);
+ $oidString = floor($firstOctet / 40).'.'.($firstOctet % 40);
+ $oidString .= '.'.self::parseOid($binaryData, $offsetIndex, $contentLength - 1);
+
+ $parsedObject = new self($oidString);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+
+ /**
+ * Parses an object identifier except for the first octet, which is parsed
+ * differently. This way relative object identifiers can also be parsed
+ * using this.
+ *
+ * @param $binaryData
+ * @param $offsetIndex
+ * @param $octetsToRead
+ *
+ * @throws ParserException
+ *
+ * @return string
+ */
+ protected static function parseOid(&$binaryData, &$offsetIndex, $octetsToRead)
+ {
+ $oid = '';
+
+ while ($octetsToRead > 0) {
+ $octets = '';
+
+ do {
+ if (0 === $octetsToRead) {
+ throw new ParserException('Malformed ASN.1 Object Identifier', $offsetIndex - 1);
+ }
+
+ $octetsToRead--;
+ $octet = $binaryData[$offsetIndex++];
+ $octets .= $octet;
+ } while (ord($octet) & 0x80);
+
+ $oid .= sprintf('%d.', Base128::decode($octets));
+ }
+
+ // Remove trailing '.'
+ return substr($oid, 0, -1) ?: '';
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use Exception;
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+
+class OctetString extends ASNObject implements Parsable
+{
+ protected $value;
+
+ public function __construct($value)
+ {
+ if (is_string($value)) {
+ // remove gaps between hex digits
+ $value = preg_replace('/\s|0x/', '', $value);
+ } elseif (is_numeric($value)) {
+ $value = dechex($value);
+ } elseif ($value === null) {
+ return;
+ } else {
+ throw new Exception('OctetString: unrecognized input type!');
+ }
+
+ if (strlen($value) % 2 != 0) {
+ // transform values like 1F2 to 01F2
+ $value = '0'.$value;
+ }
+
+ $this->value = $value;
+ }
+
+ public function getType()
+ {
+ return Identifier::OCTETSTRING;
+ }
+
+ protected function calculateContentLength()
+ {
+ return strlen($this->value) / 2;
+ }
+
+ protected function getEncodedValue()
+ {
+ $value = $this->value;
+ $result = '';
+
+ //Actual content
+ while (strlen($value) >= 2) {
+ // get the hex value byte by byte from the string and and add it to binary result
+ $result .= chr(hexdec(substr($value, 0, 2)));
+ $value = substr($value, 2);
+ }
+
+ return $result;
+ }
+
+ public function getContent()
+ {
+ return strtoupper($this->value);
+ }
+
+ public function getBinaryContent()
+ {
+ return $this->getEncodedValue();
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ $value = substr($binaryData, $offsetIndex, $contentLength);
+ $offsetIndex += $contentLength;
+
+ $parsedObject = new self(bin2hex($value));
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class PrintableString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 PrintableString.
+ *
+ * The ITU-T X.680 Table 8 permits the following characters:
+ * Latin capital letters A,B, ... Z
+ * Latin small letters a,b, ... z
+ * Digits 0,1, ... 9
+ * SPACE (space)
+ * APOSTROPHE '
+ * LEFT PARENTHESIS (
+ * RIGHT PARENTHESIS )
+ * PLUS SIGN +
+ * COMMA ,
+ * HYPHEN-MINUS -
+ * FULL STOP .
+ * SOLIDUS /
+ * COLON :
+ * EQUALS SIGN =
+ * QUESTION MARK ?
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowNumbers();
+ $this->allowAllLetters();
+ $this->allowSpaces();
+ $this->allowCharacters("'", '(', ')', '+', '-', '.', ',', '/', ':', '=', '?');
+ }
+
+ public function getType()
+ {
+ return Identifier::PRINTABLE_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use Exception;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+class RelativeObjectIdentifier extends ObjectIdentifier implements Parsable
+{
+ public function __construct($subIdentifiers)
+ {
+ $this->value = $subIdentifiers;
+ $this->subIdentifiers = explode('.', $subIdentifiers);
+ $nrOfSubIdentifiers = count($this->subIdentifiers);
+
+ for ($i = 0; $i < $nrOfSubIdentifiers; $i++) {
+ if (is_numeric($this->subIdentifiers[$i])) {
+ // enforce the integer type
+ $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]);
+ } else {
+ throw new Exception("[{$subIdentifiers}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!');
+ }
+ }
+ }
+
+ public function getType()
+ {
+ return Identifier::RELATIVE_OID;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::RELATIVE_OID, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1);
+
+ try {
+ $oidString = self::parseOid($binaryData, $offsetIndex, $contentLength);
+ } catch (ParserException $e) {
+ throw new ParserException('Malformed ASN.1 Relative Object Identifier', $e->getOffset());
+ }
+
+ $parsedObject = new self($oidString);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\Construct;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+
+class Sequence extends Construct implements Parsable
+{
+ public function getType()
+ {
+ return Identifier::SEQUENCE;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\Identifier;
+
+class Set extends Sequence
+{
+ public function getType()
+ {
+ return Identifier::SET;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class T61String extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 T61 String.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @see http://en.wikipedia.org/wiki/ITU_T.61
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::T61_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractTime;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Exception\ParserException;
+
+/**
+ * This ASN.1 universal type contains the calendar date and time.
+ *
+ * The precision is one minute or one second and optionally a
+ * local time differential from coordinated universal time.
+ *
+ * Decoding of this type will accept the Basic Encoding Rules (BER)
+ * The encoding will comply with the Distinguished Encoding Rules (DER).
+ */
+class UTCTime extends AbstractTime implements Parsable
+{
+ public function getType()
+ {
+ return Identifier::UTC_TIME;
+ }
+
+ protected function calculateContentLength()
+ {
+ return 13; // Content is a string o the following format: YYMMDDhhmmssZ (13 octets)
+ }
+
+ protected function getEncodedValue()
+ {
+ return $this->value->format('ymdHis').'Z';
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::UTC_TIME, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex, 11);
+
+ $format = 'ymdGi';
+ $dateTimeString = substr($binaryData, $offsetIndex, 10);
+ $offsetIndex += 10;
+
+ // extract optional seconds part
+ if ($binaryData[$offsetIndex] != 'Z'
+ && $binaryData[$offsetIndex] != '+'
+ && $binaryData[$offsetIndex] != '-') {
+ $dateTimeString .= substr($binaryData, $offsetIndex, 2);
+ $offsetIndex += 2;
+ $format .= 's';
+ }
+
+ $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC'));
+
+ // extract time zone settings
+ if ($binaryData[$offsetIndex] == '+'
+ || $binaryData[$offsetIndex] == '-') {
+ $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime);
+ } elseif ($binaryData[$offsetIndex++] != 'Z') {
+ throw new ParserException('Invalid UTC String', $offsetIndex);
+ }
+
+ $parsedObject = new self($dateTime);
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class UTF8String extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 Universal String.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::UTF8_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class UniversalString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 Universal String.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @see http://en.wikipedia.org/wiki/Universal_Character_Set
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::UNIVERSAL_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1\Universal;
+
+use FG\ASN1\AbstractString;
+use FG\ASN1\Identifier;
+
+class VisibleString extends AbstractString
+{
+ /**
+ * Creates a new ASN.1 Visible String.
+ * TODO The encodable characters of this type are not yet checked.
+ *
+ * @param string $string
+ */
+ public function __construct($string)
+ {
+ $this->value = $string;
+ $this->allowAll();
+ }
+
+ public function getType()
+ {
+ return Identifier::VISIBLE_STRING;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+class UnknownConstructedObject extends Construct
+{
+ private $identifier;
+ private $contentLength;
+
+ /**
+ * @param string $binaryData
+ * @param int $offsetIndex
+ *
+ * @throws \FG\ASN1\Exception\ParserException
+ */
+ public function __construct($binaryData, &$offsetIndex)
+ {
+ $this->identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex);
+ $this->contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ $children = [];
+ $octetsToRead = $this->contentLength;
+ while ($octetsToRead > 0) {
+ $newChild = ASNObject::fromBinary($binaryData, $offsetIndex);
+ $octetsToRead -= $newChild->getObjectLength();
+ $children[] = $newChild;
+ }
+
+ parent::__construct(...$children);
+ }
+
+ public function getType()
+ {
+ return ord($this->identifier);
+ }
+
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ protected function calculateContentLength()
+ {
+ return $this->contentLength;
+ }
+
+ protected function getEncodedValue()
+ {
+ return '';
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\ASN1;
+
+class UnknownObject extends ASNObject
+{
+ /** @var string */
+ private $value;
+
+ private $identifier;
+
+ /**
+ * @param string|int $identifier Either the first identifier octet as int or all identifier bytes as a string
+ * @param int $contentLength
+ */
+ public function __construct($identifier, $contentLength)
+ {
+ if (is_int($identifier)) {
+ $identifier = chr($identifier);
+ }
+
+ $this->identifier = $identifier;
+ $this->value = "Unparsable Object ({$contentLength} bytes)";
+ $this->setContentLength($contentLength);
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ public function getType()
+ {
+ return ord($this->identifier[0]);
+ }
+
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ protected function calculateContentLength()
+ {
+ return $this->getContentLength();
+ }
+
+ protected function getEncodedValue()
+ {
+ return '';
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\Utility;
+
+/**
+ * Class BigInteger
+ * Utility class to remove dependence on a single large number library. Not intended for external use, this class only
+ * implements the functionality needed throughout this project.
+ *
+ * Instances are immutable, all operations return a new instance with the result.
+ *
+ * @package FG\Utility
+ * @internal
+ */
+abstract class BigInteger
+{
+ /**
+ * Force a preference on the underlying big number implementation, useful for testing.
+ * @var string|null
+ */
+ private static $_prefer;
+
+ public static function setPrefer($prefer = null)
+ {
+ self::$_prefer = $prefer;
+ }
+
+ /**
+ * Create a BigInteger instance based off the base 10 string or an integer.
+ * @param string|int $val
+ * @return BigInteger
+ * @throws \InvalidArgumentException
+ */
+ public static function create($val)
+ {
+ if (self::$_prefer) {
+ switch (self::$_prefer) {
+ case 'gmp':
+ $ret = new BigIntegerGmp();
+ break;
+ case 'bcmath':
+ $ret = new BigIntegerBcmath();
+ break;
+ default:
+ throw new \UnexpectedValueException('Unknown number implementation: ' . self::$_prefer);
+ }
+ }
+ else {
+ // autodetect
+ if (function_exists('gmp_add')) {
+ $ret = new BigIntegerGmp();
+ }
+ elseif (function_exists('bcadd')) {
+ $ret = new BigIntegerBcmath();
+ } else {
+ throw new \RuntimeException('Requires GMP or bcmath extension.');
+ }
+ }
+
+ if (is_int($val)) {
+ $ret->_fromInteger($val);
+ }
+ else {
+ // convert to string, if not already one
+ $val = (string)$val;
+
+ // validate string
+ if (!preg_match('/^-?[0-9]+$/', $val)) {
+ throw new \InvalidArgumentException('Expects a string representation of an integer.');
+ }
+ $ret->_fromString($val);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * BigInteger constructor.
+ * Prevent directly instantiating object, use BigInteger::create instead.
+ */
+ protected function __construct()
+ {
+
+ }
+
+ /**
+ * Subclasses must provide clone functionality.
+ * @return BigInteger
+ */
+ abstract public function __clone();
+
+ /**
+ * Assign the instance value from base 10 string.
+ * @param string $str
+ */
+ abstract protected function _fromString($str);
+
+ /**
+ * Assign the instance value from an integer type.
+ * @param int $integer
+ */
+ abstract protected function _fromInteger($integer);
+
+ /**
+ * Must provide string implementation that returns base 10 number.
+ * @return string
+ */
+ abstract public function __toString();
+
+ /* INFORMATIONAL FUNCTIONS */
+
+ /**
+ * Return integer, if possible. Throws an exception if the number can not be represented as a native integer.
+ * @return int
+ * @throws \OverflowException
+ */
+ abstract public function toInteger();
+
+ /**
+ * Is represented integer negative?
+ * @return bool
+ */
+ abstract public function isNegative();
+
+ /**
+ * Compare the integer with $number, returns a negative integer if $this is less than number, returns 0 if $this is
+ * equal to number and returns a positive integer if $this is greater than number.
+ * @param BigInteger|string|int $number
+ * @return int
+ */
+ abstract public function compare($number);
+
+ /* MODIFY */
+
+ /**
+ * Add another integer $b and returns the result.
+ * @param BigInteger|string|int $b
+ * @return BigInteger
+ */
+ abstract public function add($b);
+
+ /**
+ * Subtract $b from $this and returns the result.
+ * @param BigInteger|string|int $b
+ * @return BigInteger
+ */
+ abstract public function subtract($b);
+
+ /**
+ * Multiply value.
+ * @param BigInteger|string|int $b
+ * @return BigInteger
+ */
+ abstract public function multiply($b);
+
+ /**
+ * The value $this modulus $b.
+ * @param BigInteger|string|int $b
+ * @return BigInteger
+ */
+ abstract public function modulus($b);
+
+ /**
+ * Raise $this to the power of $b and returns the result.
+ * @param BigInteger|string|int $b
+ * @return BigInteger
+ */
+ abstract public function toPower($b);
+
+ /**
+ * Shift the value to the right by a set number of bits and returns the result.
+ * @param int $bits
+ * @return BigInteger
+ */
+ abstract public function shiftRight($bits = 8);
+
+ /**
+ * Shift the value to the left by a set number of bits and returns the result.
+ * @param int $bits
+ * @return BigInteger
+ */
+ abstract public function shiftLeft($bits = 8);
+
+ /**
+ * Returns the absolute value.
+ * @return BigInteger
+ */
+ abstract public function absoluteValue();
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\Utility;
+
+/**
+ * Class BigIntegerBcmath
+ * Integer representation of big numbers using the bcmath library to perform large operations.
+ * @package FG\Utility
+ * @internal
+ */
+class BigIntegerBcmath extends BigInteger
+{
+ protected $_str;
+
+ public function __clone()
+ {
+ // nothing needed to copy
+ }
+
+ protected function _fromString($str)
+ {
+ $this->_str = (string)$str;
+ }
+
+ protected function _fromInteger($integer)
+ {
+ $this->_str = (string)$integer;
+ }
+
+ public function __toString()
+ {
+ return $this->_str;
+ }
+
+ public function toInteger()
+ {
+ if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) {
+ throw new \OverflowException(sprintf('Can not represent %s as integer.', $this->_str));
+ }
+ return (int)$this->_str;
+ }
+
+ public function isNegative()
+ {
+ return bccomp($this->_str, '0', 0) < 0;
+ }
+
+ protected function _unwrap($number)
+ {
+ if ($number instanceof self) {
+ return $number->_str;
+ }
+ return $number;
+ }
+
+ public function compare($number)
+ {
+ return bccomp($this->_str, $this->_unwrap($number), 0);
+ }
+
+ public function add($b)
+ {
+ $ret = new self();
+ $ret->_str = bcadd($this->_str, $this->_unwrap($b), 0);
+ return $ret;
+ }
+
+ public function subtract($b)
+ {
+ $ret = new self();
+ $ret->_str = bcsub($this->_str, $this->_unwrap($b), 0);
+ return $ret;
+ }
+
+ public function multiply($b)
+ {
+ $ret = new self();
+ $ret->_str = bcmul($this->_str, $this->_unwrap($b), 0);
+ return $ret;
+ }
+
+ public function modulus($b)
+ {
+ $ret = new self();
+ if ($this->isNegative()) {
+ // bcmod handles negative numbers differently
+ $b = $this->_unwrap($b);
+ $ret->_str = bcsub($b, bcmod(bcsub('0', $this->_str, 0), $b), 0);
+ }
+ else {
+ $ret->_str = bcmod($this->_str, $this->_unwrap($b));
+ }
+ return $ret;
+ }
+
+ public function toPower($b)
+ {
+ $ret = new self();
+ $ret->_str = bcpow($this->_str, $this->_unwrap($b), 0);
+ return $ret;
+ }
+
+ public function shiftRight($bits = 8)
+ {
+ $ret = new self();
+ $ret->_str = bcdiv($this->_str, bcpow('2', $bits));
+ return $ret;
+ }
+
+ public function shiftLeft($bits = 8) {
+ $ret = new self();
+ $ret->_str = bcmul($this->_str, bcpow('2', $bits));
+ return $ret;
+ }
+
+ public function absoluteValue()
+ {
+ $ret = new self();
+ if (-1 === bccomp($this->_str, '0', 0)) {
+ $ret->_str = bcsub('0', $this->_str, 0);
+ }
+ else {
+ $ret->_str = $this->_str;
+ }
+ return $ret;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\Utility;
+
+/**
+ * Class BigIntegerGmp
+ * Integer representation of big numbers using the GMP extension to perform operations.
+ * @package FG\Utility
+ * @internal
+ */
+class BigIntegerGmp extends BigInteger
+{
+ /**
+ * Resource handle.
+ * @var \GMP
+ */
+ protected $_rh;
+
+ public function __clone()
+ {
+ $this->_rh = gmp_add($this->_rh, 0);
+ }
+
+ protected function _fromString($str)
+ {
+ $this->_rh = gmp_init($str, 10);
+ }
+
+ protected function _fromInteger($integer)
+ {
+ $this->_rh = gmp_init($integer, 10);
+ }
+
+ public function __toString()
+ {
+ return gmp_strval($this->_rh, 10);
+ }
+
+ public function toInteger()
+ {
+ if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) {
+ throw new \OverflowException(sprintf('Can not represent %s as integer.', $this));
+ }
+ return gmp_intval($this->_rh);
+ }
+
+ public function isNegative()
+ {
+ return gmp_sign($this->_rh) === -1;
+ }
+
+ protected function _unwrap($number)
+ {
+ if ($number instanceof self) {
+ return $number->_rh;
+ }
+ return $number;
+ }
+
+ public function compare($number)
+ {
+ return gmp_cmp($this->_rh, $this->_unwrap($number));
+ }
+
+ public function add($b)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_add($this->_rh, $this->_unwrap($b));
+ return $ret;
+ }
+
+ public function subtract($b)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_sub($this->_rh, $this->_unwrap($b));
+ return $ret;
+ }
+
+ public function multiply($b)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_mul($this->_rh, $this->_unwrap($b));
+ return $ret;
+ }
+
+ public function modulus($b)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_mod($this->_rh, $this->_unwrap($b));
+ return $ret;
+ }
+
+ public function toPower($b)
+ {
+ if ($b instanceof self) {
+ // gmp_pow accepts just an integer
+ if ($b->compare(PHP_INT_MAX) > 0) {
+ throw new \UnexpectedValueException('Unable to raise to power greater than PHP_INT_MAX.');
+ }
+ $b = gmp_intval($b->_rh);
+ }
+ $ret = new self();
+ $ret->_rh = gmp_pow($this->_rh, $b);
+ return $ret;
+ }
+
+ public function shiftRight($bits=8)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_div($this->_rh, gmp_pow(2, $bits));
+ return $ret;
+ }
+
+ public function shiftLeft($bits=8)
+ {
+ $ret = new self();
+ $ret->_rh = gmp_mul($this->_rh, gmp_pow(2, $bits));
+ return $ret;
+ }
+
+ public function absoluteValue()
+ {
+ $ret = new self();
+ $ret->_rh = gmp_abs($this->_rh);
+ return $ret;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509;
+
+use FG\ASN1\Universal\NullObject;
+use FG\ASN1\Composite\AttributeTypeAndValue;
+
+class AlgorithmIdentifier extends AttributeTypeAndValue
+{
+ public function __construct($objectIdentifierString)
+ {
+ parent::__construct($objectIdentifierString, new NullObject());
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509\CSR;
+
+use FG\ASN1\ASNObject;
+use FG\X509\CertificateExtensions;
+use FG\ASN1\OID;
+use FG\ASN1\Parsable;
+use FG\ASN1\Construct;
+use FG\ASN1\Identifier;
+use FG\ASN1\Universal\Set;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\ObjectIdentifier;
+
+class Attributes extends Construct implements Parsable
+{
+ public function getType()
+ {
+ return 0xA0;
+ }
+
+ public function addAttribute($objectIdentifier, Set $attribute)
+ {
+ if (is_string($objectIdentifier)) {
+ $objectIdentifier = new ObjectIdentifier($objectIdentifier);
+ }
+ $attributeSequence = new Sequence($objectIdentifier, $attribute);
+ $attributeSequence->getNumberOfLengthOctets(); // length and number of length octets is calculated
+ $this->addChild($attributeSequence);
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], 0xA0, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+ $octetsToRead = $contentLength;
+
+ $parsedObject = new self();
+ while ($octetsToRead > 0) {
+ $initialOffset = $offsetIndex; // used to calculate how much bits have been read
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++);
+ self::parseContentLength($binaryData, $offsetIndex);
+
+ $objectIdentifier = ObjectIdentifier::fromBinary($binaryData, $offsetIndex);
+ $oidString = $objectIdentifier->getContent();
+ if ($oidString == OID::PKCS9_EXTENSION_REQUEST) {
+ $attribute = CertificateExtensions::fromBinary($binaryData, $offsetIndex);
+ } else {
+ $attribute = ASNObject::fromBinary($binaryData, $offsetIndex);
+ }
+
+ $parsedObject->addAttribute($objectIdentifier, $attribute);
+ $octetsToRead -= ($offsetIndex - $initialOffset);
+ }
+
+ $parsedObject->setContentLength($contentLength);
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509\CSR;
+
+use FG\ASN1\OID;
+use FG\ASN1\Universal\Integer;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\Sequence;
+use FG\X509\CertificateSubject;
+use FG\X509\AlgorithmIdentifier;
+use FG\X509\PublicKey;
+
+class CSR extends Sequence
+{
+ const CSR_VERSION_NR = 0;
+
+ protected $subject;
+ protected $publicKey;
+ protected $signature;
+ protected $signatureAlgorithm;
+
+ protected $startSequence;
+
+ /**
+ * @param string $commonName
+ * @param string $email
+ * @param string $organization
+ * @param string $locality
+ * @param string $state
+ * @param string $country
+ * @param string $organizationalUnit
+ * @param string $publicKey
+ * @param string $signature
+ * @param string $signatureAlgorithm
+ */
+ public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit, $publicKey, $signature = null, $signatureAlgorithm = OID::SHA1_WITH_RSA_SIGNATURE)
+ {
+ $this->subject = new CertificateSubject(
+ $commonName,
+ $email,
+ $organization,
+ $locality,
+ $state,
+ $country,
+ $organizationalUnit
+ );
+ $this->publicKey = $publicKey;
+ $this->signature = $signature;
+ $this->signatureAlgorithm = $signatureAlgorithm;
+
+ if (isset($signature)) {
+ $this->createCSRSequence();
+ }
+ }
+
+ protected function createCSRSequence()
+ {
+ $versionNr = new Integer(self::CSR_VERSION_NR);
+ $publicKey = new PublicKey($this->publicKey);
+ $signature = new BitString($this->signature);
+ $signatureAlgorithm = new AlgorithmIdentifier($this->signatureAlgorithm);
+
+ $certRequestInfo = new Sequence($versionNr, $this->subject, $publicKey);
+
+ // Clear the underlying Construct
+ $this->rewind();
+ $this->children = [];
+ $this->addChild($certRequestInfo);
+ $this->addChild($signatureAlgorithm);
+ $this->addChild($signature);
+ }
+
+ public function getSignatureSubject()
+ {
+ $versionNr = new Integer(self::CSR_VERSION_NR);
+ $publicKey = new PublicKey($this->publicKey);
+
+ $certRequestInfo = new Sequence($versionNr, $this->subject, $publicKey);
+ return $certRequestInfo->getBinary();
+ }
+
+ public function setSignature($signature, $signatureAlgorithm = OID::SHA1_WITH_RSA_SIGNATURE)
+ {
+ $this->signature = $signature;
+ $this->signatureAlgorithm = $signatureAlgorithm;
+
+ $this->createCSRSequence();
+ }
+
+ public function __toString()
+ {
+ $tmp = base64_encode($this->getBinary());
+
+ for ($i = 0; $i < strlen($tmp); $i++) {
+ if (($i + 2) % 65 == 0) {
+ $tmp = substr($tmp, 0, $i + 1)."\n".substr($tmp, $i + 1);
+ }
+ }
+
+ $result = '-----BEGIN CERTIFICATE REQUEST-----'.PHP_EOL;
+ $result .= $tmp.PHP_EOL;
+ $result .= '-----END CERTIFICATE REQUEST-----';
+
+ return $result;
+ }
+
+ public function getVersion()
+ {
+ return self::CSR_VERSION_NR;
+ }
+
+ public function getOrganizationName()
+ {
+ return $this->subject->getOrganization();
+ }
+
+ public function getLocalName()
+ {
+ return $this->subject->getLocality();
+ }
+
+ public function getState()
+ {
+ return $this->subject->getState();
+ }
+
+ public function getCountry()
+ {
+ return $this->subject->getCountry();
+ }
+
+ public function getOrganizationalUnit()
+ {
+ return $this->subject->getOrganizationalUnit();
+ }
+
+ public function getPublicKey()
+ {
+ return $this->publicKey;
+ }
+
+ public function getSignature()
+ {
+ return $this->signature;
+ }
+
+ public function getSignatureAlgorithm()
+ {
+ return $this->signatureAlgorithm;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509;
+
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\OID;
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Universal\OctetString;
+use FG\ASN1\Universal\Set;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\ObjectIdentifier;
+use FG\X509\SAN\SubjectAlternativeNames;
+
+class CertificateExtensions extends Set implements Parsable
+{
+ private $innerSequence;
+ private $extensions = [];
+
+ public function __construct()
+ {
+ $this->innerSequence = new Sequence();
+ parent::__construct($this->innerSequence);
+ }
+
+ public function addSubjectAlternativeNames(SubjectAlternativeNames $sans)
+ {
+ $this->addExtension(OID::CERT_EXT_SUBJECT_ALT_NAME, $sans);
+ }
+
+ private function addExtension($oidString, ASNObject $extension)
+ {
+ $sequence = new Sequence();
+ $sequence->addChild(new ObjectIdentifier($oidString));
+ $sequence->addChild($extension);
+
+ $this->innerSequence->addChild($sequence);
+ $this->extensions[] = $extension;
+ }
+
+ public function getContent()
+ {
+ return $this->extensions;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::SET, $offsetIndex++);
+ self::parseContentLength($binaryData, $offsetIndex);
+
+ $tmpOffset = $offsetIndex;
+ $extensions = Sequence::fromBinary($binaryData, $offsetIndex);
+ $tmpOffset += 1 + $extensions->getNumberOfLengthOctets();
+
+ $parsedObject = new self();
+ foreach ($extensions as $extension) {
+ if ($extension->getType() != Identifier::SEQUENCE) {
+ //FIXME wrong offset index
+ throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Sequence but got '.$extension->getTypeName(), $offsetIndex);
+ }
+
+ $tmpOffset += 1 + $extension->getNumberOfLengthOctets();
+ $children = $extension->getChildren();
+ if (count($children) < 2) {
+ throw new ParserException('Could not parse Certificate Extensions: Needs at least two child elements per extension sequence (object identifier and octet string)', $tmpOffset);
+ }
+ /** @var \FG\ASN1\ASNObject $objectIdentifier */
+ $objectIdentifier = $children[0];
+
+ /** @var OctetString $octetString */
+ $octetString = $children[1];
+
+ if ($objectIdentifier->getType() != Identifier::OBJECT_IDENTIFIER) {
+ throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Object Identifier but got '.$extension->getTypeName(), $tmpOffset);
+ }
+
+ $tmpOffset += $objectIdentifier->getObjectLength();
+
+ if ($objectIdentifier->getContent() == OID::CERT_EXT_SUBJECT_ALT_NAME) {
+ $sans = SubjectAlternativeNames::fromBinary($binaryData, $tmpOffset);
+ $parsedObject->addSubjectAlternativeNames($sans);
+ } else {
+ // can now only parse SANs. There might be more in the future
+ $tmpOffset += $octetString->getObjectLength();
+ }
+ }
+
+ $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ )
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509;
+
+use FG\ASN1\Composite\RelativeDistinguishedName;
+use FG\ASN1\Identifier;
+use FG\ASN1\OID;
+use FG\ASN1\Parsable;
+use FG\ASN1\Composite\RDNString;
+use FG\ASN1\Universal\Sequence;
+
+class CertificateSubject extends Sequence implements Parsable
+{
+ private $commonName;
+ private $email;
+ private $organization;
+ private $locality;
+ private $state;
+ private $country;
+ private $organizationalUnit;
+
+ /**
+ * @param string $commonName
+ * @param string $email
+ * @param string $organization
+ * @param string $locality
+ * @param string $state
+ * @param string $country
+ * @param string $organizationalUnit
+ */
+ public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit)
+ {
+ parent::__construct(
+ new RDNString(OID::COUNTRY_NAME, $country),
+ new RDNString(OID::STATE_OR_PROVINCE_NAME, $state),
+ new RDNString(OID::LOCALITY_NAME, $locality),
+ new RDNString(OID::ORGANIZATION_NAME, $organization),
+ new RDNString(OID::OU_NAME, $organizationalUnit),
+ new RDNString(OID::COMMON_NAME, $commonName),
+ new RDNString(OID::PKCS9_EMAIL, $email)
+ );
+
+ $this->commonName = $commonName;
+ $this->email = $email;
+ $this->organization = $organization;
+ $this->locality = $locality;
+ $this->state = $state;
+ $this->country = $country;
+ $this->organizationalUnit = $organizationalUnit;
+ }
+
+ public function getCommonName()
+ {
+ return $this->commonName;
+ }
+
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ public function getOrganization()
+ {
+ return $this->organization;
+ }
+
+ public function getLocality()
+ {
+ return $this->locality;
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function getCountry()
+ {
+ return $this->country;
+ }
+
+ public function getOrganizationalUnit()
+ {
+ return $this->organizationalUnit;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ $names = [];
+ $octetsToRead = $contentLength;
+ while ($octetsToRead > 0) {
+ $relativeDistinguishedName = RelativeDistinguishedName::fromBinary($binaryData, $offsetIndex);
+ $octetsToRead -= $relativeDistinguishedName->getObjectLength();
+ $names[] = $relativeDistinguishedName;
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509;
+
+use FG\ASN1\OID;
+use FG\ASN1\Universal\NullObject;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\ObjectIdentifier;
+
+class PrivateKey extends Sequence
+{
+ /**
+ * @param string $hexKey
+ * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString
+ */
+ public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION)
+ {
+ parent::__construct(
+ new Sequence(
+ new ObjectIdentifier($algorithmIdentifierString),
+ new NullObject()
+ ),
+ new BitString($hexKey)
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509;
+
+use FG\ASN1\OID;
+use FG\ASN1\Universal\NullObject;
+use FG\ASN1\Universal\Sequence;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\ObjectIdentifier;
+
+class PublicKey extends Sequence
+{
+ /**
+ * @param string $hexKey
+ * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString
+ */
+ public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION)
+ {
+ parent::__construct(
+ new Sequence(
+ new ObjectIdentifier($algorithmIdentifierString),
+ new NullObject()
+ ),
+ new BitString($hexKey)
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509\SAN;
+
+use FG\ASN1\Universal\GeneralString;
+
+class DNSName extends GeneralString
+{
+ const IDENTIFIER = 0x82; // not sure yet why this is the identifier used in SAN extensions
+
+ public function __construct($dnsNameString)
+ {
+ parent::__construct($dnsNameString);
+ }
+
+ public function getType()
+ {
+ return self::IDENTIFIER;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509\SAN;
+
+use FG\ASN1\ASNObject;
+use FG\ASN1\Parsable;
+use FG\ASN1\Exception\ParserException;
+
+class IPAddress extends ASNObject implements Parsable
+{
+ const IDENTIFIER = 0x87; // not sure yet why this is the identifier used in SAN extensions
+
+ /** @var string */
+ private $value;
+
+ public function __construct($ipAddressString)
+ {
+ $this->value = $ipAddressString;
+ }
+
+ public function getType()
+ {
+ return self::IDENTIFIER;
+ }
+
+ public function getContent()
+ {
+ return $this->value;
+ }
+
+ protected function calculateContentLength()
+ {
+ return 4;
+ }
+
+ protected function getEncodedValue()
+ {
+ $ipParts = explode('.', $this->value);
+ $binary = chr($ipParts[0]);
+ $binary .= chr($ipParts[1]);
+ $binary .= chr($ipParts[2]);
+ $binary .= chr($ipParts[3]);
+
+ return $binary;
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], self::IDENTIFIER, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+ if ($contentLength != 4) {
+ throw new ParserException("A FG\\X509\SAN\IPAddress should have a content length of 4. Extracted length was {$contentLength}", $offsetIndex);
+ }
+
+ $ipAddressString = ord($binaryData[$offsetIndex++]).'.';
+ $ipAddressString .= ord($binaryData[$offsetIndex++]).'.';
+ $ipAddressString .= ord($binaryData[$offsetIndex++]).'.';
+ $ipAddressString .= ord($binaryData[$offsetIndex++]);
+
+ $parsedObject = new self($ipAddressString);
+ $parsedObject->getObjectLength();
+
+ return $parsedObject;
+ }
+}
--- /dev/null
+<?php
+/*
+ * This file is part of the PHPASN1 library.
+ *
+ * Copyright © Friedrich Große <friedrich.grosse@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FG\X509\SAN;
+
+use FG\ASN1\Exception\ParserException;
+use FG\ASN1\ASNObject;
+use FG\ASN1\OID;
+use FG\ASN1\Parsable;
+use FG\ASN1\Identifier;
+use FG\ASN1\Universal\Sequence;
+
+/**
+ * See section 8.3.2.1 of ITU-T X.509.
+ */
+class SubjectAlternativeNames extends ASNObject implements Parsable
+{
+ private $alternativeNamesSequence;
+
+ public function __construct()
+ {
+ $this->alternativeNamesSequence = new Sequence();
+ }
+
+ protected function calculateContentLength()
+ {
+ return $this->alternativeNamesSequence->getObjectLength();
+ }
+
+ public function getType()
+ {
+ return Identifier::OCTETSTRING;
+ }
+
+ public function addDomainName(DNSName $domainName)
+ {
+ $this->alternativeNamesSequence->addChild($domainName);
+ }
+
+ public function addIP(IPAddress $ip)
+ {
+ $this->alternativeNamesSequence->addChild($ip);
+ }
+
+ public function getContent()
+ {
+ return $this->alternativeNamesSequence->getContent();
+ }
+
+ protected function getEncodedValue()
+ {
+ return $this->alternativeNamesSequence->getBinary();
+ }
+
+ public static function fromBinary(&$binaryData, &$offsetIndex = 0)
+ {
+ self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++);
+ $contentLength = self::parseContentLength($binaryData, $offsetIndex);
+
+ if ($contentLength < 2) {
+ throw new ParserException('Can not parse Subject Alternative Names: The Sequence within the octet string after the Object identifier '.OID::CERT_EXT_SUBJECT_ALT_NAME." is too short ({$contentLength} octets)", $offsetIndex);
+ }
+
+ $offsetOfSequence = $offsetIndex;
+ $sequence = Sequence::fromBinary($binaryData, $offsetIndex);
+ $offsetOfSequence += $sequence->getNumberOfLengthOctets() + 1;
+
+ if ($sequence->getObjectLength() != $contentLength) {
+ throw new ParserException('Can not parse Subject Alternative Names: The Sequence length does not match the length of the surrounding octet string', $offsetIndex);
+ }
+
+ $parsedObject = new self();
+ /** @var \FG\ASN1\ASNObject $object */
+ foreach ($sequence as $object) {
+ if ($object->getType() == DNSName::IDENTIFIER) {
+ $domainName = DNSName::fromBinary($binaryData, $offsetOfSequence);
+ $parsedObject->addDomainName($domainName);
+ } elseif ($object->getType() == IPAddress::IDENTIFIER) {
+ $ip = IPAddress::fromBinary($binaryData, $offsetOfSequence);
+ $parsedObject->addIP($ip);
+ } else {
+ throw new ParserException('Could not parse Subject Alternative Name: Only DNSName and IP SANs are currently supported', $offsetIndex);
+ }
+ }
+
+ $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ )
+ return $parsedObject;
+ }
+}
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2016 ignace nyamagana butera
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+{
+ "name": "league/uri-interfaces",
+ "description" : "Common interface for URI representation",
+ "keywords": [
+ "url",
+ "uri",
+ "rfc3986",
+ "rfc3987"
+ ],
+ "license": "MIT",
+ "homepage": "http://github.com/thephpleague/uri-interfaces",
+ "authors": [
+ {
+ "name" : "Ignace Nyamagana Butera",
+ "email" : "nyamsprod@gmail.com",
+ "homepage" : "https://nyamsprod.com"
+ }
+ ],
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nyamsprod"
+ }
+ ],
+ "require": {
+ "php" : "^7.2 || ^8.0",
+ "ext-json": "*"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.19",
+ "phpstan/phpstan": "^0.12.90",
+ "phpstan/phpstan-strict-rules": "^0.12.9",
+ "phpstan/phpstan-phpunit": "^0.12.19",
+ "phpunit/phpunit": "^8.5.15 || ^9.5"
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src/"
+ }
+ },
+ "scripts": {
+ "phpunit": "phpunit --coverage-text",
+ "phpcs": "php-cs-fixer fix --dry-run --diff -vvv --allow-risky=yes --ansi",
+ "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi",
+ "phpstan": "phpstan analyse -l max -c phpstan.neon src --ansi --memory-limit 192M",
+ "test": [
+ "@phpunit",
+ "@phpstan",
+ "@phpcs:fix"
+ ]
+ },
+ "scripts-descriptions": {
+ "phpunit": "Runs package test suite",
+ "phpstan": "Runs complete codebase static analysis",
+ "phpcs": "Runs coding style testing",
+ "phpcs:fix": "Fix coding style issues",
+ "test": "Runs all tests"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "suggest": {
+ "ext-intl": "to use the IDNA feature",
+ "symfony/intl": "to use the IDNA feature via Symfony Polyfill"
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+
+interface AuthorityInterface extends UriComponentInterface
+{
+ /**
+ * Returns the host component of the authority.
+ */
+ public function getHost(): ?string;
+
+ /**
+ * Returns the port component of the authority.
+ */
+ public function getPort(): ?int;
+
+ /**
+ * Returns the user information component of the authority.
+ */
+ public function getUserInfo(): ?string;
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * A null value provided for the host is equivalent to removing the host
+ * information.
+ *
+ * @param ?string $host
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ * @throws IdnSupportMissing for component or transformations
+ * requiring IDN support when IDN support is not present
+ * or misconfigured.
+ */
+ public function withHost(?string $host): self;
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param ?int $port
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withPort(?int $port): self;
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; a null value for the user is equivalent to removing user
+ * information.
+ *
+ * @param ?string $user
+ * @param ?string $password
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withUserInfo(?string $user, ?string $password = null): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface DataPathInterface extends PathInterface
+{
+ /**
+ * Retrieve the data mime type associated to the URI.
+ *
+ * If no mimetype is present, this method MUST return the default mimetype 'text/plain'.
+ *
+ * @see http://tools.ietf.org/html/rfc2397#section-2
+ */
+ public function getMimeType(): string;
+
+ /**
+ * Retrieve the parameters associated with the Mime Type of the URI.
+ *
+ * If no parameters is present, this method MUST return the default parameter 'charset=US-ASCII'.
+ *
+ * @see http://tools.ietf.org/html/rfc2397#section-2
+ */
+ public function getParameters(): string;
+
+ /**
+ * Retrieve the mediatype associated with the URI.
+ *
+ * If no mediatype is present, this method MUST return the default parameter 'text/plain;charset=US-ASCII'.
+ *
+ * @see http://tools.ietf.org/html/rfc2397#section-3
+ *
+ * @return string The URI scheme.
+ */
+ public function getMediaType(): string;
+
+ /**
+ * Retrieves the data string.
+ *
+ * Retrieves the data part of the path. If no data part is provided return
+ * a empty string
+ */
+ public function getData(): string;
+
+ /**
+ * Tells whether the data is binary safe encoded.
+ */
+ public function isBinaryData(): bool;
+
+ /**
+ * Save the data to a specific file.
+ */
+ public function save(string $path, string $mode = 'w'): \SplFileObject;
+
+ /**
+ * Returns an instance where the data part is base64 encoded.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance where the data part is base64 encoded
+ */
+ public function toBinary(): self;
+
+ /**
+ * Returns an instance where the data part is url encoded following RFC3986 rules.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance where the data part is url encoded
+ */
+ public function toAscii(): self;
+
+ /**
+ * Return an instance with the specified mediatype parameters.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified mediatype parameters.
+ *
+ * Users must provide encoded characters.
+ *
+ * An empty parameters value is equivalent to removing the parameter.
+ */
+ public function withParameters(string $parameters): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\SyntaxError;
+
+/**
+ * @extends \IteratorAggregate<string>
+ */
+interface DomainHostInterface extends \Countable, HostInterface, \IteratorAggregate
+{
+ /**
+ * Returns the labels total number.
+ */
+ public function count(): int;
+
+ /**
+ * Iterate over the Domain labels.
+ *
+ * @return \Iterator<string>
+ */
+ public function getIterator(): \Iterator;
+
+ /**
+ * Retrieves a single host label.
+ *
+ * If the label offset has not been set, returns the null value.
+ */
+ public function get(int $offset): ?string;
+
+ /**
+ * Returns the associated key for a specific label or all the keys.
+ *
+ * @param ?string $label
+ *
+ * @return int[]
+ */
+ public function keys(?string $label = null): array;
+
+ /**
+ * Tells whether the domain is absolute.
+ */
+ public function isAbsolute(): bool;
+
+ /**
+ * Prepends a label to the host.
+ */
+ public function prepend(string $label): self;
+
+ /**
+ * Appends a label to the host.
+ */
+ public function append(string $label): self;
+
+ /**
+ * Returns an instance with its Root label.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ public function withRootLabel(): self;
+
+ /**
+ * Returns an instance without its Root label.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ public function withoutRootLabel(): self;
+
+ /**
+ * Returns an instance with the modified label.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the new label
+ *
+ * If $key is non-negative, the added label will be the label at $key position from the start.
+ * If $key is negative, the added label will be the label at $key position from the end.
+ *
+ * @throws SyntaxError If the key is invalid
+ */
+ public function withLabel(int $key, string $label): self;
+
+ /**
+ * Returns an instance without the specified label.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified component
+ *
+ * If $key is non-negative, the removed label will be the label at $key position from the start.
+ * If $key is negative, the removed label will be the label at $key position from the end.
+ *
+ * @param int ...$keys
+ *
+ * @throws SyntaxError If the key is invalid
+ */
+ public function withoutLabel(int ...$keys): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface FragmentInterface extends UriComponentInterface
+{
+ /**
+ * Returns the decoded fragment.
+ */
+ public function decoded(): ?string;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface HostInterface extends UriComponentInterface
+{
+ /**
+ * Returns the ascii representation.
+ */
+ public function toAscii(): ?string;
+
+ /**
+ * Returns the unicode representation.
+ */
+ public function toUnicode(): ?string;
+
+ /**
+ * Returns the IP version.
+ *
+ * If the host is a not an IP this method will return null
+ */
+ public function getIpVersion(): ?string;
+
+ /**
+ * Returns the IP component If the Host is an IP address.
+ *
+ * If the host is a not an IP this method will return null
+ */
+ public function getIp(): ?string;
+
+ /**
+ * Tells whether the host is a domain name.
+ */
+ public function isDomain(): bool;
+
+ /**
+ * Tells whether the host is an IP Address.
+ */
+ public function isIp(): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface IpHostInterface extends HostInterface
+{
+ /**
+ * Returns whether or not the host is an IPv4 address.
+ */
+ public function isIpv4(): bool;
+ /**
+ * Returns whether or not the host is an IPv6 address.
+ */
+ public function isIpv6(): bool;
+
+ /**
+ * Returns whether or not the host is an IPv6 address.
+ */
+ public function isIpFuture(): bool;
+
+ /**
+ * Returns whether or not the host has a ZoneIdentifier.
+ *
+ * @see http://tools.ietf.org/html/rfc6874#section-4
+ */
+ public function hasZoneIdentifier(): bool;
+
+ /**
+ * Returns an host without its zone identifier according to RFC6874.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance without the host zone identifier according to RFC6874
+ *
+ * @see http://tools.ietf.org/html/rfc6874#section-4
+ */
+ public function withoutZoneIdentifier(): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\SyntaxError;
+
+interface PathInterface extends UriComponentInterface
+{
+ /**
+ * Returns the decoded path.
+ */
+ public function decoded(): string;
+
+ /**
+ * Returns whether or not the path is absolute or relative.
+ */
+ public function isAbsolute(): bool;
+
+ /**
+ * Returns whether or not the path has a trailing delimiter.
+ */
+ public function hasTrailingSlash(): bool;
+
+ /**
+ * Returns an instance without dot segments.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component normalized by removing
+ * the dot segment.
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withoutDotSegments(): self;
+
+ /**
+ * Returns an instance with a leading slash.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component with a leading slash
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withLeadingSlash(): self;
+
+ /**
+ * Returns an instance without a leading slash.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component without a leading slash
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withoutLeadingSlash(): self;
+
+ /**
+ * Returns an instance with a trailing slash.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component with a trailing slash
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withTrailingSlash(): self;
+
+ /**
+ * Returns an instance without a trailing slash.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component without a trailing slash
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withoutTrailingSlash(): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface PortInterface extends UriComponentInterface
+{
+ /**
+ * Returns the integer representation of the Port.
+ */
+ public function toInt(): ?int;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+/**
+ * @extends \IteratorAggregate<array{0:string, 1:string|null}>
+ */
+interface QueryInterface extends \Countable, \IteratorAggregate, UriComponentInterface
+{
+ /**
+ * Returns the query separator.
+ */
+ public function getSeparator(): string;
+
+ /**
+ * Returns the number of key/value pairs present in the object.
+ */
+ public function count(): int;
+
+ /**
+ * Returns an iterator allowing to go through all key/value pairs contained in this object.
+ *
+ * The pair is represented as an array where the first value is the pair key
+ * and the second value the pair value.
+ *
+ * The key of each pair is a string
+ * The value of each pair is a scalar or the null value
+ *
+ * @return \Iterator<int, array{0:string, 1:string|null}>
+ */
+ public function getIterator(): \Iterator;
+
+ /**
+ * Returns an iterator allowing to go through all key/value pairs contained in this object.
+ *
+ * The return type is as a Iterator where its offset is the pair key and its value the pair value.
+ *
+ * The key of each pair is a string
+ * The value of each pair is a scalar or the null value
+ *
+ * @return iterable<string, string|null>
+ */
+ public function pairs(): iterable;
+
+ /**
+ * Tells whether a pair with a specific name exists.
+ *
+ * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
+ */
+ public function has(string $key): bool;
+
+ /**
+ * Returns the first value associated to the given pair name.
+ *
+ * If no value is found null is returned
+ *
+ * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get
+ */
+ public function get(string $key): ?string;
+
+ /**
+ * Returns all the values associated to the given pair name as an array or all
+ * the instance pairs.
+ *
+ * If no value is found an empty array is returned
+ *
+ * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall
+ *
+ * @return array<int, string|null>
+ */
+ public function getAll(string $key): array;
+
+ /**
+ * Returns the store PHP variables as elements of an array.
+ *
+ * The result is similar as PHP parse_str when used with its
+ * second argument with the difference that variable names are
+ * not mangled.
+ *
+ * If a key is submitted it will returns the value attached to it or null
+ *
+ * @see http://php.net/parse_str
+ * @see https://wiki.php.net/rfc/on_demand_name_mangling
+ *
+ * @param ?string $key
+ * @return mixed the collection of stored PHP variables or the empty array if no input is given,
+ * the single value of a stored PHP variable or null if the variable is not present in the collection
+ */
+ public function params(?string $key = null);
+
+ /**
+ * Returns the RFC1738 encoded query.
+ */
+ public function toRFC1738(): ?string;
+
+ /**
+ * Returns the RFC3986 encoded query.
+ *
+ * @see ::getContent
+ */
+ public function toRFC3986(): ?string;
+
+ /**
+ * Returns an instance with a different separator.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the query component with a different separator
+ */
+ public function withSeparator(string $separator): self;
+
+ /**
+ * Sorts the query string by offset, maintaining offset to data correlations.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified query
+ *
+ * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort
+ */
+ public function sort(): self;
+
+ /**
+ * Returns an instance without duplicate key/value pair.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the query component normalized by removing
+ * duplicate pairs whose key/value are the same.
+ */
+ public function withoutDuplicates(): self;
+
+ /**
+ * Returns an instance without empty key/value where the value is the null value.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the query component normalized by removing
+ * empty pairs.
+ *
+ * A pair is considered empty if its value is equal to the null value
+ */
+ public function withoutEmptyPairs(): self;
+
+ /**
+ * Returns an instance where numeric indices associated to PHP's array like key are removed.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the query component normalized so that numeric indexes
+ * are removed from the pair key value.
+ *
+ * ie.: toto[3]=bar[3]&foo=bar becomes toto[]=bar[3]&foo=bar
+ */
+ public function withoutNumericIndices(): self;
+
+ /**
+ * Returns an instance with the a new key/value pair added to it.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified query
+ *
+ * If the pair already exists the value will replace the existing value.
+ *
+ * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set
+ *
+ * @param ?string $value
+ */
+ public function withPair(string $key, ?string $value): self;
+
+ /**
+ * Returns an instance with the new pairs set to it.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified query
+ *
+ * @see ::withPair
+ */
+ public function merge(string $query): self;
+
+ /**
+ * Returns an instance without the specified keys.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified component
+ *
+ * @param string ...$keys
+ */
+ public function withoutPair(string ...$keys): self;
+
+ /**
+ * Returns a new instance with a specified key/value pair appended as a new pair.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified query
+ *
+ * @param ?string $value
+ */
+ public function appendTo(string $key, ?string $value): self;
+
+ /**
+ * Returns an instance with the new pairs appended to it.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified query
+ *
+ * If the pair already exists the value will be added to it.
+ */
+ public function append(string $query): self;
+
+ /**
+ * Returns an instance without the specified params.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified component without PHP's value.
+ * PHP's mangled is not taken into account.
+ *
+ * @param string ...$keys
+ */
+ public function withoutParam(string ...$keys): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\SyntaxError;
+
+/**
+ * @extends \IteratorAggregate<string>
+ */
+interface SegmentedPathInterface extends \Countable, \IteratorAggregate, PathInterface
+{
+ /**
+ * Returns the total number of segments in the path.
+ */
+ public function count(): int;
+
+ /**
+ * Iterate over the path segment.
+ *
+ * @return \Iterator<string>
+ */
+ public function getIterator(): \Iterator;
+
+ /**
+ * Returns parent directory's path.
+ */
+ public function getDirname(): string;
+
+ /**
+ * Returns the path basename.
+ */
+ public function getBasename(): string;
+
+ /**
+ * Returns the basename extension.
+ */
+ public function getExtension(): string;
+
+ /**
+ * Retrieves a single path segment.
+ *
+ * If the segment offset has not been set, returns null.
+ */
+ public function get(int $offset): ?string;
+
+ /**
+ * Returns the associated key for a specific segment.
+ *
+ * If a value is specified only the keys associated with
+ * the given value will be returned
+ *
+ * @param ?string $segment
+ *
+ * @return int[]
+ */
+ public function keys(?string $segment = null): array;
+
+ /**
+ * Appends a segment to the path.
+ */
+ public function append(string $segment): self;
+
+ /**
+ * Prepends a segment to the path.
+ */
+ public function prepend(string $segment): self;
+
+ /**
+ * Returns an instance with the modified segment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the new segment
+ *
+ * If $key is non-negative, the added segment will be the segment at $key position from the start.
+ * If $key is negative, the added segment will be the segment at $key position from the end.
+ *
+ * @param ?string $segment
+ *
+ * @throws SyntaxError If the key is invalid
+ */
+ public function withSegment(int $key, ?string $segment): self;
+
+ /**
+ * Returns an instance without the specified segment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified component
+ *
+ * If $key is non-negative, the removed segment will be the segment at $key position from the start.
+ * If $key is negative, the removed segment will be the segment at $key position from the end.
+ *
+ * @param int ...$keys remaining keys to remove
+ *
+ * @throws SyntaxError If the key is invalid
+ */
+ public function withoutSegment(int ...$keys): self;
+
+ /**
+ * Returns an instance without duplicate delimiters.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the path component normalized by removing
+ * multiple consecutive empty segment
+ */
+ public function withoutEmptySegments(): self;
+
+ /**
+ * Returns an instance with the specified parent directory's path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the extension basename modified.
+ *
+ * @param ?string $path
+ */
+ public function withDirname(?string $path): self;
+
+ /**
+ * Returns an instance with the specified basename.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the extension basename modified.
+ *
+ * @param ?string $basename
+ */
+ public function withBasename(?string $basename): self;
+
+ /**
+ * Returns an instance with the specified basename extension.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the extension basename modified.
+ *
+ * @param ?string $extension
+ */
+ public function withExtension(?string $extension): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+
+interface UriComponentInterface extends \JsonSerializable
+{
+ /**
+ * Returns the instance content.
+ *
+ * If the instance is defined, the value returned MUST be encoded according to the
+ * selected encoding algorithm. In any case, the value MUST NOT double-encode any character
+ * depending on the selected encoding algorithm.
+ *
+ * To determine what characters to encode, please refer to RFC 3986, Sections 2 and 3.
+ * or RFC 3987 Section 3. By default the content is encoded according to RFC3986
+ *
+ * If the instance is not defined null is returned
+ */
+ public function getContent(): ?string;
+
+ /**
+ * Returns the instance string representation.
+ *
+ * If the instance is defined, the value returned MUST be percent-encoded,
+ * but MUST NOT double-encode any characters. To determine what characters
+ * to encode, please refer to RFC 3986, Sections 2 and 3.
+ *
+ * If the instance is not defined an empty string is returned
+ */
+ public function __toString(): string;
+
+ /**
+ * Returns the instance json representation.
+ *
+ * If the instance is defined, the value returned MUST be percent-encoded,
+ * but MUST NOT double-encode any characters. To determine what characters
+ * to encode, please refer to RFC 3986 or RFC 1738.
+ *
+ * If the instance is not defined null is returned
+ */
+ public function jsonSerialize(): ?string;
+
+ /**
+ * Returns the instance string representation with its optional URI delimiters.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode any
+ * characters. To determine what characters to encode, please refer to RFC 3986,
+ * Sections 2 and 3.
+ *
+ * If the instance is not defined an empty string is returned
+ */
+ public function getUriComponent(): string;
+
+ /**
+ * Returns an instance with the specified content.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified content.
+ *
+ * Users can provide both encoded and decoded content characters.
+ *
+ * A null value is equivalent to removing the component content.
+ *
+ *
+ * @param ?string $content
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ * @throws IdnSupportMissing for component or transformations
+ * requiring IDN support when IDN support is not present
+ * or misconfigured.
+ */
+ public function withContent(?string $content): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use Throwable;
+
+interface UriException extends Throwable
+{
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+
+interface UriInterface extends \JsonSerializable
+{
+ /**
+ * Returns the string representation as a URI reference.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ */
+ public function __toString(): string;
+
+ /**
+ * Returns the string representation as a URI reference.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @see ::__toString
+ */
+ public function jsonSerialize(): string;
+
+ /**
+ * Retrieve the scheme component of the URI.
+ *
+ * If no scheme is present, this method MUST return a null value.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.1.
+ *
+ * The trailing ":" character is not part of the scheme and MUST NOT be
+ * added.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.1
+ */
+ public function getScheme(): ?string;
+
+ /**
+ * Retrieve the authority component of the URI.
+ *
+ * If no scheme is present, this method MUST return a null value.
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ */
+ public function getAuthority(): ?string;
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no scheme is present, this method MUST return a null value.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ */
+ public function getUserInfo(): ?string;
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present this method MUST return a null value.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ public function getHost(): ?string;
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ */
+ public function getPort(): ?int;
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ */
+ public function getPath(): string;
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no host is present this method MUST return a null value.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ */
+ public function getQuery(): ?string;
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no host is present this method MUST return a null value.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ */
+ public function getFragment(): ?string;
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * A null value provided for the scheme is equivalent to removing the scheme
+ * information.
+ *
+ * @param ?string $scheme
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withScheme(?string $scheme): self;
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; a null value for the user is equivalent to removing user
+ * information.
+ *
+ * @param ?string $user
+ * @param ?string $password
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withUserInfo(?string $user, ?string $password = null): self;
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * A null value provided for the host is equivalent to removing the host
+ * information.
+ *
+ * @param ?string $host
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ * @throws IdnSupportMissing for component or transformations
+ * requiring IDN support when IDN support is not present
+ * or misconfigured.
+ */
+ public function withHost(?string $host): self;
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param ?int $port
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withPort(?int $port): self;
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withPath(string $path): self;
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * A null value provided for the query is equivalent to removing the query
+ * information.
+ *
+ * @param ?string $query
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withQuery(?string $query): self;
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * A null value provided for the fragment is equivalent to removing the fragment
+ * information.
+ *
+ * @param ?string $fragment
+ *
+ * @throws SyntaxError for invalid component or transformations
+ * that would result in a object in invalid state.
+ */
+ public function withFragment(?string $fragment): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Contracts;
+
+interface UserInfoInterface extends UriComponentInterface
+{
+ /**
+ * Returns the user component part.
+ */
+ public function getUser(): ?string;
+
+ /**
+ * Returns the pass component part.
+ */
+ public function getPass(): ?string;
+
+ /**
+ * Returns an instance with the specified user and/or pass.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user.
+ *
+ * An empty user is equivalent to removing the user information.
+ *
+ * @param ?string $user
+ * @param ?string $pass
+ */
+ public function withUserInfo(?string $user, ?string $pass = null): self;
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Exceptions;
+
+use League\Uri\Contracts\UriException;
+
+class FileinfoSupportMissing extends \RuntimeException implements UriException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Exceptions;
+
+use League\Uri\Contracts\UriException;
+
+class IdnSupportMissing extends \RuntimeException implements UriException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Exceptions;
+
+use League\Uri\Idna\IdnaInfo;
+
+final class IdnaConversionFailed extends SyntaxError
+{
+ /** @var IdnaInfo|null */
+ private $idnaInfo;
+
+ private function __construct(string $message, IdnaInfo $idnaInfo = null)
+ {
+ parent::__construct($message);
+ $this->idnaInfo = $idnaInfo;
+ }
+
+ public static function dueToIDNAError(string $domain, IdnaInfo $idnaInfo): self
+ {
+ return new self(
+ 'The host `'.$domain.'` is invalid : '.implode(', ', $idnaInfo->errorList()).' .',
+ $idnaInfo
+ );
+ }
+
+ public static function dueToInvalidHost(string $domain): self
+ {
+ return new self('The host `'.$domain.'` is not a valid IDN host');
+ }
+
+ public function idnaInfo(): ?IdnaInfo
+ {
+ return $this->idnaInfo;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Exceptions;
+
+use League\Uri\Contracts\UriException;
+
+class SyntaxError extends \InvalidArgumentException implements UriException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Idna;
+
+use League\Uri\Exceptions\IdnaConversionFailed;
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+use function defined;
+use function function_exists;
+use function idn_to_ascii;
+use function idn_to_utf8;
+use function rawurldecode;
+use const INTL_IDNA_VARIANT_UTS46;
+
+/**
+ * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
+ */
+final class Idna
+{
+ private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/';
+ private const MAX_DOMAIN_LENGTH = 253;
+ private const MAX_LABEL_LENGTH = 63;
+
+ /**
+ * General registered name regular expression.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @see https://regex101.com/r/fptU8V/1
+ */
+ private const REGEXP_REGISTERED_NAME = '/
+ (?(DEFINE)
+ (?<unreserved>[a-z0-9_~\-]) # . is missing as it is used to separate labels
+ (?<sub_delims>[!$&\'()*+,;=])
+ (?<encoded>%[A-F0-9]{2})
+ (?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
+ )
+ ^(?:(?®_name)\.)*(?®_name)\.?$
+ /ix';
+
+ /**
+ * IDNA options.
+ */
+ public const IDNA_DEFAULT = 0;
+ public const IDNA_ALLOW_UNASSIGNED = 1;
+ public const IDNA_USE_STD3_RULES = 2;
+ public const IDNA_CHECK_BIDI = 4;
+ public const IDNA_CHECK_CONTEXTJ = 8;
+ public const IDNA_NONTRANSITIONAL_TO_ASCII = 0x10;
+ public const IDNA_NONTRANSITIONAL_TO_UNICODE = 0x20;
+ public const IDNA_CHECK_CONTEXTO = 0x40;
+
+ /**
+ * IDNA errors.
+ */
+ public const ERROR_NONE = 0;
+ public const ERROR_EMPTY_LABEL = 1;
+ public const ERROR_LABEL_TOO_LONG = 2;
+ public const ERROR_DOMAIN_NAME_TOO_LONG = 4;
+ public const ERROR_LEADING_HYPHEN = 8;
+ public const ERROR_TRAILING_HYPHEN = 0x10;
+ public const ERROR_HYPHEN_3_4 = 0x20;
+ public const ERROR_LEADING_COMBINING_MARK = 0x40;
+ public const ERROR_DISALLOWED = 0x80;
+ public const ERROR_PUNYCODE = 0x100;
+ public const ERROR_LABEL_HAS_DOT = 0x200;
+ public const ERROR_INVALID_ACE_LABEL = 0x400;
+ public const ERROR_BIDI = 0x800;
+ public const ERROR_CONTEXTJ = 0x1000;
+ public const ERROR_CONTEXTO_PUNCTUATION = 0x2000;
+ public const ERROR_CONTEXTO_DIGITS = 0x4000;
+
+ /**
+ * IDNA default options.
+ */
+ public const IDNA2008_ASCII = self::IDNA_NONTRANSITIONAL_TO_ASCII
+ | self::IDNA_CHECK_BIDI
+ | self::IDNA_USE_STD3_RULES
+ | self::IDNA_CHECK_CONTEXTJ;
+ public const IDNA2008_UNICODE = self::IDNA_NONTRANSITIONAL_TO_UNICODE
+ | self::IDNA_CHECK_BIDI
+ | self::IDNA_USE_STD3_RULES
+ | self::IDNA_CHECK_CONTEXTJ;
+
+ /**
+ * @codeCoverageIgnore
+ */
+ private static function supportsIdna(): void
+ {
+ static $idnSupport;
+ if (null === $idnSupport) {
+ $idnSupport = function_exists('\idn_to_ascii') && defined('\INTL_IDNA_VARIANT_UTS46');
+ }
+
+ if (!$idnSupport) {
+ throw new IdnSupportMissing('IDN host can not be processed. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.');
+ }
+ }
+
+ /**
+ * Converts the input to its IDNA ASCII form.
+ *
+ * This method returns the string converted to IDN ASCII form
+ *
+ * @throws SyntaxError if the string can not be converted to ASCII using IDN UTS46 algorithm
+ */
+ public static function toAscii(string $domain, int $options): IdnaInfo
+ {
+ $domain = rawurldecode($domain);
+
+ if (1 === preg_match(self::REGEXP_IDNA_PATTERN, $domain)) {
+ self::supportsIdna();
+
+ /* @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
+ idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
+ if ([] === $idnaInfo) {
+ return IdnaInfo::fromIntl([
+ 'result' => strtolower($domain),
+ 'isTransitionalDifferent' => false,
+ 'errors' => self::validateDomainAndLabelLength($domain),
+ ]);
+ }
+
+ /* @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
+ return IdnaInfo::fromIntl($idnaInfo);
+ }
+
+ $error = self::ERROR_NONE;
+ if (1 !== preg_match(self::REGEXP_REGISTERED_NAME, $domain)) {
+ $error |= self::ERROR_DISALLOWED;
+ }
+
+ return IdnaInfo::fromIntl([
+ 'result' => strtolower($domain),
+ 'isTransitionalDifferent' => false,
+ 'errors' => self::validateDomainAndLabelLength($domain) | $error,
+ ]);
+ }
+
+ /**
+ * Converts the input to its IDNA UNICODE form.
+ *
+ * This method returns the string converted to IDN UNICODE form
+ *
+ * @throws SyntaxError if the string can not be converted to UNICODE using IDN UTS46 algorithm
+ */
+ public static function toUnicode(string $domain, int $options): IdnaInfo
+ {
+ $domain = rawurldecode($domain);
+
+ if (false === stripos($domain, 'xn--')) {
+ return IdnaInfo::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => self::ERROR_NONE]);
+ }
+
+ self::supportsIdna();
+
+ /* @param-out array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
+ idn_to_utf8($domain, $options, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
+ if ([] === $idnaInfo) {
+ throw IdnaConversionFailed::dueToInvalidHost($domain);
+ }
+
+ /* @var array{errors: int, isTransitionalDifferent: bool, result: string} $idnaInfo */
+ return IdnaInfo::fromIntl($idnaInfo);
+ }
+
+ /**
+ * Adapted from https://github.com/TRowbotham/idna.
+ *
+ * @see https://github.com/TRowbotham/idna/blob/master/src/Idna.php#L236
+ */
+ private static function validateDomainAndLabelLength(string $domain): int
+ {
+ $error = self::ERROR_NONE;
+ $labels = explode('.', $domain);
+ $maxDomainSize = self::MAX_DOMAIN_LENGTH;
+ $length = count($labels);
+
+ // If the last label is empty and it is not the first label, then it is the root label.
+ // Increase the max size by 1, making it 254, to account for the root label's "."
+ // delimiter. This also means we don't need to check the last label's length for being too
+ // long.
+ if ($length > 1 && $labels[$length - 1] === '') {
+ ++$maxDomainSize;
+ array_pop($labels);
+ }
+
+ if (strlen($domain) > $maxDomainSize) {
+ $error |= self::ERROR_DOMAIN_NAME_TOO_LONG;
+ }
+
+ foreach ($labels as $label) {
+ if (strlen($label) > self::MAX_LABEL_LENGTH) {
+ $error |= self::ERROR_LABEL_TOO_LONG;
+
+ break;
+ }
+ }
+
+ return $error;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Idna;
+
+use function array_filter;
+use const ARRAY_FILTER_USE_KEY;
+
+/**
+ * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
+ */
+final class IdnaInfo
+{
+ private const ERRORS = [
+ Idna::ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty',
+ Idna::ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes',
+ Idna::ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form',
+ Idna::ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")',
+ Idna::ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")',
+ Idna::ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions',
+ Idna::ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark',
+ Idna::ERROR_DISALLOWED => 'a label or domain name contains disallowed characters',
+ Idna::ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode',
+ Idna::ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop',
+ Idna::ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string',
+ Idna::ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)',
+ Idna::ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements',
+ Idna::ERROR_CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits',
+ Idna::ERROR_CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts',
+ ];
+
+ /** @var string */
+ private $result;
+
+ /** @var bool */
+ private $isTransitionalDifferent;
+
+ /** @var int */
+ private $errors;
+
+ /**
+ * @var array<int, string>
+ */
+ private $errorList;
+
+ private function __construct(string $result, bool $isTransitionalDifferent, int $errors)
+ {
+ $this->result = $result;
+ $this->errors = $errors;
+ $this->isTransitionalDifferent = $isTransitionalDifferent;
+ $this->errorList = array_filter(
+ self::ERRORS,
+ function (int $error): bool {
+ return 0 !== ($error & $this->errors);
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+ }
+
+ /**
+ * @param array{result:string, isTransitionalDifferent:bool, errors:int} $infos
+ */
+ public static function fromIntl(array $infos): self
+ {
+ return new self($infos['result'], $infos['isTransitionalDifferent'], $infos['errors']);
+ }
+
+ /**
+ * @param array{result:string, isTransitionalDifferent:bool, errors:int} $properties
+ */
+ public static function __set_state(array $properties): self
+ {
+ return self::fromIntl($properties);
+ }
+
+ public function result(): string
+ {
+ return $this->result;
+ }
+
+ public function isTransitionalDifferent(): bool
+ {
+ return $this->isTransitionalDifferent;
+ }
+
+ public function errors(): int
+ {
+ return $this->errors;
+ }
+
+ public function error(int $error): ?string
+ {
+ return $this->errorList[$error] ?? null;
+ }
+
+ /**
+ * @return array<int, string>
+ */
+ public function errorList(): array
+ {
+ return $this->errorList;
+ }
+}
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2015 ignace nyamagana butera
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+{
+ "name": "league/uri",
+ "type": "library",
+ "description" : "URI manipulation library",
+ "keywords": [
+ "url",
+ "uri",
+ "rfc3986",
+ "rfc3987",
+ "rfc6570",
+ "psr-7",
+ "parse_url",
+ "http",
+ "https",
+ "ws",
+ "ftp",
+ "data-uri",
+ "file-uri",
+ "middleware",
+ "parse_str",
+ "query-string",
+ "querystring",
+ "hostname",
+ "uri-template"
+ ],
+ "license": "MIT",
+ "homepage": "https://uri.thephpleague.com",
+ "authors": [
+ {
+ "name" : "Ignace Nyamagana Butera",
+ "email" : "nyamsprod@gmail.com",
+ "homepage" : "https://nyamsprod.com"
+ }
+ ],
+ "support": {
+ "forum": "https://thephpleague.slack.com",
+ "docs": "https://uri.thephpleague.com",
+ "issues": "https://github.com/thephpleague/uri/issues"
+ },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nyamsprod"
+ }
+ ],
+ "require": {
+ "php": "^8.1",
+ "ext-json": "*",
+ "psr/http-message": "^1.0.1",
+ "league/uri-interfaces": "^2.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^v3.9.5",
+ "nyholm/psr7": "^1.5.1",
+ "php-http/psr7-integration-tests": "^1.1.1",
+ "phpbench/phpbench": "^1.2.6",
+ "phpstan/phpstan": "^1.8.5",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.1.1",
+ "phpstan/phpstan-strict-rules": "^1.4.3",
+ "phpunit/phpunit": "^9.5.24",
+ "psr/http-factory": "^1.0.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LeagueTest\\Uri\\": "tests"
+ }
+ },
+ "conflict": {
+ "league/uri-schemes": "^1.0"
+ },
+ "scripts": {
+ "benchmark": "phpbench run src --report=default",
+ "phpcs": "php-cs-fixer fix -v --diff --dry-run --allow-risky=yes --ansi",
+ "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi",
+ "phpstan": "phpstan analyse -l max -c phpstan.neon src --ansi --memory-limit=256M",
+ "phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text",
+ "test": [
+ "@phpunit",
+ "@phpstan",
+ "@phpcs"
+ ]
+ },
+ "scripts-descriptions": {
+ "phpcs": "Runs coding style test suite",
+ "phpstan": "Runs complete codebase static analysis",
+ "phpunit": "Runs unit and functional testing",
+ "test": "Runs full test suite"
+ },
+ "suggest": {
+ "league/uri-components" : "Needed to easily manipulate URI objects",
+ "ext-intl" : "Needed to improve host validation",
+ "ext-fileinfo": "Needed to create Data URI from a filepath",
+ "psr/http-factory": "Needed to use the URI factory"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\Exceptions;
+
+use InvalidArgumentException;
+use League\Uri\Contracts\UriException;
+
+class TemplateCanNotBeExpanded extends InvalidArgumentException implements UriException
+{
+ public static function dueToUnableToProcessValueListWithPrefix(string $variableName): self
+ {
+ return new self('The ":" modifier can not be applied on "'.$variableName.'" since it is a list of values.');
+ }
+
+ public static function dueToNestedListOfValue(string $variableName): self
+ {
+ return new self('The "'.$variableName.'" can not be a nested list.');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use JsonSerializable;
+use League\Uri\Contracts\UriInterface;
+use League\Uri\Exceptions\SyntaxError;
+use Psr\Http\Message\UriInterface as Psr7UriInterface;
+use Stringable;
+use function is_scalar;
+
+final class Http implements Psr7UriInterface, JsonSerializable
+{
+ private function __construct(private readonly UriInterface $uri)
+ {
+ $this->validate($this->uri);
+ }
+
+ /**
+ * Validate the submitted uri against PSR-7 UriInterface.
+ *
+ * @throws SyntaxError if the given URI does not follow PSR-7 UriInterface rules
+ */
+ private function validate(UriInterface $uri): void
+ {
+ if (null === $uri->getScheme() && '' === $uri->getHost()) {
+ throw new SyntaxError('An URI without scheme can not contains a empty host string according to PSR-7: '.$uri);
+ }
+
+ $port = $uri->getPort();
+ if (null !== $port && ($port < 0 || $port > 65535)) {
+ throw new SyntaxError('The URI port is outside the established TCP and UDP port ranges: '.$uri);
+ }
+ }
+
+ /**
+ * @param array{uri:UriInterface} $components
+ */
+ public static function __set_state(array $components): self
+ {
+ return new self($components['uri']);
+ }
+
+ /**
+ * Create a new instance from a string.
+ */
+ public static function createFromString(Stringable|UriInterface|String $uri = ''): self
+ {
+ return new self(Uri::createFromString($uri));
+ }
+
+ /**
+ * Create a new instance from a hash of parse_url parts.
+ *
+ * @param array $components a hash representation of the URI similar
+ * to PHP parse_url function result
+ */
+ public static function createFromComponents(array $components): self
+ {
+ return new self(Uri::createFromComponents($components));
+ }
+
+ /**
+ * Create a new instance from the environment.
+ */
+ public static function createFromServer(array $server): self
+ {
+ return new self(Uri::createFromServer($server));
+ }
+
+ /**
+ * Create a new instance from a URI and a Base URI.
+ *
+ * The returned URI must be absolute.
+ */
+ public static function createFromBaseUri(
+ Stringable|UriInterface|String $uri,
+ Stringable|UriInterface|String $base_uri = null
+ ): self {
+ return new self(Uri::createFromBaseUri($uri, $base_uri));
+ }
+
+ /**
+ * Create a new instance from a URI object.
+ */
+ public static function createFromUri(Psr7UriInterface|UriInterface $uri): self
+ {
+ if ($uri instanceof UriInterface) {
+ return new self($uri);
+ }
+
+ return new self(Uri::createFromUri($uri));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getScheme(): string
+ {
+ return (string) $this->uri->getScheme();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAuthority(): string
+ {
+ return (string) $this->uri->getAuthority();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getUserInfo(): string
+ {
+ return (string) $this->uri->getUserInfo();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getHost(): string
+ {
+ return (string) $this->uri->getHost();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getPort(): ?int
+ {
+ return $this->uri->getPort();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getPath(): string
+ {
+ return $this->uri->getPath();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getQuery(): string
+ {
+ return (string) $this->uri->getQuery();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getFragment(): string
+ {
+ return (string) $this->uri->getFragment();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function __toString(): string
+ {
+ return $this->uri->__toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function jsonSerialize(): string
+ {
+ return $this->uri->__toString();
+ }
+
+ /**
+ * Safely stringify input when possible for League UriInterface compatibility.
+ *
+ * @throws SyntaxError
+ */
+ private function filterInput(mixed $str): string|null
+ {
+ if (!is_scalar($str) && !$str instanceof Stringable) {
+ throw new SyntaxError('The component must be a string, a scalar or a Stringable object; `'.gettype($str).'` given.');
+ }
+
+ $str = (string) $str;
+ if ('' === $str) {
+ return null;
+ }
+
+ return $str;
+ }
+
+ private function newInstance(UriInterface $uri): self
+ {
+ if ((string) $uri === (string) $this->uri) {
+ return $this;
+ }
+
+ return new self($uri);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withScheme($scheme): self
+ {
+ return $this->newInstance($this->uri->withScheme($this->filterInput($scheme)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withUserInfo($user, $password = null): self
+ {
+ return $this->newInstance($this->uri->withUserInfo($this->filterInput($user), $password));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withHost($host): self
+ {
+ return $this->newInstance($this->uri->withHost($this->filterInput($host)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withPort($port): self
+ {
+ return $this->newInstance($this->uri->withPort($port));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withPath($path): self
+ {
+ return $this->newInstance($this->uri->withPath($path));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withQuery($query): self
+ {
+ return $this->newInstance($this->uri->withQuery($this->filterInput($query)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withFragment($fragment): self
+ {
+ return $this->newInstance($this->uri->withFragment($this->filterInput($fragment)));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use Psr\Http\Message\UriFactoryInterface;
+use Psr\Http\Message\UriInterface;
+
+final class HttpFactory implements UriFactoryInterface
+{
+ public function createUri(string $uri = ''): UriInterface
+ {
+ return Http::createFromString($uri);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use finfo;
+use League\Uri\Contracts\UriInterface;
+use League\Uri\Exceptions\FileinfoSupportMissing;
+use League\Uri\Exceptions\IdnaConversionFailed;
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\Idna\Idna;
+use Psr\Http\Message\UriInterface as Psr7UriInterface;
+use SensitiveParameter;
+use Stringable;
+use TypeError;
+use function array_filter;
+use function array_key_first;
+use function array_map;
+use function base64_decode;
+use function base64_encode;
+use function count;
+use function explode;
+use function file_get_contents;
+use function filter_var;
+use function implode;
+use function in_array;
+use function inet_pton;
+use function is_int;
+use function is_scalar;
+use function is_string;
+use function ltrim;
+use function preg_match;
+use function preg_replace;
+use function preg_replace_callback;
+use function rawurlencode;
+use function str_contains;
+use function str_replace;
+use function strlen;
+use function strpos;
+use function strspn;
+use function strtolower;
+use function substr;
+use const FILEINFO_MIME;
+use const FILTER_FLAG_IPV4;
+use const FILTER_FLAG_IPV6;
+use const FILTER_NULL_ON_FAILURE;
+use const FILTER_VALIDATE_BOOLEAN;
+use const FILTER_VALIDATE_IP;
+
+final class Uri implements UriInterface
+{
+ /**
+ * RFC3986 invalid characters.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-2.2
+ *
+ * @var string
+ */
+ private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/';
+
+ /**
+ * RFC3986 Sub delimiter characters regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-2.2
+ *
+ * @var string
+ */
+ private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";
+
+ /**
+ * RFC3986 unreserved characters regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-2.3
+ *
+ * @var string
+ */
+ private const REGEXP_CHARS_UNRESERVED = 'A-Za-z\d_\-\.~';
+
+ /**
+ * RFC3986 schema regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.1
+ */
+ private const REGEXP_SCHEME = ',^[a-z]([-a-z\d+.]+)?$,i';
+
+ /**
+ * RFC3986 host identified by a registered name regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ private const REGEXP_HOST_REGNAME = '/^(
+ (?<unreserved>[a-z\d_~\-\.])|
+ (?<sub_delims>[!$&\'()*+,;=])|
+ (?<encoded>%[A-F\d]{2})
+ )+$/x';
+
+ /**
+ * RFC3986 delimiters of the generic URI components regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-2.2
+ */
+ private const REGEXP_HOST_GEN_DELIMS = '/[:\/?#\[\]@ ]/'; // Also includes space.
+
+ /**
+ * RFC3986 IPvFuture regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ private const REGEXP_HOST_IPFUTURE = '/^
+ v(?<version>[A-F\d])+\.
+ (?:
+ (?<unreserved>[a-z\d_~\-\.])|
+ (?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
+ )+
+ $/ix';
+
+ /**
+ * RFC3986 IPvFuture host and port component.
+ */
+ private const REGEXP_HOST_PORT = ',^(?<host>(\[.*]|[^:])*)(:(?<port>[^/?#]*))?$,x';
+
+ /**
+ * Significant 10 bits of IP to detect Zone ID regular expression pattern.
+ */
+ private const HOST_ADDRESS_BLOCK = "\xfe\x80";
+
+ /**
+ * Regular expression pattern to for file URI.
+ * <volume> contains the volume but not the volume separator.
+ * The volume separator may be URL-encoded (`|` as `%7C`) by ::formatPath(),
+ * so we account for that here.
+ */
+ private const REGEXP_FILE_PATH = ',^(?<delim>/)?(?<volume>[a-zA-Z])(?:[:|\|]|%7C)(?<rest>.*)?,';
+
+ /**
+ * Mimetype regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc2397
+ */
+ private const REGEXP_MIMETYPE = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,';
+
+ /**
+ * Base64 content regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc2397
+ */
+ private const REGEXP_BINARY = ',(;|^)base64$,';
+
+ /**
+ * Windows file path string regular expression pattern.
+ * <root> contains both the volume and volume separator.
+ */
+ private const REGEXP_WINDOW_PATH = ',^(?<root>[a-zA-Z][:|\|]),';
+
+ /**
+ * Supported schemes and corresponding default port.
+ *
+ * @var array<string, int|null>
+ */
+ private const SCHEME_DEFAULT_PORT = [
+ 'data' => null,
+ 'file' => null,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'http' => 80,
+ 'https' => 443,
+ 'ws' => 80,
+ 'wss' => 443,
+ ];
+
+ /**
+ * Maximum number of formatted host cached.
+ *
+ * @var int
+ */
+ private const MAXIMUM_FORMATTED_HOST_CACHED = 100;
+
+ /**
+ * All ASCII letters sorted by typical frequency of occurrence.
+ *
+ * @var string
+ */
+ private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
+
+ private ?string $scheme;
+ private ?string $user_info;
+ private ?string $host;
+ private ?int $port;
+ private ?string $authority;
+ private string $path;
+ private ?string $query;
+ private ?string $fragment;
+ private ?string $uri;
+
+ private function __construct(
+ ?string $scheme,
+ ?string $user,
+ #[SensitiveParameter] ?string $pass,
+ ?string $host,
+ ?int $port,
+ string $path,
+ ?string $query,
+ ?string $fragment
+ ) {
+ $this->scheme = $this->formatScheme($scheme);
+ $this->user_info = $this->formatUserInfo($user, $pass);
+ $this->host = $this->formatHost($host);
+ $this->port = $this->formatPort($port);
+ $this->authority = $this->setAuthority();
+ $this->path = $this->formatPath($path);
+ $this->query = $this->formatQueryAndFragment($query);
+ $this->fragment = $this->formatQueryAndFragment($fragment);
+
+ $this->assertValidState();
+ }
+
+ /**
+ * Format the Scheme and Host component.
+ *
+ * @throws SyntaxError if the scheme is invalid
+ */
+ private function formatScheme(?string $scheme): ?string
+ {
+ if (null === $scheme || array_key_exists($scheme, self::SCHEME_DEFAULT_PORT)) {
+ return $scheme;
+ }
+
+ $formatted_scheme = strtolower($scheme);
+ if (array_key_exists($formatted_scheme, self::SCHEME_DEFAULT_PORT) || 1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) {
+ return $formatted_scheme;
+ }
+
+ throw new SyntaxError('The scheme `'.$scheme.'` is invalid.');
+ }
+
+ /**
+ * Set the UserInfo component.
+ */
+ private function formatUserInfo(
+ ?string $user,
+ #[SensitiveParameter] ?string $password
+ ): ?string {
+ if (null === $user) {
+ return null;
+ }
+
+ static $user_pattern = '/[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
+ $user = preg_replace_callback($user_pattern, Uri::urlEncodeMatch(...), $user);
+ if (null === $password) {
+ return $user;
+ }
+
+ static $password_pattern = '/[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
+
+ return $user.':'.preg_replace_callback($password_pattern, Uri::urlEncodeMatch(...), $password);
+ }
+
+ /**
+ * Returns the RFC3986 encoded string matched.
+ */
+ private static function urlEncodeMatch(array $matches): string
+ {
+ return rawurlencode($matches[0]);
+ }
+
+ /**
+ * Validate and Format the Host component.
+ */
+ private function formatHost(?string $host): ?string
+ {
+ if (null === $host || '' === $host) {
+ return $host;
+ }
+
+ static $formattedHostCache = [];
+ if (isset($formattedHostCache[$host])) {
+ return $formattedHostCache[$host];
+ }
+
+ $formattedHost = '[' === $host[0] ? $this->formatIp($host) : $this->formatRegisteredName($host);
+ $formattedHostCache[$host] = $formattedHost;
+ if (self::MAXIMUM_FORMATTED_HOST_CACHED < count($formattedHostCache)) {
+ unset($formattedHostCache[array_key_first($formattedHostCache)]);
+ }
+
+ return $formattedHost;
+ }
+
+ /**
+ * Validate and format a registered name.
+ *
+ * The host is converted to its ascii representation if needed
+ *
+ * @throws IdnSupportMissing if the submitted host required missing or misconfigured IDN support
+ * @throws SyntaxError if the submitted host is not a valid registered name
+ */
+ private function formatRegisteredName(string $host): string
+ {
+ $formatted_host = rawurldecode($host);
+ if (1 === preg_match(self::REGEXP_HOST_REGNAME, $formatted_host)) {
+ return $formatted_host;
+ }
+
+ if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) {
+ throw new SyntaxError('The host `'.$host.'` is invalid : a registered name can not contain URI delimiters or spaces.');
+ }
+
+ $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
+ if (0 !== $info->errors()) {
+ throw IdnaConversionFailed::dueToIDNAError($host, $info);
+ }
+
+ return $info->result();
+ }
+
+ /**
+ * Validate and Format the IPv6/IPvfuture host.
+ *
+ * @throws SyntaxError if the submitted host is not a valid IP host
+ */
+ private function formatIp(string $host): string
+ {
+ $ip = substr($host, 1, -1);
+ if (false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return $host;
+ }
+
+ if (1 === preg_match(self::REGEXP_HOST_IPFUTURE, $ip, $matches) && !in_array($matches['version'], ['4', '6'], true)) {
+ return $host;
+ }
+
+ $pos = strpos($ip, '%');
+ if (false === $pos) {
+ throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
+ }
+
+ if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) {
+ throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
+ }
+
+ $ip = substr($ip, 0, $pos);
+ if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
+ }
+
+ //Only the address block fe80::/10 can have a Zone ID attach to
+ //let's detect the link local significant 10 bits
+ if (str_starts_with((string)inet_pton($ip), self::HOST_ADDRESS_BLOCK)) {
+ return $host;
+ }
+
+ throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
+ }
+
+ /**
+ * Format the Port component.
+ *
+ * @throws SyntaxError
+ */
+ private function formatPort(Stringable|string|int|null $port = null): ?int
+ {
+ if (null === $port || '' === $port) {
+ return null;
+ }
+
+ if (!is_int($port) && !(is_string($port) && 1 === preg_match('/^\d*$/', $port))) {
+ throw new SyntaxError('The port is expected to be an integer or a string representing an integer; '.gettype($port).' given.');
+ }
+
+ $port = (int) $port;
+ if (0 > $port) {
+ throw new SyntaxError('The port `'.$port.'` is invalid.');
+ }
+
+ $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null;
+ if ($defaultPort === $port) {
+ return null;
+ }
+
+ return $port;
+ }
+
+ public static function __set_state(array $components): self
+ {
+ $components['user'] = null;
+ $components['pass'] = null;
+ if (null !== $components['user_info']) {
+ [$components['user'], $components['pass']] = explode(':', $components['user_info'], 2) + [1 => null];
+ }
+
+ return new self(
+ $components['scheme'],
+ $components['user'],
+ $components['pass'],
+ $components['host'],
+ $components['port'],
+ $components['path'],
+ $components['query'],
+ $components['fragment']
+ );
+ }
+
+ /**
+ * Creates a new instance from a URI and a Base URI.
+ *
+ * The returned URI must be absolute.
+ */
+ public static function createFromBaseUri(
+ Stringable|UriInterface|String $uri,
+ Stringable|UriInterface|String $base_uri = null
+ ): UriInterface {
+ if (!$uri instanceof UriInterface) {
+ $uri = self::createFromString($uri);
+ }
+
+ if (null === $base_uri) {
+ if (null === $uri->getScheme()) {
+ throw new SyntaxError('the URI `'.$uri.'` must be absolute.');
+ }
+
+ if (null === $uri->getAuthority()) {
+ return $uri;
+ }
+
+ /** @var UriInterface $uri */
+ $uri = UriResolver::resolve($uri, $uri->withFragment(null)->withQuery(null)->withPath(''));
+
+ return $uri;
+ }
+
+ if (!$base_uri instanceof UriInterface) {
+ $base_uri = self::createFromString($base_uri);
+ }
+
+ if (null === $base_uri->getScheme()) {
+ throw new SyntaxError('the base URI `'.$base_uri.'` must be absolute.');
+ }
+
+ /** @var UriInterface $uri */
+ $uri = UriResolver::resolve($uri, $base_uri);
+
+ return $uri;
+ }
+
+ /**
+ * Create a new instance from a string.
+ */
+ public static function createFromString(Stringable|string $uri = ''): self
+ {
+ $components = UriString::parse($uri);
+
+ return new self(
+ $components['scheme'],
+ $components['user'],
+ $components['pass'],
+ $components['host'],
+ $components['port'],
+ $components['path'],
+ $components['query'],
+ $components['fragment']
+ );
+ }
+
+ /**
+ * Create a new instance from a hash representation of the URI similar
+ * to PHP parse_url function result.
+ */
+ public static function createFromComponents(array $components = []): self
+ {
+ $components += [
+ 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
+ 'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
+ ];
+
+ return new self(
+ $components['scheme'],
+ $components['user'],
+ $components['pass'],
+ $components['host'],
+ $components['port'],
+ $components['path'],
+ $components['query'],
+ $components['fragment']
+ );
+ }
+
+ /**
+ * Create a new instance from a data file path.
+ *
+ * @param resource|null $context
+ *
+ * @throws FileinfoSupportMissing If ext/fileinfo is not installed
+ * @throws SyntaxError If the file does not exist or is not readable
+ */
+ public static function createFromDataPath(string $path, $context = null): self
+ {
+ static $finfo_support = null;
+ $finfo_support = $finfo_support ?? class_exists(finfo::class);
+
+ // @codeCoverageIgnoreStart
+ if (!$finfo_support) {
+ throw new FileinfoSupportMissing('Please install ext/fileinfo to use the '.__METHOD__.'() method.');
+ }
+ // @codeCoverageIgnoreEnd
+
+ $file_args = [$path, false];
+ $mime_args = [$path, FILEINFO_MIME];
+ if (null !== $context) {
+ $file_args[] = $context;
+ $mime_args[] = $context;
+ }
+
+ set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true);
+ $raw = file_get_contents(...$file_args);
+ restore_error_handler();
+
+ if (false === $raw) {
+ throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.');
+ }
+
+ $mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mime_args);
+
+ return Uri::createFromComponents([
+ 'scheme' => 'data',
+ 'path' => str_replace(' ', '', $mimetype.';base64,'.base64_encode($raw)),
+ ]);
+ }
+
+ /**
+ * Create a new instance from a Unix path string.
+ */
+ public static function createFromUnixPath(string $uri = ''): self
+ {
+ $uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
+ if ('/' !== ($uri[0] ?? '')) {
+ return Uri::createFromComponents(['path' => $uri]);
+ }
+
+ return Uri::createFromComponents(['path' => $uri, 'scheme' => 'file', 'host' => '']);
+ }
+
+ /**
+ * Create a new instance from a local Windows path string.
+ */
+ public static function createFromWindowsPath(string $uri = ''): self
+ {
+ $root = '';
+ if (1 === preg_match(self::REGEXP_WINDOW_PATH, $uri, $matches)) {
+ $root = substr($matches['root'], 0, -1).':';
+ $uri = substr($uri, strlen($root));
+ }
+ $uri = str_replace('\\', '/', $uri);
+ $uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
+
+ //Local Windows absolute path
+ if ('' !== $root) {
+ return Uri::createFromComponents(['path' => '/'.$root.$uri, 'scheme' => 'file', 'host' => '']);
+ }
+
+ //UNC Windows Path
+ if (!str_starts_with($uri, '//')) {
+ return Uri::createFromComponents(['path' => $uri]);
+ }
+
+ $parts = explode('/', substr($uri, 2), 2) + [1 => null];
+
+ return Uri::createFromComponents(['host' => $parts[0], 'path' => '/'.$parts[1], 'scheme' => 'file']);
+ }
+
+ /**
+ * Create a new instance from a URI object.
+ */
+ public static function createFromUri(Psr7UriInterface|UriInterface $uri): self
+ {
+ if ($uri instanceof UriInterface) {
+ $user_info = $uri->getUserInfo();
+ $user = null;
+ $pass = null;
+ if (null !== $user_info) {
+ [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
+ }
+
+ return new self(
+ $uri->getScheme(),
+ $user,
+ $pass,
+ $uri->getHost(),
+ $uri->getPort(),
+ $uri->getPath(),
+ $uri->getQuery(),
+ $uri->getFragment()
+ );
+ }
+
+ $scheme = $uri->getScheme();
+ if ('' === $scheme) {
+ $scheme = null;
+ }
+
+ $fragment = $uri->getFragment();
+ if ('' === $fragment) {
+ $fragment = null;
+ }
+
+ $query = $uri->getQuery();
+ if ('' === $query) {
+ $query = null;
+ }
+
+ $host = $uri->getHost();
+ if ('' === $host) {
+ $host = null;
+ }
+
+ $user_info = $uri->getUserInfo();
+ $user = null;
+ $pass = null;
+ if ('' !== $user_info) {
+ [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
+ }
+
+ return new self(
+ $scheme,
+ $user,
+ $pass,
+ $host,
+ $uri->getPort(),
+ $uri->getPath(),
+ $query,
+ $fragment
+ );
+ }
+
+ /**
+ * Create a new instance from the environment.
+ */
+ public static function createFromServer(array $server): self
+ {
+ $components = ['scheme' => self::fetchScheme($server)];
+ [$components['user'], $components['pass']] = self::fetchUserInfo($server);
+ [$components['host'], $components['port']] = self::fetchHostname($server);
+ [$components['path'], $components['query']] = self::fetchRequestUri($server);
+
+ return Uri::createFromComponents($components);
+ }
+
+ /**
+ * Returns the environment scheme.
+ */
+ private static function fetchScheme(array $server): string
+ {
+ $server += ['HTTPS' => ''];
+ $res = filter_var($server['HTTPS'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+
+ return false !== $res ? 'https' : 'http';
+ }
+
+ /**
+ * Returns the environment user info.
+ *
+ * @return non-empty-array{0:?string, 1:?string}
+ */
+ private static function fetchUserInfo(array $server): array
+ {
+ $server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => ''];
+ $user = $server['PHP_AUTH_USER'];
+ $pass = $server['PHP_AUTH_PW'];
+ if (str_starts_with(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) {
+ $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true);
+ if (false === $userinfo) {
+ throw new SyntaxError('The user info could not be detected');
+ }
+ [$user, $pass] = explode(':', $userinfo, 2) + [1 => null];
+ }
+
+ if (null !== $user) {
+ $user = rawurlencode($user);
+ }
+
+ if (null !== $pass) {
+ $pass = rawurlencode($pass);
+ }
+
+ return [$user, $pass];
+ }
+
+ /**
+ * Returns the environment host.
+ *
+ * @throws SyntaxError If the host can not be detected
+ *
+ * @return array{0:string|null, 1:int|null}
+ */
+ private static function fetchHostname(array $server): array
+ {
+ $server += ['SERVER_PORT' => null];
+ if (null !== $server['SERVER_PORT']) {
+ $server['SERVER_PORT'] = (int) $server['SERVER_PORT'];
+ }
+
+ if (isset($server['HTTP_HOST']) && 1 === preg_match(self::REGEXP_HOST_PORT, $server['HTTP_HOST'], $matches)) {
+ if (isset($matches['port'])) {
+ $matches['port'] = (int) $matches['port'];
+ }
+
+ return [
+ $matches['host'],
+ $matches['port'] ?? $server['SERVER_PORT'],
+ ];
+ }
+
+ if (!isset($server['SERVER_ADDR'])) {
+ throw new SyntaxError('The host could not be detected');
+ }
+
+ if (false === filter_var($server['SERVER_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ $server['SERVER_ADDR'] = '['.$server['SERVER_ADDR'].']';
+ }
+
+ return [$server['SERVER_ADDR'], $server['SERVER_PORT']];
+ }
+
+ /**
+ * Returns the environment path.
+ *
+ * @return non-empty-array{0:?string, 1:?string}
+ */
+ private static function fetchRequestUri(array $server): array
+ {
+ $server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null];
+ if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) {
+ return explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
+ }
+
+ if (isset($server['REQUEST_URI'])) {
+ [$path] = explode('?', $server['REQUEST_URI'], 2);
+ $query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null;
+
+ return [$path, $query];
+ }
+
+ return [$server['PHP_SELF'], $server['QUERY_STRING']];
+ }
+
+ /**
+ * Generate the URI authority part.
+ */
+ private function setAuthority(): ?string
+ {
+ $authority = null;
+ if (null !== $this->user_info) {
+ $authority = $this->user_info.'@';
+ }
+
+ if (null !== $this->host) {
+ $authority .= $this->host;
+ }
+
+ if (null !== $this->port) {
+ $authority .= ':'.$this->port;
+ }
+
+ return $authority;
+ }
+
+ /**
+ * Format the Path component.
+ */
+ private function formatPath(string $path): string
+ {
+ if ('data' === $this->scheme) {
+ $path = $this->formatDataPath($path);
+ }
+
+ if ('/' !== $path) {
+ static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/}{]++|%(?![A-Fa-f\d]{2})/';
+
+ $path = (string) preg_replace_callback($pattern, Uri::urlEncodeMatch(...), $path);
+ }
+
+ if ('file' === $this->scheme) {
+ $path = $this->formatFilePath($path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Filter the Path component.
+ *
+ * @link https://tools.ietf.org/html/rfc2397
+ *
+ * @throws SyntaxError If the path is not compliant with RFC2397
+ */
+ private function formatDataPath(string $path): string
+ {
+ if ('' == $path) {
+ return 'text/plain;charset=us-ascii,';
+ }
+
+ if (strlen($path) !== strspn($path, self::ASCII) || !str_contains($path, ',')) {
+ throw new SyntaxError('The path `'.$path.'` is invalid according to RFC2937.');
+ }
+
+ $parts = explode(',', $path, 2) + [1 => null];
+ $mediatype = explode(';', (string) $parts[0], 2) + [1 => null];
+ $data = (string) $parts[1];
+ $mimetype = $mediatype[0];
+ if (null === $mimetype || '' === $mimetype) {
+ $mimetype = 'text/plain';
+ }
+
+ $parameters = $mediatype[1];
+ if (null === $parameters || '' === $parameters) {
+ $parameters = 'charset=us-ascii';
+ }
+
+ $this->assertValidPath($mimetype, $parameters, $data);
+
+ return $mimetype.';'.$parameters.','.$data;
+ }
+
+ /**
+ * Assert the path is a compliant with RFC2397.
+ *
+ * @link https://tools.ietf.org/html/rfc2397
+ *
+ * @throws SyntaxError If the mediatype or the data are not compliant with the RFC2397
+ */
+ private function assertValidPath(string $mimetype, string $parameters, string $data): void
+ {
+ if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) {
+ throw new SyntaxError('The path mimetype `'.$mimetype.'` is invalid.');
+ }
+
+ $is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches);
+ if ($is_binary) {
+ $parameters = substr($parameters, 0, - strlen($matches[0]));
+ }
+
+ $res = array_filter(array_filter(explode(';', $parameters), $this->validateParameter(...)));
+ if ([] !== $res) {
+ throw new SyntaxError('The path paremeters `'.$parameters.'` is invalid.');
+ }
+
+ if (!$is_binary) {
+ return;
+ }
+
+ $res = base64_decode($data, true);
+ if (false === $res || $data !== base64_encode($res)) {
+ throw new SyntaxError('The path data `'.$data.'` is invalid.');
+ }
+ }
+
+ /**
+ * Validate mediatype parameter.
+ */
+ private function validateParameter(string $parameter): bool
+ {
+ $properties = explode('=', $parameter);
+
+ return 2 != count($properties) || 'base64' === strtolower($properties[0]);
+ }
+
+ /**
+ * Format path component for file scheme.
+ */
+ private function formatFilePath(string $path): string
+ {
+ $replace = static function (array $matches): string {
+ return $matches['delim'].$matches['volume'].':'.$matches['rest'];
+ };
+
+ return (string) preg_replace_callback(self::REGEXP_FILE_PATH, $replace, $path);
+ }
+
+ /**
+ * Format the Query or the Fragment component.
+ *
+ * Returns a array containing:
+ * <ul>
+ * <li> the formatted component (a string or null)</li>
+ * <li> a boolean flag telling wether the delimiter is to be added to the component
+ * when building the URI string representation</li>
+ * </ul>
+ */
+ private function formatQueryAndFragment(?string $component): ?string
+ {
+ if (null === $component || '' === $component) {
+ return $component;
+ }
+
+ static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';
+ return preg_replace_callback($pattern, Uri::urlEncodeMatch(...), $component);
+ }
+
+ /**
+ * assert the URI internal state is valid.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3
+ * @link https://tools.ietf.org/html/rfc3986#section-3.3
+ *
+ * @throws SyntaxError if the URI is in an invalid state according to RFC3986
+ * @throws SyntaxError if the URI is in an invalid state according to scheme specific rules
+ */
+ private function assertValidState(): void
+ {
+ if (null !== $this->authority && ('' !== $this->path && '/' !== $this->path[0])) {
+ throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.');
+ }
+
+ if (null === $this->authority && str_starts_with($this->path, '//')) {
+ throw new SyntaxError('If there is no authority the path `'.$this->path.'` can not start with a `//`.');
+ }
+
+ $pos = strpos($this->path, ':');
+ if (null === $this->authority
+ && null === $this->scheme
+ && false !== $pos
+ && !str_contains(substr($this->path, 0, $pos), '/')
+ ) {
+ throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.');
+ }
+
+ $this->uri = null;
+
+ if (! match ($this->scheme) {
+ 'data' => $this->isUriWithSchemeAndPathOnly(),
+ 'file' => $this->isUriWithSchemeHostAndPathOnly(),
+ 'ftp', 'gopher' => $this->isNonEmptyHostUriWithoutFragmentAndQuery(),
+ 'http', 'https' => $this->isNonEmptyHostUri(),
+ 'ws', 'wss' => $this->isNonEmptyHostUriWithoutFragment(),
+ default => true,
+ }) {
+ throw new SyntaxError('The uri `'.$this.'` is invalid for the `'.$this->scheme.'` scheme.');
+ }
+ }
+
+ /**
+ * URI validation for URI schemes which allows only scheme and path components.
+ */
+ private function isUriWithSchemeAndPathOnly(): bool
+ {
+ return null === $this->authority
+ && null === $this->query
+ && null === $this->fragment;
+ }
+
+ /**
+ * URI validation for URI schemes which allows only scheme, host and path components.
+ */
+ private function isUriWithSchemeHostAndPathOnly(): bool
+ {
+ return null === $this->user_info
+ && null === $this->port
+ && null === $this->query
+ && null === $this->fragment
+ && !('' != $this->scheme && null === $this->host);
+ }
+
+ /**
+ * URI validation for URI schemes which disallow the empty '' host.
+ */
+ private function isNonEmptyHostUri(): bool
+ {
+ return '' !== $this->host
+ && !(null !== $this->scheme && null === $this->host);
+ }
+
+ /**
+ * URI validation for URIs schemes which disallow the empty '' host
+ * and forbids the fragment component.
+ */
+ private function isNonEmptyHostUriWithoutFragment(): bool
+ {
+ return $this->isNonEmptyHostUri() && null === $this->fragment;
+ }
+
+ /**
+ * URI validation for URIs schemes which disallow the empty '' host
+ * and forbids fragment and query components.
+ */
+ private function isNonEmptyHostUriWithoutFragmentAndQuery(): bool
+ {
+ return $this->isNonEmptyHostUri() && null === $this->fragment && null === $this->query;
+ }
+
+ /**
+ * Generate the URI string representation from its components.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ private function getUriString(
+ ?string $scheme,
+ ?string $authority,
+ string $path,
+ ?string $query,
+ ?string $fragment
+ ): string {
+ if (null !== $scheme) {
+ $scheme = $scheme.':';
+ }
+
+ if (null !== $authority) {
+ $authority = '//'.$authority;
+ }
+
+ if (null !== $query) {
+ $query = '?'.$query;
+ }
+
+ if (null !== $fragment) {
+ $fragment = '#'.$fragment;
+ }
+
+ return $scheme.$authority.$path.$query.$fragment;
+ }
+
+ public function toString(): string
+ {
+ return $this->uri ??= $this->getUriString(
+ $this->scheme,
+ $this->authority,
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function __toString(): string
+ {
+ return $this->toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function jsonSerialize(): string
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @return array{
+ * scheme:?string,
+ * user_info:?string,
+ * host:?string,
+ * port:?int,
+ * path:string,
+ * query:?string,
+ * fragment:?string
+ * }
+ */
+ public function __debugInfo(): array
+ {
+ return [
+ 'scheme' => $this->scheme,
+ 'user_info' => isset($this->user_info) ? preg_replace(',:(.*).?$,', ':***', $this->user_info) : null,
+ 'host' => $this->host,
+ 'port' => $this->port,
+ 'path' => $this->path,
+ 'query' => $this->query,
+ 'fragment' => $this->fragment,
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getScheme(): ?string
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAuthority(): ?string
+ {
+ return $this->authority;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getUserInfo(): ?string
+ {
+ return $this->user_info;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getHost(): ?string
+ {
+ return $this->host;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getPort(): ?int
+ {
+ return $this->port;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getPath(): string
+ {
+ if (str_starts_with($this->path, '//')) {
+ return '/'.ltrim($this->path, '/');
+ }
+
+ return $this->path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getQuery(): ?string
+ {
+ return $this->query;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getFragment(): ?string
+ {
+ return $this->fragment;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withScheme($scheme): UriInterface
+ {
+ $scheme = $this->formatScheme($this->filterString($scheme));
+ if ($scheme === $this->scheme) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->scheme = $scheme;
+ $clone->port = $clone->formatPort($clone->port);
+ $clone->authority = $clone->setAuthority();
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * Filter a string.
+ *
+ * @param mixed $str the value to evaluate as a string
+ *
+ * @throws SyntaxError if the submitted data can not be converted to string
+ */
+ private function filterString(mixed $str): ?string
+ {
+ if (null === $str) {
+ return null;
+ }
+
+ if (!is_scalar($str) && !$str instanceof Stringable) {
+ throw new SyntaxError('The component must be a string, a scalar or a Stringable object; `'.gettype($str).'` given.');
+ }
+
+ $str = (string) $str;
+ if (1 !== preg_match(self::REGEXP_INVALID_CHARS, $str)) {
+ return $str;
+ }
+
+ throw new SyntaxError('The component `'.$str.'` contains invalid characters.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withUserInfo(
+ $user,
+ #[SensitiveParameter] $password = null
+ ): UriInterface {
+ $user_info = null;
+ $user = $this->filterString($user);
+ if (null !== $password) {
+ $password = $this->filterString($password);
+ }
+
+ if ('' !== $user) {
+ $user_info = $this->formatUserInfo($user, $password);
+ }
+
+ if ($user_info === $this->user_info) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->user_info = $user_info;
+ $clone->authority = $clone->setAuthority();
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withHost($host): UriInterface
+ {
+ $host = $this->formatHost($this->filterString($host));
+ if ($host === $this->host) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->host = $host;
+ $clone->authority = $clone->setAuthority();
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withPort($port): UriInterface
+ {
+ $port = $this->formatPort($port);
+ if ($port === $this->port) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->port = $port;
+ $clone->authority = $clone->setAuthority();
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param string|object $path
+ */
+ public function withPath($path): UriInterface
+ {
+ $path = $this->filterString($path);
+ if (null === $path) {
+ throw new TypeError('A path must be a string NULL given.');
+ }
+
+ $path = $this->formatPath($path);
+ if ($path === $this->path) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->path = $path;
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withQuery($query): UriInterface
+ {
+ $query = $this->formatQueryAndFragment($this->filterString($query));
+ if ($query === $this->query) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->query = $query;
+ $clone->assertValidState();
+
+ return $clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function withFragment($fragment): UriInterface
+ {
+ $fragment = $this->formatQueryAndFragment($this->filterString($fragment));
+ if ($fragment === $this->fragment) {
+ return $this;
+ }
+
+ $clone = clone $this;
+ $clone->fragment = $fragment;
+ $clone->assertValidState();
+
+ return $clone;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use League\Uri\Contracts\UriInterface;
+use Psr\Http\Message\UriInterface as Psr7UriInterface;
+use function explode;
+use function implode;
+use function preg_replace_callback;
+use function rawurldecode;
+
+final class UriInfo
+{
+ private const REGEXP_ENCODED_CHARS = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|A|F]|6[1-9|A-F]|7[\d|E]),i';
+ private const WHATWG_SPECIAL_SCHEMES = ['ftp', 'http', 'https', 'ws', 'wss'];
+
+ /**
+ * @codeCoverageIgnore
+ */
+ private function __construct()
+ {
+ }
+
+
+ private static function emptyComponentValue(Psr7UriInterface|UriInterface $uri): ?string
+ {
+ return $uri instanceof Psr7UriInterface ? '' : null;
+ }
+
+ /**
+ * Normalizes an URI for comparison.
+ */
+ private static function normalize(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
+ {
+ $null = self::emptyComponentValue($uri);
+
+ $path = $uri->getPath();
+ if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) {
+ $path = UriResolver::resolve($uri, $uri->withPath('')->withQuery($null))->getPath();
+ }
+
+ $query = $uri->getQuery();
+ $fragment = $uri->getFragment();
+ $fragmentOrig = $fragment;
+ $pairs = null === $query ? [] : explode('&', $query);
+ sort($pairs, SORT_REGULAR);
+
+ $replace = static fn (array $matches): string => rawurldecode($matches[0]);
+
+ $retval = preg_replace_callback(self::REGEXP_ENCODED_CHARS, $replace, [$path, implode('&', $pairs), $fragment]);
+ if (null !== $retval) {
+ [$path, $query, $fragment] = $retval + ['', $null, $null];
+ }
+
+ if ($null !== $uri->getAuthority() && '' === $path) {
+ $path = '/';
+ }
+
+ return $uri
+ ->withHost(Uri::createFromComponents(['host' => $uri->getHost()])->getHost())
+ ->withPath($path)
+ ->withQuery([] === $pairs ? $null : $query)
+ ->withFragment($null === $fragmentOrig ? $fragmentOrig : $fragment);
+ }
+
+ /**
+ * Tells whether the URI represents an absolute URI.
+ */
+ public static function isAbsolute(Psr7UriInterface|UriInterface $uri): bool
+ {
+ return self::emptyComponentValue($uri) !== $uri->getScheme();
+ }
+
+ /**
+ * Tell whether the URI represents a network path.
+ */
+ public static function isNetworkPath(Psr7UriInterface|UriInterface $uri): bool
+ {
+ $null = self::emptyComponentValue($uri);
+
+ return $null === $uri->getScheme() && $null !== $uri->getAuthority();
+ }
+
+ /**
+ * Tells whether the URI represents an absolute path.
+ */
+ public static function isAbsolutePath(Psr7UriInterface|UriInterface $uri): bool
+ {
+ $null = self::emptyComponentValue($uri);
+
+ return $null === $uri->getScheme()
+ && $null === $uri->getAuthority()
+ && '/' === ($uri->getPath()[0] ?? '');
+ }
+
+ /**
+ * Tell whether the URI represents a relative path.
+ *
+ */
+ public static function isRelativePath(Psr7UriInterface|UriInterface $uri): bool
+ {
+ $null = self::emptyComponentValue($uri);
+
+ return $null === $uri->getScheme()
+ && $null === $uri->getAuthority()
+ && '/' !== ($uri->getPath()[0] ?? '');
+ }
+
+ /**
+ * Tells whether both URI refers to the same document.
+ */
+ public static function isSameDocument(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): bool
+ {
+ $uri = self::normalize($uri);
+ $base_uri = self::normalize($base_uri);
+
+ return (string) $uri->withFragment($uri instanceof Psr7UriInterface ? '' : null)
+ === (string) $base_uri->withFragment($base_uri instanceof Psr7UriInterface ? '' : null);
+ }
+
+ /**
+ * Returns the URI origin property as defined by WHATWG URL living standard.
+ *
+ * {@see https://url.spec.whatwg.org/#origin}
+ *
+ * For URI without a special scheme the method returns null
+ * For URI with the file scheme the method will return null (as this is left to the implementation decision)
+ * For URI with a special scheme the method returns the scheme followed by its authority (without the userinfo part)
+ */
+ public static function getOrigin(Psr7UriInterface|UriInterface $uri): ?string
+ {
+ $scheme = $uri->getScheme();
+ if ('blob' === $scheme) {
+ $uri = Uri::createFromString($uri->getPath());
+ $scheme = $uri->getScheme();
+ }
+
+ if (in_array($scheme, self::WHATWG_SPECIAL_SCHEMES, true)) {
+ $null = self::emptyComponentValue($uri);
+
+ return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Tells whether two URI do not share the same origin.
+ *
+ * @see UriInfo::getOrigin()
+ */
+ public static function isCrossOrigin(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): bool
+ {
+ return null === ($uriString = self::getOrigin(Uri::createFromUri($uri)))
+ || null === ($baseUriString = self::getOrigin(Uri::createFromUri($base_uri)))
+ || $uriString !== $baseUriString;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use League\Uri\Contracts\UriInterface;
+use Psr\Http\Message\UriInterface as Psr7UriInterface;
+use function array_pop;
+use function array_reduce;
+use function count;
+use function end;
+use function explode;
+use function implode;
+use function in_array;
+use function str_repeat;
+use function strpos;
+use function substr;
+
+final class UriResolver
+{
+ /**
+ * @var array<string,int>
+ */
+ const DOT_SEGMENTS = ['.' => 1, '..' => 1];
+
+ /**
+ * @codeCoverageIgnore
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Resolves an URI against a base URI using RFC3986 rules.
+ *
+ * If the first argument is a UriInterface the method returns a UriInterface object
+ * If the first argument is a Psr7UriInterface the method returns a Psr7UriInterface object
+ */
+ public static function resolve(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): Psr7UriInterface|UriInterface
+ {
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+
+ if ($null !== $uri->getScheme()) {
+ return $uri
+ ->withPath(self::removeDotSegments($uri->getPath()));
+ }
+
+ if ($null !== $uri->getAuthority()) {
+ return $uri
+ ->withScheme($base_uri->getScheme())
+ ->withPath(self::removeDotSegments($uri->getPath()));
+ }
+
+ $user = $null;
+ $pass = null;
+ $userInfo = $base_uri->getUserInfo();
+ if (null !== $userInfo) {
+ [$user, $pass] = explode(':', $userInfo, 2) + [1 => null];
+ }
+
+ [$uri_path, $uri_query] = self::resolvePathAndQuery($uri, $base_uri);
+
+ return $uri
+ ->withPath(self::removeDotSegments($uri_path))
+ ->withQuery($uri_query)
+ ->withHost($base_uri->getHost())
+ ->withPort($base_uri->getPort())
+ ->withUserInfo((string) $user, $pass)
+ ->withScheme($base_uri->getScheme())
+ ;
+ }
+
+ /**
+ * Remove dot segments from the URI path.
+ */
+ private static function removeDotSegments(string $path): string
+ {
+ if (!str_contains($path, '.')) {
+ return $path;
+ }
+
+ $old_segments = explode('/', $path);
+ $new_path = implode('/', array_reduce($old_segments, UriResolver::reducer(...), []));
+ if (isset(self::DOT_SEGMENTS[end($old_segments)])) {
+ $new_path .= '/';
+ }
+
+ // @codeCoverageIgnoreStart
+ // added because some PSR-7 implementations do not respect RFC3986
+ if (str_starts_with($path, '/') && !str_starts_with($new_path, '/')) {
+ return '/'.$new_path;
+ }
+ // @codeCoverageIgnoreEnd
+
+ return $new_path;
+ }
+
+ /**
+ * Remove dot segments.
+ *
+ * @return array<int, string>
+ */
+ private static function reducer(array $carry, string $segment): array
+ {
+ if ('..' === $segment) {
+ array_pop($carry);
+
+ return $carry;
+ }
+
+ if (!isset(self::DOT_SEGMENTS[$segment])) {
+ $carry[] = $segment;
+ }
+
+ return $carry;
+ }
+
+ /**
+ * Resolves an URI path and query component.
+ *
+ * @return array{0:string, 1:string|null}
+ */
+ private static function resolvePathAndQuery(
+ Psr7UriInterface|UriInterface $uri,
+ Psr7UriInterface|UriInterface $base_uri
+ ): array {
+ $target_path = $uri->getPath();
+ $target_query = $uri->getQuery();
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+ $baseNull = $base_uri instanceof Psr7UriInterface ? '' : null;
+
+ if (str_starts_with($target_path, '/')) {
+ return [$target_path, $target_query];
+ }
+
+ if ('' === $target_path) {
+ if ($null === $target_query) {
+ $target_query = $base_uri->getQuery();
+ }
+
+ $target_path = $base_uri->getPath();
+ //@codeCoverageIgnoreStart
+ //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction
+ if ($baseNull !== $base_uri->getAuthority() && !str_starts_with($target_path, '/')) {
+ $target_path = '/'.$target_path;
+ }
+ //@codeCoverageIgnoreEnd
+
+ return [$target_path, $target_query];
+ }
+
+ $base_path = $base_uri->getPath();
+ if ($baseNull !== $base_uri->getAuthority() && '' === $base_path) {
+ $target_path = '/'.$target_path;
+ }
+
+ if ('' !== $base_path) {
+ $segments = explode('/', $base_path);
+ array_pop($segments);
+ if ([] !== $segments) {
+ $target_path = implode('/', $segments).'/'.$target_path;
+ }
+ }
+
+ return [$target_path, $target_query];
+ }
+
+ /**
+ * Relativizes an URI according to a base URI.
+ *
+ * This method MUST retain the state of the submitted URI instance, and return
+ * an URI instance of the same type that contains the applied modifications.
+ *
+ * This method MUST be transparent when dealing with error and exceptions.
+ * It MUST not alter of silence them apart from validating its own parameters.
+ */
+ public static function relativize(
+ Psr7UriInterface|UriInterface $uri,
+ Psr7UriInterface|UriInterface $base_uri
+ ): Psr7UriInterface|UriInterface {
+ $uri = self::formatHost($uri);
+ $base_uri = self::formatHost($base_uri);
+ if (!self::isRelativizable($uri, $base_uri)) {
+ return $uri;
+ }
+
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+ $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null);
+ $target_path = $uri->getPath();
+ if ($target_path !== $base_uri->getPath()) {
+ return $uri->withPath(self::relativizePath($target_path, $base_uri->getPath()));
+ }
+
+ if (self::componentEquals('query', $uri, $base_uri)) {
+ return $uri->withPath('')->withQuery($null);
+ }
+
+ if ($null === $uri->getQuery()) {
+ return $uri->withPath(self::formatPathWithEmptyBaseQuery($target_path));
+ }
+
+ return $uri->withPath('');
+ }
+
+ /**
+ * Tells whether the component value from both URI object equals.
+ */
+ private static function componentEquals(
+ string $property,
+ Psr7UriInterface|UriInterface $uri,
+ Psr7UriInterface|UriInterface $base_uri
+ ): bool {
+ return self::getComponent($property, $uri) === self::getComponent($property, $base_uri);
+ }
+
+ /**
+ * Returns the component value from the submitted URI object.
+ */
+ private static function getComponent(string $property, Psr7UriInterface|UriInterface $uri): ?string
+ {
+ $component = match ($property) {
+ 'query' => $uri->getQuery(),
+ 'authority' => $uri->getAuthority(),
+ default => $uri->getScheme(), //scheme
+ };
+
+ if ($uri instanceof Psr7UriInterface && '' === $component) {
+ return null;
+ }
+
+ return $component;
+ }
+
+ /**
+ * Filter the URI object.
+ */
+ private static function formatHost(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
+ {
+ if (!$uri instanceof Psr7UriInterface) {
+ return $uri;
+ }
+
+ $host = $uri->getHost();
+ if ('' === $host) {
+ return $uri;
+ }
+
+ return $uri->withHost((string) Uri::createFromComponents(['host' => $host])->getHost());
+ }
+
+ /**
+ * Tells whether the submitted URI object can be relativize.
+ */
+ private static function isRelativizable(
+ Psr7UriInterface|UriInterface $uri,
+ Psr7UriInterface|UriInterface $base_uri
+ ): bool {
+ return !UriInfo::isRelativePath($uri)
+ && self::componentEquals('scheme', $uri, $base_uri)
+ && self::componentEquals('authority', $uri, $base_uri);
+ }
+
+ /**
+ * Relatives the URI for an authority-less target URI.
+ */
+ private static function relativizePath(string $path, string $basepath): string
+ {
+ $base_segments = self::getSegments($basepath);
+ $target_segments = self::getSegments($path);
+ $target_basename = array_pop($target_segments);
+ array_pop($base_segments);
+ foreach ($base_segments as $offset => $segment) {
+ if (!isset($target_segments[$offset]) || $segment !== $target_segments[$offset]) {
+ break;
+ }
+ unset($base_segments[$offset], $target_segments[$offset]);
+ }
+ $target_segments[] = $target_basename;
+
+ return self::formatPath(
+ str_repeat('../', count($base_segments)).implode('/', $target_segments),
+ $basepath
+ );
+ }
+
+ /**
+ * returns the path segments.
+ *
+ * @return string[]
+ */
+ private static function getSegments(string $path): array
+ {
+ if ('' !== $path && '/' === $path[0]) {
+ $path = substr($path, 1);
+ }
+
+ return explode('/', $path);
+ }
+
+ /**
+ * Formatting the path to keep a valid URI.
+ */
+ private static function formatPath(string $path, string $basepath): string
+ {
+ if ('' === $path) {
+ return in_array($basepath, ['', '/'], true) ? $basepath : './';
+ }
+
+ if (false === ($colon_pos = strpos($path, ':'))) {
+ return $path;
+ }
+
+ $slash_pos = strpos($path, '/');
+ if (false === $slash_pos || $colon_pos < $slash_pos) {
+ return "./$path";
+ }
+
+ return $path;
+ }
+
+ /**
+ * Formatting the path to keep a resolvable URI.
+ */
+ private static function formatPathWithEmptyBaseQuery(string $path): string
+ {
+ $target_segments = self::getSegments($path);
+ /** @var string $basename */
+ $basename = end($target_segments);
+
+ return '' === $basename ? './' : $basename;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use League\Uri\Exceptions\IdnaConversionFailed;
+use League\Uri\Exceptions\IdnSupportMissing;
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\Idna\Idna;
+use Stringable;
+use function array_merge;
+use function explode;
+use function filter_var;
+use function inet_pton;
+use function preg_match;
+use function rawurldecode;
+use function sprintf;
+use function strpos;
+use function substr;
+use const FILTER_FLAG_IPV6;
+use const FILTER_VALIDATE_IP;
+
+/**
+ * A class to parse a URI string according to RFC3986.
+ *
+ * @link https://tools.ietf.org/html/rfc3986
+ * @package League\Uri
+ * @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ * @since 6.0.0
+ */
+final class UriString
+{
+ /**
+ * Default URI component values.
+ */
+ private const URI_COMPONENTS = [
+ 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
+ 'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
+ ];
+
+ /**
+ * Simple URI which do not need any parsing.
+ */
+ private const URI_SCHORTCUTS = [
+ '' => [],
+ '#' => ['fragment' => ''],
+ '?' => ['query' => ''],
+ '?#' => ['query' => '', 'fragment' => ''],
+ '/' => ['path' => '/'],
+ '//' => ['host' => ''],
+ ];
+
+ /**
+ * Range of invalid characters in URI string.
+ */
+ private const REGEXP_INVALID_URI_CHARS = '/[\x00-\x1f\x7f]/';
+
+ /**
+ * RFC3986 regular expression URI splitter.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#appendix-B
+ */
+ private const REGEXP_URI_PARTS = ',^
+ (?<scheme>(?<scontent>[^:/?\#]+):)? # URI scheme component
+ (?<authority>//(?<acontent>[^/?\#]*))? # URI authority part
+ (?<path>[^?\#]*) # URI path component
+ (?<query>\?(?<qcontent>[^\#]*))? # URI query component
+ (?<fragment>\#(?<fcontent>.*))? # URI fragment component
+ ,x';
+
+ /**
+ * URI scheme regular expresssion.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.1
+ */
+ private const REGEXP_URI_SCHEME = '/^([a-z][a-z\d+.-]*)?$/i';
+
+ /**
+ * IPvFuture regular expression.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ private const REGEXP_IP_FUTURE = '/^
+ v(?<version>[A-F0-9])+\.
+ (?:
+ (?<unreserved>[a-z0-9_~\-\.])|
+ (?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
+ )+
+ $/ix';
+
+ /**
+ * General registered name regular expression.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ private const REGEXP_REGISTERED_NAME = '/(?(DEFINE)
+ (?<unreserved>[a-z0-9_~\-]) # . is missing as it is used to separate labels
+ (?<sub_delims>[!$&\'()*+,;=])
+ (?<encoded>%[A-F0-9]{2})
+ (?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
+ )
+ ^(?:(?®_name)\.)*(?®_name)\.?$/ix';
+
+ /**
+ * Invalid characters in host regular expression.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ */
+ private const REGEXP_INVALID_HOST_CHARS = '/
+ [:\/?#\[\]@ ] # gen-delims characters as well as the space character
+ /ix';
+
+ /**
+ * Invalid path for URI without scheme and authority regular expression.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.3
+ */
+ private const REGEXP_INVALID_PATH = ',^(([^/]*):)(.*)?/,';
+
+ /**
+ * Host and Port splitter regular expression.
+ */
+ private const REGEXP_HOST_PORT = ',^(?<host>\[.*\]|[^:]*)(:(?<port>.*))?$,';
+
+ /**
+ * IDN Host detector regular expression.
+ */
+ private const REGEXP_IDN_PATTERN = '/[^\x20-\x7f]/';
+
+ /**
+ * Only the address block fe80::/10 can have a Zone ID attach to
+ * let's detect the link local significant 10 bits.
+ */
+ private const ZONE_ID_ADDRESS_BLOCK = "\xfe\x80";
+
+ /**
+ * Generate an URI string representation from its parsed representation
+ * returned by League\UriString::parse() or PHP's parse_url.
+ *
+ * If you supply your own array, you are responsible for providing
+ * valid components without their URI delimiters.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ * @link https://tools.ietf.org/html/rfc3986#section-7.5
+ *
+ * @param array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:?string, query:?string, fragment:?string} $components
+ */
+ public static function build(array $components): string
+ {
+ $result = $components['path'] ?? '';
+ if (isset($components['query'])) {
+ $result .= '?'.$components['query'];
+ }
+
+ if (isset($components['fragment'])) {
+ $result .= '#'.$components['fragment'];
+ }
+
+ $scheme = null;
+ if (isset($components['scheme'])) {
+ $scheme = $components['scheme'].':';
+ }
+
+ if (!isset($components['host'])) {
+ return $scheme.$result;
+ }
+
+ $scheme .= '//';
+ $authority = $components['host'];
+ if (isset($components['port'])) {
+ $authority .= ':'.$components['port'];
+ }
+
+ if (!isset($components['user'])) {
+ return $scheme.$authority.$result;
+ }
+
+ $authority = '@'.$authority;
+ if (!isset($components['pass'])) {
+ return $scheme.$components['user'].$authority.$result;
+ }
+
+ return $scheme.$components['user'].':'.$components['pass'].$authority.$result;
+ }
+
+ /**
+ * Parse an URI string into its components.
+ *
+ * This method parses a URI and returns an associative array containing any
+ * of the various components of the URI that are present.
+ *
+ * <code>
+ * $components = (new Parser())->parse('http://foo@test.example.com:42?query#');
+ * var_export($components);
+ * //will display
+ * array(
+ * 'scheme' => 'http', // the URI scheme component
+ * 'user' => 'foo', // the URI user component
+ * 'pass' => null, // the URI pass component
+ * 'host' => 'test.example.com', // the URI host component
+ * 'port' => 42, // the URI port component
+ * 'path' => '', // the URI path component
+ * 'query' => 'query', // the URI query component
+ * 'fragment' => '', // the URI fragment component
+ * );
+ * </code>
+ *
+ * The returned array is similar to PHP's parse_url return value with the following
+ * differences:
+ *
+ * <ul>
+ * <li>All components are always present in the returned array</li>
+ * <li>Empty and undefined component are treated differently. And empty component is
+ * set to the empty string while an undefined component is set to the `null` value.</li>
+ * <li>The path component is never undefined</li>
+ * <li>The method parses the URI following the RFC3986 rules but you are still
+ * required to validate the returned components against its related scheme specific rules.</li>
+ * </ul>
+ *
+ * @link https://tools.ietf.org/html/rfc3986
+ *
+ * @param Stringable|string|int|float $uri any scalar or stringable object
+ *
+ * @throws SyntaxError if the URI contains invalid characters
+ * @throws SyntaxError if the URI contains an invalid scheme
+ * @throws SyntaxError if the URI contains an invalid path
+ *
+ * @return array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string}
+ */
+ public static function parse(Stringable|string|int|float $uri): array
+ {
+ $uri = (string) $uri;
+ if (isset(self::URI_SCHORTCUTS[$uri])) {
+ /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
+ $components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
+
+ return $components;
+ }
+
+ if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) {
+ throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri));
+ }
+
+ //if the first character is a known URI delimiter parsing can be simplified
+ $first_char = $uri[0];
+
+ //The URI is made of the fragment only
+ if ('#' === $first_char) {
+ [, $fragment] = explode('#', $uri, 2);
+ $components = self::URI_COMPONENTS;
+ $components['fragment'] = $fragment;
+
+ return $components;
+ }
+
+ //The URI is made of the query and fragment
+ if ('?' === $first_char) {
+ [, $partial] = explode('?', $uri, 2);
+ [$query, $fragment] = explode('#', $partial, 2) + [1 => null];
+ $components = self::URI_COMPONENTS;
+ $components['query'] = $query;
+ $components['fragment'] = $fragment;
+
+ return $components;
+ }
+
+ //use RFC3986 URI regexp to split the URI
+ preg_match(self::REGEXP_URI_PARTS, $uri, $parts);
+ $parts += ['query' => '', 'fragment' => ''];
+
+ if (':' === $parts['scheme'] || 1 !== preg_match(self::REGEXP_URI_SCHEME, $parts['scontent'])) {
+ throw new SyntaxError(sprintf('The uri `%s` contains an invalid scheme', $uri));
+ }
+
+ if ('' === $parts['scheme'].$parts['authority'] && 1 === preg_match(self::REGEXP_INVALID_PATH, $parts['path'])) {
+ throw new SyntaxError(sprintf('The uri `%s` contains an invalid path.', $uri));
+ }
+
+ /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
+ $components = array_merge(
+ self::URI_COMPONENTS,
+ '' === $parts['authority'] ? [] : self::parseAuthority($parts['acontent']),
+ [
+ 'path' => $parts['path'],
+ 'scheme' => '' === $parts['scheme'] ? null : $parts['scontent'],
+ 'query' => '' === $parts['query'] ? null : $parts['qcontent'],
+ 'fragment' => '' === $parts['fragment'] ? null : $parts['fcontent'],
+ ]
+ );
+
+ return $components;
+ }
+
+ /**
+ * Parses the URI authority part.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2
+ *
+ * @throws SyntaxError If the port component is invalid
+ *
+ * @return array{user:?string, pass:?string, host:?string, port:?int}
+ */
+ private static function parseAuthority(string $authority): array
+ {
+ $components = ['user' => null, 'pass' => null, 'host' => '', 'port' => null];
+ if ('' === $authority) {
+ return $components;
+ }
+
+ $parts = explode('@', $authority, 2);
+ if (isset($parts[1])) {
+ [$components['user'], $components['pass']] = explode(':', $parts[0], 2) + [1 => null];
+ }
+
+ preg_match(self::REGEXP_HOST_PORT, $parts[1] ?? $parts[0], $matches);
+ $matches += ['port' => ''];
+
+ $components['port'] = self::filterPort($matches['port']);
+ $components['host'] = self::filterHost($matches['host']);
+
+ return $components;
+ }
+
+ /**
+ * Filter and format the port component.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ *
+ * @throws SyntaxError if the registered name is invalid
+ */
+ private static function filterPort(string $port): ?int
+ {
+ if ('' === $port) {
+ return null;
+ }
+
+ if (1 === preg_match('/^\d*$/', $port)) {
+ return (int) $port;
+ }
+
+ throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
+ }
+
+ /**
+ * Returns whether a hostname is valid.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ *
+ * @throws SyntaxError if the registered name is invalid
+ */
+ private static function filterHost(string $host): string
+ {
+ if ('' === $host) {
+ return $host;
+ }
+
+ if ('[' !== $host[0] || !str_ends_with($host, ']')) {
+ return self::filterRegisteredName($host);
+ }
+
+ if (!self::isIpHost(substr($host, 1, -1))) {
+ throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host));
+ }
+
+ return $host;
+ }
+
+ /**
+ * Returns whether the host is an IPv4 or a registered named.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ *
+ * @throws SyntaxError if the registered name is invalid
+ * @throws IdnSupportMissing if IDN support or ICU requirement are not available or met.
+ */
+ private static function filterRegisteredName(string $host): string
+ {
+ $formatted_host = rawurldecode($host);
+ if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formatted_host)) {
+ return $host;
+ }
+
+ //to test IDN host non-ascii characters must be present in the host
+ if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formatted_host)) {
+ throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host));
+ }
+
+ $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
+ if (0 !== $info->errors()) {
+ throw IdnaConversionFailed::dueToIDNAError($host, $info);
+ }
+
+ return $host;
+ }
+
+ /**
+ * Validates a IPv6/IPvfuture host.
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @link https://tools.ietf.org/html/rfc6874#section-2
+ * @link https://tools.ietf.org/html/rfc6874#section-4
+ */
+ private static function isIpHost(string $ip_host): bool
+ {
+ if (false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return true;
+ }
+
+ if (1 === preg_match(self::REGEXP_IP_FUTURE, $ip_host, $matches)) {
+ return !in_array($matches['version'], ['4', '6'], true);
+ }
+
+ $pos = strpos($ip_host, '%');
+ if (false === $pos || 1 === preg_match(
+ self::REGEXP_INVALID_HOST_CHARS,
+ rawurldecode(substr($ip_host, $pos))
+ )) {
+ return false;
+ }
+
+ $ip_host = substr($ip_host, 0, $pos);
+
+ return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)
+ && str_starts_with((string)inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use League\Uri\Contracts\UriException;
+use League\Uri\Contracts\UriInterface;
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\Exceptions\TemplateCanNotBeExpanded;
+use League\Uri\UriTemplate\Template;
+use League\Uri\UriTemplate\VariableBag;
+use Stringable;
+
+/**
+ * Defines the URI Template syntax and the process for expanding a URI Template into a URI reference.
+ *
+ * @link https://tools.ietf.org/html/rfc6570
+ * @package League\Uri
+ * @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ * @since 6.1.0
+ *
+ * Based on GuzzleHttp\UriTemplate class in Guzzle v6.5.
+ * @link https://github.com/guzzle/guzzle/blob/6.5/src/UriTemplate.php
+ */
+final class UriTemplate
+{
+ public readonly Template $template;
+ public readonly VariableBag $defaultVariables;
+
+ /**
+ * @throws SyntaxError if the template syntax is invalid
+ * @throws TemplateCanNotBeExpanded if the template variables are invalid
+ */
+ public function __construct(Template|Stringable|string $template, VariableBag|array $defaultVariables = [])
+ {
+ $this->template = $template instanceof Template ? $template : Template::createFromString($template);
+ $this->defaultVariables = $this->filterVariables($defaultVariables);
+ }
+
+ public static function __set_state(array $properties): self
+ {
+ return new self($properties['template'], $properties['defaultVariables']);
+ }
+
+ /**
+ * Filters out variables for the given template.
+ *
+ * @param array<string,string|array<string>> $variables
+ */
+ private function filterVariables(VariableBag|array $variables): VariableBag
+ {
+ return array_reduce(
+ $this->template->variableNames,
+ function (VariableBag $curry, string $name) use ($variables): VariableBag {
+ if (isset($variables[$name])) {
+ $curry[$name] = $variables[$name];
+ }
+
+ return $curry;
+ },
+ new VariableBag()
+ );
+ }
+
+ /**
+ * The template string.
+ */
+ public function getTemplate(): string
+ {
+ return $this->template->value;
+ }
+
+ /**
+ * Returns the names of the variables in the template, in order.
+ *
+ * @return string[]
+ */
+ public function getVariableNames(): array
+ {
+ return $this->template->variableNames;
+ }
+
+ /**
+ * Returns the default values used to expand the template.
+ *
+ * The returned list only contains variables whose name is part of the current template.
+ *
+ * @return array<string,string|array>
+ */
+ public function getDefaultVariables(): array
+ {
+ return $this->defaultVariables->all();
+ }
+
+ /**
+ * Returns a new instance with the updated default variables.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the modified default variables.
+ *
+ * If present, variables whose name is not part of the current template
+ * possible variable names are removed.
+ */
+ public function withDefaultVariables(VariableBag|array $defaultDefaultVariables): self
+ {
+ return new self($this->template, $defaultDefaultVariables);
+ }
+
+ /**
+ * @throws TemplateCanNotBeExpanded if the variable contains nested array values
+ * @throws UriException if the resulting expansion can not be converted to a UriInterface instance
+ */
+ public function expand(VariableBag|array $variables = []): UriInterface
+ {
+ return Uri::createFromString(
+ $this->template->expand(
+ $this->filterVariables($variables)->replace($this->defaultVariables)
+ )
+ );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\UriTemplate;
+
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\Exceptions\TemplateCanNotBeExpanded;
+use function array_filter;
+use function array_map;
+use function array_unique;
+use function explode;
+use function implode;
+use function preg_match;
+use function rawurlencode;
+use function str_replace;
+use function substr;
+
+final class Expression
+{
+ /**
+ * Expression regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc6570#section-2.2
+ */
+ private const REGEXP_EXPRESSION = '/^\{
+ (?:
+ (?<operator>[\.\/;\?&\=,\!@\|\+#])?
+ (?<variables>[^\}]*)
+ )
+ \}$/x';
+
+ /**
+ * Reserved Operator characters.
+ *
+ * @link https://tools.ietf.org/html/rfc6570#section-2.2
+ */
+ private const RESERVED_OPERATOR = '=,!@|';
+
+ /**
+ * Processing behavior according to the expression type operator.
+ *
+ * @link https://tools.ietf.org/html/rfc6570#appendix-A
+ */
+ private const OPERATOR_HASH_LOOKUP = [
+ '' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
+ '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
+ '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
+ ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
+ '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
+ '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
+ ];
+
+ /** @var array<VarSpecifier> */
+ private array $varSpecifiers;
+ private string $joiner;
+ /** @var array<string> */
+ public readonly array $variableNames;
+ public readonly string $value;
+
+ private function __construct(private string $operator, VarSpecifier ...$varSpecifiers)
+ {
+ $this->varSpecifiers = $varSpecifiers;
+ $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
+ $this->variableNames = array_unique(array_map(
+ static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name,
+ $varSpecifiers
+ ));
+ $this->value = '{'.$operator.implode(',', array_map(
+ static fn (VarSpecifier $varSpecifier): string => $varSpecifier->toString(),
+ $varSpecifiers
+ )).'}';
+ }
+
+ /**
+ * @param array{operator:string, varSpecifiers:array<VarSpecifier>} $properties
+ */
+ public static function __set_state(array $properties): self
+ {
+ return new self($properties['operator'], ...$properties['varSpecifiers']);
+ }
+
+ /**
+ * @throws SyntaxError if the expression is invalid
+ * @throws SyntaxError if the operator used in the expression is invalid
+ * @throws SyntaxError if the variable specifiers is invalid
+ */
+ public static function createFromString(string $expression): self
+ {
+ if (1 !== preg_match(self::REGEXP_EXPRESSION, $expression, $parts)) {
+ throw new SyntaxError('The expression "'.$expression.'" is invalid.');
+ }
+
+ /** @var array{operator:string, variables:string} $parts */
+ $parts = $parts + ['operator' => ''];
+ if ('' !== $parts['operator'] && str_contains(self::RESERVED_OPERATOR, $parts['operator'])) {
+ throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.');
+ }
+
+ return new Expression($parts['operator'], ...array_map(
+ static fn (string $varSpec): VarSpecifier => VarSpecifier::createFromString($varSpec),
+ explode(',', $parts['variables'])
+ ));
+ }
+
+ /**
+ * Returns the expression string representation.
+ *
+ * @deprecated since version 6.6.0 use the readonly property instead
+ * @codeCoverageIgnore
+ */
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @deprecated since version 6.6.0 use the readonly property instead
+ * @codeCoverageIgnore
+ *
+ * @return array<string>
+ */
+ public function variableNames(): array
+ {
+ return $this->variableNames;
+ }
+
+ public function expand(VariableBag $variables): string
+ {
+ $parts = [];
+ foreach ($this->varSpecifiers as $varSpecifier) {
+ $parts[] = $this->replace($varSpecifier, $variables);
+ }
+
+ $expanded = implode($this->joiner, array_filter($parts, static fn ($value): bool => '' !== $value));
+ if ('' === $expanded) {
+ return $expanded;
+ }
+
+ $prefix = self::OPERATOR_HASH_LOOKUP[$this->operator]['prefix'];
+ if ('' === $prefix) {
+ return $expanded;
+ }
+
+ return $prefix.$expanded;
+ }
+
+ /**
+ * Replaces an expression with the given variables.
+ *
+ * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
+ * @throws TemplateCanNotBeExpanded if the variables contains nested array values
+ */
+ private function replace(VarSpecifier $varSpec, VariableBag $variables): string
+ {
+ $value = $variables->fetch($varSpec->name);
+ if (null === $value) {
+ return '';
+ }
+
+ $useQuery = self::OPERATOR_HASH_LOOKUP[$this->operator]['query'];
+ [$expanded, $actualQuery] = $this->inject($value, $varSpec, $useQuery);
+ if (!$actualQuery) {
+ return $expanded;
+ }
+
+ if ('&' !== $this->joiner && '' === $expanded) {
+ return $varSpec->name;
+ }
+
+ return $varSpec->name.'='.$expanded;
+ }
+
+ /**
+ * @param string|array<string> $value
+ *
+ * @return array{0:string, 1:bool}
+ */
+ private function inject(array|string $value, VarSpecifier $varSpec, bool $useQuery): array
+ {
+ if (is_string($value)) {
+ return $this->replaceString($value, $varSpec, $useQuery);
+ }
+
+ return $this->replaceList($value, $varSpec, $useQuery);
+ }
+
+ /**
+ * Expands an expression using a string value.
+ *
+ * @return array{0:string, 1:bool}
+ */
+ private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array
+ {
+ if (':' === $varSpec->modifier) {
+ $value = substr($value, 0, $varSpec->position);
+ }
+
+ if (in_array($this->operator, ['+', '#'], true)) {
+ return [$this->decodeReserved(rawurlencode($value)), $useQuery];
+ }
+
+ return [rawurlencode($value), $useQuery];
+ }
+
+ /**
+ * Expands an expression using a list of values.
+ *
+ * @param array<string> $value
+ *
+ * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
+ *
+ * @return array{0:string, 1:bool}
+ */
+ private function replaceList(array $value, VarSpecifier $varSpec, bool $useQuery): array
+ {
+ if ([] === $value) {
+ return ['', false];
+ }
+
+ if (':' === $varSpec->modifier) {
+ throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name);
+ }
+
+ $pairs = [];
+ $isList = array_is_list($value);
+ foreach ($value as $key => $var) {
+ if (!$isList) {
+ $key = rawurlencode((string) $key);
+ }
+
+ $var = rawurlencode($var);
+ if (in_array($this->operator, ['+', '#'], true)) {
+ $var = $this->decodeReserved($var);
+ }
+
+ if ('*' === $varSpec->modifier) {
+ if (!$isList) {
+ $var = $key.'='.$var;
+ } elseif ($key > 0 && $useQuery) {
+ $var = $varSpec->name.'='.$var;
+ }
+ }
+
+ $pairs[$key] = $var;
+ }
+
+ if ('*' === $varSpec->modifier) {
+ if (!$isList) {
+ // Don't prepend the value name when using the `explode` modifier with an associative array.
+ $useQuery = false;
+ }
+
+ return [implode($this->joiner, $pairs), $useQuery];
+ }
+
+ if (!$isList) {
+ // When an associative array is encountered and the `explode` modifier is not set, then
+ // the result must be a comma separated list of keys followed by their respective values.
+ foreach ($pairs as $offset => &$data) {
+ $data = $offset.','.$data;
+ }
+
+ unset($data);
+ }
+
+ return [implode(',', $pairs), $useQuery];
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and # modifiers).
+ */
+ private function decodeReserved(string $str): string
+ {
+ static $delimiters = [
+ ':', '/', '?', '#', '[', ']', '@', '!', '$',
+ '&', '\'', '(', ')', '*', '+', ',', ';', '=',
+ ];
+
+ static $delimitersEncoded = [
+ '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24',
+ '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D',
+ ];
+
+ return str_replace($delimitersEncoded, $delimiters, $str);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\UriTemplate;
+
+use League\Uri\Exceptions\SyntaxError;
+use League\Uri\Exceptions\TemplateCanNotBeExpanded;
+use Stringable;
+use function array_unique;
+use function preg_match_all;
+use function preg_replace;
+use function str_contains;
+use const PREG_SET_ORDER;
+
+final class Template
+{
+ /**
+ * Expression regular expression pattern.
+ */
+ private const REGEXP_EXPRESSION_DETECTOR = '/\{[^}]*}/x';
+
+ /** @var array<string, Expression> */
+ private array $expressions = [];
+ /** @var array<string> */
+ public readonly array $variableNames;
+
+ private function __construct(public readonly string $value, Expression ...$expressions)
+ {
+ $variableNames = [];
+ foreach ($expressions as $expression) {
+ $this->expressions[$expression->value] = $expression;
+ $variableNames = [...$variableNames, ...$expression->variableNames];
+ }
+
+ $this->variableNames = array_unique($variableNames);
+ }
+
+ /**
+ * @param array{value:string, template?:string, expressions:array<string, Expression>} $properties
+ */
+ public static function __set_state(array $properties): self
+ {
+ return new self($properties['template'] ?? $properties['value'], ...array_values($properties['expressions']));
+ }
+
+ /**
+ * @throws SyntaxError if the template contains invalid expressions
+ * @throws SyntaxError if the template contains invalid variable specification
+ */
+ public static function createFromString(Stringable|string $template): self
+ {
+ $template = (string) $template;
+ /** @var string $remainder */
+ $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
+ if (str_contains($remainder, '{') || str_contains($remainder, '}')) {
+ throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
+ }
+
+ $names = [];
+ preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $findings, PREG_SET_ORDER);
+ $arguments = [];
+ foreach ($findings as $finding) {
+ if (!isset($names[$finding[0]])) {
+ $arguments[] = Expression::createFromString($finding[0]);
+ $names[$finding[0]] = 1;
+ }
+ }
+
+ return new self($template, ...$arguments);
+ }
+
+ /**
+ * @deprecated since version 6.6.0 use the readonly property instead
+ * @codeCoverageIgnore
+ */
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @deprecated since version 6.6.0 use the readonly property instead
+ * @codeCoverageIgnore
+ *
+ * @return array<string>
+ */
+ public function variableNames(): array
+ {
+ return $this->variableNames;
+ }
+
+ /**
+ * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
+ * @throws TemplateCanNotBeExpanded if the variables contains nested array values
+ */
+ public function expand(VariableBag $variables): string
+ {
+ $uriString = $this->value;
+ /** @var Expression $expression */
+ foreach ($this->expressions as $pattern => $expression) {
+ $uriString = str_replace($pattern, $expression->expand($variables), $uriString);
+ }
+
+ return $uriString;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\UriTemplate;
+
+use League\Uri\Exceptions\SyntaxError;
+use function preg_match;
+
+final class VarSpecifier
+{
+ /**
+ * Variables specification regular expression pattern.
+ *
+ * @link https://tools.ietf.org/html/rfc6570#section-2.3
+ */
+ private const REGEXP_VARSPEC = '/^
+ (?<name>(?:[A-z0-9_\.]|%[0-9a-fA-F]{2})+)
+ (?<modifier>\:(?<position>\d+)|\*)?
+ $/x';
+
+ private function __construct(
+ public readonly string $name,
+ public readonly string $modifier,
+ public readonly int $position
+ ) {
+ }
+
+ /**
+ * @param array{name: string, modifier:string, position:int} $properties
+ */
+ public static function __set_state(array $properties): self
+ {
+ return new self($properties['name'], $properties['modifier'], $properties['position']);
+ }
+
+ public static function createFromString(string $specification): self
+ {
+ if (1 !== preg_match(self::REGEXP_VARSPEC, $specification, $parsed)) {
+ throw new SyntaxError('The variable specification "'.$specification.'" is invalid.');
+ }
+
+ $parsed += ['modifier' => '', 'position' => ''];
+ if ('' !== $parsed['position']) {
+ $parsed['position'] = (int) $parsed['position'];
+ $parsed['modifier'] = ':';
+ }
+
+ if ('' === $parsed['position']) {
+ $parsed['position'] = 0;
+ }
+
+ if (10000 <= $parsed['position']) {
+ throw new SyntaxError('The variable specification "'.$specification.'" is invalid the position modifier must be lower than 10000.');
+ }
+
+ return new self($parsed['name'], $parsed['modifier'], $parsed['position']);
+ }
+
+ public function toString(): string
+ {
+ if (0 < $this->position) {
+ return $this->name.$this->modifier.$this->position;
+ }
+
+ return $this->name.$this->modifier;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @deprecated since version 6.6.0 use the readonly property instead
+ */
+ public function name(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @deprecated since version 6.6.0 use the readonly property instead
+ */
+ public function modifier(): string
+ {
+ return $this->modifier;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @deprecated since version 6.6.0 use the readonly property instead
+ */
+ public function position(): int
+ {
+ return $this->position;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri\UriTemplate;
+
+use ArrayAccess;
+use Countable;
+use League\Uri\Exceptions\TemplateCanNotBeExpanded;
+use Stringable;
+use function is_bool;
+use function is_object;
+use function is_scalar;
+
+/**
+ * @implements ArrayAccess<string, string|bool|int|float|array<string|bool|int|float>>
+ */
+final class VariableBag implements ArrayAccess, Countable
+{
+ /**
+ * @var array<string,string|array<string>>
+ */
+ private array $variables = [];
+
+ /**
+ * @param iterable<string,string|bool|int|float|array<string|bool|int|float>> $variables
+ */
+ public function __construct(iterable $variables = [])
+ {
+ foreach ($variables as $name => $value) {
+ $this->assign($name, $value);
+ }
+ }
+
+ public function count(): int
+ {
+ return count($this->variables);
+ }
+
+ /**
+ * @param array{variables: array<string,string|array<string>>} $properties
+ */
+ public static function __set_state(array $properties): self
+ {
+ return new self($properties['variables']);
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return array_key_exists($offset, $this->variables);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ unset($this->variables[$offset]);
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ $this->assign($offset, $value); /* @phpstan-ignore-line */
+ }
+
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->fetch($offset);
+ }
+
+ /**
+ * @return array<string,string|array<string>>
+ */
+ public function all(): array
+ {
+ return $this->variables;
+ }
+
+ /**
+ * Tells whether the bag is empty or not.
+ */
+ public function isEmpty(): bool
+ {
+ return [] === $this->variables;
+ }
+
+ /**
+ * Fetches the variable value if none found returns null.
+ *
+ * @return null|string|array<string>
+ */
+ public function fetch(string $name): null|string|array
+ {
+ return $this->variables[$name] ?? null;
+ }
+
+ /**
+ * @param string|bool|int|float|null|array<string|bool|int|float> $value
+ */
+ public function assign(string $name, string|bool|int|float|array|null $value): void
+ {
+ $this->variables[$name] = $this->normalizeValue($value, $name, true);
+ }
+
+ /**
+ * @param Stringable|string|float|int|bool|null $value the value to be expanded
+ *
+ * @throws TemplateCanNotBeExpanded if the value contains nested list
+ */
+ private function normalizeValue(Stringable|array|string|float|int|bool|null $value, string $name, bool $isNestedListAllowed): array|string
+ {
+ return match (true) {
+ is_bool($value) => true === $value ? '1' : '0',
+ (null === $value || is_scalar($value) || is_object($value)) => (string) $value,
+ !$isNestedListAllowed => throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name),
+ default => array_map(fn ($var): array|string => self::normalizeValue($var, $name, false), $value),
+ };
+ }
+
+ /**
+ * Replaces elements from passed variables into the current instance.
+ */
+ public function replace(VariableBag $variables): self
+ {
+ return new self($this->variables + $variables->variables);
+ }
+}
--- /dev/null
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.3
+
+Add `source` link in composer.json. No code changes.
+
+## 1.0.2
+
+Allow PSR-7 (psr/http-message) 2.0. No code changes.
+
+## 1.0.1
+
+Allow installation with PHP 8. No code changes.
+
+## 1.0.0
+
+First stable release. No changes since 0.3.0.
+
+## 0.3.0
+
+Added Interface suffix on exceptions
+
+## 0.2.0
+
+All exceptions are in `Psr\Http\Client` namespace
+
+## 0.1.0
+
+First release
--- /dev/null
+Copyright (c) 2017 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+HTTP Client
+===========
+
+This repository holds all the common code related to [PSR-18 (HTTP Client)][psr-url].
+
+Note that this is not a HTTP Client implementation of its own. It is merely abstractions that describe the components of a HTTP Client.
+
+The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
+
+[psr-url]: https://www.php-fig.org/psr/psr-18
+[package-url]: https://packagist.org/packages/psr/http-client
+[implementation-url]: https://packagist.org/providers/psr/http-client-implementation
--- /dev/null
+{
+ "name": "psr/http-client",
+ "description": "Common interface for HTTP clients",
+ "keywords": ["psr", "psr-18", "http", "http-client"],
+ "homepage": "https://github.com/php-fig/http-client",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Client;
+
+/**
+ * Every HTTP client related exception MUST implement this interface.
+ */
+interface ClientExceptionInterface extends \Throwable
+{
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+interface ClientInterface
+{
+ /**
+ * Sends a PSR-7 request and returns a PSR-7 response.
+ *
+ * @param RequestInterface $request
+ *
+ * @return ResponseInterface
+ *
+ * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
+ */
+ public function sendRequest(RequestInterface $request): ResponseInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Thrown when the request cannot be completed because of network issues.
+ *
+ * There is no response object as this exception is thrown when no response has been received.
+ *
+ * Example: the target host name can not be resolved or the connection failed.
+ */
+interface NetworkExceptionInterface extends ClientExceptionInterface
+{
+ /**
+ * Returns the request.
+ *
+ * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
+ *
+ * @return RequestInterface
+ */
+ public function getRequest(): RequestInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Exception for when a request failed.
+ *
+ * Examples:
+ * - Request is invalid (e.g. method is missing)
+ * - Runtime request errors (e.g. the body stream is not seekable)
+ */
+interface RequestExceptionInterface extends ClientExceptionInterface
+{
+ /**
+ * Returns the request.
+ *
+ * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
+ *
+ * @return RequestInterface
+ */
+ public function getRequest(): RequestInterface;
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2018 PHP-FIG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+HTTP Factories
+==============
+
+This repository holds all interfaces related to [PSR-17 (HTTP Factories)][psr-url].
+
+Note that this is not a HTTP Factory implementation of its own. It is merely interfaces that describe the components of a HTTP Factory.
+
+The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
+
+[psr-url]: https://www.php-fig.org/psr/psr-17/
+[package-url]: https://packagist.org/packages/psr/http-factory
+[implementation-url]: https://packagist.org/providers/psr/http-factory-implementation
--- /dev/null
+{
+ "name": "psr/http-factory",
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "psr",
+ "psr-7",
+ "psr-17",
+ "http",
+ "factory",
+ "message",
+ "request",
+ "response"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface RequestFactoryInterface
+{
+ /**
+ * Create a new request.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ *
+ * @return RequestInterface
+ */
+ public function createRequest(string $method, $uri): RequestInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface ResponseFactoryInterface
+{
+ /**
+ * Create a new response.
+ *
+ * @param int $code HTTP status code; defaults to 200
+ * @param string $reasonPhrase Reason phrase to associate with status code
+ * in generated response; if none is provided implementations MAY use
+ * the defaults as suggested in the HTTP specification.
+ *
+ * @return ResponseInterface
+ */
+ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface ServerRequestFactoryInterface
+{
+ /**
+ * Create a new server request.
+ *
+ * Note that server-params are taken precisely as given - no parsing/processing
+ * of the given values is performed, and, in particular, no attempt is made to
+ * determine the HTTP method or URI, which must be provided explicitly.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ * @param array $serverParams Array of SAPI parameters with which to seed
+ * the generated request instance.
+ *
+ * @return ServerRequestInterface
+ */
+ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface StreamFactoryInterface
+{
+ /**
+ * Create a new stream from a string.
+ *
+ * The stream SHOULD be created with a temporary resource.
+ *
+ * @param string $content String content with which to populate the stream.
+ *
+ * @return StreamInterface
+ */
+ public function createStream(string $content = ''): StreamInterface;
+
+ /**
+ * Create a stream from an existing file.
+ *
+ * The file MUST be opened using the given mode, which may be any mode
+ * supported by the `fopen` function.
+ *
+ * The `$filename` MAY be any string supported by `fopen()`.
+ *
+ * @param string $filename Filename or stream URI to use as basis of stream.
+ * @param string $mode Mode with which to open the underlying filename/stream.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException If the file cannot be opened.
+ * @throws \InvalidArgumentException If the mode is invalid.
+ */
+ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
+
+ /**
+ * Create a new stream from an existing resource.
+ *
+ * The stream MUST be readable and may be writable.
+ *
+ * @param resource $resource PHP resource to use as basis of stream.
+ *
+ * @return StreamInterface
+ */
+ public function createStreamFromResource($resource): StreamInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface UploadedFileFactoryInterface
+{
+ /**
+ * Create a new uploaded file.
+ *
+ * If a size is not provided it will be determined by checking the size of
+ * the file.
+ *
+ * @see http://php.net/manual/features.file-upload.post-method.php
+ * @see http://php.net/manual/features.file-upload.errors.php
+ *
+ * @param StreamInterface $stream Underlying stream representing the
+ * uploaded file content.
+ * @param int|null $size in bytes
+ * @param int $error PHP file upload error
+ * @param string|null $clientFilename Filename as provided by the client, if any.
+ * @param string|null $clientMediaType Media type as provided by the client, if any.
+ *
+ * @return UploadedFileInterface
+ *
+ * @throws \InvalidArgumentException If the file resource is not readable.
+ */
+ public function createUploadedFile(
+ StreamInterface $stream,
+ ?int $size = null,
+ int $error = \UPLOAD_ERR_OK,
+ ?string $clientFilename = null,
+ ?string $clientMediaType = null
+ ): UploadedFileInterface;
+}
--- /dev/null
+<?php
+
+namespace Psr\Http\Message;
+
+interface UriFactoryInterface
+{
+ /**
+ * Create a new URI.
+ *
+ * @param string $uri
+ *
+ * @return UriInterface
+ *
+ * @throws \InvalidArgumentException If the given URI cannot be parsed.
+ */
+ public function createUri(string $uri = ''): UriInterface;
+}
--- /dev/null
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
--- /dev/null
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+Before reading the usage guide we recommend reading the PSR-7 interfaces method list:
+
+* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md)
+* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md)
\ No newline at end of file
--- /dev/null
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
--- /dev/null
+# Interfaces
+
+The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces.
+
+The interfaces defined in PSR-7 are the following:
+
+| Class Name | Description |
+|---|---|
+| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message |
+| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. |
+| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. |
+| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. |
+| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream |
+| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. |
+| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. |
+
+## `Psr\Http\Message\MessageInterface` Methods
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getProtocolVersion()` | Retrieve HTTP protocol version | 1.0 or 1.1 |
+| `withProtocolVersion($version)` | Returns new message instance with given HTTP protocol version | |
+| `getHeaders()` | Retrieve all HTTP Headers | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) |
+| `hasHeader($name)` | Checks if HTTP Header with given name exists | |
+| `getHeader($name)` | Retrieves a array with the values for a single header | |
+| `getHeaderLine($name)` | Retrieves a comma-separated string of the values for a single header | |
+| `withHeader($name, $value)` | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. |
+| `withAddedHeader($name, $value)` | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created |
+| `withoutHeader($name)` | Removes HTTP Header with given name| |
+| `getBody()` | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`|
+| `withBody(StreamInterface $body)` | Returns new message instance with given HTTP Message Body | |
+
+
+## `Psr\Http\Message\RequestInterface` Methods
+
+Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getRequestTarget()` | Retrieves the message's request target | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) |
+| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target | |
+| `getMethod()` | Retrieves the HTTP method of the request. | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) |
+| `withMethod($method)` | Returns a new message instance with the provided HTTP method | |
+| `getUri()` | Retrieves the URI instance | |
+| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI | |
+
+
+## `Psr\Http\Message\ServerRequestInterface` Methods
+
+Same methods as `Psr\Http\Message\RequestInterface` + the following methods:
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getServerParams() ` | Retrieve server parameters | Typically derived from `$_SERVER` |
+| `getCookieParams()` | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` |
+| `withCookieParams(array $cookies)` | Returns a new request instance with the specified cookies | |
+| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments | |
+| `getUploadedFiles()` | Retrieve normalized file upload data | |
+| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files | |
+| `getParsedBody()` | Retrieve any parameters provided in the request body | |
+| `withParsedBody($data)` | Returns a new request instance with the specified body parameters | |
+| `getAttributes()` | Retrieve attributes derived from the request | |
+| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute | |
+| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute | |
+| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute | |
+
+## `Psr\Http\Message\ResponseInterface` Methods:
+
+Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getStatusCode()` | Gets the response status code. | |
+| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | |
+| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | |
+
+## `Psr\Http\Message\StreamInterface` Methods
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | |
+| `close()` | Closes the stream and any underlying resources. | |
+| `detach()` | Separates any underlying resources from the stream. | |
+| `getSize()` | Get the size of the stream if known. | |
+| `eof()` | Returns true if the stream is at the end of the stream.| |
+| `isSeekable()` | Returns whether or not the stream is seekable. | |
+| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | |
+| `rewind()` | Seek to the beginning of the stream. | |
+| `isWritable()` | Returns whether or not the stream is writable. | |
+| `write($string)` | Write data to the stream. | |
+| `isReadable()` | Returns whether or not the stream is readable. | |
+| `read($length)` | Read data from the stream. | |
+| `getContents()` | Returns the remaining contents in a string | |
+| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | |
+
+## `Psr\Http\Message\UriInterface` Methods
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getScheme()` | Retrieve the scheme component of the URI. | |
+| `getAuthority()` | Retrieve the authority component of the URI. | |
+| `getUserInfo()` | Retrieve the user information component of the URI. | |
+| `getHost()` | Retrieve the host component of the URI. | |
+| `getPort()` | Retrieve the port component of the URI. | |
+| `getPath()` | Retrieve the path component of the URI. | |
+| `getQuery()` | Retrieve the query string of the URI. | |
+| `getFragment()` | Retrieve the fragment component of the URI. | |
+| `withScheme($scheme)` | Return an instance with the specified scheme. | |
+| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | |
+| `withHost($host)` | Return an instance with the specified host. | |
+| `withPort($port)` | Return an instance with the specified port. | |
+| `withPath($path)` | Return an instance with the specified path. | |
+| `withQuery($query)` | Return an instance with the specified query string. | |
+| `withFragment($fragment)` | Return an instance with the specified URI fragment. | |
+| `__toString()` | Return the string representation as a URI reference. | |
+
+## `Psr\Http\Message\UploadedFileInterface` Methods
+
+| Method Name | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getStream()` | Retrieve a stream representing the uploaded file. | |
+| `moveTo($targetPath)` | Move the uploaded file to a new location. | |
+| `getSize()` | Retrieve the file size. | |
+| `getError()` | Retrieve the error associated with the uploaded file. | |
+| `getClientFilename()` | Retrieve the filename sent by the client. | |
+| `getClientMediaType()` | Retrieve the media type sent by the client. | |
+
+> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
+> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
+
--- /dev/null
+### PSR-7 Usage
+
+All PSR-7 applications comply with these interfaces
+They were created to establish a standard between middleware implementations.
+
+> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
+> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
+
+
+The following examples will illustrate how basic operations are done in PSR-7.
+
+##### Examples
+
+
+For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc)
+All PSR-7 implementations should have the same behaviour.
+
+The following will be assumed:
+`$request` is an object of `Psr\Http\Message\RequestInterface` and
+
+`$response` is an object implementing `Psr\Http\Message\RequestInterface`
+
+
+### Working with HTTP Headers
+
+#### Adding headers to response:
+
+```php
+$response->withHeader('My-Custom-Header', 'My Custom Message');
+```
+
+#### Appending values to headers
+
+```php
+$response->withAddedHeader('My-Custom-Header', 'The second message');
+```
+
+#### Checking if header exists:
+
+```php
+$request->hasHeader('My-Custom-Header'); // will return false
+$response->hasHeader('My-Custom-Header'); // will return true
+```
+
+> Note: My-Custom-Header was only added in the Response
+
+#### Getting comma-separated values from a header (also applies to request)
+
+```php
+// getting value from request headers
+$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
+// getting value from response headers
+$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message"
+```
+
+#### Getting array of value from a header (also applies to request)
+```php
+// getting value from request headers
+$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"]
+// getting value from response headers
+$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"]
+```
+
+#### Removing headers from HTTP Messages
+```php
+// removing a header from Request, removing deprecated "Content-MD5" header
+$request->withoutHeader('Content-MD5');
+
+// removing a header from Response
+// effect: the browser won't know the size of the stream
+// the browser will download the stream till it ends
+$response->withoutHeader('Content-Length');
+```
+
+### Working with HTTP Message Body
+
+When working with the PSR-7 there are two methods of implementation:
+#### 1. Getting the body separately
+
+> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented.
+
+```php
+$body = $response->getBody();
+// operations on body, eg. read, write, seek
+// ...
+// replacing the old body
+$response->withBody($body);
+// this last statement is optional as we working with objects
+// in this case the "new" body is same with the "old" one
+// the $body variable has the same value as the one in $request, only the reference is passed
+```
+
+#### 2. Working directly on response
+
+> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required
+
+```php
+$response->getBody()->write('hello');
+```
+
+### Getting the body contents
+
+The following snippet gets the contents of a stream contents.
+> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream.
+```php
+$body = $response->getBody();
+$body->rewind(); // or $body->seek(0);
+$bodyText = $body->getContents();
+```
+> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended.
+
+### Append to body
+
+```php
+$response->getBody()->write('Hello'); // writing directly
+$body = $request->getBody(); // which is a `StreamInterface`
+$body->write('xxxxx');
+```
+
+### Prepend to body
+Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended.
+The following example will explain the behaviour of streams.
+
+```php
+// assuming our response is initially empty
+$body = $repsonse->getBody();
+// writing the string "abcd"
+$body->write('abcd');
+
+// seeking to start of stream
+$body->seek(0);
+// writing 'ef'
+$body->write('ef'); // at this point the stream contains "efcd"
+```
+
+#### Prepending by rewriting separately
+
+```php
+// assuming our response body stream only contains: "abcd"
+$body = $response->getBody();
+$body->rewind();
+$contents = $body->getContents(); // abcd
+// seeking the stream to beginning
+$body->rewind();
+$body->write('ef'); // stream contains "efcd"
+$body->write($contents); // stream contains "efabcd"
+```
+
+> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`.
+
+#### Prepending by using contents as a string
+```php
+$body = $response->getBody();
+$body->rewind();
+$contents = $body->getContents(); // efabcd
+$contents = 'ef'.$contents;
+$body->rewind();
+$body->write($contents);
+```
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * HTTP messages consist of requests from a client to a server and responses
+ * from a server to a client. This interface defines the methods common to
+ * each.
+ *
+ * Messages are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ *
+ * @link http://www.ietf.org/rfc/rfc7230.txt
+ * @link http://www.ietf.org/rfc/rfc7231.txt
+ */
+interface MessageInterface
+{
+ /**
+ * Retrieves the HTTP protocol version as a string.
+ *
+ * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
+ *
+ * @return string HTTP protocol version.
+ */
+ public function getProtocolVersion();
+
+ /**
+ * Return an instance with the specified HTTP protocol version.
+ *
+ * The version string MUST contain only the HTTP version number (e.g.,
+ * "1.1", "1.0").
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new protocol version.
+ *
+ * @param string $version HTTP protocol version
+ * @return static
+ */
+ public function withProtocolVersion(string $version);
+
+ /**
+ * Retrieves all message header values.
+ *
+ * The keys represent the header name as it will be sent over the wire, and
+ * each value is an array of strings associated with the header.
+ *
+ * // Represent the headers as a string
+ * foreach ($message->getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader(string $name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader(string $name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine(string $name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader(string $name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader(string $name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader(string $name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, client-side request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * During construction, implementations MUST attempt to set the Host header from
+ * a provided URI if no Host header is provided.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface RequestInterface extends MessageInterface
+{
+ /**
+ * Retrieves the message's request target.
+ *
+ * Retrieves the message's request-target either as it will appear (for
+ * clients), as it appeared at request (for servers), or as it was
+ * specified for the instance (see withRequestTarget()).
+ *
+ * In most cases, this will be the origin-form of the composed URI,
+ * unless a value was provided to the concrete implementation (see
+ * withRequestTarget() below).
+ *
+ * If no URI is available, and no request-target has been specifically
+ * provided, this method MUST return the string "/".
+ *
+ * @return string
+ */
+ public function getRequestTarget();
+
+ /**
+ * Return an instance with the specific request-target.
+ *
+ * If the request needs a non-origin-form request-target — e.g., for
+ * specifying an absolute-form, authority-form, or asterisk-form —
+ * this method may be used to create an instance with the specified
+ * request-target, verbatim.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request target.
+ *
+ * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
+ * request-target forms allowed in request messages)
+ * @param string $requestTarget
+ * @return static
+ */
+ public function withRequestTarget(string $requestTarget);
+
+ /**
+ * Retrieves the HTTP method of the request.
+ *
+ * @return string Returns the request method.
+ */
+ public function getMethod();
+
+ /**
+ * Return an instance with the provided HTTP method.
+ *
+ * While HTTP method names are typically all uppercase characters, HTTP
+ * method names are case-sensitive and thus implementations SHOULD NOT
+ * modify the given string.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request method.
+ *
+ * @param string $method Case-sensitive method.
+ * @return static
+ * @throws \InvalidArgumentException for invalid HTTP methods.
+ */
+ public function withMethod(string $method);
+
+ /**
+ * Retrieves the URI instance.
+ *
+ * This method MUST return a UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @return UriInterface Returns a UriInterface instance
+ * representing the URI of the request.
+ */
+ public function getUri();
+
+ /**
+ * Returns an instance with the provided URI.
+ *
+ * This method MUST update the Host header of the returned request by
+ * default if the URI contains a host component. If the URI does not
+ * contain a host component, any pre-existing Host header MUST be carried
+ * over to the returned request.
+ *
+ * You can opt-in to preserving the original state of the Host header by
+ * setting `$preserveHost` to `true`. When `$preserveHost` is set to
+ * `true`, this method interacts with the Host header in the following ways:
+ *
+ * - If the Host header is missing or empty, and the new URI contains
+ * a host component, this method MUST update the Host header in the returned
+ * request.
+ * - If the Host header is missing or empty, and the new URI does not contain a
+ * host component, this method MUST NOT update the Host header in the returned
+ * request.
+ * - If a Host header is present and non-empty, this method MUST NOT update
+ * the Host header in the returned request.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @param UriInterface $uri New request URI to use.
+ * @param bool $preserveHost Preserve the original state of the Host header.
+ * @return static
+ */
+ public function withUri(UriInterface $uri, bool $preserveHost = false);
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, server-side response.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - Status code and reason phrase
+ * - Headers
+ * - Message body
+ *
+ * Responses are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ResponseInterface extends MessageInterface
+{
+ /**
+ * Gets the response status code.
+ *
+ * The status code is a 3-digit integer result code of the server's attempt
+ * to understand and satisfy the request.
+ *
+ * @return int Status code.
+ */
+ public function getStatusCode();
+
+ /**
+ * Return an instance with the specified status code and, optionally, reason phrase.
+ *
+ * If no reason phrase is specified, implementations MAY choose to default
+ * to the RFC 7231 or IANA recommended reason phrase for the response's
+ * status code.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated status and reason phrase.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * @param int $code The 3-digit integer result code to set.
+ * @param string $reasonPhrase The reason phrase to use with the
+ * provided status code; if none is provided, implementations MAY
+ * use the defaults as suggested in the HTTP specification.
+ * @return static
+ * @throws \InvalidArgumentException For invalid status code arguments.
+ */
+ public function withStatus(int $code, string $reasonPhrase = '');
+
+ /**
+ * Gets the response reason phrase associated with the status code.
+ *
+ * Because a reason phrase is not a required element in a response
+ * status line, the reason phrase value MAY be null. Implementations MAY
+ * choose to return the default RFC 7231 recommended reason phrase (or those
+ * listed in the IANA HTTP Status Code Registry) for the response's
+ * status code.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * @return string Reason phrase; must return an empty string if none present.
+ */
+ public function getReasonPhrase();
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an incoming, server-side HTTP request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * Additionally, it encapsulates all data as it has arrived to the
+ * application from the CGI and/or PHP environment, including:
+ *
+ * - The values represented in $_SERVER.
+ * - Any cookies provided (generally via $_COOKIE)
+ * - Query string arguments (generally via $_GET, or as parsed via parse_str())
+ * - Upload files, if any (as represented by $_FILES)
+ * - Deserialized body parameters (generally from $_POST)
+ *
+ * $_SERVER values MUST be treated as immutable, as they represent application
+ * state at the time of request; as such, no methods are provided to allow
+ * modification of those values. The other values provide such methods, as they
+ * can be restored from $_SERVER or the request body, and may need treatment
+ * during the application (e.g., body parameters may be deserialized based on
+ * content type).
+ *
+ * Additionally, this interface recognizes the utility of introspecting a
+ * request to derive and match additional parameters (e.g., via URI path
+ * matching, decrypting cookie values, deserializing non-form-encoded body
+ * content, matching authorization headers to users, etc). These parameters
+ * are stored in an "attributes" property.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ServerRequestInterface extends RequestInterface
+{
+ /**
+ * Retrieve server parameters.
+ *
+ * Retrieves data related to the incoming request environment,
+ * typically derived from PHP's $_SERVER superglobal. The data IS NOT
+ * REQUIRED to originate from $_SERVER.
+ *
+ * @return array
+ */
+ public function getServerParams();
+
+ /**
+ * Retrieve cookies.
+ *
+ * Retrieves cookies sent by the client to the server.
+ *
+ * The data MUST be compatible with the structure of the $_COOKIE
+ * superglobal.
+ *
+ * @return array
+ */
+ public function getCookieParams();
+
+ /**
+ * Return an instance with the specified cookies.
+ *
+ * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
+ * be compatible with the structure of $_COOKIE. Typically, this data will
+ * be injected at instantiation.
+ *
+ * This method MUST NOT update the related Cookie header of the request
+ * instance, nor related values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated cookie values.
+ *
+ * @param array $cookies Array of key/value pairs representing cookies.
+ * @return static
+ */
+ public function withCookieParams(array $cookies);
+
+ /**
+ * Retrieve query string arguments.
+ *
+ * Retrieves the deserialized query string arguments, if any.
+ *
+ * Note: the query params might not be in sync with the URI or server
+ * params. If you need to ensure you are only getting the original
+ * values, you may need to parse the query string from `getUri()->getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute(string $name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute(string $name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute(string $name);
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Describes a data stream.
+ *
+ * Typically, an instance will wrap a PHP stream; this interface provides
+ * a wrapper around the most common operations, including serialization of
+ * the entire stream to a string.
+ */
+interface StreamInterface
+{
+ /**
+ * Reads all data from the stream into a string, from the beginning to end.
+ *
+ * This method MUST attempt to seek to the beginning of the stream before
+ * reading data and read the stream until the end is reached.
+ *
+ * Warning: This could attempt to load a large amount of data into memory.
+ *
+ * This method MUST NOT raise an exception in order to conform with PHP's
+ * string casting operations.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Closes the stream and any underlying resources.
+ *
+ * @return void
+ */
+ public function close();
+
+ /**
+ * Separates any underlying resources from the stream.
+ *
+ * After the stream has been detached, the stream is in an unusable state.
+ *
+ * @return resource|null Underlying PHP stream, if any
+ */
+ public function detach();
+
+ /**
+ * Get the size of the stream if known.
+ *
+ * @return int|null Returns the size in bytes if known, or null if unknown.
+ */
+ public function getSize();
+
+ /**
+ * Returns the current position of the file read/write pointer
+ *
+ * @return int Position of the file pointer
+ * @throws \RuntimeException on error.
+ */
+ public function tell();
+
+ /**
+ * Returns true if the stream is at the end of the stream.
+ *
+ * @return bool
+ */
+ public function eof();
+
+ /**
+ * Returns whether or not the stream is seekable.
+ *
+ * @return bool
+ */
+ public function isSeekable();
+
+ /**
+ * Seek to a position in the stream.
+ *
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @param int $offset Stream offset
+ * @param int $whence Specifies how the cursor position will be calculated
+ * based on the seek offset. Valid values are identical to the built-in
+ * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
+ * offset bytes SEEK_CUR: Set position to current location plus offset
+ * SEEK_END: Set position to end-of-stream plus offset.
+ * @throws \RuntimeException on failure.
+ */
+ public function seek(int $offset, int $whence = SEEK_SET);
+
+ /**
+ * Seek to the beginning of the stream.
+ *
+ * If the stream is not seekable, this method will raise an exception;
+ * otherwise, it will perform a seek(0).
+ *
+ * @see seek()
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @throws \RuntimeException on failure.
+ */
+ public function rewind();
+
+ /**
+ * Returns whether or not the stream is writable.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write data to the stream.
+ *
+ * @param string $string The string that is to be written.
+ * @return int Returns the number of bytes written to the stream.
+ * @throws \RuntimeException on failure.
+ */
+ public function write(string $string);
+
+ /**
+ * Returns whether or not the stream is readable.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Read data from the stream.
+ *
+ * @param int $length Read up to $length bytes from the object and return
+ * them. Fewer than $length bytes may be returned if underlying stream
+ * call returns fewer bytes.
+ * @return string Returns the data read from the stream, or an empty string
+ * if no bytes are available.
+ * @throws \RuntimeException if an error occurs.
+ */
+ public function read(int $length);
+
+ /**
+ * Returns the remaining contents in a string
+ *
+ * @return string
+ * @throws \RuntimeException if unable to read or an error occurs while
+ * reading.
+ */
+ public function getContents();
+
+ /**
+ * Get stream metadata as an associative array or retrieve a specific key.
+ *
+ * The keys returned are identical to the keys returned from PHP's
+ * stream_get_meta_data() function.
+ *
+ * @link http://php.net/manual/en/function.stream-get-meta-data.php
+ * @param string|null $key Specific metadata to retrieve.
+ * @return array|mixed|null Returns an associative array if no key is
+ * provided. Returns a specific key value if a key is provided and the
+ * value is found, or null if the key is not found.
+ */
+ public function getMetadata(?string $key = null);
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a file uploaded through an HTTP request.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ */
+interface UploadedFileInterface
+{
+ /**
+ * Retrieve a stream representing the uploaded file.
+ *
+ * This method MUST return a StreamInterface instance, representing the
+ * uploaded file. The purpose of this method is to allow utilizing native PHP
+ * stream functionality to manipulate the file upload, such as
+ * stream_copy_to_stream() (though the result will need to be decorated in a
+ * native PHP stream wrapper to work with such functions).
+ *
+ * If the moveTo() method has been called previously, this method MUST raise
+ * an exception.
+ *
+ * @return StreamInterface Stream representation of the uploaded file.
+ * @throws \RuntimeException in cases when no stream is available or can be
+ * created.
+ */
+ public function getStream();
+
+ /**
+ * Move the uploaded file to a new location.
+ *
+ * Use this method as an alternative to move_uploaded_file(). This method is
+ * guaranteed to work in both SAPI and non-SAPI environments.
+ * Implementations must determine which environment they are in, and use the
+ * appropriate method (move_uploaded_file(), rename(), or a stream
+ * operation) to perform the operation.
+ *
+ * $targetPath may be an absolute path, or a relative path. If it is a
+ * relative path, resolution should be the same as used by PHP's rename()
+ * function.
+ *
+ * The original file or stream MUST be removed on completion.
+ *
+ * If this method is called more than once, any subsequent calls MUST raise
+ * an exception.
+ *
+ * When used in an SAPI environment where $_FILES is populated, when writing
+ * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
+ * used to ensure permissions and upload status are verified correctly.
+ *
+ * If you wish to move to a stream, use getStream(), as SAPI operations
+ * cannot guarantee writing to stream destinations.
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws \InvalidArgumentException if the $targetPath specified is invalid.
+ * @throws \RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo(string $targetPath);
+
+ /**
+ * Retrieve the file size.
+ *
+ * Implementations SHOULD return the value stored in the "size" key of
+ * the file in the $_FILES array if available, as PHP calculates this based
+ * on the actual size transmitted.
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize();
+
+ /**
+ * Retrieve the error associated with the uploaded file.
+ *
+ * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
+ *
+ * If the file was uploaded successfully, this method MUST return
+ * UPLOAD_ERR_OK.
+ *
+ * Implementations SHOULD return the value stored in the "error" key of
+ * the file in the $_FILES array.
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError();
+
+ /**
+ * Retrieve the filename sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious filename with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "name" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename();
+
+ /**
+ * Retrieve the media type sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious media type with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "type" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The media type sent by the client or null if none
+ * was provided.
+ */
+ public function getClientMediaType();
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a URI.
+ *
+ * This interface is meant to represent URIs according to RFC 3986 and to
+ * provide methods for most common operations. Additional functionality for
+ * working with URIs can be provided on top of the interface or externally.
+ * Its primary use is for HTTP requests, but may also be used in other
+ * contexts.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ *
+ * Typically the Host header will be also be present in the request message.
+ * For server-side requests, the scheme will typically be discoverable in the
+ * server parameters.
+ *
+ * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
+ */
+interface UriInterface
+{
+ /**
+ * Retrieve the scheme component of the URI.
+ *
+ * If no scheme is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.1.
+ *
+ * The trailing ":" character is not part of the scheme and MUST NOT be
+ * added.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.1
+ * @return string The URI scheme.
+ */
+ public function getScheme();
+
+ /**
+ * Retrieve the authority component of the URI.
+ *
+ * If no authority information is present, this method MUST return an empty
+ * string.
+ *
+ * The authority syntax of the URI is:
+ *
+ * <pre>
+ * [user-info@]host[:port]
+ * </pre>
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme(string $scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo(string $user, ?string $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost(string $host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort(?int $port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath(string $path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery(string $query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment(string $fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
--- /dev/null
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger implementation that other Loggers can inherit from.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+abstract class AbstractLogger implements LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->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 $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $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 $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels.
+ */
+class LogLevel
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger-aware instance.
+ */
+interface LoggerAwareInterface
+{
+ /**
+ * Sets a logger instance on the object.
+ *
+ * @param LoggerInterface $logger
+ *
+ * @return void
+ */
+ public function setLogger(LoggerInterface $logger);
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Basic Implementation of LoggerAwareInterface.
+ */
+trait LoggerAwareTrait
+{
+ /**
+ * The logger instance.
+ *
+ * @var LoggerInterface|null
+ */
+ protected $logger;
+
+ /**
+ * Sets a logger.
+ *
+ * @param LoggerInterface $logger
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger instance.
+ *
+ * The message MUST be a string or object implementing __toString().
+ *
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data. The only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array());
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array());
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array());
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array());
+
+ /**
+ * 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 $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array());
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array());
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array());
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array());
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param mixed[] $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array());
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger trait that classes unable to extend AbstractLogger
+ * (because they extend another class, etc) can include.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+trait LoggerTrait
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->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 $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $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 $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
--- /dev/null
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This Logger can be used to avoid conditional log calls.
+ *
+ * Logging should always be optional, and if no logger is provided to your
+ * library creating a NullLogger instance to have something to throw logs at
+ * is a good way to avoid littering your code with `if ($this->logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Log\Test;
+
+/**
+ * This class is internal and does not follow the BC promise.
+ *
+ * Do NOT use this class in any way.
+ *
+ * @internal
+ */
+class DummyTest
+{
+ public function __toString()
+ {
+ return 'DummyTest';
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Provides a base test class for ensuring compliance with the LoggerInterface.
+ *
+ * Implementors can extend the class and implement abstract methods to run this
+ * as part of their test suite.
+ */
+abstract class LoggerInterfaceTest extends TestCase
+{
+ /**
+ * @return LoggerInterface
+ */
+ abstract public function getLogger();
+
+ /**
+ * This must return the log messages in order.
+ *
+ * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
--- /dev/null
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\AbstractLogger;
+
+/**
+ * Used for testing purposes.
+ *
+ * It records all records and gives you access to them for verification.
+ *
+ * @method bool hasEmergency($record)
+ * @method bool hasAlert($record)
+ * @method bool hasCritical($record)
+ * @method bool hasError($record)
+ * @method bool hasWarning($record)
+ * @method bool hasNotice($record)
+ * @method bool hasInfo($record)
+ * @method bool hasDebug($record)
+ *
+ * @method bool hasEmergencyRecords()
+ * @method bool hasAlertRecords()
+ * @method bool hasCriticalRecords()
+ * @method bool hasErrorRecords()
+ * @method bool hasWarningRecords()
+ * @method bool hasNoticeRecords()
+ * @method bool hasInfoRecords()
+ * @method bool hasDebugRecords()
+ *
+ * @method bool hasEmergencyThatContains($message)
+ * @method bool hasAlertThatContains($message)
+ * @method bool hasCriticalThatContains($message)
+ * @method bool hasErrorThatContains($message)
+ * @method bool hasWarningThatContains($message)
+ * @method bool hasNoticeThatContains($message)
+ * @method bool hasInfoThatContains($message)
+ * @method bool hasDebugThatContains($message)
+ *
+ * @method bool hasEmergencyThatMatches($message)
+ * @method bool hasAlertThatMatches($message)
+ * @method bool hasCriticalThatMatches($message)
+ * @method bool hasErrorThatMatches($message)
+ * @method bool hasWarningThatMatches($message)
+ * @method bool hasNoticeThatMatches($message)
+ * @method bool hasInfoThatMatches($message)
+ * @method bool hasDebugThatMatches($message)
+ *
+ * @method bool hasEmergencyThatPasses($message)
+ * @method bool hasAlertThatPasses($message)
+ * @method bool hasCriticalThatPasses($message)
+ * @method bool hasErrorThatPasses($message)
+ * @method bool hasWarningThatPasses($message)
+ * @method bool hasNoticeThatPasses($message)
+ * @method bool hasInfoThatPasses($message)
+ * @method bool hasDebugThatPasses($message)
+ */
+class TestLogger extends AbstractLogger
+{
+ /**
+ * @var array
+ */
+ public $records = [];
+
+ public $recordsByLevel = [];
+
+ /**
+ * @inheritdoc
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $record = [
+ 'level' => $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
--- /dev/null
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+<?php
+
+use Psr\Log\LoggerInterface;
+
+class Foo
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ try {
+ $this->doSomethingElse();
+ } catch (Exception $exception) {
+ $this->logger->error('Oh no!', array('exception' => $exception));
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
--- /dev/null
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
--- /dev/null
+Copyright (c) 2015-2022 Ben Ramsey <ben@benramsey.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+<h1 align="center">ramsey/collection</h1>
+
+<p align="center">
+ <strong>A PHP library for representing and manipulating collections.</strong>
+</p>
+
+<p align="center">
+ <a href="https://github.com/ramsey/collection"><img src="http://img.shields.io/badge/source-ramsey/collection-blue.svg?style=flat-square" alt="Source Code"></a>
+ <a href="https://packagist.org/packages/ramsey/collection"><img src="https://img.shields.io/packagist/v/ramsey/collection.svg?style=flat-square&label=release" alt="Download Package"></a>
+ <a href="https://php.net"><img src="https://img.shields.io/packagist/php-v/ramsey/collection.svg?style=flat-square&colorB=%238892BF" alt="PHP Programming Language"></a>
+ <a href="https://github.com/ramsey/collection/blob/master/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/collection.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
+ <a href="https://github.com/ramsey/collection/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/collection/continuous-integration.yml?branch=main&logo=github&style=flat-square" alt="Build Status"></a>
+ <a href="https://codecov.io/gh/ramsey/collection"><img src="https://img.shields.io/codecov/c/gh/ramsey/collection?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
+</p>
+
+## About
+
+ramsey/collection is a PHP library for representing and manipulating collections.
+
+Much inspiration for this library came from the [Java Collections Framework][java].
+
+This project adheres to a [code of conduct](CODE_OF_CONDUCT.md).
+By participating in this project and its community, you are expected to
+uphold this code.
+
+## Installation
+
+Install this package as a dependency using [Composer](https://getcomposer.org).
+
+``` bash
+composer require ramsey/collection
+```
+
+## Usage
+
+Examples of how to use this library may be found in the
+[Wiki pages](https://github.com/ramsey/collection/wiki/Examples).
+
+## Contributing
+
+Contributions are welcome! To contribute, please familiarize yourself with
+[CONTRIBUTING.md](CONTRIBUTING.md).
+
+## Coordinated Disclosure
+
+Keeping user information safe and secure is a top priority, and we welcome the
+contribution of external security researchers. If you believe you've found a
+security issue in software that is maintained in this repository, please read
+[SECURITY.md][] for instructions on submitting a vulnerability report.
+
+## Copyright and License
+
+The ramsey/collection library is copyright © [Ben Ramsey](https://benramsey.com)
+and licensed for use under the terms of the
+MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
+
+
+[java]: http://docs.oracle.com/javase/8/docs/technotes/guides/collections/index.html
+[security.md]: https://github.com/ramsey/collection/blob/main/SECURITY.md
--- /dev/null
+<!--
+ This policy template was created using the HackerOne Policy Builder [1],
+ with guidance from the National Telecommunications and Information
+ Administration Coordinated Vulnerability Disclosure Template [2].
+ -->
+
+# Vulnerability Disclosure Policy (VDP)
+
+## Brand Promise
+
+<!--
+ This is your brand promise. Its objective is to "demonstrate a clear, good
+ faith commitment to customers and other stakeholders potentially impacted by
+ security vulnerabilities" [2].
+-->
+
+Keeping user information safe and secure is a top priority, and we welcome the
+contribution of external security researchers.
+
+## Scope
+
+<!--
+ This is your initial scope. It tells vulnerability finders and reporters
+ "which systems and capabilities are 'fair game' versus 'off limits'" [2].
+ For software packages, this is often a list of currently maintained versions
+ of the package.
+-->
+
+If you believe you've found a security issue in software that is maintained in
+this repository, we encourage you to notify us.
+
+| Version | In scope | Source code |
+| ------- | :------: | ----------- |
+| latest | ✅ | https://github.com/ramsey/collection |
+
+## How to Submit a Report
+
+<!--
+ This is your communication process. It tells security researchers how to
+ contact you to report a vulnerability. It may be a link to a web form that
+ uses HTTPS for secure communication, or it may be an email address.
+ Optionally, you may choose to include a PGP public key, so that researchers
+ may send you encrypted messages.
+-->
+
+To submit a vulnerability report, please contact us at security@ramsey.dev.
+Your submission will be reviewed and validated by a member of our team.
+
+## Safe Harbor
+
+<!--
+ This section assures vulnerability finders and reporters that they will
+ receive good faith responses to their good faith acts. In other words,
+ "we will not take legal action if..." [2].
+-->
+
+We support safe harbor for security researchers who:
+
+* Make a good faith effort to avoid privacy violations, destruction of data, and
+ interruption or degradation of our services.
+* Only interact with accounts you own or with explicit permission of the account
+ holder. If you do encounter Personally Identifiable Information (PII) contact
+ us immediately, do not proceed with access, and immediately purge any local
+ information.
+* Provide us with a reasonable amount of time to resolve vulnerabilities prior
+ to any disclosure to the public or a third party.
+
+We will consider activities conducted consistent with this policy to constitute
+"authorized" conduct and will not pursue civil action or initiate a complaint to
+law enforcement. We will help to the extent we can if legal action is initiated
+by a third party against you.
+
+Please submit a report to us before engaging in conduct that may be inconsistent
+with or unaddressed by this policy.
+
+## Preferences
+
+<!--
+ The preferences section sets expectations based on priority and submission
+ volume, rather than legal objection or restriction [2].
+
+ According to the NTIA [2]:
+
+ This section is a living document that sets expectations for preferences
+ and priorities, typically maintained by the support and engineering
+ team. This can outline classes of vulnerabilities, reporting style
+ (crash dumps, CVSS scoring, proof-of-concept, etc.), tools, etc. Too
+ many preferences can set the wrong tone or make reporting findings
+ difficult to navigate. This section also sets expectations to the
+ researcher community for what types of issues are considered important
+ or not.
+-->
+
+* Please provide detailed reports with reproducible steps and a clearly defined
+ impact.
+* Include the version number of the vulnerable package in your report
+* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
+
+<!--
+ References
+
+ [1] HackerOne. Policy builder. Retrieved from https://hackerone.com/policy-builder/
+
+ [2] NTIA Safety Working Group. 2016. "Early stage" coordinated vulnerability
+ disclosure template: Version 1.1. (15 December 2016). Retrieved from
+ https://www.ntia.doc.gov/files/ntia/publications/ntia_vuln_disclosure_early_stage_template.pdf
+-->
+
+## Encryption Key for security@ramsey.dev
+
+For increased privacy when reporting sensitive issues, you may encrypt your
+message using the following public key:
+
+```
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBF+Z9gEBEACbT/pIx8RR0K18t8Z2rDnmEV44YdT7HNsMdq+D6SAlx8UUb6AU
+jGIbV9dgBgGNtOLU1pxloaJwL9bWIRbj+X/Qb2WNIP//Vz1Y40ox1dSpfCUrizXx
+kb4p58Xml0PsB8dg3b4RDUgKwGC37ne5xmDnigyJPbiB2XJ6Xc46oPCjh86XROTK
+wEBB2lY67ClBlSlvC2V9KmbTboRQkLdQDhOaUosMb99zRb0EWqDLaFkZVjY5HI7i
+0pTveE6dI12NfHhTwKjZ5pUiAZQGlKA6J1dMjY2unxHZkQj5MlMfrLSyJHZxccdJ
+xD94T6OTcTHt/XmMpI2AObpewZDdChDQmcYDZXGfAhFoJmbvXsmLMGXKgzKoZ/ls
+RmLsQhh7+/r8E+Pn5r+A6Hh4uAc14ApyEP0ckKeIXw1C6pepHM4E8TEXVr/IA6K/
+z6jlHORixIFX7iNOnfHh+qwOgZw40D6JnBfEzjFi+T2Cy+JzN2uy7I8UnecTMGo3
+5t6astPy6xcH6kZYzFTV7XERR6LIIVyLAiMFd8kF5MbJ8N5ElRFsFHPW+82N2HDX
+c60iSaTB85k6R6xd8JIKDiaKE4sSuw2wHFCKq33d/GamYezp1wO+bVUQg88efljC
+2JNFyD+vl30josqhw1HcmbE1TP3DlYeIL5jQOlxCMsgai6JtTfHFM/5MYwARAQAB
+tBNzZWN1cml0eUByYW1zZXkuZGV2iQJUBBMBCAA+FiEE4drPD+/ofZ570fAYq0bv
+vXQCywIFAl+Z9gECGwMFCQeGH4AFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ
+q0bvvXQCywIkEA//Qcwv8MtTCy01LHZd9c7VslwhNdXQDYymcTyjcYw8x7O22m4B
+3hXE6vqAplFhVxxkqXB2ef0tQuzxhPHNJgkCE4Wq4i+V6qGpaSVHQT2W6DN/NIhL
+vS8OdScc6zddmIbIkSrzVVAtjwehFNEIrX3DnbbbK+Iku7vsKT5EclOluIsjlYoX
+goW8IeReyDBqOe2H3hoCGw6EA0D/NYV2bJnfy53rXVIyarsXXeOLp7eNEH6Td7aW
+PVSrMZJe1t+knrEGnEdrXWzlg4lCJJCtemGv+pKBUomnyISXSdqyoRCCzvQjqyig
+2kRebUX8BXPW33p4OXPj9sIboUOjZwormWwqqbFMO+J4TiVCUoEoheI7emPFRcNN
+QtPJrjbY1++OznBc0GRpfeUkGoU1cbRl1bnepnFIZMTDLkrVW6I1Y4q8ZVwX3BkE
+N81ctFrRpHBlU36EdHvjPQmGtuiL77Qq3fWmMv7yTvK1wHJAXfEb0ZJWHZCbck3w
+l0CVq0Z+UUAOM8Rp1N0N8m92xtapav0qCFU9qzf2J5qX6GRmWv+d29wPgFHzDWBm
+nnrYYIA4wJLx00U6SMcVBSnNe91B+RfGY5XQhbWPjQQecOGCSDsxaFAq2MeOVJyZ
+bIjLYfG9GxoLKr5R7oLRJvZI4nKKBc1Kci/crZbdiSdQhSQGlDz88F1OHeCIdQQQ
+EQgAHRYhBOhdAxHd+lus86YQ57Atl5icjAcbBQJfmfdIAAoJELAtl5icjAcbFVcA
+/1LqB3ZjsnXDAvvAXZVjSPqofSlpMLeRQP6IM/A9Odq0AQCZrtZc1knOMGEcjppK
+Rk+sy/R0Mshy8TDuaZIRgh2Ux7kCDQRfmfYBARAAmchKzzVz7IaEq7PnZDb3szQs
+T/+E9F3m39yOpV4fEB1YzObonFakXNT7Gw2tZEx0eitUMqQ/13jjfu3UdzlKl2bR
+qA8LrSQRhB+PTC9A1XvwxCUYhhjGiLzJ9CZL6hBQB43qHOmE9XJPme90geLsF+gK
+u39Waj1SNWzwGg+Gy1Gl5f2AJoDTxznreCuFGj+Vfaczt/hlfgqpOdb9jsmdoE7t
+3DSWppA9dRHWwQSgE6J28rR4QySBcqyXS6IMykqaJn7Z26yNIaITLnHCZOSY8zhP
+ha7GFsN549EOCgECbrnPt9dmI2+hQE0RO0e7SOBNsIf5sz/i7urhwuj0CbOqhjc2
+X1AEVNFCVcb6HPi/AWefdFCRu0gaWQxn5g+9nkq5slEgvzCCiKYzaBIcr8qR6Hb4
+FaOPVPxO8vndRouq57Ws8XpAwbPttioFuCqF4u9K+tK/8e2/R8QgRYJsE3Cz/Fu8
++pZFpMnqbDEbK3DL3ss+1ed1sky+mDV8qXXeI33XW5hMFnk1JWshUjHNlQmE6ftC
+U0xSTMVUtwJhzH2zDp8lEdu7qi3EsNULOl68ozDr6soWAvCbHPeTdTOnFySGCleG
+/3TonsoZJs/sSPPJnxFQ1DtgQL6EbhIwa0ZwU4eKYVHZ9tjxuMX3teFzRvOrJjgs
++ywGlsIURtEckT5Y6nMAEQEAAYkCPAQYAQgAJhYhBOHazw/v6H2ee9HwGKtG7710
+AssCBQJfmfYBAhsMBQkHhh+AAAoJEKtG7710AssC8NcP/iDAcy1aZFvkA0EbZ85p
+i7/+ywtE/1wF4U4/9OuLcoskqGGnl1pJNPooMOSBCfreoTB8HimT0Fln0CoaOm4Q
+pScNq39JXmf4VxauqUJVARByP6zUfgYarqoaZNeuFF0S4AZJ2HhGzaQPjDz1uKVM
+PE6tQSgQkFzdZ9AtRA4vElTH6yRAgmepUsOihk0b0gUtVnwtRYZ8e0Qt3ie97a73
+DxLgAgedFRUbLRYiT0vNaYbainBsLWKpN/T8odwIg/smP0Khjp/ckV60cZTdBiPR
+szBTPJESMUTu0VPntc4gWwGsmhZJg/Tt/qP08XYo3VxNYBegyuWwNR66zDWvwvGH
+muMv5UchuDxp6Rt3JkIO4voMT1JSjWy9p8krkPEE4V6PxAagLjdZSkt92wVLiK5x
+y5gNrtPhU45YdRAKHr36OvJBJQ42CDaZ6nzrzghcIp9CZ7ANHrI+QLRM/csz+AGA
+szSp6S4mc1lnxxfbOhPPpebZPn0nIAXoZnnoVKdrxBVedPQHT59ZFvKTQ9Fs7gd3
+sYNuc7tJGFGC2CxBH4ANDpOQkc5q9JJ1HSGrXU3juxIiRgfA26Q22S9c71dXjElw
+Ri584QH+bL6kkYmm8xpKF6TVwhwu5xx/jBPrbWqFrtbvLNrnfPoapTihBfdIhkT6
+nmgawbBHA02D5xEqB5SU3WJu
+=eJNx
+-----END PGP PUBLIC KEY BLOCK-----
+```
--- /dev/null
+{
+ "name": "ramsey/collection",
+ "description": "A PHP library for representing and manipulating collections.",
+ "license": "MIT",
+ "type": "library",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "captainhook/plugin-composer": "^5.3",
+ "ergebnis/composer-normalize": "^2.45",
+ "fakerphp/faker": "^1.24",
+ "hamcrest/hamcrest-php": "^2.0",
+ "jangregor/phpstan-prophecy": "^2.1",
+ "mockery/mockery": "^1.6",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpspec/prophecy-phpunit": "^2.3",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5",
+ "ramsey/coding-standard": "^2.3",
+ "ramsey/conventional-commits": "^1.6",
+ "roave/security-advisories": "dev-latest"
+ },
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Ramsey\\Collection\\Test\\": "tests/"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "captainhook/plugin-composer": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "ergebnis/composer-normalize": true,
+ "phpstan/extension-installer": true
+ },
+ "sort-packages": true
+ },
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ },
+ "ramsey/conventional-commits": {
+ "configFile": "conventional-commits.json"
+ }
+ },
+ "scripts": {
+ "dev:analyze": [
+ "@dev:analyze:phpstan"
+ ],
+ "dev:analyze:phpstan": "phpstan analyse --ansi --memory-limit=1G",
+ "dev:build:clean": "git clean -fX build/",
+ "dev:lint": [
+ "@dev:lint:syntax",
+ "@dev:lint:style"
+ ],
+ "dev:lint:fix": "phpcbf",
+ "dev:lint:style": "phpcs --colors",
+ "dev:lint:syntax": "parallel-lint --colors src/ tests/",
+ "dev:test": [
+ "@dev:lint",
+ "@dev:analyze",
+ "@dev:test:unit"
+ ],
+ "dev:test:coverage:ci": "phpunit --colors=always --coverage-text --coverage-clover build/coverage/clover.xml --coverage-cobertura build/coverage/cobertura.xml --coverage-crap4j build/coverage/crap4j.xml --coverage-xml build/coverage/coverage-xml --log-junit build/junit.xml",
+ "dev:test:coverage:html": "phpunit --colors=always --coverage-html build/coverage/coverage-html/",
+ "dev:test:unit": "phpunit --colors=always",
+ "test": "@dev:test"
+ },
+ "scripts-descriptions": {
+ "dev:analyze": "Runs all static analysis checks.",
+ "dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
+ "dev:build:clean": "Cleans the build/ directory.",
+ "dev:lint": "Runs all linting checks.",
+ "dev:lint:fix": "Auto-fixes coding standards issues, if possible.",
+ "dev:lint:style": "Checks for coding standards issues.",
+ "dev:lint:syntax": "Checks for syntax errors.",
+ "dev:test": "Runs linting, static analysis, and unit tests.",
+ "dev:test:coverage:ci": "Runs unit tests and generates CI coverage reports.",
+ "dev:test:coverage:html": "Runs unit tests and generates HTML coverage report.",
+ "dev:test:unit": "Runs unit tests.",
+ "test": "Runs linting, static analysis, and unit tests."
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+use ArrayIterator;
+use Traversable;
+
+use function count;
+
+/**
+ * This class provides a basic implementation of `ArrayInterface`, to minimize
+ * the effort required to implement this interface.
+ *
+ * @template T
+ * @implements ArrayInterface<T>
+ */
+abstract class AbstractArray implements ArrayInterface
+{
+ /**
+ * The items of this array.
+ *
+ * @var array<array-key, T>
+ */
+ protected array $data = [];
+
+ /**
+ * Constructs a new array object.
+ *
+ * @param array<array-key, T> $data The initial items to add to this array.
+ */
+ public function __construct(array $data = [])
+ {
+ // Invoke offsetSet() for each value added; in this way, subclasses
+ // 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<array-key, T>
+ */
+ 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(mixed $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 the value stored at the offset, or null if the offset
+ * does not exist.
+ */
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->data[$offset];
+ }
+
+ /**
+ * 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.
+ */
+ public function offsetSet(mixed $offset, mixed $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(mixed $offset): void
+ {
+ unset($this->data[$offset]);
+ }
+
+ /**
+ * 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<array-key, T>
+ */
+ public function __serialize(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * Adds unserialized data to the object.
+ *
+ * @param array<array-key, T> $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 $this->data === [];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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\InvalidPropertyOrMethod;
+use Ramsey\Collection\Exception\NoSuchElementException;
+use Ramsey\Collection\Exception\UnsupportedOperationException;
+use Ramsey\Collection\Tool\TypeTrait;
+use Ramsey\Collection\Tool\ValueExtractorTrait;
+use Ramsey\Collection\Tool\ValueToStringTrait;
+
+use function array_filter;
+use function array_key_first;
+use function array_key_last;
+use function array_map;
+use function array_merge;
+use function array_reduce;
+use function array_search;
+use function array_udiff;
+use function array_uintersect;
+use function in_array;
+use function is_int;
+use function is_object;
+use function spl_object_id;
+use function sprintf;
+use function usort;
+
+/**
+ * This class provides a basic implementation of `CollectionInterface`, to
+ * minimize the effort required to implement this interface
+ *
+ * @template T
+ * @extends AbstractArray<T>
+ * @implements CollectionInterface<T>
+ */
+abstract class AbstractCollection extends AbstractArray implements CollectionInterface
+{
+ use TypeTrait;
+ use ValueToStringTrait;
+ use ValueExtractorTrait;
+
+ /**
+ * @throws InvalidArgumentException if $element is of the wrong type.
+ */
+ public function add(mixed $element): bool
+ {
+ $this[] = $element;
+
+ return true;
+ }
+
+ public function contains(mixed $element, bool $strict = true): bool
+ {
+ return in_array($element, $this->data, $strict);
+ }
+
+ /**
+ * @throws InvalidArgumentException if $element is of the wrong type.
+ */
+ public function offsetSet(mixed $offset, mixed $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;
+ }
+ }
+
+ public function remove(mixed $element): bool
+ {
+ if (($position = array_search($element, $this->data, true)) !== false) {
+ unset($this[$position]);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call column() on this
+ * collection.
+ *
+ * @inheritDoc
+ */
+ public function column(string $propertyOrMethod): array
+ {
+ $temp = [];
+
+ foreach ($this->data as $item) {
+ $temp[] = $this->extractValue($item, $propertyOrMethod);
+ }
+
+ return $temp;
+ }
+
+ /**
+ * @return T
+ *
+ * @throws NoSuchElementException if this collection is empty.
+ */
+ public function first(): mixed
+ {
+ $firstIndex = array_key_first($this->data);
+
+ if ($firstIndex === null) {
+ throw new NoSuchElementException('Can\'t determine first item. Collection is empty');
+ }
+
+ return $this->data[$firstIndex];
+ }
+
+ /**
+ * @return T
+ *
+ * @throws NoSuchElementException if this collection is empty.
+ */
+ public function last(): mixed
+ {
+ $lastIndex = array_key_last($this->data);
+
+ if ($lastIndex === null) {
+ throw new NoSuchElementException('Can\'t determine last item. Collection is empty');
+ }
+
+ return $this->data[$lastIndex];
+ }
+
+ /**
+ * @return CollectionInterface<T>
+ *
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call sort() on this
+ * collection.
+ */
+ public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): CollectionInterface
+ {
+ $collection = clone $this;
+
+ usort(
+ $collection->data,
+ function (mixed $a, mixed $b) use ($propertyOrMethod, $order): int {
+ $aValue = $this->extractValue($a, $propertyOrMethod);
+ $bValue = $this->extractValue($b, $propertyOrMethod);
+
+ return ($aValue <=> $bValue) * ($order === Sort::Descending ? -1 : 1);
+ },
+ );
+
+ return $collection;
+ }
+
+ /**
+ * @param callable(T): bool $callback A callable to use for filtering elements.
+ *
+ * @return CollectionInterface<T>
+ */
+ public function filter(callable $callback): CollectionInterface
+ {
+ $collection = clone $this;
+ $collection->data = array_merge([], array_filter($collection->data, $callback));
+
+ return $collection;
+ }
+
+ /**
+ * @return CollectionInterface<T>
+ *
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call where() on this
+ * collection.
+ */
+ public function where(?string $propertyOrMethod, mixed $value): CollectionInterface
+ {
+ return $this->filter(
+ fn (mixed $item): bool => $this->extractValue($item, $propertyOrMethod) === $value,
+ );
+ }
+
+ /**
+ * @param callable(T): TCallbackReturn $callback A callable to apply to each
+ * item of the collection.
+ *
+ * @return CollectionInterface<TCallbackReturn>
+ *
+ * @template TCallbackReturn
+ */
+ public function map(callable $callback): CollectionInterface
+ {
+ return new Collection('mixed', array_map($callback, $this->data));
+ }
+
+ /**
+ * @param callable(TCarry, T): TCarry $callback A callable to apply to each
+ * item of the collection to reduce it to a single value.
+ * @param TCarry $initial This is the initial value provided to the callback.
+ *
+ * @return TCarry
+ *
+ * @template TCarry
+ */
+ public function reduce(callable $callback, mixed $initial): mixed
+ {
+ return array_reduce($this->data, $callback, $initial);
+ }
+
+ /**
+ * @param CollectionInterface<T> $other The collection to check for divergent
+ * items.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if the compared collections are of
+ * differing types.
+ */
+ 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());
+
+ $collection = clone $this;
+ $collection->data = array_merge($diffAtoB, $diffBtoA);
+
+ return $collection;
+ }
+
+ /**
+ * @param CollectionInterface<T> $other The collection to check for
+ * intersecting items.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if the compared collections are of
+ * differing types.
+ */
+ public function intersect(CollectionInterface $other): CollectionInterface
+ {
+ $this->compareCollectionTypes($other);
+
+ $collection = clone $this;
+ $collection->data = array_uintersect($this->data, $other->toArray(), $this->getComparator());
+
+ return $collection;
+ }
+
+ /**
+ * @param CollectionInterface<T> ...$collections The collections to merge.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if unable to merge any of the given
+ * collections or items within the given collections due to type
+ * mismatch errors.
+ */
+ 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 ($this->getUniformType($collection) !== $this->getUniformType($this)) {
+ 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;
+ }
+
+ /**
+ * @param CollectionInterface<T> $other
+ *
+ * @throws CollectionMismatchException
+ */
+ 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 ($this->getUniformType($other) !== $this->getUniformType($this)) {
+ throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
+ }
+ }
+
+ private function getComparator(): Closure
+ {
+ return function (mixed $a, mixed $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);
+ };
+ }
+
+ /**
+ * @param CollectionInterface<mixed> $collection
+ */
+ private function getUniformType(CollectionInterface $collection): string
+ {
+ return match ($collection->getType()) {
+ 'integer' => 'int',
+ 'boolean' => 'bool',
+ 'double' => 'float',
+ default => $collection->getType(),
+ };
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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<T>
+ */
+abstract class AbstractSet extends AbstractCollection
+{
+ public function add(mixed $element): bool
+ {
+ if ($this->contains($element)) {
+ return false;
+ }
+
+ // Call offsetSet() on the parent instead of add(), since calling
+ // parent::add() will invoke $this->offsetSet(), which will call
+ // $this->contains() a second time. This can cause performance issues
+ // with extremely large collections. For more information, see
+ // https://github.com/ramsey/collection/issues/68.
+ parent::offsetSet(null, $element);
+
+ return true;
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ if ($this->contains($value)) {
+ return;
+ }
+
+ parent::offsetSet($offset, $value);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+use ArrayAccess;
+use Countable;
+use IteratorAggregate;
+
+/**
+ * `ArrayInterface` provides traversable array functionality to data types.
+ *
+ * @template T
+ * @extends ArrayAccess<array-key, T>
+ * @extends IteratorAggregate<array-key, T>
+ */
+interface ArrayInterface extends
+ ArrayAccess,
+ Countable,
+ IteratorAggregate
+{
+ /**
+ * Removes all items from this array.
+ */
+ public function clear(): void;
+
+ /**
+ * Returns a native PHP array representation of this array object.
+ *
+ * @return array<array-key, T>
+ */
+ public function toArray(): array;
+
+ /**
+ * Returns `true` if this array is empty.
+ */
+ public function isEmpty(): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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:
+ *
+ * ```
+ * $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:
+ *
+ * ```
+ * namespace My\Foo;
+ *
+ * class FooCollection extends \Ramsey\Collection\AbstractCollection
+ * {
+ * public function getType()
+ * {
+ * return 'My\\Foo';
+ * }
+ * }
+ * ```
+ *
+ * And then use it similarly to the earlier example:
+ *
+ * ```
+ * $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:
+ *
+ * ```
+ * if ($collection instanceof \My\Foo\FooCollection) {
+ * // the collection is a collection of My\Foo objects
+ * }
+ * ```
+ *
+ * @template T
+ * @extends AbstractCollection<T>
+ */
+class Collection extends AbstractCollection
+{
+ /**
+ * Constructs a collection object of the specified type, optionally with the
+ * specified data.
+ *
+ * @param string $collectionType The type or class name associated with this
+ * collection.
+ * @param array<array-key, T> $data The initial items to store in the collection.
+ */
+ public function __construct(private readonly string $collectionType, array $data = [])
+ {
+ parent::__construct($data);
+ }
+
+ public function getType(): string
+ {
+ return $this->collectionType;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+use Ramsey\Collection\Exception\CollectionMismatchException;
+use Ramsey\Collection\Exception\InvalidArgumentException;
+use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
+use Ramsey\Collection\Exception\NoSuchElementException;
+use Ramsey\Collection\Exception\UnsupportedOperationException;
+
+/**
+ * A collection represents a group of values, known as its elements.
+ *
+ * Some collections allow duplicate elements and others do not. Some are ordered
+ * and others unordered.
+ *
+ * @template T
+ * @extends ArrayInterface<T>
+ */
+interface CollectionInterface extends ArrayInterface
+{
+ /**
+ * 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.
+ *
+ * @throws InvalidArgumentException if the collection refuses to add the
+ * $element for any reason other than that it already contains the element.
+ */
+ public function add(mixed $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.
+ */
+ public function contains(mixed $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.
+ */
+ public function remove(mixed $element): bool;
+
+ /**
+ * Returns the values from the given property, method, or array key.
+ *
+ * @param string $propertyOrMethod The name of the property, method, or
+ * array key to evaluate and return.
+ *
+ * @return list<mixed>
+ *
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call column() on this
+ * collection.
+ */
+ public function column(string $propertyOrMethod): array;
+
+ /**
+ * Returns the first item of the collection.
+ *
+ * @return T
+ *
+ * @throws NoSuchElementException if this collection is empty.
+ */
+ public function first(): mixed;
+
+ /**
+ * Returns the last item of the collection.
+ *
+ * @return T
+ *
+ * @throws NoSuchElementException if this collection is empty.
+ */
+ public function last(): mixed;
+
+ /**
+ * Sort the collection by a property, method, or array key with the given
+ * sort order.
+ *
+ * If $propertyOrMethod is `null`, this will sort by comparing each element.
+ *
+ * This will always leave the original collection untouched and will return
+ * a new one.
+ *
+ * @param string | null $propertyOrMethod The property, method, or array key
+ * to sort by.
+ * @param Sort $order The sort order for the resulting collection.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call sort() on this
+ * collection.
+ */
+ public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): 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<T>
+ */
+ public function filter(callable $callback): self;
+
+ /**
+ * Create a new collection where the result of the given property, method,
+ * or array key of each item in the collection equals the given value.
+ *
+ * This will always leave the original collection untouched and will return
+ * a new one.
+ *
+ * @param string | null $propertyOrMethod The property, method, or array key
+ * to evaluate. If `null`, the element itself is compared to $value.
+ * @param mixed $value The value to match.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
+ * on the elements in this collection.
+ * @throws UnsupportedOperationException if unable to call where() on this
+ * collection.
+ */
+ public function where(?string $propertyOrMethod, mixed $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<TCallbackReturn>
+ *
+ * @template TCallbackReturn
+ */
+ public function map(callable $callback): self;
+
+ /**
+ * Apply a given callback method on each item of the collection
+ * to reduce it to a single value.
+ *
+ * See the {@link http://php.net/manual/en/function.array-reduce.php PHP array_reduce() documentation}
+ * for examples of how the `$callback` and `$initial` parameters work.
+ *
+ * @param callable(TCarry, T): TCarry $callback A callable to apply to each
+ * item of the collection to reduce it to a single value.
+ * @param TCarry $initial This is the initial value provided to the callback.
+ *
+ * @return TCarry
+ *
+ * @template TCarry
+ */
+ public function reduce(callable $callback, mixed $initial): mixed;
+
+ /**
+ * Create a new collection with divergent items between current and given
+ * collection.
+ *
+ * @param CollectionInterface<T> $other The collection to check for divergent
+ * items.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if the compared collections are of
+ * differing types.
+ */
+ public function diff(CollectionInterface $other): self;
+
+ /**
+ * Create a new collection with intersecting item between current and given
+ * collection.
+ *
+ * @param CollectionInterface<T> $other The collection to check for
+ * intersecting items.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if the compared collections are of
+ * differing types.
+ */
+ public function intersect(CollectionInterface $other): self;
+
+ /**
+ * Merge current items and items of given collections into a new one.
+ *
+ * @param CollectionInterface<T> ...$collections The collections to merge.
+ *
+ * @return CollectionInterface<T>
+ *
+ * @throws CollectionMismatchException if unable to merge any of the given
+ * collections or items within the given collections due to type
+ * mismatch errors.
+ */
+ public function merge(CollectionInterface ...$collections): self;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 function array_key_last;
+use function array_pop;
+use function array_unshift;
+
+/**
+ * This class provides a basic implementation of `DoubleEndedQueueInterface`, to
+ * minimize the effort required to implement this interface.
+ *
+ * @template T
+ * @extends Queue<T>
+ * @implements DoubleEndedQueueInterface<T>
+ */
+class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface
+{
+ /**
+ * Constructs a double-ended queue (dequeue) object of the specified type,
+ * optionally with the specified data.
+ *
+ * @param string $queueType The type or class name associated with this dequeue.
+ * @param array<array-key, T> $data The initial items to store in the dequeue.
+ */
+ public function __construct(private readonly string $queueType, array $data = [])
+ {
+ parent::__construct($this->queueType, $data);
+ }
+
+ /**
+ * @throws InvalidArgumentException if $element is of the wrong type
+ */
+ public function addFirst(mixed $element): bool
+ {
+ if ($this->checkType($this->getType(), $element) === false) {
+ throw new InvalidArgumentException(
+ 'Value must be of type ' . $this->getType() . '; value is '
+ . $this->toolValueToString($element),
+ );
+ }
+
+ array_unshift($this->data, $element);
+
+ return true;
+ }
+
+ /**
+ * @throws InvalidArgumentException if $element is of the wrong type
+ */
+ public function addLast(mixed $element): bool
+ {
+ return $this->add($element);
+ }
+
+ public function offerFirst(mixed $element): bool
+ {
+ try {
+ return $this->addFirst($element);
+ } catch (InvalidArgumentException) {
+ return false;
+ }
+ }
+
+ public function offerLast(mixed $element): bool
+ {
+ return $this->offer($element);
+ }
+
+ /**
+ * @return T the first element in this queue.
+ *
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public function removeFirst(): mixed
+ {
+ return $this->remove();
+ }
+
+ /**
+ * @return T the last element in this queue.
+ *
+ * @throws NoSuchElementException if this queue is empty.
+ */
+ public function removeLast(): mixed
+ {
+ return $this->pollLast() ?? throw new NoSuchElementException(
+ 'Can\'t return element from Queue. Queue is empty.',
+ );
+ }
+
+ /**
+ * @return T | null the head of this queue, or `null` if this queue is empty.
+ */
+ public function pollFirst(): mixed
+ {
+ return $this->poll();
+ }
+
+ /**
+ * @return T | null the tail of this queue, or `null` if this queue is empty.
+ */
+ public function pollLast(): mixed
+ {
+ return array_pop($this->data);
+ }
+
+ /**
+ * @return T the head of this queue.
+ *
+ * @throws NoSuchElementException if this queue is empty.
+ */
+ public function firstElement(): mixed
+ {
+ return $this->element();
+ }
+
+ /**
+ * @return T the tail of this queue.
+ *
+ * @throws NoSuchElementException if this queue is empty.
+ */
+ public function lastElement(): mixed
+ {
+ return $this->peekLast() ?? throw new NoSuchElementException(
+ 'Can\'t return element from Queue. Queue is empty.',
+ );
+ }
+
+ /**
+ * @return T | null the head of this queue, or `null` if this queue is empty.
+ */
+ public function peekFirst(): mixed
+ {
+ return $this->peek();
+ }
+
+ /**
+ * @return T | null the tail of this queue, or `null` if this queue is empty.
+ */
+ public function peekLast(): mixed
+ {
+ $lastIndex = array_key_last($this->data);
+
+ if ($lastIndex === null) {
+ return null;
+ }
+
+ return $this->data[$lastIndex];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+use Ramsey\Collection\Exception\NoSuchElementException;
+use RuntimeException;
+
+/**
+ * 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:
+ *
+ * <table>
+ * <caption>Summary of DoubleEndedQueueInterface methods</caption>
+ * <thead>
+ * <tr>
+ * <th></th>
+ * <th colspan=2>First Element (Head)</th>
+ * <th colspan=2>Last Element (Tail)</th>
+ * </tr>
+ * <tr>
+ * <td></td>
+ * <td><em>Throws exception</em></td>
+ * <td><em>Special value</em></td>
+ * <td><em>Throws exception</em></td>
+ * <td><em>Special value</em></td>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <th>Insert</th>
+ * <td><code>addFirst()</code></td>
+ * <td><code>offerFirst()</code></td>
+ * <td><code>addLast()</code></td>
+ * <td><code>offerLast()</code></td>
+ * </tr>
+ * <tr>
+ * <th>Remove</th>
+ * <td><code>removeFirst()</code></td>
+ * <td><code>pollFirst()</code></td>
+ * <td><code>removeLast()</code></td>
+ * <td><code>pollLast()</code></td>
+ * </tr>
+ * <tr>
+ * <th>Examine</th>
+ * <td><code>firstElement()</code></td>
+ * <td><code>peekFirst()</code></td>
+ * <td><code>lastElement()</code></td>
+ * <td><code>peekLast()</code></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * 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:
+ *
+ * <table>
+ * <caption>Comparison of QueueInterface and DoubleEndedQueueInterface methods</caption>
+ * <thead>
+ * <tr>
+ * <th>QueueInterface Method</th>
+ * <th>DoubleEndedQueueInterface Method</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td><code>add()</code></td>
+ * <td><code>addLast()</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>offer()</code></td>
+ * <td><code>offerLast()</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>remove()</code></td>
+ * <td><code>removeFirst()</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>poll()</code></td>
+ * <td><code>pollFirst()</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>element()</code></td>
+ * <td><code>firstElement()</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>peek()</code></td>
+ * <td><code>peekFirst()</code></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * 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:
+ *
+ * <table>
+ * <caption>Comparison of stack concepts and DoubleEndedQueueInterface methods</caption>
+ * <thead>
+ * <tr>
+ * <th>Stack concept</th>
+ * <th>DoubleEndedQueueInterface Method</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td><em>push</em></td>
+ * <td><code>addFirst()</code></td>
+ * </tr>
+ * <tr>
+ * <td><em>pop</em></td>
+ * <td><code>removeFirst()</code></td>
+ * </tr>
+ * <tr>
+ * <td><em>peek</em></td>
+ * <td><code>peekFirst()</code></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * 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<T>
+ */
+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`.
+ */
+ public function addFirst(mixed $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`.
+ */
+ public function addLast(mixed $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`.
+ */
+ public function offerFirst(mixed $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`.
+ */
+ public function offerLast(mixed $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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use Throwable;
+
+interface CollectionException extends Throwable
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use RuntimeException;
+
+/**
+ * Thrown when attempting to operate on collections of differing types.
+ */
+class CollectionMismatchException extends RuntimeException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use InvalidArgumentException as PhpInvalidArgumentException;
+
+/**
+ * Thrown to indicate an argument is not of the expected type.
+ */
+class InvalidArgumentException extends PhpInvalidArgumentException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use RuntimeException;
+
+/**
+ * Thrown when attempting to evaluate a property, method, or array key
+ * that doesn't exist on an element or cannot otherwise be evaluated in the
+ * current context.
+ */
+class InvalidPropertyOrMethod extends RuntimeException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use RuntimeException;
+
+/**
+ * Thrown when attempting to access an element that does not exist.
+ */
+class NoSuchElementException extends RuntimeException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use OutOfBoundsException as PhpOutOfBoundsException;
+
+/**
+ * Thrown when attempting to access an element out of the range of the collection.
+ */
+class OutOfBoundsException extends PhpOutOfBoundsException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Exception;
+
+use RuntimeException;
+
+/**
+ * Thrown to indicate that the requested operation is not supported.
+ */
+class UnsupportedOperationException extends RuntimeException implements CollectionException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+/**
+ * `GenericArray` represents a standard array object.
+ *
+ * @extends AbstractArray<mixed>
+ */
+class GenericArray extends AbstractArray
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 Traversable;
+
+use function array_key_exists;
+use function array_keys;
+use function in_array;
+use function var_export;
+
+/**
+ * This class provides a basic implementation of `MapInterface`, to minimize the
+ * effort required to implement this interface.
+ *
+ * @template K of array-key
+ * @template T
+ * @extends AbstractArray<T>
+ * @implements MapInterface<K, T>
+ */
+abstract class AbstractMap extends AbstractArray implements MapInterface
+{
+ /**
+ * @param array<K, T> $data The initial items to add to this map.
+ */
+ public function __construct(array $data = [])
+ {
+ parent::__construct($data);
+ }
+
+ /**
+ * @return Traversable<K, T>
+ */
+ public function getIterator(): Traversable
+ {
+ return parent::getIterator();
+ }
+
+ /**
+ * @param K $offset The offset to set
+ * @param T $value The value to set at the given offset.
+ *
+ * @inheritDoc
+ */
+ public function offsetSet(mixed $offset, mixed $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;
+ }
+
+ public function containsKey(int | string $key): bool
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ public function containsValue(mixed $value): bool
+ {
+ return in_array($value, $this->data, true);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function keys(): array
+ {
+ /** @var list<K> */
+ return array_keys($this->data);
+ }
+
+ /**
+ * @param K $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.
+ */
+ public function get(int | string $key, mixed $defaultValue = null): mixed
+ {
+ return $this[$key] ?? $defaultValue;
+ }
+
+ /**
+ * @param K $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`.
+ */
+ public function put(int | string $key, mixed $value): mixed
+ {
+ $previousValue = $this->get($key);
+ $this[$key] = $value;
+
+ return $previousValue;
+ }
+
+ /**
+ * @param K $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`.
+ */
+ public function putIfAbsent(int | string $key, mixed $value): mixed
+ {
+ $currentValue = $this->get($key);
+
+ if ($currentValue === null) {
+ $this[$key] = $value;
+ }
+
+ return $currentValue;
+ }
+
+ /**
+ * @param K $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`.
+ */
+ public function remove(int | string $key): mixed
+ {
+ $previousValue = $this->get($key);
+ unset($this[$key]);
+
+ return $previousValue;
+ }
+
+ public function removeIf(int | string $key, mixed $value): bool
+ {
+ if ($this->get($key) === $value) {
+ unset($this[$key]);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param K $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`.
+ */
+ public function replace(int | string $key, mixed $value): mixed
+ {
+ $currentValue = $this->get($key);
+
+ if ($this->containsKey($key)) {
+ $this[$key] = $value;
+ }
+
+ return $currentValue;
+ }
+
+ public function replaceIf(int | string $key, mixed $oldValue, mixed $newValue): bool
+ {
+ if ($this->get($key) === $oldValue) {
+ $this[$key] = $newValue;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return array<K, T>
+ */
+ public function __serialize(): array
+ {
+ /** @var array<K, T> */
+ return parent::__serialize();
+ }
+
+ /**
+ * @return array<K, T>
+ */
+ public function toArray(): array
+ {
+ /** @var array<K, T> */
+ return parent::toArray();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 of array-key
+ * @template T
+ * @extends AbstractMap<K, T>
+ * @implements TypedMapInterface<K, T>
+ */
+abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
+{
+ use TypeTrait;
+ use ValueToStringTrait;
+
+ /**
+ * @param K $offset
+ * @param T $value
+ *
+ * @inheritDoc
+ */
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ 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),
+ );
+ }
+
+ parent::offsetSet($offset, $value);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Map;
+
+/**
+ * `AssociativeArrayMap` represents a standard associative array object.
+ *
+ * @extends AbstractMap<string, mixed>
+ */
+class AssociativeArrayMap extends AbstractMap
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 K of array-key
+ * @template T
+ * @extends ArrayInterface<T>
+ */
+interface MapInterface extends ArrayInterface
+{
+ /**
+ * Returns `true` if this map contains a mapping for the specified key.
+ *
+ * @param K $key The key to check in the map.
+ */
+ public function containsKey(int | string $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.
+ */
+ public function containsValue(mixed $value): bool;
+
+ /**
+ * Return an array of the keys contained in this map.
+ *
+ * @return list<K>
+ */
+ 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 K $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.
+ */
+ public function get(int | string $key, mixed $defaultValue = null): mixed;
+
+ /**
+ * 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 K $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`.
+ */
+ public function put(int | string $key, mixed $value): mixed;
+
+ /**
+ * 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 K $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`.
+ */
+ public function putIfAbsent(int | string $key, mixed $value): mixed;
+
+ /**
+ * Removes the mapping for a key from this map if it is present.
+ *
+ * @param K $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`.
+ */
+ public function remove(int | string $key): mixed;
+
+ /**
+ * 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 K $key The key to remove from the map.
+ * @param T $value The value to match.
+ *
+ * @return bool true if the value was removed.
+ */
+ public function removeIf(int | string $key, mixed $value): bool;
+
+ /**
+ * Replaces the entry for the specified key only if it is currently mapped
+ * to some value.
+ *
+ * @param K $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`.
+ */
+ public function replace(int | string $key, mixed $value): mixed;
+
+ /**
+ * 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 K $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.
+ */
+ public function replaceIf(int | string $key, mixed $oldValue, mixed $newValue): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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<string, mixed>
+ */
+class NamedParameterMap extends AbstractMap
+{
+ use TypeTrait;
+ use ValueToStringTrait;
+
+ /**
+ * Named parameters defined for this map.
+ *
+ * @var array<string, string>
+ */
+ private readonly array $namedParameters;
+
+ /**
+ * Constructs a new `NamedParameterMap`.
+ *
+ * @param array<array-key, string> $namedParameters The named parameters defined for this map.
+ * @param array<string, mixed> $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<string, string>
+ */
+ public function getNamedParameters(): array
+ {
+ return $this->namedParameters;
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ if (!array_key_exists($offset, $this->namedParameters)) {
+ throw new InvalidArgumentException(
+ 'Attempting to set value for unconfigured parameter \''
+ . $this->toolValueToString($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<array-key, string> $namedParameters The named parameters to filter.
+ *
+ * @return array<string, string>
+ */
+ 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) ?: [];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Map;
+
+/**
+ * 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:
+ *
+ * ```
+ * $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:
+ *
+ * ```
+ * class FooTypedMap extends AbstractTypedMap
+ * {
+ * public function getKeyType()
+ * {
+ * return 'int';
+ * }
+ *
+ * public function getValueType()
+ * {
+ * return Foo::class;
+ * }
+ * }
+ * ```
+ *
+ * … but you also may use the `TypedMap` class:
+ *
+ * ```
+ * class FooTypedMap extends TypedMap
+ * {
+ * public function __constructor(array $data = [])
+ * {
+ * parent::__construct('int', Foo::class, $data);
+ * }
+ * }
+ * ```
+ *
+ * @template K of array-key
+ * @template T
+ * @extends AbstractTypedMap<K, T>
+ */
+class TypedMap extends AbstractTypedMap
+{
+ /**
+ * 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<K, T> $data The initial data to set for this map.
+ */
+ public function __construct(
+ private readonly string $keyType,
+ private readonly string $valueType,
+ array $data = [],
+ ) {
+ parent::__construct($data);
+ }
+
+ public function getKeyType(): string
+ {
+ return $this->keyType;
+ }
+
+ public function getValueType(): string
+ {
+ return $this->valueType;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 K of array-key
+ * @template T
+ * @extends MapInterface<K, T>
+ */
+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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+
+use function array_key_first;
+
+/**
+ * This class provides a basic implementation of `QueueInterface`, to minimize
+ * the effort required to implement this interface.
+ *
+ * @template T
+ * @extends AbstractArray<T>
+ * @implements QueueInterface<T>
+ */
+class Queue extends AbstractArray implements QueueInterface
+{
+ use TypeTrait;
+ use ValueToStringTrait;
+
+ /**
+ * Constructs a queue object of the specified type, optionally with the
+ * specified data.
+ *
+ * @param string $queueType The type or class name associated with this queue.
+ * @param array<array-key, T> $data The initial items to store in the queue.
+ */
+ public function __construct(private readonly string $queueType, array $data = [])
+ {
+ 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.
+ *
+ * @throws InvalidArgumentException if $value is of the wrong type.
+ */
+ public function offsetSet(mixed $offset, mixed $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;
+ }
+
+ /**
+ * @throws InvalidArgumentException if $value is of the wrong type.
+ */
+ public function add(mixed $element): bool
+ {
+ $this[] = $element;
+
+ return true;
+ }
+
+ /**
+ * @return T
+ *
+ * @throws NoSuchElementException if this queue is empty.
+ */
+ public function element(): mixed
+ {
+ return $this->peek() ?? throw new NoSuchElementException(
+ 'Can\'t return element from Queue. Queue is empty.',
+ );
+ }
+
+ public function offer(mixed $element): bool
+ {
+ try {
+ return $this->add($element);
+ } catch (InvalidArgumentException) {
+ return false;
+ }
+ }
+
+ /**
+ * @return T | null
+ */
+ public function peek(): mixed
+ {
+ $index = array_key_first($this->data);
+
+ if ($index === null) {
+ return null;
+ }
+
+ return $this[$index];
+ }
+
+ /**
+ * @return T | null
+ */
+ public function poll(): mixed
+ {
+ $index = array_key_first($this->data);
+
+ if ($index === null) {
+ return null;
+ }
+
+ $head = $this[$index];
+ unset($this[$index]);
+
+ return $head;
+ }
+
+ /**
+ * @return T
+ *
+ * @throws NoSuchElementException if this queue is empty.
+ */
+ public function remove(): mixed
+ {
+ return $this->poll() ?? throw new NoSuchElementException(
+ 'Can\'t return element from Queue. Queue is empty.',
+ );
+ }
+
+ public function getType(): string
+ {
+ return $this->queueType;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+use Ramsey\Collection\Exception\NoSuchElementException;
+use RuntimeException;
+
+/**
+ * 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.
+ *
+ * <table>
+ * <caption>Summary of QueueInterface methods</caption>
+ * <thead>
+ * <tr>
+ * <td></td>
+ * <td><em>Throws exception</em></td>
+ * <td><em>Returns special value</em></td>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <th>Insert</th>
+ * <td><code>add()</code></td>
+ * <td><code>offer()</code></td>
+ * </tr>
+ * <tr>
+ * <th>Remove</th>
+ * <td><code>remove()</code></td>
+ * <td><code>poll()</code></td>
+ * </tr>
+ * <tr>
+ * <th>Examine</th>
+ * <td><code>element()</code></td>
+ * <td><code>peek()</code></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * 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<T>
+ */
+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`.
+ */
+ public function add(mixed $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(): mixed;
+
+ /**
+ * 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`.
+ */
+ public function offer(mixed $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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * 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(): mixed;
+
+ /**
+ * Returns the type associated with this queue.
+ */
+ public function getType(): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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:
+ *
+ * ```
+ * $foo = new \My\Foo();
+ * $set = new Set(\My\Foo::class);
+ *
+ * $set->add($foo); // returns TRUE, the element doesn't exist
+ * $set->add($foo); // returns FALSE, the element already exists
+ *
+ * $bar = new \My\Foo();
+ * $set->add($bar); // returns TRUE, $bar !== $foo
+ * ```
+ *
+ * @template T
+ * @extends AbstractSet<T>
+ */
+class Set extends AbstractSet
+{
+ /**
+ * Constructs a set object of the specified type, optionally with the
+ * specified data.
+ *
+ * @param string $setType The type or class name associated with this set.
+ * @param array<array-key, T> $data The initial items to store in the set.
+ */
+ public function __construct(private readonly string $setType, array $data = [])
+ {
+ parent::__construct($data);
+ }
+
+ public function getType(): string
+ {
+ return $this->setType;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection;
+
+/**
+ * Collection sorting
+ */
+enum Sort: string
+{
+ /**
+ * Sort items in a collection in ascending order.
+ */
+ case Ascending = 'asc';
+
+ /**
+ * Sort items in a collection in descending order.
+ */
+ case Descending = 'desc';
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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, mixed $value): bool
+ {
+ return match ($type) {
+ 'array' => is_array($value),
+ 'bool', 'boolean' => is_bool($value),
+ 'callable' => is_callable($value),
+ 'float', 'double' => is_float($value),
+ 'int', 'integer' => is_int($value),
+ 'null' => $value === null,
+ 'numeric' => is_numeric($value),
+ 'object' => is_object($value),
+ 'resource' => is_resource($value),
+ 'scalar' => is_scalar($value),
+ 'string' => is_string($value),
+ 'mixed' => true,
+ default => $value instanceof $type,
+ };
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Tool;
+
+use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
+use Ramsey\Collection\Exception\UnsupportedOperationException;
+use ReflectionProperty;
+
+use function is_array;
+use function is_object;
+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
+{
+ /**
+ * Returns the type associated with this collection.
+ */
+ abstract public function getType(): string;
+
+ /**
+ * Extracts the value of the given property, method, or array key from the
+ * element.
+ *
+ * If `$propertyOrMethod` is `null`, we return the element as-is.
+ *
+ * @param mixed $element The element to extract the value from.
+ * @param string | null $propertyOrMethod The property or method for which the
+ * value should be extracted.
+ *
+ * @return mixed the value extracted from the specified property, method,
+ * or array key, or the element itself.
+ *
+ * @throws InvalidPropertyOrMethod
+ * @throws UnsupportedOperationException
+ */
+ protected function extractValue(mixed $element, ?string $propertyOrMethod): mixed
+ {
+ if ($propertyOrMethod === null) {
+ return $element;
+ }
+
+ if (!is_object($element) && !is_array($element)) {
+ throw new UnsupportedOperationException(sprintf(
+ 'The collection type "%s" does not support the $propertyOrMethod parameter',
+ $this->getType(),
+ ));
+ }
+
+ if (is_array($element)) {
+ return $element[$propertyOrMethod] ?? throw new InvalidPropertyOrMethod(sprintf(
+ 'Key or index "%s" not found in collection elements',
+ $propertyOrMethod,
+ ));
+ }
+
+ if (property_exists($element, $propertyOrMethod) && method_exists($element, $propertyOrMethod)) {
+ $reflectionProperty = new ReflectionProperty($element, $propertyOrMethod);
+ if ($reflectionProperty->isPublic()) {
+ return $element->$propertyOrMethod;
+ }
+
+ return $element->{$propertyOrMethod}();
+ }
+
+ if (property_exists($element, $propertyOrMethod)) {
+ return $element->$propertyOrMethod;
+ }
+
+ if (method_exists($element, $propertyOrMethod)) {
+ return $element->{$propertyOrMethod}();
+ }
+
+ if (isset($element->$propertyOrMethod)) {
+ return $element->$propertyOrMethod;
+ }
+
+ throw new InvalidPropertyOrMethod(sprintf(
+ 'Method or property "%s" not defined in %s',
+ $propertyOrMethod,
+ $element::class,
+ ));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/collection library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Collection\Tool;
+
+use DateTimeInterface;
+
+use function assert;
+use function get_resource_type;
+use function is_array;
+use function is_bool;
+use function is_callable;
+use function is_object;
+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(mixed $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 . ')';
+ }
+
+ // From here, $value should be an object.
+ assert(is_object($value));
+
+ // __toString() is implemented
+ if (is_callable([$value, '__toString'])) {
+ /** @var string */
+ return $value->__toString();
+ }
+
+ // object of type \DateTime
+ if ($value instanceof DateTimeInterface) {
+ return $value->format('c');
+ }
+
+ // unknown type
+ return '(' . $value::class . ' Object)';
+ }
+}
--- /dev/null
+Copyright (c) 2012-2025 Ben Ramsey <ben@benramsey.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+<h1 align="center">ramsey/uuid</h1>
+
+<p align="center">
+ <strong>A PHP library for generating and working with UUIDs.</strong>
+</p>
+
+<p align="center">
+ <a href="https://github.com/ramsey/uuid"><img src="http://img.shields.io/badge/source-ramsey/uuid-blue.svg?style=flat-square" alt="Source Code"></a>
+ <a href="https://packagist.org/packages/ramsey/uuid"><img src="https://img.shields.io/packagist/v/ramsey/uuid.svg?style=flat-square&label=release" alt="Download Package"></a>
+ <a href="https://php.net"><img src="https://img.shields.io/packagist/php-v/ramsey/uuid.svg?style=flat-square&colorB=%238892BF" alt="PHP Programming Language"></a>
+ <a href="https://github.com/ramsey/uuid/blob/4.x/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/uuid.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
+ <a href="https://github.com/ramsey/uuid/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/uuid/continuous-integration.yml?branch=4.x&logo=github&style=flat-square" alt="Build Status"></a>
+ <a href="https://app.codecov.io/gh/ramsey/uuid/branch/4.x"><img src="https://img.shields.io/codecov/c/github/ramsey/uuid/4.x?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
+</p>
+
+ramsey/uuid is a PHP library for generating and working with universally unique
+identifiers (UUIDs).
+
+This project adheres to a [code of conduct](CODE_OF_CONDUCT.md).
+By participating in this project and its community, you are expected to
+uphold this code.
+
+Much inspiration for this library came from the [Java][javauuid] and
+[Python][pyuuid] UUID libraries.
+
+## Installation
+
+The preferred method of installation is via [Composer][]. Run the following
+command to install the package and add it as a requirement to your project's
+`composer.json`:
+
+```bash
+composer require ramsey/uuid
+```
+
+## Upgrading to Version 4
+
+See the documentation for a thorough upgrade guide:
+
+* [Upgrading ramsey/uuid Version 3 to 4](https://uuid.ramsey.dev/en/stable/upgrading/3-to-4.html)
+
+## Documentation
+
+Please see <https://uuid.ramsey.dev> for documentation, tips, examples, and
+frequently asked questions.
+
+## Contributing
+
+Contributions are welcome! To contribute, please familiarize yourself with
+[CONTRIBUTING.md](CONTRIBUTING.md).
+
+## Coordinated Disclosure
+
+Keeping user information safe and secure is a top priority, and we welcome the
+contribution of external security researchers. If you believe you've found a
+security issue in software that is maintained in this repository, please read
+[SECURITY.md][] for instructions on submitting a vulnerability report.
+
+## ramsey/uuid for Enterprise
+
+Available as part of the Tidelift Subscription.
+
+The maintainers of ramsey/uuid and thousands of other packages are working with
+Tidelift to deliver commercial support and maintenance for the open source
+packages you use to build your applications. Save time, reduce risk, and improve
+code health, while paying the maintainers of the exact packages you use.
+[Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-uuid?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
+
+## Copyright and License
+
+The ramsey/uuid library is copyright © [Ben Ramsey](https://benramsey.com/) and
+licensed for use under the MIT License (MIT). Please see [LICENSE][] for more
+information.
+
+[rfc4122]: http://tools.ietf.org/html/rfc4122
+[conduct]: https://github.com/ramsey/uuid/blob/4.x/CODE_OF_CONDUCT.md
+[javauuid]: http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html
+[pyuuid]: http://docs.python.org/3/library/uuid.html
+[composer]: http://getcomposer.org/
+[contributing.md]: https://github.com/ramsey/uuid/blob/4.x/CONTRIBUTING.md
+[security.md]: https://github.com/ramsey/uuid/blob/4.x/SECURITY.md
+[license]: https://github.com/ramsey/uuid/blob/4.x/LICENSE
--- /dev/null
+{
+ "name": "ramsey/uuid",
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "license": "MIT",
+ "type": "library",
+ "keywords": [
+ "uuid",
+ "identifier",
+ "guid"
+ ],
+ "require": {
+ "php": "^8.0",
+ "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
+ "ramsey/collection": "^1.2 || ^2.0"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.25",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "ergebnis/composer-normalize": "^2.47",
+ "mockery/mockery": "^1.6",
+ "paragonie/random-lib": "^2",
+ "php-mock/php-mock": "^2.6",
+ "php-mock/php-mock-mockery": "^1.5",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpbench/phpbench": "^1.2.14",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "slevomat/coding-standard": "^8.18",
+ "squizlabs/php_codesniffer": "^3.13"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "suggest": {
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Ramsey\\Uuid\\Benchmark\\": "tests/benchmark/",
+ "Ramsey\\Uuid\\StaticAnalysis\\": "tests/static-analysis/",
+ "Ramsey\\Uuid\\Test\\": "tests/"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "captainhook/plugin-composer": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "ergebnis/composer-normalize": true,
+ "phpstan/extension-installer": true
+ },
+ "sort-packages": true
+ },
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ }
+ },
+ "scripts": {
+ "dev:analyze": "@dev:analyze:phpstan",
+ "dev:analyze:phpstan": "phpstan analyse --ansi --memory-limit 1G",
+ "dev:bench": "@php -d 'error_reporting=24575' vendor/bin/phpbench run",
+ "dev:build:clean": "git clean -fX build/",
+ "dev:lint": [
+ "@dev:lint:syntax",
+ "@dev:lint:style"
+ ],
+ "dev:lint:fix": "phpcbf --cache=build/cache/phpcs.cache",
+ "dev:lint:style": "phpcs --cache=build/cache/phpcs.cache --colors",
+ "dev:lint:syntax": "parallel-lint --colors src/ tests/",
+ "dev:test": [
+ "@dev:lint",
+ "@dev:bench",
+ "@dev:analyze",
+ "@dev:test:unit"
+ ],
+ "dev:test:coverage:ci": "@php -d 'xdebug.mode=coverage' vendor/bin/phpunit --colors=always --coverage-text --coverage-clover build/coverage/clover.xml --coverage-cobertura build/coverage/cobertura.xml --coverage-crap4j build/coverage/crap4j.xml --coverage-xml build/coverage/coverage-xml --log-junit build/junit.xml",
+ "dev:test:coverage:html": "@php -d 'xdebug.mode=coverage' vendor/bin/phpunit --colors=always --coverage-html build/coverage/coverage-html/",
+ "dev:test:unit": "phpunit --colors=always",
+ "test": "@dev:test"
+ },
+ "scripts-descriptions": {
+ "dev:analyze": "Runs all static analysis checks.",
+ "dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
+ "dev:bench": "Runs PHPBench benchmark tests.",
+ "dev:build:clean": "Cleans the build/ directory.",
+ "dev:lint": "Runs all linting checks.",
+ "dev:lint:fix": "Auto-fixes coding standards issues, if possible.",
+ "dev:lint:style": "Checks for coding standards issues.",
+ "dev:lint:syntax": "Checks for syntax errors.",
+ "dev:test": "Runs linting, static analysis, and unit tests.",
+ "dev:test:coverage:ci": "Runs unit tests and generates CI coverage reports.",
+ "dev:test:coverage:html": "Runs unit tests and generates HTML coverage report.",
+ "dev:test:unit": "Runs unit tests.",
+ "test": "Runs linting, static analysis, and unit tests."
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid;
+
+/**
+ * Provides binary math utilities
+ */
+class BinaryUtils
+{
+ /**
+ * Applies the variant field to the 16-bit clock sequence
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ *
+ * @param int $clockSeq The 16-bit clock sequence value before the variant is applied
+ *
+ * @return int The 16-bit clock sequence multiplexed with the UUID variant
+ *
+ * @pure
+ */
+ public static function applyVariant(int $clockSeq): int
+ {
+ return ($clockSeq & 0x3fff) | 0x8000;
+ }
+
+ /**
+ * Applies the version field to the 16-bit `time_hi_and_version` field
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ *
+ * @param int $timeHi The value of the 16-bit `time_hi_and_version` field before the version is applied
+ * @param int $version The 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
+ *
+ * @pure
+ */
+ public static function applyVersion(int $timeHi, int $version): int
+ {
+ return ($timeHi & 0x0fff) | ($version << 12);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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<T>` instead.
+ *
+ * @extends AbstractCollection<UuidBuilderInterface>
+ */
+class BuilderCollection extends AbstractCollection
+{
+ public function getType(): string
+ {
+ return UuidBuilderInterface::class;
+ }
+
+ 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
+ */
+ public function unserialize($serialized): void
+ {
+ /** @var array<array-key, UuidBuilderInterface> $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 {
+ /** @phpstan-ignore instanceof.alwaysTrue */
+ return $unserialized instanceof UuidBuilderInterface;
+ },
+ );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Builder;
+
+use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder;
+
+/**
+ * @deprecated Please transition to {@see Rfc4122UuidBuilder}.
+ *
+ * @immutable
+ */
+class DefaultUuidBuilder extends Rfc4122UuidBuilder
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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. Please transition to {@see DefaultUuidBuilder}.
+ *
+ * @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
+ *
+ * @phpstan-impure
+ */
+ public function build(CodecInterface $codec, string $bytes): UuidInterface
+ {
+ return new DegradedUuid(new Rfc4122Fields($bytes), $this->numberConverter, $codec, $this->timeConverter);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+class FallbackBuilder implements UuidBuilderInterface
+{
+ /**
+ * @param iterable<UuidBuilderInterface> $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
+ *
+ * @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,
+ );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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
+ *
+ * @pure
+ */
+ public function build(CodecInterface $codec, string $bytes): UuidInterface;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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 non-empty-string Hexadecimal string representation of a UUID
+ *
+ * @pure
+ */
+ 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 non-empty-string Binary string representation of a UUID
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ public function decodeBytes(string $bytes): UuidInterface;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+class GuidStringCodec extends StringCodec
+{
+ public function encode(UuidInterface $uuid): string
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $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
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $bytes = $this->getBytes($encodedUuid);
+
+ /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */
+ return $this->getBuilder()->build($this, $this->swapBytes($bytes));
+ }
+
+ public function decodeBytes(string $bytes): UuidInterface
+ {
+ // Call parent::decode() to preserve the 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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 INSERT and
+ * SELECT statements 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.
+ *
+ * @deprecated Please migrate to {@link https://uuid.ramsey.dev/en/stable/rfc4122/version6.html Version 6, reordered time-based UUIDs}.
+ *
+ * @link https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ Storing UUID Values in MySQL
+ *
+ * @immutable
+ */
+class OrderedTimeCodec extends StringCodec
+{
+ /**
+ * Returns a binary string representation of a UUID, with the timestamp fields rearranged for optimized storage
+ *
+ * @return non-empty-string
+ */
+ public function encodeBinary(UuidInterface $uuid): string
+ {
+ if (
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ !($uuid->getFields() instanceof Rfc4122FieldsInterface)
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME
+ ) {
+ throw new InvalidArgumentException('Expected version 1 (time-based) UUID');
+ }
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $bytes = $uuid->getFields()->getBytes();
+
+ 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);
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $fields = $uuid->getFields();
+
+ if (!$fields instanceof Rfc4122FieldsInterface || $fields->getVersion() !== Uuid::UUID_TYPE_TIME) {
+ throw new UnsupportedOperationException(
+ 'Attempting to decode a non-time-based UUID using OrderedTimeCodec',
+ );
+ }
+
+ return $uuid;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122) UUIDs
+ *
+ * @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
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $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),
+ );
+ }
+
+ /**
+ * @return non-empty-string
+ */
+ 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
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * ```
+ * $factory = new UuidFactory();
+ *
+ * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder()));
+ *
+ * $factory->setRandomGenerator(new CombGenerator(
+ * $factory->getRandomGenerator(),
+ * $factory->getNumberConverter(),
+ * ));
+ *
+ * $timestampFirstComb = $factory->uuid4();
+ * ```
+ *
+ * @deprecated Please migrate to {@link https://uuid.ramsey.dev/en/stable/rfc4122/version7.html Version 7, Unix Epoch Time UUIDs}.
+ *
+ * @link https://web.archive.org/web/20240118030355/https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys
+ *
+ * @immutable
+ */
+class TimestampFirstCombCodec extends StringCodec
+{
+ /**
+ * @return non-empty-string
+ */
+ public function encode(UuidInterface $uuid): string
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $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))
+ );
+ }
+
+ /**
+ * @return non-empty-string
+ */
+ 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
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $bytes = $this->getBytes($encodedUuid);
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ return $this->getBuilder()->build($this, $this->swapBytes($bytes));
+ }
+
+ public function decodeBytes(string $bytes): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ return $this->getBuilder()->build($this, $this->swapBytes($bytes));
+ }
+
+ /**
+ * Swaps bytes according to the timestamp-first COMB rules
+ *
+ * @pure
+ */
+ private function swapBytes(string $bytes): string
+ {
+ $first48Bits = substr($bytes, 0, 6);
+ $last48Bits = substr($bytes, -6);
+
+ return substr_replace(substr_replace($bytes, $last48Bits, 0, 6), $first48Bits, -6);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * ```
+ * $factory = new UuidFactory();
+ *
+ * $factory->setCodec(new TimestampLastCombCodec($factory->getUuidBuilder()));
+ *
+ * $factory->setRandomGenerator(new CombGenerator(
+ * $factory->getRandomGenerator(),
+ * $factory->getNumberConverter(),
+ * ));
+ *
+ * $timestampLastComb = $factory->uuid4();
+ * ```
+ *
+ * @deprecated Please use {@see StringCodec} instead.
+ *
+ * @immutable
+ */
+class TimestampLastCombCodec extends StringCodec
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 Please transition to {@see GenericNumberConverter}.
+ *
+ * @immutable
+ */
+class BigNumberConverter implements NumberConverterInterface
+{
+ private NumberConverterInterface $converter;
+
+ public function __construct()
+ {
+ $this->converter = new GenericNumberConverter(new BrickMathCalculator());
+ }
+
+ /**
+ * @pure
+ */
+ public function fromHex(string $hex): string
+ {
+ return $this->converter->fromHex($hex);
+ }
+
+ /**
+ * @pure
+ */
+ public function toHex(string $number): string
+ {
+ return $this->converter->toHex($number);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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. Please
+ * transition to {@see GenericNumberConverter}.
+ *
+ * @immutable
+ */
+class DegradedNumberConverter extends BigNumberConverter
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+class GenericNumberConverter implements NumberConverterInterface
+{
+ public function __construct(private CalculatorInterface $calculator)
+ {
+ }
+
+ /**
+ * @pure
+ */
+ public function fromHex(string $hex): string
+ {
+ return $this->calculator->fromBase($hex, 16)->toString();
+ }
+
+ /**
+ * @pure
+ */
+ public function toHex(string $number): string
+ {
+ /** @phpstan-ignore return.type, possiblyImpure.new */
+ return $this->calculator->toBase(new IntegerObject($number), 16);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+interface NumberConverterInterface
+{
+ /**
+ * Converts a hexadecimal number into a string integer representation of the number
+ *
+ * The integer representation returned is a string representation of the integer to accommodate unsigned integers
+ * that are greater than `PHP_INT_MAX`.
+ *
+ * @param string $hex The hexadecimal string representation to convert
+ *
+ * @return numeric-string String representation of an integer
+ *
+ * @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 that are greater than `PHP_INT_MAX`.
+ *
+ * @return non-empty-string Hexadecimal string
+ *
+ * @pure
+ */
+ public function toHex(string $number): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 Please transition to {@see GenericTimeConverter}.
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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. Please transition to
+ * {@see GenericTimeConverter}.
+ *
+ * @immutable
+ */
+class DegradedTimeConverter extends BigNumberTimeConverter
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ $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), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ // Convert the microseconds into a count of 100-nanosecond intervals.
+ $usec = $this->calculator->multiply(
+ $timestamp->getMicroseconds(),
+ new IntegerObject(self::MICROSECOND_INTERVALS), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ /**
+ * Combine the intervals of seconds and microseconds 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
+ * @phpstan-ignore possiblyImpure.new
+ */
+ $uuidTime = $this->calculator->add($sec, $usec, new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS));
+
+ /**
+ * PHPStan considers CalculatorInterface::toHexadecimal, Hexadecimal:toString impure.
+ *
+ * @phpstan-ignore possiblyImpure.new
+ */
+ return new Hexadecimal(str_pad($this->calculator->toHexadecimal($uuidTime)->toString(), 16, '0', STR_PAD_LEFT));
+ }
+
+ 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), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ // Convert the 100-nanosecond intervals into seconds and microseconds.
+ $unixTimestamp = $this->calculator->divide(
+ RoundingMode::HALF_UP,
+ 6,
+ $epochNanoseconds,
+ new IntegerObject(self::SECOND_INTERVALS), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ $split = explode('.', (string) $unixTimestamp, 2);
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Time($split[0], $split[1] ?? 0);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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 = 10_000_000;
+
+ /**
+ * 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); /** @phpstan-ignore possiblyImpure.new */
+ $microseconds = new IntegerObject($microseconds); /** @phpstan-ignore possiblyImpure.new */
+
+ // 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.
+ // @phpstan-ignore function.alreadyNarrowedType (the integer value might have overflowed)
+ if (!is_int($uuidTime)) {
+ return $this->fallbackConverter->calculateTime(
+ $seconds->toString(),
+ $microseconds->toString(),
+ );
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ 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(
+ ($timestamp->toString() - self::GREGORIAN_TO_UNIX_INTERVALS) / self::SECOND_INTERVALS,
+ );
+
+ if (count($splitTime) === 0) {
+ return $this->fallbackConverter->convertTime($uuidTimestamp);
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Time($splitTime['sec'], $splitTime['usec']);
+ }
+
+ /**
+ * @param float | int $time The time to split into seconds and microseconds
+ *
+ * @return string[]
+ *
+ * @pure
+ */
+ 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),
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+class UnixTimeConverter implements TimeConverterInterface
+{
+ private const MILLISECONDS = 1000;
+
+ public function __construct(private CalculatorInterface $calculator)
+ {
+ }
+
+ public function calculateTime(string $seconds, string $microseconds): Hexadecimal
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ $timestamp = new Time($seconds, $microseconds);
+
+ // Convert the seconds into milliseconds.
+ $sec = $this->calculator->multiply(
+ $timestamp->getSeconds(),
+ new IntegerObject(self::MILLISECONDS) /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ // 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), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ /** @var IntegerObject $unixTime */
+ $unixTime = $this->calculator->add($sec, $usec);
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Hexadecimal(
+ str_pad(
+ $this->calculator->toHexadecimal($unixTime)->toString(),
+ 12,
+ '0',
+ STR_PAD_LEFT
+ ),
+ );
+ }
+
+ public function convertTime(Hexadecimal $uuidTimestamp): Time
+ {
+ $milliseconds = $this->calculator->toInteger($uuidTimestamp);
+
+ $unixTimestamp = $this->calculator->divide(
+ RoundingMode::HALF_UP,
+ 6,
+ $milliseconds,
+ new IntegerObject(self::MILLISECONDS), /** @phpstan-ignore possiblyImpure.new */
+ );
+
+ $split = explode('.', (string) $unixTimestamp, 2);
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Time($split[0], $split[1] ?? '0');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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 9562 (formerly RFC 4122) variant UUIDs
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#appendix-A RFC 9562, Appendix A. Test Vectors
+ *
+ * @param string $seconds A string representation 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
+ *
+ * @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}
+ *
+ * @pure
+ */
+ public function convertTime(Hexadecimal $uuidTimestamp): Time;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 any type declarations using this class to {@see UuidInterface}.
+ *
+ * @immutable
+ */
+class DegradedUuid extends Uuid
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * @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();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Exception;
+
+use InvalidArgumentException as PhpInvalidArgumentException;
+
+/**
+ * Thrown to indicate that the argument received is not valid
+ */
+class InvalidArgumentException extends PhpInvalidArgumentException implements UuidExceptionInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Exception;
+
+use LogicException as PhpLogicException;
+
+/**
+ * Thrown to indicate that the requested operation is not supported
+ */
+class UnsupportedOperationException extends PhpLogicException implements UuidExceptionInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Exception;
+
+use Throwable;
+
+interface UuidExceptionInterface extends Throwable
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @phpstan-ignore constructor.unusedParameter ($forceNoBigNumber is deprecated)
+ */
+ 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);
+
+ 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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Fields;
+
+use Serializable;
+
+/**
+ * UUIDs consist of unsigned integers, the bytes of which are separated into fields and arranged in a particular layout
+ * defined by the specification for the variant
+ *
+ * @immutable
+ */
+interface FieldsInterface extends Serializable
+{
+ /**
+ * Returns the bytes that comprise the fields
+ *
+ * @pure
+ */
+ public function getBytes(): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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 the 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
+ */
+ public function unserialize(string $data): void
+ {
+ if (strlen($data) === 16) {
+ $this->__construct($data);
+ } else {
+ $this->__construct(base64_decode($data));
+ }
+ }
+
+ /**
+ * @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']);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * ```
+ * $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.
+ *
+ * ```
+ * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder()));
+ * ```
+ *
+ * @deprecated Please migrate to {@link https://uuid.ramsey.dev/en/stable/rfc4122/version7.html Version 7, Unix Epoch Time UUIDs}.
+ *
+ * @link https://web.archive.org/web/20240118030355/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
+ );
+ }
+
+ if ($length % 2 !== 0) {
+ throw new InvalidArgumentException('Length must be an even number');
+ }
+
+ $hash = '';
+
+ /** @phpstan-ignore greater.alwaysTrue (TIMESTAMP_BYTES constant could change in child classes) */
+ 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 the current timestamp as a string integer, precise to 0.00001 seconds
+ */
+ private function timestamp(): string
+ {
+ $time = explode(' ', microtime(false));
+
+ return $time[1] . substr($time[0], 2, 5);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 4,294,967,295. 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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+ /**
+ * @pure
+ */
+ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string
+ {
+ try {
+ return hash($hashAlgorithm, $ns->getBytes() . $name, true);
+ } catch (ValueError $e) {
+ throw new NameException(
+ message: sprintf('Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm),
+ previous: $e,
+ );
+ }
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562, section 6.3.
+ $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));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @pure
+ */
+ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+{
+ /**
+ * @pure
+ */
+ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string
+ {
+ $uuid = match ($hashAlgorithm) {
+ 'md5' => uuid_generate_md5($ns->toString(), $name), /** @phpstan-ignore possiblyImpure.functionCall */
+ 'sha1' => uuid_generate_sha1($ns->toString(), $name), /** @phpstan-ignore possiblyImpure.functionCall */
+ default => throw new NameException(
+ sprintf('Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm),
+ ),
+ };
+
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ return (string) uuid_parse($uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 (string) uuid_parse($uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 (string) uuid_parse($uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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);
+ }
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 to generate of random binary data
+ *
+ * @return string A binary string
+ */
+ public function generate(int $length): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Generator;
+
+use Brick\Math\BigInteger;
+use DateTimeInterface;
+use Ramsey\Uuid\Type\Hexadecimal;
+
+use function assert;
+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, combining 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 | null $dateTime A date-time instance to use when generating bytes
+ */
+ public function generate($node = null, ?int $clockSeq = null, ?DateTimeInterface $dateTime = null): string
+ {
+ if ($dateTime === null) {
+ $time = microtime(false);
+ $time = substr($time, 11) . substr($time, 2, 3);
+ } else {
+ $time = $dateTime->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);
+ }
+
+ assert(strlen($time) === 6);
+
+ 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 (<https://github.com/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 number to get 5 extra 24-bit numbers. Then, we consume
+ * each number 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`.
+ */
+ 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] + 1 + (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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 consist of a set of named fields, according to RFC 9562 (formerly RFC 4122)
+ *
+ * @see Guid
+ *
+ * @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 9562 (formerly 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 string[] $hex */
+ $hex = unpack(
+ 'H*',
+ pack(
+ 'v*',
+ hexdec(bin2hex(substr($this->bytes, 2, 2))),
+ hexdec(bin2hex(substr($this->bytes, 0, 2))),
+ ),
+ );
+
+ return new Hexadecimal($hex[1] ?? '');
+ }
+
+ public function getTimeMid(): Hexadecimal
+ {
+ // Swap the bytes from little endian to network byte order.
+ /** @var string[] $hex */
+ $hex = unpack('H*', pack('v', hexdec(bin2hex(substr($this->bytes, 4, 2)))));
+
+ return new Hexadecimal($hex[1] ?? '');
+ }
+
+ public function getTimeHiAndVersion(): Hexadecimal
+ {
+ // Swap the bytes from little endian to network byte order.
+ /** @var string[] $hex */
+ $hex = unpack('H*', pack('v', hexdec(bin2hex(substr($this->bytes, 6, 2)))));
+
+ return new Hexadecimal($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 int[] $parts */
+ $parts = unpack('n*', $this->bytes);
+
+ return ($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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 GUID definition.
+ *
+ * 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
+ *
+ * @immutable
+ */
+final class Guid extends Uuid
+{
+ public function __construct(
+ Fields $fields,
+ NumberConverterInterface $numberConverter,
+ CodecInterface $codec,
+ TimeConverterInterface $timeConverter,
+ ) {
+ parent::__construct($fields, $numberConverter, $codec, $timeConverter);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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
+ *
+ * @pure
+ */
+ public function build(CodecInterface $codec, string $bytes): UuidInterface
+ {
+ try {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Guid($this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter);
+ } catch (Throwable $e) {
+ /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */
+ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Proxy method to allow injecting a mock for testing
+ *
+ * @pure
+ */
+ protected function buildFields(string $bytes): Fields
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Fields($bytes);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * > [!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.
+ *
+ * @internal this type is used internally for performance reasons and is not supposed to be directly referenced in consumer libraries.
+ */
+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;
+
+ /**
+ * @param non-empty-string $uuid
+ */
+ public function __construct(private string $uuid)
+ {
+ }
+
+ 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: non-empty-string}
+ */
+ public function __serialize(): array
+ {
+ return ['string' => $this->uuid];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param non-empty-string $data
+ */
+ public function unserialize(string $data): void
+ {
+ $this->uuid = $data;
+ }
+
+ /**
+ * @param array{string?: non-empty-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']);
+ }
+
+ public function getNumberConverter(): NumberConverterInterface
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getNumberConverter();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFieldsHex(): array
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getFieldsHex();
+ }
+
+ public function getClockSeqHiAndReservedHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getClockSeqHiAndReservedHex();
+ }
+
+ public function getClockSeqLowHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getClockSeqLowHex();
+ }
+
+ public function getClockSequenceHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getClockSequenceHex();
+ }
+
+ public function getDateTime(): DateTimeInterface
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getDateTime();
+ }
+
+ public function getLeastSignificantBitsHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getLeastSignificantBitsHex();
+ }
+
+ public function getMostSignificantBitsHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getMostSignificantBitsHex();
+ }
+
+ public function getNodeHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getNodeHex();
+ }
+
+ public function getTimeHiAndVersionHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getTimeHiAndVersionHex();
+ }
+
+ public function getTimeLowHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getTimeLowHex();
+ }
+
+ public function getTimeMidHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getTimeMidHex();
+ }
+
+ public function getTimestampHex(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getTimestampHex();
+ }
+
+ public function getUrn(): string
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getUrn();
+ }
+
+ public function getVariant(): ?int
+ {
+ return ($this->unwrapped ?? $this->unwrap())->getVariant();
+ }
+
+ 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();
+ }
+
+ public function getBytes(): string
+ {
+ /**
+ * @var non-empty-string
+ * @phpstan-ignore possiblyImpure.functionCall, possiblyImpure.functionCall
+ */
+ 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.
+ */
+ public function getClockSeqHiAndReserved(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getClockSeqLow(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getClockSequence(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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()}.
+ */
+ 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()}.
+ */
+ 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.
+ */
+ public function getNode(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getTimeHiAndVersion(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getTimeLow(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getTimeMid(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ return $instance->getNumberConverter()->fromHex($fields->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.
+ */
+ public function getTimestamp(): string
+ {
+ $instance = ($this->unwrapped ?? $this->unwrap());
+
+ $fields = $instance->getFields();
+ assert($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface);
+
+ 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;
+ }
+
+ private function unwrap(): UuidInterface
+ {
+ return $this->unwrapped = (new UuidFactory())->fromString($this->uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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());
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ 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());
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ 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());
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new IntegerObject((string) $product);
+ }
+
+ public function divide(
+ int $roundingMode,
+ int $scale,
+ NumberInterface $dividend,
+ NumberInterface ...$divisors,
+ ): NumberInterface {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $brickRounding = $this->getBrickRoundingMode($roundingMode);
+
+ $quotient = BigDecimal::of($dividend->toString());
+
+ foreach ($divisors as $divisor) {
+ $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding);
+ }
+
+ if ($scale === 0) {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new IntegerObject((string) $quotient->toBigInteger());
+ }
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Decimal((string) $quotient);
+ }
+
+ public function fromBase(string $value, int $base): IntegerObject
+ {
+ try {
+ /** @phpstan-ignore possiblyImpure.new */
+ 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
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ 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
+ *
+ * @return BrickMathRounding::*
+ */
+ private function getBrickRoundingMode(int $roundingMode)
+ {
+ return self::ROUNDING_MODE_MAP[$roundingMode] ?? BrickMathRounding::UNNECESSARY;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ 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
+ *
+ * @pure
+ */
+ public function toBase(IntegerObject $value, int $base): string;
+
+ /**
+ * Converts an Integer instance to a Hexadecimal instance
+ *
+ * @pure
+ */
+ public function toHexadecimal(IntegerObject $value): Hexadecimal;
+
+ /**
+ * Converts a Hexadecimal instance to an Integer instance
+ *
+ * @pure
+ */
+ public function toInteger(Hexadecimal $value): IntegerObject;
+}
--- /dev/null
+<?php
+
+/**
+ * This file was originally part of brick/math
+ *
+ * Copyright (c) 2013-present Benjamin Morel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * @link https://github.com/brick/math brick/math at GitHub
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Math;
+
+/**
+ * Specifies a rounding behavior for numerical operations capable of discarding precision.
+ *
+ * Each rounding mode indicates how the least significant returned digit of a rounded result is to be calculated. If
+ * fewer digits are returned than the digits needed to represent the exact numerical result, the discarded digits will
+ * be referred to as the discarded fraction regardless of the digits' contribution to the value of the number. In other
+ * words, considered as a numerical value, the discarded fraction could have an absolute value greater than one.
+ */
+final class RoundingMode
+{
+ /**
+ * Asserts that the requested operation has an exact result; hence no rounding is necessary.
+ */
+ public const UNNECESSARY = 0;
+
+ /**
+ * Rounds away from zero.
+ *
+ * Always increments the digit prior to a nonzero discarded fraction. Note that this rounding mode never decreases
+ * the magnitude of the calculated value.
+ */
+ public const UP = 1;
+
+ /**
+ * Rounds towards zero.
+ *
+ * Never increments the digit prior to a discarded fraction (i.e., truncates). Note that this rounding mode never
+ * increases the magnitude of the calculated value.
+ */
+ public const DOWN = 2;
+
+ /**
+ * Rounds towards positive infinity.
+ *
+ * If the result is positive, behaves as for UP; if negative, behaves as for DOWN. Note that this rounding mode
+ * never decreases the calculated value.
+ */
+ public const CEILING = 3;
+
+ /**
+ * Rounds towards negative infinity.
+ *
+ * If the result is positive, behave as for DOWN; if negative, behave as for UP. Note that this rounding mode never
+ * increases the calculated value.
+ */
+ public const FLOOR = 4;
+
+ /**
+ * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
+ *
+ * Behaves as for UP if the discarded fraction is >= 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;
+
+ /**
+ * Private constructor. This class is not instantiable.
+ *
+ * @codeCoverageIgnore
+ */
+ private function __construct()
+ {
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122) standard
+ *
+ * Since some systems may create nonstandard UUIDs, this implements the {@see FieldsInterface}, so that functionality of
+ * a nonstandard UUID is not degraded, in the event these UUIDs are expected to contain RFC 9562 (formerly RFC 4122) fields.
+ *
+ * Internally, this class represents the fields together as a 16-byte binary string.
+ *
+ * @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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122)
+ *
+ * @immutable
+ * @pure
+ */
+final class Uuid extends BaseUuid
+{
+ public function __construct(
+ Fields $fields,
+ NumberConverterInterface $numberConverter,
+ CodecInterface $codec,
+ TimeConverterInterface $timeConverter,
+ ) {
+ parent::__construct($fields, $numberConverter, $codec, $timeConverter);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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
+ *
+ * @pure
+ */
+ public function build(CodecInterface $codec, string $bytes): UuidInterface
+ {
+ try {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Uuid($this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter);
+ } catch (Throwable $e) {
+ /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */
+ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Proxy method to allow injecting a mock for testing
+ *
+ * @pure
+ */
+ protected function buildFields(string $bytes): Fields
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Fields($bytes);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 as BaseUuid;
+
+/**
+ * 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
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.6 RFC 9562, 5.6. UUID Version 6
+ *
+ * @immutable
+ */
+class UuidV6 extends BaseUuid implements UuidInterface
+{
+ use TimeTrait;
+
+ /**
+ * Creates a version 6 (reordered 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() !== BaseUuid::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 = BaseUuid::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 = BaseUuid::fromBytes((string) hex2bin($hex));
+
+ return $uuid->toUuidV6();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 IntegerObject | int | float | string | 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 IntegerObject | int | float | string | 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
+ {
+ return !str_contains(strtolower((string) ini_get('disable_functions')), 'shell_exec');
+ }
+
+ /**
+ * Returns the PHP_OS string
+ */
+ private function getOs(): string
+ {
+ /** @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://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab Well-known SID Structures
+ * @link https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers#well-known-sids Well-known SIDs
+ * @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), escape: '\\')[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 '';
+ }
+
+ $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 '';
+ }
+
+ $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));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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<NodeProviderInterface> $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(message: 'Unable to find a suitable node provider', previous: $lastProviderException);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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<T>` instead.
+ *
+ * @extends AbstractCollection<NodeProviderInterface>
+ */
+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
+ */
+ public function unserialize($serialized): void
+ {
+ /** @var array<array-key, NodeProviderInterface> $data */
+ $data = unserialize($serialized, [
+ 'allowed_classes' => [
+ Hexadecimal::class,
+ RandomNodeProvider::class,
+ StaticNodeProvider::class,
+ SystemNodeProvider::class,
+ ],
+ ]);
+
+ /** @phpstan-ignore-next-line */
+ $this->data = array_filter($data, fn ($unserialized): bool => $unserialized instanceof NodeProviderInterface);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 https://www.rfc-editor.org/rfc/rfc9562#section-6.10 RFC 9562, 6.10. UUIDs 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 9562, section 6.10.
+ $nodeMsb = hex2bin(str_pad(dechex(hexdec(bin2hex($nodeMsb)) | 0x010000), 6, '0', STR_PAD_LEFT));
+
+ return new Hexadecimal(str_pad(bin2hex($nodeMsb . $nodeLsb), 12, '0', STR_PAD_LEFT));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 https://www.rfc-editor.org/rfc/rfc9562#section-6.10 RFC 9562, 6.10. UUIDs 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));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 found
+ */
+ protected function getNodeFromSystem(): string
+ {
+ /** @var string | null $node */
+ static $node = null;
+
+ if ($node !== null) {
+ return $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
+ {
+ if (str_contains(strtolower((string) ini_get('disable_functions')), 'passthru')) {
+ return '';
+ }
+
+ /** @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
+ {
+ /** @var string $phpOs */
+ $phpOs = constant('PHP_OS');
+
+ if (strtoupper($phpOs) !== 'LINUX') {
+ return '';
+ }
+
+ $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT);
+
+ if ($addressPaths === false || count($addressPaths) === 0) {
+ return '';
+ }
+
+ /** @var array<array-key, string> $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 (mixed $address): bool {
+ assert(is_string($address));
+
+ return $address !== '00:00:00:00:00:00' && preg_match(self::SYSFS_PATTERN, $address);
+ });
+
+ /** @var bool | string $mac */
+ $mac = reset($macs);
+
+ return (string) $mac;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 IntegerObject | int | string $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 IntegerObject | int | string $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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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']);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122) variant UUIDs consist of a set of named fields
+ *
+ * Internally, this class represents the fields together as a 16-byte binary string.
+ *
+ * @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 9562 (formerly 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 9562 (formerly RFC 4122) variant',
+ );
+ }
+
+ if (!$this->isCorrectVersion()) {
+ throw new InvalidArgumentException(
+ 'The byte string received does not contain a valid RFC 9562 (formerly RFC 4122) version',
+ );
+ }
+ }
+
+ /**
+ * @pure
+ */
+ 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 timestamp
+ * fidelity, for version 2 UUIDs. The timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9
+ * seconds, and 496,730 microseconds).
+ *
+ * For version 6 UUIDs, the timestamp order is reversed from the typical RFC 9562 (formerly 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
+ {
+ return new Hexadecimal(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()
+ ),
+ });
+ }
+
+ 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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+
+/**
+ * UUID fields, as defined by RFC 4122
+ *
+ * This interface defines the fields of an RFC 4122 variant UUID. Since RFC 9562 removed the concept of fields and
+ * instead defined layouts that are specific to a given version, this interface is a legacy artifact of the earlier, and
+ * now obsolete, RFC 4122.
+ *
+ * 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 https://www.rfc-editor.org/rfc/rfc4122#section-4.1 RFC 4122, 4.1. Format
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4 RFC 9562, 4. UUID Format
+ *
+ * @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 9562 (formerly RFC 4122) variant
+ * - 6 - Reserved, Microsoft Corporation backward compatibility
+ * - 7 - Reserved for future definition
+ *
+ * For RFC 9562 (formerly RFC 4122) variant UUIDs, this value should always be the integer `2`.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ public function getVariant(): int;
+
+ /**
+ * Returns the UUID 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 Gregorian time UUID
+ * 7. Unix Epoch time UUID
+ * 8. Custom format UUID
+ *
+ * This returns `null` if the UUID is not an RFC 9562 (formerly RFC 4122) variant, since the version is only
+ * meaningful for this variant.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ *
+ * @pure
+ */
+ public function getVersion(): ?int;
+
+ /**
+ * Returns true if these fields represent a nil UUID
+ *
+ * The nil UUID is a special form of UUID that is specified to have all 128 bits set to zero.
+ *
+ * @pure
+ */
+ public function isNil(): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+/**
+ * Provides common functionality for max UUIDs
+ *
+ * @immutable
+ */
+trait MaxTrait
+{
+ /**
+ * Returns the bytes that comprise the fields
+ *
+ * @pure
+ */
+ abstract public function getBytes(): string;
+
+ /**
+ * Returns true if the byte string represents a max UUID
+ *
+ * @pure
+ */
+ public function isMax(): bool
+ {
+ return $this->getBytes() === "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+use Ramsey\Uuid\Uuid;
+
+/**
+ * The max UUID is a special form of UUID that has all 128 bits set to one (`1`)
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.10 RFC 9562, 5.10. Max UUID
+ *
+ * @immutable
+ */
+final class MaxUuid extends Uuid implements UuidInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+/**
+ * Provides common functionality for nil UUIDs
+ *
+ * @immutable
+ */
+trait NilTrait
+{
+ /**
+ * Returns the bytes that comprise the fields
+ *
+ * @pure
+ */
+ 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";
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+use Ramsey\Uuid\Uuid;
+
+/**
+ * The nil UUID is a special form of UUID that has all 128 bits set to zero (`0`)
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.9 RFC 9562, 5.9. Nil UUID
+ *
+ * @immutable
+ */
+final class NilUuid extends Uuid implements UuidInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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);
+ }
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly 4122) UUIDs
+ *
+ * @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
+ *
+ * @pure
+ */
+ public function build(CodecInterface $codec, string $bytes): UuidInterface
+ {
+ try {
+ /** @var Fields $fields */
+ $fields = $this->buildFields($bytes);
+
+ if ($fields->isNil()) {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter);
+ }
+
+ if ($fields->isMax()) {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new MaxUuid($fields, $this->numberConverter, $codec, $this->timeConverter);
+ }
+
+ return match ($fields->getVersion()) {
+ /** @phpstan-ignore possiblyImpure.new */
+ Uuid::UUID_TYPE_TIME => new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter),
+ Uuid::UUID_TYPE_DCE_SECURITY
+ /** @phpstan-ignore possiblyImpure.new */
+ => new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter),
+ /** @phpstan-ignore possiblyImpure.new */
+ Uuid::UUID_TYPE_HASH_MD5 => new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter),
+ /** @phpstan-ignore possiblyImpure.new */
+ Uuid::UUID_TYPE_RANDOM => new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter),
+ /** @phpstan-ignore possiblyImpure.new */
+ Uuid::UUID_TYPE_HASH_SHA1 => new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter),
+ Uuid::UUID_TYPE_REORDERED_TIME
+ /** @phpstan-ignore possiblyImpure.new */
+ => new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter),
+ Uuid::UUID_TYPE_UNIX_TIME
+ /** @phpstan-ignore possiblyImpure.new */
+ => new UuidV7($fields, $this->numberConverter, $codec, $this->unixTimeConverter),
+ /** @phpstan-ignore possiblyImpure.new */
+ Uuid::UUID_TYPE_CUSTOM => new UuidV8($fields, $this->numberConverter, $codec, $this->timeConverter),
+ default => throw new UnsupportedOperationException(
+ 'The UUID version in the given fields is not supported by this UUID builder',
+ ),
+ };
+ } catch (Throwable $e) {
+ /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */
+ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Proxy method to allow injecting a mock for testing
+ *
+ * @pure
+ */
+ protected function buildFields(string $bytes): FieldsInterface
+ {
+ /** @phpstan-ignore possiblyImpure.new */
+ return new Fields($bytes);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+use Ramsey\Uuid\UuidInterface as BaseUuidInterface;
+
+/**
+ * A universally unique identifier (UUID), as defined in RFC 9562 (formerly RFC 4122)
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562 RFC 9562
+ *
+ * @immutable
+ */
+interface UuidInterface extends BaseUuidInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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, combined into a 128-bit unsigned integer
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.1 RFC 9562, 5.1. UUID Version 1
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 timestamp fidelity, 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 496,730 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://www.rfc-editor.org/rfc/rfc9562#section-5.2 RFC 9562, 5.2. UUID Version 2
+ * @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)
+ *
+ * @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()));
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 a combination of a namespace and name that are hashed into a 128-bit unsigned
+ * integer using the MD5 hashing algorithm
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.3 RFC 9562, 5.3. UUID Version 3
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.4 RFC 9562, 5.4. UUID Version 4
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 a combination of a namespace and name that are hashed into a 128-bit unsigned
+ * integer using the SHA1 hashing algorithm
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.5 RFC 9562, 5.5. UUID Version 5
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Rfc4122;
+
+use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6;
+
+/**
+ * Reordered Gregorian time, or version 6, UUIDs include timestamp, clock sequence, and node values that are combined
+ * into a 128-bit unsigned integer
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-5.6 RFC 9562, 5.6. UUID Version 6
+ *
+ * @immutable
+ */
+final class UuidV6 extends NonstandardUuidV6 implements UuidInterface
+{
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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://www.rfc-editor.org/rfc/rfc9562#section-5.7 RFC 9562, 5.7. UUID Version 7
+ *
+ * @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);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+
+/**
+ * Custom format, or version 8, UUIDs provide an RFC-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://www.rfc-editor.org/rfc/rfc9562#section-5.8 RFC 9562, 5.8. UUID Version 8
+ *
+ * @immutable
+ */
+final class UuidV8 extends Uuid implements UuidInterface
+{
+ /**
+ * Creates a version 8 (custom format) 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 format) UUID',
+ );
+ }
+
+ parent::__construct($fields, $numberConverter, $codec, $timeConverter);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122) variant
+ *
+ * @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';
+
+ /**
+ * @return non-empty-string
+ */
+ public function getPattern(): string
+ {
+ return self::VALID_PATTERN;
+ }
+
+ public function validate(string $uuid): bool
+ {
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ $uuid = strtolower(str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid));
+
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ return $uuid === Uuid::NIL || $uuid === Uuid::MAX || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122)
+ *
+ * @immutable
+ */
+trait VariantTrait
+{
+ /**
+ * Returns the bytes that comprise the fields
+ */
+ abstract public function getBytes(): string;
+
+ /**
+ * 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 9562 (formerly RFC 4122) variant
+ * - 6 - Reserved, Microsoft Corporation backward compatibility
+ * - 7 - Reserved for future definition
+ *
+ * For RFC 9562 (formerly RFC 4122) variant UUIDs, this value should always be the integer `2`.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ public function getVariant(): int
+ {
+ if (strlen($this->getBytes()) !== 16) {
+ throw new InvalidBytesException('Invalid number of bytes');
+ }
+
+ // According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
+ // {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.10 5.10}, the Max UUID falls within the range
+ // of the future variant.
+ if ($this->isMax()) {
+ return Uuid::RESERVED_FUTURE;
+ }
+
+ // According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
+ // {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.9 5.9}, the Nil UUID falls within the range
+ // of the Apollo NCS variant.
+ if ($this->isNil()) {
+ return Uuid::RESERVED_NCS;
+ }
+
+ /** @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.
+ $msb = substr(str_pad(decbin($parts[5]), 16, '0', STR_PAD_LEFT), 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;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 9562 (formerly RFC 4122)
+ *
+ * @immutable
+ */
+trait VersionTrait
+{
+ /**
+ * Returns the UUID 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 Gregorian time UUID
+ * 7. Unix Epoch time UUID
+ * 8. Custom format UUID
+ *
+ * This returns `null` if the UUID is not an RFC 9562 (formerly RFC 4122) variant, since the version is only
+ * meaningful for this variant.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ *
+ * @pure
+ */
+ 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 9562 (formerly 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,
+ };
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * @immutable
+ */
+final class Decimal implements NumberInterface
+{
+ private string $value;
+ private bool $isNegative;
+
+ 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;
+ } else {
+ $this->isNegative = false;
+ }
+
+ $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
+ */
+ 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']);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * @immutable
+ */
+final class Hexadecimal implements TypeInterface
+{
+ /**
+ * @var non-empty-string
+ */
+ 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);
+ }
+
+ /**
+ * @return non-empty-string
+ *
+ * @pure
+ */
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return non-empty-string
+ */
+ public function __toString(): string
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @return non-empty-string
+ */
+ public function jsonSerialize(): string
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @return non-empty-string
+ */
+ 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
+ */
+ 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 non-empty-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');
+ }
+
+ /** @var non-empty-string */
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * @immutable
+ */
+final class Integer implements NumberInterface
+{
+ /**
+ * @var numeric-string
+ */
+ private string $value;
+
+ /**
+ * @phpstan-ignore property.readOnlyByPhpDocDefaultValue
+ */
+ private bool $isNegative = false;
+
+ public function __construct(self | float | int | string $value)
+ {
+ $this->value = $value instanceof self ? (string) $value : $this->prepareValue($value);
+ }
+
+ public function isNegative(): bool
+ {
+ return $this->isNegative;
+ }
+
+ /**
+ * @return numeric-string
+ *
+ * @pure
+ */
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @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
+ */
+ 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 the 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;
+
+ /** @phpstan-ignore property.readOnlyByPhpDocAssignNotInConstructor */
+ $this->isNegative = true;
+ }
+
+ assert(is_numeric($value));
+
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+interface NumberInterface extends TypeInterface
+{
+ /**
+ * Returns true if this number is less than zero
+ */
+ public function isNegative(): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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.
+ *
+ * @immutable
+ */
+final class Time implements TypeInterface
+{
+ private IntegerObject $seconds;
+ private IntegerObject $microseconds;
+
+ public function __construct(
+ IntegerObject | float | int | string $seconds,
+ IntegerObject | float | int | string $microseconds = 0,
+ ) {
+ $this->seconds = new IntegerObject($seconds);
+ $this->microseconds = new IntegerObject($microseconds);
+ }
+
+ /**
+ * @pure
+ */
+ public function getSeconds(): IntegerObject
+ {
+ return $this->seconds;
+ }
+
+ /**
+ * @pure
+ */
+ 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
+ */
+ public function unserialize(string $data): void
+ {
+ /** @var array{seconds?: float | int | string, microseconds?: float | int | 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']);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @immutable
+ */
+interface TypeInterface extends JsonSerializable, Serializable
+{
+ /**
+ * @pure
+ */
+ public function toString(): string;
+
+ /**
+ * @pure
+ */
+ public function __toString(): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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\InvalidArgumentException;
+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
+ *
+ * @immutable
+ */
+class Uuid implements UuidInterface
+{
+ use DeprecatedUuidMethodsTrait;
+
+ /**
+ * When this namespace is specified, the name string is a fully qualified domain name
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-6.6 RFC 9562, 6.6. Namespace ID Usage and Allocation
+ */
+ public const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
+
+ /**
+ * When this namespace is specified, the name string is a URL
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-6.6 RFC 9562, 6.6. Namespace ID Usage and Allocation
+ */
+ public const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
+
+ /**
+ * When this namespace is specified, the name string is an ISO OID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-6.6 RFC 9562, 6.6. Namespace ID Usage and Allocation
+ */
+ 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 https://www.rfc-editor.org/rfc/rfc9562#section-6.6 RFC 9562, 6.6. Namespace ID Usage and Allocation
+ */
+ 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 https://www.rfc-editor.org/rfc/rfc9562#section-5.9 RFC 9562, 5.9. 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://www.rfc-editor.org/rfc/rfc9562#section-5.10 RFC 9562, 5.10. Max UUID
+ */
+ public const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
+
+ /**
+ * Variant: reserved, NCS backward compatibility
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ public const RESERVED_NCS = 0;
+
+ /**
+ * Variant: the UUID layout specified in RFC 9562 (formerly RFC 4122)
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ * @see Uuid::RFC_9562
+ */
+ public const RFC_4122 = 2;
+
+ /**
+ * Variant: the UUID layout specified in RFC 9562 (formerly RFC 4122)
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ public const RFC_9562 = 2;
+
+ /**
+ * Variant: reserved, Microsoft Corporation backward compatibility
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ public const RESERVED_MICROSOFT = 6;
+
+ /**
+ * Variant: reserved for future definition
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 RFC 9562, 4.1. Variant Field
+ */
+ 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://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ public const UUID_TYPE_TIME = 1;
+
+ /**
+ * Version 2 (DCE Security) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ 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://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ public const UUID_TYPE_HASH_MD5 = 3;
+
+ /**
+ * Version 4 (random) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ public const UUID_TYPE_RANDOM = 4;
+
+ /**
+ * Version 5 (name-based and hashed with SHA1) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ 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 Gregorian time) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ public const UUID_TYPE_REORDERED_TIME = 6;
+
+ /**
+ * Version 7 (Unix Epoch time) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ public const UUID_TYPE_UNIX_TIME = 7;
+
+ /**
+ * Version 8 (custom format) UUID
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field
+ */
+ 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',
+ ];
+
+ /**
+ * @phpstan-ignore property.readOnlyByPhpDocDefaultValue
+ */
+ 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
+ * @phpstan-ignore property.readOnlyByPhpDocDefaultValue
+ */
+ 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 9562 (formerly
+ * 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;
+ }
+
+ /**
+ * @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->codec->encode($this);
+ }
+
+ /**
+ * @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);
+ }
+
+ /** @phpstan-ignore property.readOnlyByPhpDocAssignNotInConstructor */
+ $this->codec = $uuid->codec;
+
+ /** @phpstan-ignore property.readOnlyByPhpDocAssignNotInConstructor */
+ $this->numberConverter = $uuid->numberConverter;
+
+ /** @phpstan-ignore property.readOnlyByPhpDocAssignNotInConstructor */
+ $this->fields = $uuid->fields;
+
+ /** @phpstan-ignore property.readOnlyByPhpDocAssignNotInConstructor */
+ $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;
+ }
+
+ /**
+ * @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();
+ }
+
+ /**
+ * @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
+ *
+ * @throws InvalidArgumentException
+ *
+ * @pure
+ */
+ public static function fromBytes(string $bytes): UuidInterface
+ {
+ /** @phpstan-ignore impure.staticPropertyAccess */
+ 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),
+ );
+ }
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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
+ *
+ * @throws InvalidArgumentException
+ *
+ * @pure
+ */
+ public static function fromString(string $uuid): UuidInterface
+ {
+ $uuid = strtolower($uuid);
+ /** @phpstan-ignore impure.staticPropertyAccess, possiblyImpure.functionCall */
+ if (!self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) {
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ assert($uuid !== '');
+
+ /** @phpstan-ignore possiblyImpure.new */
+ return new LazyUuidFromString($uuid);
+ }
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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
+ *
+ * @throws InvalidArgumentException
+ *
+ * @pure
+ */
+ public static function fromHexadecimal(Hexadecimal $hex): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $factory = self::getFactory();
+
+ if (method_exists($factory, 'fromHexadecimal')) {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $uuid = $factory->fromHexadecimal($hex);
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ assert($uuid instanceof UuidInterface);
+
+ return $uuid;
+ }
+
+ 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
+ *
+ * @throws InvalidArgumentException
+ *
+ * @pure
+ */
+ public static function fromInteger(string $integer): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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
+ *
+ * @phpstan-assert-if-true =non-empty-string $uuid
+ *
+ * @pure
+ */
+ public static function isValid(string $uuid): bool
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */
+ 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 UuidInterface | string $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
+ *
+ * @pure
+ */
+ public static function uuid3($ns, string $name): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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 UuidInterface | string $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
+ *
+ * @pure
+ */
+ public static function uuid5($ns, string $name): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ return self::getFactory()->uuid5($ns, $name);
+ }
+
+ /**
+ * Returns a version 6 (reordered Gregorian 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 format) 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
+ *
+ * @pure
+ */
+ public static function uuid8(string $bytes): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ $factory = self::getFactory();
+
+ if (method_exists($factory, 'uuid8')) {
+ /**
+ * @var UuidInterface
+ * @phpstan-ignore possiblyImpure.methodCall
+ */
+ return $factory->uuid8($bytes);
+ }
+
+ throw new UnsupportedOperationException('The provided factory does not support the uuid8() method');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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;
+ }
+
+ 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;
+ }
+
+ /**
+ * @pure
+ */
+ public function fromBytes(string $bytes): UuidInterface
+ {
+ return $this->codec->decodeBytes($bytes);
+ }
+
+ /**
+ * @pure
+ */
+ public function fromString(string $uuid): UuidInterface
+ {
+ $uuid = strtolower($uuid);
+
+ return $this->codec->decode($uuid);
+ }
+
+ /**
+ * @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);
+ $bytes = $timeGenerator->generate($node?->toString(), $clockSeq);
+
+ return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME);
+ }
+
+ /**
+ * @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
+ * @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
+ * @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
+ {
+ $bytes = $this->timeGenerator->generate($node?->toString(), $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 format) 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
+ *
+ * @pure
+ */
+ public function uuid8(string $bytes): UuidInterface
+ {
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ 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
+ *
+ * @pure
+ */
+ public function uuid(string $bytes): UuidInterface
+ {
+ return $this->uuidBuilder->build($this->codec, $bytes);
+ }
+
+ /**
+ * Returns a version 3 or 5 namespaced Uuid
+ *
+ * @param UuidInterface | string $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
+ *
+ * @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);
+
+ /** @phpstan-ignore possiblyImpure.methodCall */
+ return $this->uuidFromBytesAndVersion(substr($bytes, 0, 16), $version);
+ }
+
+ /**
+ * Returns a Uuid created from the provided bytes and version
+ *
+ * @param string $bytes The byte string to convert to a UUID
+ * @param int $version The version to apply to the UUID
+ *
+ * @return UuidInterface An instance of UuidInterface, created from the byte string and version
+ */
+ private function uuidFromBytesAndVersion(string $bytes, int $version): UuidInterface
+ {
+ /** @var int[] $unpackedTime */
+ $unpackedTime = unpack('n*', substr($bytes, 6, 2));
+ $timeHi = $unpackedTime[1];
+ $timeHiAndVersion = pack('n*', BinaryUtils::applyVersion($timeHi, $version));
+
+ /** @var int[] $unpackedClockSeq */
+ $unpackedClockSeq = unpack('n*', substr($bytes, 8, 2));
+ $clockSeqHi = $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);
+ }
+
+ return $this->uuid($bytes);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 the 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
+ *
+ * @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
+ *
+ * @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
+ *
+ * @pure
+ */
+ public function fromString(string $uuid): UuidInterface;
+
+ /**
+ * Returns the validator used by the factory
+ */
+ 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 a 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 UuidInterface | string $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
+ *
+ * @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 UuidInterface | string $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
+ *
+ * @pure
+ */
+ public function uuid5($ns, string $name): UuidInterface;
+
+ /**
+ * Returns a version 6 (reordered Gregorian 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;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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.
+ *
+ * @param UuidInterface $other The UUID to compare
+ *
+ * @return int<-1,1> -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
+ *
+ * @return non-empty-string
+ *
+ * @pure
+ */
+ 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://www.rfc-editor.org/rfc/rfc9562.html#section-4 RFC 9562, 4. UUID Format
+ * @link https://www.rfc-editor.org/rfc/rfc9562.html#section-7 RFC 9562, 7. IANA Considerations
+ * @link https://www.rfc-editor.org/rfc/rfc4122.html#section-3 RFC 4122, 3. Namespace Registration Template
+ */
+ public function getUrn(): string;
+
+ /**
+ * Returns the string standard representation of the UUID
+ *
+ * @return non-empty-string
+ *
+ * @pure
+ */
+ public function toString(): string;
+
+ /**
+ * Casts the UUID to the string standard representation
+ *
+ * @return non-empty-string
+ *
+ * @pure
+ */
+ public function __toString(): string;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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
+ *
+ * @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';
+
+ /**
+ * @return non-empty-string
+ */
+ public function getPattern(): string
+ {
+ return self::VALID_PATTERN;
+ }
+
+ public function validate(string $uuid): bool
+ {
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid);
+
+ /** @phpstan-ignore possiblyImpure.functionCall */
+ return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @license http://opensource.org/licenses/MIT MIT
+ */
+
+declare(strict_types=1);
+
+namespace Ramsey\Uuid\Validator;
+
+/**
+ * A validator validates a string as a proper UUID
+ *
+ * @immutable
+ */
+interface ValidatorInterface
+{
+ /**
+ * Returns the regular expression pattern used by this validator
+ *
+ * @return non-empty-string The regular expression pattern this validator uses
+ */
+ 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
+ *
+ * @pure
+ */
+ public function validate(string $uuid): bool;
+}
--- /dev/null
+<?php
+
+/**
+ * This file is part of the ramsey/uuid library
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
+ * @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 a 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 UuidInterface | string $ns The namespace (must be a valid UUID)
+ *
+ * @return non-empty-string Version 3 UUID as a string
+ *
+ * @pure
+ */
+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 UuidInterface | string $ns The namespace (must be a valid UUID)
+ *
+ * @return non-empty-string Version 5 UUID as a string
+ *
+ * @pure
+ */
+function v5($ns, string $name): string
+{
+ return Uuid::uuid5($ns, $name)->toString();
+}
+
+/**
+ * Returns a version 6 (reordered Gregorian 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 format) 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
+ *
+ * @pure
+ */
+function v8(string $bytes): string
+{
+ return Uuid::uuid8($bytes)->toString();
+}
--- /dev/null
+github: Spomky
+patreon: FlorentMorselli
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2014-2018 Spomky-Labs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
--- /dev/null
+{
+ "name": "spomky-labs/base64url",
+ "description": "Base 64 URL Safe Encoding/Decoding PHP Library",
+ "type": "library",
+ "license": "MIT",
+ "keywords": ["Base64", "URL", "Safe", "RFC4648"],
+ "homepage": "https://github.com/Spomky-Labs/base64url",
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky-Labs/base64url/contributors"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Base64Url\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Base64Url\\Test\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.11|^0.12",
+ "phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
+ "phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
+ "phpstan/phpstan-phpunit": "^0.11|^0.12",
+ "phpstan/phpstan-strict-rules": "^0.11|^0.12"
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Base64Url;
+
+use function base64_decode;
+use function base64_encode;
+use InvalidArgumentException;
+use function rtrim;
+use function strtr;
+
+/**
+ * Encode and decode data into Base64 Url Safe.
+ */
+final class Base64Url
+{
+ /**
+ * @param string $data The data to encode
+ * @param bool $usePadding If true, the "=" padding at end of the encoded value are kept, else it is removed
+ *
+ * @return string The data encoded
+ */
+ public static function encode(string $data, bool $usePadding = false): string
+ {
+ $encoded = strtr(base64_encode($data), '+/', '-_');
+
+ return true === $usePadding ? $encoded : rtrim($encoded, '=');
+ }
+
+ /**
+ * @param string $data The data to decode
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return string The data decoded
+ */
+ public static function decode(string $data): string
+ {
+ $decoded = base64_decode(strtr($data, '-_', '+/'), true);
+ if (false === $decoded) {
+ throw new InvalidArgumentException('Invalid data provided');
+ }
+
+ return $decoded;
+ }
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2018 Spomky-Labs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+{
+ "name": "spomky-labs/cbor-php",
+ "type": "library",
+ "license": "MIT",
+ "keywords": ["CBOR", "Concise Binary Object Representation", "RFC7049"],
+ "description": "CBOR Encoder/Decoder for PHP",
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },{
+ "name": "All contributors",
+ "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "CBOR\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "CBOR\\Test\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=7.3",
+ "brick/math": "^0.8.15|^0.9.0",
+ "ext-mbstring": "*"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "ekino/phpstan-banned-code": "^1.0",
+ "infection/infection": "^0.18|^0.25",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-beberlei-assert": "^1.0",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.12",
+ "roave/security-advisories": "dev-latest",
+ "symplify/easy-coding-standard": "^10.0"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "suggest": {
+ "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance",
+ "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags"
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use function chr;
+
+abstract class AbstractCBORObject implements CBORObject
+{
+ /**
+ * @var int
+ */
+ protected $additionalInformation;
+
+ /**
+ * @var int
+ */
+ private $majorType;
+
+ public function __construct(int $majorType, int $additionalInformation)
+ {
+ $this->majorType = $majorType;
+ $this->additionalInformation = $additionalInformation;
+ }
+
+ public function __toString(): string
+ {
+ return chr($this->majorType << 5 | $this->additionalInformation);
+ }
+
+ public function getMajorType(): int
+ {
+ return $this->majorType;
+ }
+
+ public function getAdditionalInformation(): int
+ {
+ return $this->additionalInformation;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+final class ByteStringObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_BYTE_STRING;
+
+ /**
+ * @var string
+ */
+ private $value;
+
+ /**
+ * @var string|null
+ */
+ private $length;
+
+ public function __construct(string $data)
+ {
+ [$additionalInformation, $length] = LengthCalculator::getLengthOfString($data);
+
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->length = $length;
+ $this->value = $data;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->length !== null) {
+ $result .= $this->length;
+ }
+
+ return $result . $this->value;
+ }
+
+ public static function create(string $data): self
+ {
+ return new self($data);
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ public function getLength(): int
+ {
+ return mb_strlen($this->value, '8bit');
+ }
+
+ public function normalize(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use IndefiniteLengthByteStringObject instead
+ */
+final class ByteStringWithChunkObject extends IndefiniteLengthByteStringObject
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+interface CBORObject
+{
+ public const MAJOR_TYPE_UNSIGNED_INTEGER = 0b000;
+
+ public const MAJOR_TYPE_NEGATIVE_INTEGER = 0b001;
+
+ public const MAJOR_TYPE_BYTE_STRING = 0b010;
+
+ public const MAJOR_TYPE_TEXT_STRING = 0b011;
+
+ public const MAJOR_TYPE_LIST = 0b100;
+
+ public const MAJOR_TYPE_MAP = 0b101;
+
+ public const MAJOR_TYPE_TAG = 0b110;
+
+ public const MAJOR_TYPE_OTHER_TYPE = 0b111;
+
+ public const LENGTH_1_BYTE = 0b00011000;
+
+ public const LENGTH_2_BYTES = 0b00011001;
+
+ public const LENGTH_4_BYTES = 0b00011010;
+
+ public const LENGTH_8_BYTES = 0b00011011;
+
+ public const LENGTH_INDEFINITE = 0b00011111;
+
+ public const FUTURE_USE_1 = 0b00011100;
+
+ public const FUTURE_USE_2 = 0b00011101;
+
+ public const FUTURE_USE_3 = 0b00011110;
+
+ public const OBJECT_FALSE = 20;
+
+ public const OBJECT_TRUE = 21;
+
+ public const OBJECT_NULL = 22;
+
+ public const OBJECT_UNDEFINED = 23;
+
+ public const OBJECT_SIMPLE_VALUE = 24;
+
+ public const OBJECT_HALF_PRECISION_FLOAT = 25;
+
+ public const OBJECT_SINGLE_PRECISION_FLOAT = 26;
+
+ public const OBJECT_DOUBLE_PRECISION_FLOAT = 27;
+
+ public const OBJECT_BREAK = 0b00011111;
+
+ public const TAG_STANDARD_DATETIME = 0;
+
+ public const TAG_EPOCH_DATETIME = 1;
+
+ public const TAG_UNSIGNED_BIG_NUM = 2;
+
+ public const TAG_NEGATIVE_BIG_NUM = 3;
+
+ public const TAG_DECIMAL_FRACTION = 4;
+
+ public const TAG_BIG_FLOAT = 5;
+
+ public const TAG_ENCODED_BASE64_URL = 21;
+
+ public const TAG_ENCODED_BASE64 = 22;
+
+ public const TAG_ENCODED_BASE16 = 23;
+
+ public const TAG_ENCODED_CBOR = 24;
+
+ public const TAG_URI = 32;
+
+ public const TAG_BASE64_URL = 33;
+
+ public const TAG_BASE64 = 34;
+
+ public const TAG_MIME = 36;
+
+ public const TAG_CBOR = 55799;
+
+ public function __toString(): string;
+
+ public function getMajorType(): int;
+
+ public function getAdditionalInformation(): int;
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return mixed|null
+ */
+ public function getNormalizedData(bool $ignoreTags = false);
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use CBOR\OtherObject\BreakObject;
+use CBOR\OtherObject\DoublePrecisionFloatObject;
+use CBOR\OtherObject\FalseObject;
+use CBOR\OtherObject\HalfPrecisionFloatObject;
+use CBOR\OtherObject\NullObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\OtherObject\OtherObjectManagerInterface;
+use CBOR\OtherObject\SimpleObject;
+use CBOR\OtherObject\SinglePrecisionFloatObject;
+use CBOR\OtherObject\TrueObject;
+use CBOR\OtherObject\UndefinedObject;
+use CBOR\Tag\Base16EncodingTag;
+use CBOR\Tag\Base64EncodingTag;
+use CBOR\Tag\Base64Tag;
+use CBOR\Tag\Base64UrlEncodingTag;
+use CBOR\Tag\Base64UrlTag;
+use CBOR\Tag\BigFloatTag;
+use CBOR\Tag\CBOREncodingTag;
+use CBOR\Tag\CBORTag;
+use CBOR\Tag\DatetimeTag;
+use CBOR\Tag\DecimalFractionTag;
+use CBOR\Tag\MimeTag;
+use CBOR\Tag\NegativeBigIntegerTag;
+use CBOR\Tag\TagManager;
+use CBOR\Tag\TagManagerInterface;
+use CBOR\Tag\TimestampTag;
+use CBOR\Tag\UnsignedBigIntegerTag;
+use CBOR\Tag\UriTag;
+use InvalidArgumentException;
+use function ord;
+use RuntimeException;
+use const STR_PAD_LEFT;
+
+final class Decoder implements DecoderInterface
+{
+ /**
+ * @var Tag\TagManagerInterface
+ */
+ private $tagManager;
+
+ /**
+ * @var OtherObject\OtherObjectManagerInterface
+ */
+ private $otherObjectManager;
+
+ public function __construct(
+ ?TagManagerInterface $tagManager = null,
+ ?OtherObjectManagerInterface $otherTypeManager = null
+ ) {
+ $this->tagManager = $tagManager ?? $this->generateTagManager();
+ $this->otherObjectManager = $otherTypeManager ?? $this->generateOtherObjectManager();
+ }
+
+ public static function create(
+ ?TagManagerInterface $tagManager = null,
+ ?OtherObjectManagerInterface $otherObjectManager = null
+ ): self {
+ return new self($tagManager, $otherObjectManager);
+ }
+
+ public function withTagManager(TagManagerInterface $tagManager): self
+ {
+ $this->tagManager = $tagManager;
+
+ return $this;
+ }
+
+ public function withOtherObjectManager(OtherObjectManagerInterface $otherObjectManager): self
+ {
+ $this->otherObjectManager = $otherObjectManager;
+
+ return $this;
+ }
+
+ public function decode(Stream $stream): CBORObject
+ {
+ return $this->process($stream, false);
+ }
+
+ private function process(Stream $stream, bool $breakable): CBORObject
+ {
+ $ib = ord($stream->read(1));
+ $mt = $ib >> 5;
+ $ai = $ib & 0b00011111;
+ $val = null;
+ switch ($ai) {
+ case CBORObject::LENGTH_1_BYTE: //24
+ case CBORObject::LENGTH_2_BYTES: //25
+ case CBORObject::LENGTH_4_BYTES: //26
+ case CBORObject::LENGTH_8_BYTES: //27
+ $val = $stream->read(2 ** ($ai & 0b00000111));
+ break;
+ case CBORObject::FUTURE_USE_1: //28
+ case CBORObject::FUTURE_USE_2: //29
+ case CBORObject::FUTURE_USE_3: //30
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot parse the data. Found invalid Additional Information "%s" (%d).',
+ str_pad(decbin($ai), 8, '0', STR_PAD_LEFT),
+ $ai
+ ));
+ case CBORObject::LENGTH_INDEFINITE: //31
+ return $this->processInfinite($stream, $mt, $breakable);
+ }
+
+ return $this->processFinite($stream, $mt, $ai, $val);
+ }
+
+ private function processFinite(Stream $stream, int $mt, int $ai, ?string $val): CBORObject
+ {
+ switch ($mt) {
+ case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER: //0
+ return UnsignedIntegerObject::createObjectForValue($ai, $val);
+ case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER: //1
+ return NegativeIntegerObject::createObjectForValue($ai, $val);
+ case CBORObject::MAJOR_TYPE_BYTE_STRING: //2
+ $length = $val === null ? $ai : Utils::binToInt($val);
+
+ return ByteStringObject::create($stream->read($length));
+ case CBORObject::MAJOR_TYPE_TEXT_STRING: //3
+ $length = $val === null ? $ai : Utils::binToInt($val);
+
+ return TextStringObject::create($stream->read($length));
+ case CBORObject::MAJOR_TYPE_LIST: //4
+ $object = ListObject::create();
+ $nbItems = $val === null ? $ai : Utils::binToInt($val);
+ for ($i = 0; $i < $nbItems; ++$i) {
+ $object->add($this->process($stream, false));
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_MAP: //5
+ $object = MapObject::create();
+ $nbItems = $val === null ? $ai : Utils::binToInt($val);
+ for ($i = 0; $i < $nbItems; ++$i) {
+ $object->add($this->process($stream, false), $this->process($stream, false));
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_TAG: //6
+ return $this->tagManager->createObjectForValue($ai, $val, $this->process($stream, false));
+ case CBORObject::MAJOR_TYPE_OTHER_TYPE: //7
+ return $this->otherObjectManager->createObjectForValue($ai, $val);
+ default:
+ throw new RuntimeException(sprintf(
+ 'Unsupported major type "%s" (%d).',
+ str_pad(decbin($mt), 5, '0', STR_PAD_LEFT),
+ $mt
+ )); // Should never append
+ }
+ }
+
+ private function processInfinite(Stream $stream, int $mt, bool $breakable): CBORObject
+ {
+ switch ($mt) {
+ case CBORObject::MAJOR_TYPE_BYTE_STRING: //2
+ $object = IndefiniteLengthByteStringObject::create();
+ while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
+ if (! $it instanceof ByteStringObject) {
+ throw new RuntimeException(
+ 'Unable to parse the data. Infinite Byte String object can only get Byte String objects.'
+ );
+ }
+ $object->add($it);
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_TEXT_STRING: //3
+ $object = IndefiniteLengthTextStringObject::create();
+ while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
+ if (! $it instanceof TextStringObject) {
+ throw new RuntimeException(
+ 'Unable to parse the data. Infinite Text String object can only get Text String objects.'
+ );
+ }
+ $object->add($it);
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_LIST: //4
+ $object = IndefiniteLengthListObject::create();
+ $it = $this->process($stream, true);
+ while (! $it instanceof BreakObject) {
+ $object->add($it);
+ $it = $this->process($stream, true);
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_MAP: //5
+ $object = IndefiniteLengthMapObject::create();
+ while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
+ $object->add($it, $this->process($stream, false));
+ }
+
+ return $object;
+ case CBORObject::MAJOR_TYPE_OTHER_TYPE: //7
+ if (! $breakable) {
+ throw new InvalidArgumentException('Cannot parse the data. No enclosing indefinite.');
+ }
+
+ return BreakObject::create();
+ case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER: //0
+ case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER: //1
+ case CBORObject::MAJOR_TYPE_TAG: //6
+ default:
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot parse the data. Found infinite length for Major Type "%s" (%d).',
+ str_pad(decbin($mt), 5, '0', STR_PAD_LEFT),
+ $mt
+ ));
+ }
+ }
+
+ private function generateTagManager(): TagManagerInterface
+ {
+ return TagManager::create()
+ ->add(DatetimeTag::class)
+ ->add(TimestampTag::class)
+
+ ->add(UnsignedBigIntegerTag::class)
+ ->add(NegativeBigIntegerTag::class)
+
+ ->add(DecimalFractionTag::class)
+ ->add(BigFloatTag::class)
+
+ ->add(Base64UrlEncodingTag::class)
+ ->add(Base64EncodingTag::class)
+ ->add(Base16EncodingTag::class)
+ ->add(CBOREncodingTag::class)
+
+ ->add(UriTag::class)
+ ->add(Base64UrlTag::class)
+ ->add(Base64Tag::class)
+ ->add(MimeTag::class)
+
+ ->add(CBORTag::class)
+ ;
+ }
+
+ private function generateOtherObjectManager(): OtherObjectManagerInterface
+ {
+ return OtherObjectManager::create()
+ ->add(BreakObject::class)
+ ->add(SimpleObject::class)
+ ->add(FalseObject::class)
+ ->add(TrueObject::class)
+ ->add(NullObject::class)
+ ->add(UndefinedObject::class)
+ ->add(HalfPrecisionFloatObject::class)
+ ->add(SinglePrecisionFloatObject::class)
+ ->add(DoublePrecisionFloatObject::class)
+ ;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+interface DecoderInterface
+{
+ public function decode(Stream $stream): CBORObject;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @final
+ */
+class IndefiniteLengthByteStringObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_BYTE_STRING;
+
+ private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
+
+ /**
+ * @var ByteStringObject[]
+ */
+ private $chunks = [];
+
+ public function __construct()
+ {
+ parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ foreach ($this->chunks as $chunk) {
+ $result .= $chunk->__toString();
+ }
+
+ return $result . "\xFF";
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public function add(ByteStringObject $chunk): self
+ {
+ $this->chunks[] = $chunk;
+
+ return $this;
+ }
+
+ public function append(string $chunk): self
+ {
+ $this->add(ByteStringObject::create($chunk));
+
+ return $this;
+ }
+
+ public function getValue(): string
+ {
+ $result = '';
+ foreach ($this->chunks as $chunk) {
+ $result .= $chunk->getValue();
+ }
+
+ return $result;
+ }
+
+ public function getLength(): int
+ {
+ $length = 0;
+ foreach ($this->chunks as $chunk) {
+ $length += $chunk->getLength();
+ }
+
+ return $length;
+ }
+
+ public function normalize(): string
+ {
+ $result = '';
+ foreach ($this->chunks as $chunk) {
+ $result .= $chunk->normalize();
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ $result = '';
+ foreach ($this->chunks as $chunk) {
+ $result .= $chunk->getNormalizedData($ignoreTags);
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use function array_key_exists;
+use ArrayAccess;
+use ArrayIterator;
+use function count;
+use Countable;
+use InvalidArgumentException;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-implements ArrayAccess<int, CBORObject>
+ * @phpstan-implements IteratorAggregate<int, CBORObject>
+ * @final
+ */
+class IndefiniteLengthListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_LIST;
+
+ private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
+
+ /**
+ * @var CBORObject[]
+ */
+ private $data = [];
+
+ public function __construct()
+ {
+ parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ foreach ($this->data as $object) {
+ $result .= (string) $object;
+ }
+
+ return $result . "\xFF";
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function normalize(): array
+ {
+ return array_map(static function (CBORObject $object) {
+ return $object instanceof Normalizable ? $object->normalize() : $object;
+ }, $this->data);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return mixed[]
+ */
+ public function getNormalizedData(bool $ignoreTags = false): array
+ {
+ return array_map(static function (CBORObject $object) use ($ignoreTags) {
+ return $object->getNormalizedData($ignoreTags);
+ }, $this->data);
+ }
+
+ public function add(CBORObject $item): self
+ {
+ $this->data[] = $item;
+
+ return $this;
+ }
+
+ public function has(int $index): bool
+ {
+ return array_key_exists($index, $this->data);
+ }
+
+ public function remove(int $index): self
+ {
+ if (! $this->has($index)) {
+ return $this;
+ }
+ unset($this->data[$index]);
+ $this->data = array_values($this->data);
+
+ return $this;
+ }
+
+ public function get(int $index): CBORObject
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ return $this->data[$index];
+ }
+
+ public function set(int $index, CBORObject $object): self
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ $this->data[$index] = $object;
+
+ return $this;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. No replacement
+ */
+ public function count(): int
+ {
+ return count($this->data);
+ }
+
+ /**
+ * @return Iterator<int, CBORObject>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->data);
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ public function offsetGet($offset): CBORObject
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if ($offset === null) {
+ $this->add($value);
+
+ return;
+ }
+
+ $this->set($offset, $value);
+ }
+
+ public function offsetUnset($offset): void
+ {
+ $this->remove($offset);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use function array_key_exists;
+use ArrayAccess;
+use ArrayIterator;
+use function count;
+use Countable;
+use InvalidArgumentException;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-implements ArrayAccess<int, CBORObject>
+ * @phpstan-implements IteratorAggregate<int, MapItem>
+ * @final
+ */
+class IndefiniteLengthMapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_MAP;
+
+ private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
+
+ /**
+ * @var MapItem[]
+ */
+ private $data = [];
+
+ public function __construct()
+ {
+ parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ foreach ($this->data as $object) {
+ $result .= (string) $object->getKey();
+ $result .= (string) $object->getValue();
+ }
+
+ return $result . "\xFF";
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please use "add" instead
+ */
+ public function append(CBORObject $key, CBORObject $value): self
+ {
+ return $this->add($key, $value);
+ }
+
+ public function add(CBORObject $key, CBORObject $value): self
+ {
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+ $this->data[$key->normalize()] = MapItem::create($key, $value);
+
+ return $this;
+ }
+
+ /**
+ * @param int|string $key
+ */
+ public function has($key): bool
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ /**
+ * @param int|string $index
+ */
+ public function remove($index): self
+ {
+ if (! $this->has($index)) {
+ return $this;
+ }
+ unset($this->data[$index]);
+ $this->data = array_values($this->data);
+
+ return $this;
+ }
+
+ /**
+ * @param int|string $index
+ */
+ public function get($index): CBORObject
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ return $this->data[$index]->getValue();
+ }
+
+ public function set(MapItem $object): self
+ {
+ $key = $object->getKey();
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+
+ $this->data[$key->normalize()] = $object;
+
+ return $this;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. No replacement
+ */
+ public function count(): int
+ {
+ return count($this->data);
+ }
+
+ /**
+ * @return Iterator<int, MapItem>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->data);
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function normalize(): array
+ {
+ return array_reduce($this->data, static function (array $carry, MapItem $item): array {
+ $key = $item->getKey();
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+ $valueObject = $item->getValue();
+ $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject;
+
+ return $carry;
+ }, []);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return mixed[]
+ */
+ public function getNormalizedData(bool $ignoreTags = false): array
+ {
+ return array_reduce($this->data, static function (array $carry, MapItem $item) use ($ignoreTags): array {
+ $key = $item->getKey();
+ $valueObject = $item->getValue();
+ $carry[$key->getNormalizedData($ignoreTags)] = $valueObject->getNormalizedData($ignoreTags);
+
+ return $carry;
+ }, []);
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ public function offsetGet($offset): CBORObject
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if (! $offset instanceof CBORObject) {
+ throw new InvalidArgumentException('Invalid key');
+ }
+ if (! $value instanceof CBORObject) {
+ throw new InvalidArgumentException('Invalid value');
+ }
+
+ $this->set(MapItem::create($offset, $value));
+ }
+
+ public function offsetUnset($offset): void
+ {
+ $this->remove($offset);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @final
+ */
+class IndefiniteLengthTextStringObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING;
+
+ private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
+
+ /**
+ * @var TextStringObject[]
+ */
+ private $data = [];
+
+ public function __construct()
+ {
+ parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ foreach ($this->data as $object) {
+ $result .= (string) $object;
+ }
+
+ return $result . "\xFF";
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public function add(TextStringObject $chunk): self
+ {
+ $this->data[] = $chunk;
+
+ return $this;
+ }
+
+ public function append(string $chunk): self
+ {
+ $this->add(TextStringObject::create($chunk));
+
+ return $this;
+ }
+
+ public function getValue(): string
+ {
+ $result = '';
+ foreach ($this->data as $object) {
+ $result .= $object->getValue();
+ }
+
+ return $result;
+ }
+
+ public function getLength(): int
+ {
+ $length = 0;
+ foreach ($this->data as $object) {
+ $length += $object->getLength();
+ }
+
+ return $length;
+ }
+
+ public function normalize(): string
+ {
+ $result = '';
+ foreach ($this->data as $object) {
+ $result .= $object->normalize();
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ $result = '';
+ foreach ($this->data as $object) {
+ $result .= $object->getNormalizedData($ignoreTags);
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use IndefiniteLengthListObject instead
+ */
+final class InfiniteListObject extends IndefiniteLengthListObject
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use IndefiniteLengthMapObject instead
+ */
+final class InfiniteMapObject extends IndefiniteLengthMapObject
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use Brick\Math\BigInteger;
+use function chr;
+use function count;
+use InvalidArgumentException;
+use const STR_PAD_LEFT;
+
+final class LengthCalculator
+{
+ /**
+ * @return array{int, null|string}
+ */
+ public static function getLengthOfString(string $data): array
+ {
+ $length = mb_strlen($data, '8bit');
+
+ return self::computeLength($length);
+ }
+
+ /**
+ * @param array<int|string, mixed> $data
+ *
+ * @return array{int, null|string}
+ */
+ public static function getLengthOfArray(array $data): array
+ {
+ $length = count($data);
+
+ return self::computeLength($length);
+ }
+
+ /**
+ * @return array{int, null|string}
+ */
+ private static function computeLength(int $length): array
+ {
+ switch (true) {
+ case $length <= 23:
+ return [$length, null];
+ case $length <= 0xFF:
+ return [CBORObject::LENGTH_1_BYTE, chr($length)];
+ case $length <= 0xFFFF:
+ return [CBORObject::LENGTH_2_BYTES, self::hex2bin(dechex($length))];
+ case $length <= 0xFFFFFFFF:
+ return [CBORObject::LENGTH_4_BYTES, self::hex2bin(dechex($length))];
+ case BigInteger::of($length)->isLessThanOrEqualTo(BigInteger::fromBase('FFFFFFFFFFFFFFFF', 16)):
+ return [CBORObject::LENGTH_8_BYTES, self::hex2bin(dechex($length))];
+ default:
+ return [CBORObject::LENGTH_INDEFINITE, null];
+ }
+ }
+
+ private static function hex2bin(string $data): string
+ {
+ $data = str_pad($data, (int) (2 ** ceil(log(mb_strlen($data, '8bit'), 2))), '0', STR_PAD_LEFT);
+ $result = hex2bin($data);
+ if ($result === false) {
+ throw new InvalidArgumentException('Unable to convert the data');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use function array_key_exists;
+use ArrayAccess;
+use ArrayIterator;
+use function count;
+use Countable;
+use InvalidArgumentException;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-implements ArrayAccess<int, CBORObject>
+ * @phpstan-implements IteratorAggregate<int, CBORObject>
+ */
+class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_LIST;
+
+ /**
+ * @var CBORObject[]
+ */
+ private $data;
+
+ /**
+ * @var string|null
+ */
+ private $length;
+
+ /**
+ * @param CBORObject[] $data
+ */
+ public function __construct(array $data = [])
+ {
+ [$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data);
+ array_map(static function ($item): void {
+ if (! $item instanceof CBORObject) {
+ throw new InvalidArgumentException('The list must contain only CBORObject objects.');
+ }
+ }, $data);
+
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = array_values($data);
+ $this->length = $length;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->length !== null) {
+ $result .= $this->length;
+ }
+ foreach ($this->data as $object) {
+ $result .= (string) $object;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param CBORObject[] $data
+ */
+ public static function create(array $data = []): self
+ {
+ return new self($data);
+ }
+
+ public function add(CBORObject $object): self
+ {
+ $this->data[] = $object;
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ public function has(int $index): bool
+ {
+ return array_key_exists($index, $this->data);
+ }
+
+ public function remove(int $index): self
+ {
+ if (! $this->has($index)) {
+ return $this;
+ }
+ unset($this->data[$index]);
+ $this->data = array_values($this->data);
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ public function get(int $index): CBORObject
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ return $this->data[$index];
+ }
+
+ public function set(int $index, CBORObject $object): self
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ $this->data[$index] = $object;
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ /**
+ * @return array<int, mixed>
+ */
+ public function normalize(): array
+ {
+ return array_map(static function (CBORObject $object) {
+ return $object instanceof Normalizable ? $object->normalize() : $object;
+ }, $this->data);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return array<int|string, mixed>
+ */
+ public function getNormalizedData(bool $ignoreTags = false): array
+ {
+ return array_map(static function (CBORObject $object) use ($ignoreTags) {
+ return $object->getNormalizedData($ignoreTags);
+ }, $this->data);
+ }
+
+ public function count(): int
+ {
+ return count($this->data);
+ }
+
+ /**
+ * @return Iterator<int, CBORObject>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->data);
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ public function offsetGet($offset): CBORObject
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if ($offset === null) {
+ $this->add($value);
+
+ return;
+ }
+
+ $this->set($offset, $value);
+ }
+
+ public function offsetUnset($offset): void
+ {
+ $this->remove($offset);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+class MapItem
+{
+ /**
+ * @var CBORObject
+ */
+ private $key;
+
+ /**
+ * @var CBORObject
+ */
+ private $value;
+
+ public function __construct(CBORObject $key, CBORObject $value)
+ {
+ $this->key = $key;
+ $this->value = $value;
+ }
+
+ public static function create(CBORObject $key, CBORObject $value): self
+ {
+ return new self($key, $value);
+ }
+
+ public function getKey(): CBORObject
+ {
+ return $this->key;
+ }
+
+ public function getValue(): CBORObject
+ {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use function array_key_exists;
+use ArrayAccess;
+use ArrayIterator;
+use function count;
+use Countable;
+use InvalidArgumentException;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-implements ArrayAccess<int, CBORObject>
+ * @phpstan-implements IteratorAggregate<int, MapItem>
+ */
+final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_MAP;
+
+ /**
+ * @var MapItem[]
+ */
+ private $data;
+
+ /**
+ * @var string|null
+ */
+ private $length;
+
+ /**
+ * @param MapItem[] $data
+ */
+ public function __construct(array $data = [])
+ {
+ [$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data);
+ array_map(static function ($item): void {
+ if (! $item instanceof MapItem) {
+ throw new InvalidArgumentException('The list must contain only MapItem objects.');
+ }
+ }, $data);
+
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ $this->length = $length;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->length !== null) {
+ $result .= $this->length;
+ }
+ foreach ($this->data as $object) {
+ $result .= $object->getKey()
+ ->__toString()
+ ;
+ $result .= $object->getValue()
+ ->__toString()
+ ;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param MapItem[] $data
+ */
+ public static function create(array $data = []): self
+ {
+ return new self($data);
+ }
+
+ public function add(CBORObject $key, CBORObject $value): self
+ {
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+ $this->data[$key->normalize()] = MapItem::create($key, $value);
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ /**
+ * @param int|string $key
+ */
+ public function has($key): bool
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ /**
+ * @param int|string $index
+ */
+ public function remove($index): self
+ {
+ if (! $this->has($index)) {
+ return $this;
+ }
+ unset($this->data[$index]);
+ $this->data = array_values($this->data);
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ /**
+ * @param int|string $index
+ */
+ public function get($index): CBORObject
+ {
+ if (! $this->has($index)) {
+ throw new InvalidArgumentException('Index not found.');
+ }
+
+ return $this->data[$index]->getValue();
+ }
+
+ public function set(MapItem $object): self
+ {
+ $key = $object->getKey();
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+
+ $this->data[$key->normalize()] = $object;
+ [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
+
+ return $this;
+ }
+
+ public function count(): int
+ {
+ return count($this->data);
+ }
+
+ /**
+ * @return Iterator<int, MapItem>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->data);
+ }
+
+ /**
+ * @return array<int|string, mixed>
+ */
+ public function normalize(): array
+ {
+ return array_reduce($this->data, static function (array $carry, MapItem $item): array {
+ $key = $item->getKey();
+ if (! $key instanceof Normalizable) {
+ throw new InvalidArgumentException('Invalid key. Shall be normalizable');
+ }
+ $valueObject = $item->getValue();
+ $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject;
+
+ return $carry;
+ }, []);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return array<int|string, mixed>
+ */
+ public function getNormalizedData(bool $ignoreTags = false): array
+ {
+ return array_reduce($this->data, static function (array $carry, MapItem $item) use ($ignoreTags): array {
+ $key = $item->getKey();
+ $valueObject = $item->getValue();
+ $carry[$key->getNormalizedData($ignoreTags)] = $valueObject->getNormalizedData($ignoreTags);
+
+ return $carry;
+ }, []);
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ public function offsetGet($offset): CBORObject
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if (! $offset instanceof CBORObject) {
+ throw new InvalidArgumentException('Invalid key');
+ }
+ if (! $value instanceof CBORObject) {
+ throw new InvalidArgumentException('Invalid value');
+ }
+
+ $this->set(MapItem::create($offset, $value));
+ }
+
+ public function offsetUnset($offset): void
+ {
+ $this->remove($offset);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use Brick\Math\BigInteger;
+use InvalidArgumentException;
+use const STR_PAD_LEFT;
+
+/**
+ * @final
+ */
+class NegativeIntegerObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_NEGATIVE_INTEGER;
+
+ /**
+ * @var string|null
+ */
+ private $data;
+
+ public function __construct(int $additionalInformation, ?string $data)
+ {
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->data !== null) {
+ $result .= $this->data;
+ }
+
+ return $result;
+ }
+
+ public static function createObjectForValue(int $additionalInformation, ?string $data): self
+ {
+ return new self($additionalInformation, $data);
+ }
+
+ public static function create(int $value): self
+ {
+ return self::createFromString((string) $value);
+ }
+
+ public static function createFromString(string $value): self
+ {
+ $integer = BigInteger::of($value);
+
+ return self::createBigInteger($integer);
+ }
+
+ public function getValue(): string
+ {
+ if ($this->data === null) {
+ return (string) (-1 - $this->additionalInformation);
+ }
+
+ $result = Utils::binToBigInteger($this->data);
+ $minusOne = BigInteger::of(-1);
+
+ return $minusOne->minus($result)
+ ->toBase(10)
+ ;
+ }
+
+ public function normalize(): string
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ return $this->getValue();
+ }
+
+ private static function createBigInteger(BigInteger $integer): self
+ {
+ if ($integer->isGreaterThanOrEqualTo(BigInteger::zero())) {
+ throw new InvalidArgumentException('The value must be a negative integer.');
+ }
+
+ $minusOne = BigInteger::of(-1);
+ $computed_value = $minusOne->minus($integer);
+
+ switch (true) {
+ case $computed_value->isLessThan(BigInteger::of(24)):
+ $ai = $computed_value->toInt();
+ $data = null;
+ break;
+ case $computed_value->isLessThan(BigInteger::fromBase('FF', 16)):
+ $ai = 24;
+ $data = self::hex2bin(str_pad($computed_value->toBase(16), 2, '0', STR_PAD_LEFT));
+ break;
+ case $computed_value->isLessThan(BigInteger::fromBase('FFFF', 16)):
+ $ai = 25;
+ $data = self::hex2bin(str_pad($computed_value->toBase(16), 4, '0', STR_PAD_LEFT));
+ break;
+ case $computed_value->isLessThan(BigInteger::fromBase('FFFFFFFF', 16)):
+ $ai = 26;
+ $data = self::hex2bin(str_pad($computed_value->toBase(16), 8, '0', STR_PAD_LEFT));
+ break;
+ default:
+ throw new InvalidArgumentException(
+ 'Out of range. Please use NegativeBigIntegerTag tag with ByteStringObject object instead.'
+ );
+ }
+
+ return new self($ai, $data);
+ }
+
+ private static function hex2bin(string $data): string
+ {
+ $result = hex2bin($data);
+ if ($result === false) {
+ throw new InvalidArgumentException('Unable to convert the data');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+interface Normalizable
+{
+ /**
+ * @return mixed|null
+ */
+ public function normalize();
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+abstract class OtherObject extends AbstractCBORObject
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_OTHER_TYPE;
+
+ /**
+ * @var string|null
+ */
+ protected $data;
+
+ public function __construct(int $additionalInformation, ?string $data)
+ {
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->data !== null) {
+ $result .= $this->data;
+ }
+
+ return $result;
+ }
+
+ public function getContent(): ?string
+ {
+ return $this->data;
+ }
+
+ /**
+ * @return int[]
+ */
+ abstract public static function supportedAdditionalInformation(): array;
+
+ abstract public static function createFromLoadedData(int $additionalInformation, ?string $data): self;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\OtherObject as Base;
+
+final class BreakObject extends Base
+{
+ public function __construct()
+ {
+ parent::__construct(self::OBJECT_BREAK, null);
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_BREAK];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): bool
+ {
+ return false;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use Brick\Math\BigInteger;
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+use CBOR\Utils;
+use const INF;
+use InvalidArgumentException;
+use const NAN;
+
+final class DoublePrecisionFloatObject extends Base implements Normalizable
+{
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_DOUBLE_PRECISION_FLOAT];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self($additionalInformation, $data);
+ }
+
+ public static function create(string $value): self
+ {
+ if (mb_strlen($value, '8bit') !== 8) {
+ throw new InvalidArgumentException('The value is not a valid double precision floating point');
+ }
+
+ return new self(self::OBJECT_DOUBLE_PRECISION_FLOAT, $value);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->normalize();
+ }
+
+ /**
+ * @return float|int
+ */
+ public function normalize()
+ {
+ $exponent = $this->getExponent();
+ $mantissa = $this->getMantissa();
+ $sign = $this->getSign();
+
+ if ($exponent === 0) {
+ $val = $mantissa * 2 ** (-(1022 + 52));
+ } elseif ($exponent !== 0b11111111111) {
+ $val = ($mantissa + (1 << 52)) * 2 ** ($exponent - (1023 + 52));
+ } else {
+ $val = $mantissa === 0 ? INF : NAN;
+ }
+
+ return $sign * $val;
+ }
+
+ public function getExponent(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->shiftedRight(52)->and(Utils::hexToBigInteger('7ff'))->toInt();
+ }
+
+ public function getMantissa(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('fffffffffffff'))->toInt();
+ }
+
+ public function getSign(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+ $sign = Utils::binToBigInteger($data)->shiftedRight(63);
+
+ return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+
+final class FalseObject extends Base implements Normalizable
+{
+ public function __construct()
+ {
+ parent::__construct(self::OBJECT_FALSE, null);
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_FALSE];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self();
+ }
+
+ public function normalize(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): bool
+ {
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\OtherObject as Base;
+use InvalidArgumentException;
+use function ord;
+
+final class GenericObject extends Base
+{
+ public static function supportedAdditionalInformation(): array
+ {
+ return [];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ if ($data !== null && ord($data) < 32) {
+ throw new InvalidArgumentException('Invalid simple value. Content data should not be present.');
+ }
+
+ return new self($additionalInformation, $data);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->data;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use Brick\Math\BigInteger;
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+use CBOR\Utils;
+use const INF;
+use InvalidArgumentException;
+use const NAN;
+
+final class HalfPrecisionFloatObject extends Base implements Normalizable
+{
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_HALF_PRECISION_FLOAT];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self($additionalInformation, $data);
+ }
+
+ public static function create(string $value): self
+ {
+ if (mb_strlen($value, '8bit') !== 2) {
+ throw new InvalidArgumentException('The value is not a valid half precision floating point');
+ }
+
+ return new self(self::OBJECT_HALF_PRECISION_FLOAT, $value);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->normalize();
+ }
+
+ /**
+ * @return float|int
+ */
+ public function normalize()
+ {
+ $exponent = $this->getExponent();
+ $mantissa = $this->getMantissa();
+ $sign = $this->getSign();
+
+ if ($exponent === 0) {
+ $val = $mantissa * 2 ** (-24);
+ } elseif ($exponent !== 0b11111) {
+ $val = ($mantissa + (1 << 10)) * 2 ** ($exponent - 25);
+ } else {
+ $val = $mantissa === 0 ? INF : NAN;
+ }
+
+ return $sign * $val;
+ }
+
+ public function getExponent(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->shiftedRight(10)->and(Utils::hexToBigInteger('1f'))->toInt();
+ }
+
+ public function getMantissa(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('3ff'))->toInt();
+ }
+
+ public function getSign(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+ $sign = Utils::binToBigInteger($data)->shiftedRight(15);
+
+ return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+
+final class NullObject extends Base implements Normalizable
+{
+ public function __construct()
+ {
+ parent::__construct(self::OBJECT_NULL, null);
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_NULL];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self();
+ }
+
+ public function normalize(): ?string
+ {
+ return null;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use function array_key_exists;
+use CBOR\OtherObject;
+use InvalidArgumentException;
+
+/**
+ * @final
+ */
+class OtherObjectManager implements OtherObjectManagerInterface
+{
+ /**
+ * @var string[]
+ */
+ private $classes = [];
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public function add(string $class): self
+ {
+ foreach ($class::supportedAdditionalInformation() as $ai) {
+ if ($ai < 0) {
+ throw new InvalidArgumentException('Invalid additional information.');
+ }
+ $this->classes[$ai] = $class;
+ }
+
+ return $this;
+ }
+
+ public function getClassForValue(int $value): string
+ {
+ return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericObject::class;
+ }
+
+ public function createObjectForValue(int $value, ?string $data): OtherObject
+ {
+ /** @var OtherObject $class */
+ $class = $this->getClassForValue($value);
+
+ return $class::createFromLoadedData($value, $data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\OtherObject;
+
+interface OtherObjectManagerInterface
+{
+ public function createObjectForValue(int $value, ?string $data): OtherObject;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+use CBOR\Utils;
+use function chr;
+use InvalidArgumentException;
+use function ord;
+
+final class SimpleObject extends Base implements Normalizable
+{
+ public static function supportedAdditionalInformation(): array
+ {
+ return array_merge(range(0, 19), [24]);
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ if ($additionalInformation === 24) {
+ if ($data === null) {
+ throw new InvalidArgumentException('Invalid simple value. Content data is missing.');
+ }
+ if (mb_strlen($data, '8bit') !== 1) {
+ throw new InvalidArgumentException('Invalid simple value. Content data is too long.');
+ }
+ if (ord($data) < 32) {
+ throw new InvalidArgumentException('Invalid simple value. Content data must be between 32 and 255.');
+ }
+ } elseif ($additionalInformation < 20) {
+ if ($data !== null) {
+ throw new InvalidArgumentException('Invalid simple value. Content data should not be present.');
+ }
+ }
+
+ return new self($additionalInformation, $data);
+ }
+
+ public function normalize(): int
+ {
+ if ($this->data === null) {
+ return $this->getAdditionalInformation();
+ }
+
+ return Utils::binToInt($this->data);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): int
+ {
+ return $this->normalize();
+ }
+
+ public static function create(int $value): self
+ {
+ switch (true) {
+ case $value < 32:
+ return new self($value, null);
+ case $value < 256:
+ return new self(24, chr($value));
+ default:
+ throw new InvalidArgumentException('The value is not a valid simple value.');
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use Brick\Math\BigInteger;
+use CBOR\OtherObject as Base;
+use CBOR\Utils;
+use const INF;
+use InvalidArgumentException;
+use const NAN;
+
+final class SinglePrecisionFloatObject extends Base
+{
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_SINGLE_PRECISION_FLOAT];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self($additionalInformation, $data);
+ }
+
+ public static function create(string $value): self
+ {
+ if (mb_strlen($value, '8bit') !== 4) {
+ throw new InvalidArgumentException('The value is not a valid single precision floating point');
+ }
+
+ return new self(self::OBJECT_SINGLE_PRECISION_FLOAT, $value);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->normalize();
+ }
+
+ /**
+ * @return float|int
+ */
+ public function normalize()
+ {
+ $exponent = $this->getExponent();
+ $mantissa = $this->getMantissa();
+ $sign = $this->getSign();
+
+ if ($exponent === 0) {
+ $val = $mantissa * 2 ** (-(126 + 23));
+ } elseif ($exponent !== 0b11111111) {
+ $val = ($mantissa + (1 << 23)) * 2 ** ($exponent - (127 + 23));
+ } else {
+ $val = $mantissa === 0 ? INF : NAN;
+ }
+
+ return $sign * $val;
+ }
+
+ public function getExponent(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->shiftedRight(23)->and(Utils::hexToBigInteger('ff'))->toInt();
+ }
+
+ public function getMantissa(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+
+ return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('7fffff'))->toInt();
+ }
+
+ public function getSign(): int
+ {
+ $data = $this->data;
+ Utils::assertString($data, 'Invalid data');
+ $sign = Utils::binToBigInteger($data)->shiftedRight(31);
+
+ return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\Normalizable;
+use CBOR\OtherObject as Base;
+
+final class TrueObject extends Base implements Normalizable
+{
+ public function __construct()
+ {
+ parent::__construct(self::OBJECT_TRUE, null);
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_TRUE];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self();
+ }
+
+ public function normalize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): bool
+ {
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\OtherObject;
+
+use CBOR\OtherObject as Base;
+
+final class UndefinedObject extends Base
+{
+ public function __construct()
+ {
+ parent::__construct(self::OBJECT_UNDEFINED, null);
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public static function supportedAdditionalInformation(): array
+ {
+ return [self::OBJECT_UNDEFINED];
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
+ {
+ return new self();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ *
+ * @return string
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return 'undefined';
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use NegativeIntegerObject instead
+ */
+final class SignedIntegerObject extends NegativeIntegerObject
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+interface Stream
+{
+ public function read(int $length): string;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use InvalidArgumentException;
+use RuntimeException;
+
+final class StringStream implements Stream
+{
+ /**
+ * @var resource
+ */
+ private $resource;
+
+ public function __construct(string $data)
+ {
+ $resource = fopen('php://memory', 'rb+');
+ if ($resource === false) {
+ throw new RuntimeException('Unable to open the memory');
+ }
+ $result = fwrite($resource, $data);
+ if ($result === false) {
+ throw new RuntimeException('Unable to write the memory');
+ }
+ $result = rewind($resource);
+ if ($result === false) {
+ throw new RuntimeException('Unable to rewind the memory');
+ }
+ $this->resource = $resource;
+ }
+
+ public static function create(string $data): self
+ {
+ return new self($data);
+ }
+
+ public function read(int $length): string
+ {
+ if ($length === 0) {
+ return '';
+ }
+
+ $alreadyRead = 0;
+ $data = '';
+ while ($alreadyRead < $length) {
+ $left = $length - $alreadyRead;
+ $sizeToRead = $left < 1024 && $left > 0 ? $left : 1024;
+ $newData = fread($this->resource, $sizeToRead);
+ $alreadyRead += $sizeToRead;
+
+ if ($newData === false) {
+ throw new RuntimeException('Unable to read the memory');
+ }
+ if (mb_strlen($newData, '8bit') < $sizeToRead) {
+ throw new InvalidArgumentException(sprintf(
+ 'Out of range. Expected: %d, read: %d.',
+ $length,
+ mb_strlen($data, '8bit')
+ ));
+ }
+ $data .= $newData;
+ }
+
+ if (mb_strlen($data, '8bit') !== $length) {
+ throw new InvalidArgumentException(sprintf(
+ 'Out of range. Expected: %d, read: %d.',
+ $length,
+ mb_strlen($data, '8bit')
+ ));
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use InvalidArgumentException;
+
+abstract class Tag extends AbstractCBORObject
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_TAG;
+
+ /**
+ * @var string|null
+ */
+ protected $data;
+
+ /**
+ * @var CBORObject
+ */
+ protected $object;
+
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ $this->object = $object;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->data !== null) {
+ $result .= $this->data;
+ }
+
+ return $result . $this->object;
+ }
+
+ public function getData(): ?string
+ {
+ return $this->data;
+ }
+
+ abstract public static function getTagId(): int;
+
+ abstract public static function createFromLoadedData(
+ int $additionalInformation,
+ ?string $data,
+ CBORObject $object
+ ): self;
+
+ public function getValue(): CBORObject
+ {
+ return $this->object;
+ }
+
+ /**
+ * @return array{int, null|string}
+ */
+ protected static function determineComponents(int $tag): array
+ {
+ switch (true) {
+ case $tag < 0:
+ throw new InvalidArgumentException('The value must be a positive integer.');
+ case $tag < 24:
+ return [$tag, null];
+ case $tag < 0xFF:
+ return [24, self::hex2bin(dechex($tag))];
+ case $tag < 0xFFFF:
+ return [25, self::hex2bin(dechex($tag))];
+ case $tag < 0xFFFFFFFF:
+ return [26, self::hex2bin(dechex($tag))];
+ default:
+ throw new InvalidArgumentException(
+ 'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'
+ );
+ }
+ }
+
+ private static function hex2bin(string $data): string
+ {
+ $result = hex2bin($data);
+ if ($result === false) {
+ throw new InvalidArgumentException('Unable to convert the data');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+
+final class Base16EncodingTag extends Tag
+{
+ public static function getTagId(): int
+ {
+ return self::TAG_ENCODED_BASE16;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE16);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ByteStringObject && ! $this->object instanceof IndefiniteLengthByteStringObject && ! $this->object instanceof TextStringObject && ! $this->object instanceof IndefiniteLengthTextStringObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return bin2hex($this->object->getNormalizedData($ignoreTags));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use InvalidArgumentException;
+
+final class Base64EncodingTag extends Tag
+{
+ public static function getTagId(): int
+ {
+ return self::TAG_ENCODED_BASE64;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ByteStringObject && ! $this->object instanceof IndefiniteLengthByteStringObject && ! $this->object instanceof TextStringObject && ! $this->object instanceof IndefiniteLengthTextStringObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ $result = base64_decode($this->object->getNormalizedData($ignoreTags), true);
+ if ($result === false) {
+ throw new InvalidArgumentException('Unable to decode the data');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use InvalidArgumentException;
+
+final class Base64Tag extends Tag
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Text String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_BASE64;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_BASE64);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use CBOR\Utils;
+
+final class Base64UrlEncodingTag extends Tag
+{
+ public static function getTagId(): int
+ {
+ return self::TAG_ENCODED_BASE64_URL;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64_URL);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ByteStringObject && ! $this->object instanceof IndefiniteLengthByteStringObject && ! $this->object instanceof TextStringObject && ! $this->object instanceof IndefiniteLengthTextStringObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return Utils::decode($this->object->getNormalizedData($ignoreTags));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use InvalidArgumentException;
+
+final class Base64UrlTag extends Tag
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Text String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_BASE64_URL;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_BASE64_URL);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\ListObject;
+use CBOR\NegativeIntegerObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\UnsignedIntegerObject;
+use function count;
+use function extension_loaded;
+use InvalidArgumentException;
+use RuntimeException;
+
+final class BigFloatTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! extension_loaded('bcmath')) {
+ throw new RuntimeException('The extension "bcmath" is required to use this tag');
+ }
+
+ if (! $object instanceof ListObject || count($object) !== 2) {
+ throw new InvalidArgumentException(
+ 'This tag only accepts a ListObject object that contains an exponent and a mantissa.'
+ );
+ }
+ $e = $object->get(0);
+ if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) {
+ throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.');
+ }
+ $m = $object->get(1);
+ if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) {
+ throw new InvalidArgumentException(
+ 'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'
+ );
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_BIG_FLOAT;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_BIG_FLOAT);
+
+ return new self($ai, $data, $object);
+ }
+
+ public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag
+ {
+ $object = ListObject::create()
+ ->add($e)
+ ->add($m)
+ ;
+
+ return self::create($object);
+ }
+
+ public function normalize()
+ {
+ /** @var ListObject $object */
+ $object = $this->object;
+ /** @var UnsignedIntegerObject|NegativeIntegerObject $e */
+ $e = $object->get(0);
+ /** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */
+ $m = $object->get(1);
+
+ return rtrim(bcmul($m->normalize(), bcpow('2', $e->normalize(), 100), 100), '0');
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ListObject || count($this->object) !== 2) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\Tag;
+use InvalidArgumentException;
+
+final class CBOREncodingTag extends Tag
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Byte String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_ENCODED_CBOR;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_ENCODED_CBOR);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+
+final class CBORTag extends Tag implements Normalizable
+{
+ public static function getTagId(): int
+ {
+ return self::TAG_CBOR;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_CBOR);
+
+ return new self($ai, $data, $object);
+ }
+
+ /**
+ * @return mixed|CBORObject|null
+ */
+ public function normalize()
+ {
+ return $this->object instanceof Normalizable ? $this->object->normalize() : $this->object;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use const DATE_RFC3339;
+use DateTimeImmutable;
+use DateTimeInterface;
+use InvalidArgumentException;
+
+/**
+ * @final
+ */
+class DatetimeTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Byte String object.');
+ }
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_STANDARD_DATETIME;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_STANDARD_DATETIME);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): DateTimeInterface
+ {
+ /** @var TextStringObject|IndefiniteLengthTextStringObject $object */
+ $object = $this->object;
+ $result = DateTimeImmutable::createFromFormat(DATE_RFC3339, $object->normalize());
+ if ($result !== false) {
+ return $result;
+ }
+
+ $formatted = DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $object->normalize());
+ if ($formatted === false) {
+ throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object');
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\ListObject;
+use CBOR\NegativeIntegerObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\UnsignedIntegerObject;
+use function count;
+use function extension_loaded;
+use InvalidArgumentException;
+use RuntimeException;
+
+final class DecimalFractionTag extends Tag implements Normalizable
+{
+ public function __construct(CBORObject $object)
+ {
+ if (! extension_loaded('bcmath')) {
+ throw new RuntimeException('The extension "bcmath" is required to use this tag');
+ }
+ if (! $object instanceof ListObject || count($object) !== 2) {
+ throw new InvalidArgumentException(
+ 'This tag only accepts a ListObject object that contains an exponent and a mantissa.'
+ );
+ }
+ $e = $object->get(0);
+ if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) {
+ throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.');
+ }
+ $m = $object->get(1);
+ if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) {
+ throw new InvalidArgumentException(
+ 'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'
+ );
+ }
+
+ parent::__construct(self::TAG_DECIMAL_FRACTION, null, $object);
+ }
+
+ public static function create(CBORObject $object): self
+ {
+ return new self($object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_DECIMAL_FRACTION;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($object);
+ }
+
+ public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag
+ {
+ $object = ListObject::create()
+ ->add($e)
+ ->add($m)
+ ;
+
+ return self::create($object);
+ }
+
+ public function normalize()
+ {
+ /** @var ListObject $object */
+ $object = $this->object;
+ /** @var UnsignedIntegerObject|NegativeIntegerObject $e */
+ $e = $object->get(0);
+ /** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */
+ $m = $object->get(1);
+
+ return rtrim(bcmul($m->normalize(), bcpow('10', $e->normalize(), 100), 100), '0');
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ListObject || count($this->object) !== 2) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+ $e = $this->object->get(0);
+ $m = $this->object->get(1);
+
+ if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+ if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return $this->normalize();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+/**
+ * @deprecated The class EpochTag is deprecated and will be removed in v3.0. Please use DatetimeTag instead
+ */
+final class EpochTag extends DatetimeTag
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\Tag;
+
+final class GenericTag extends Tag
+{
+ public static function getTagId(): int
+ {
+ return -1;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use InvalidArgumentException;
+
+final class MimeTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Byte String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_MIME;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_MIME);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): string
+ {
+ /** @var TextStringObject|IndefiniteLengthTextStringObject $object */
+ $object = $this->object;
+
+ return $object->normalize();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use Brick\Math\BigInteger;
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use InvalidArgumentException;
+
+final class NegativeBigIntegerTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Byte String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_NEGATIVE_BIG_NUM;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_NEGATIVE_BIG_NUM);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): string
+ {
+ /** @var ByteStringObject|IndefiniteLengthByteStringObject $object */
+ $object = $this->object;
+ $integer = BigInteger::fromBase(bin2hex($object->getValue()), 16);
+ $minusOne = BigInteger::of(-1);
+
+ return $minusOne->minus($integer)
+ ->toBase(10)
+ ;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ByteStringObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+ $integer = BigInteger::fromBase(bin2hex($this->object->getValue()), 16);
+ $minusOne = BigInteger::of(-1);
+
+ return $minusOne->minus($integer)
+ ->toBase(10)
+ ;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use UnsignedBigIntegerTag instead
+ */
+final class PositiveBigIntegerTag extends UnsignedBigIntegerTag
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use function array_key_exists;
+use CBOR\CBORObject;
+use CBOR\Tag;
+use CBOR\Utils;
+use InvalidArgumentException;
+
+/**
+ * @final
+ */
+class TagManager implements TagManagerInterface
+{
+ /**
+ * @var string[]
+ */
+ private $classes = [];
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public function add(string $class): self
+ {
+ if ($class::getTagId() < 0) {
+ throw new InvalidArgumentException('Invalid tag ID.');
+ }
+ $this->classes[$class::getTagId()] = $class;
+
+ return $this;
+ }
+
+ public function getClassForValue(int $value): string
+ {
+ return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericTag::class;
+ }
+
+ public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ $value = $additionalInformation;
+ if ($additionalInformation >= 24) {
+ Utils::assertString($data, 'Invalid data');
+ $value = Utils::binToInt($data);
+ }
+ /** @var Tag $class */
+ $class = $this->getClassForValue($value);
+
+ return $class::createFromLoadedData($additionalInformation, $data, $object);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\Tag;
+
+interface TagManagerInterface
+{
+ public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): Tag;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use TagManager instead
+ */
+class TagObjectManager extends TagManager
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\NegativeIntegerObject;
+use CBOR\Normalizable;
+use CBOR\OtherObject\DoublePrecisionFloatObject;
+use CBOR\OtherObject\HalfPrecisionFloatObject;
+use CBOR\OtherObject\SinglePrecisionFloatObject;
+use CBOR\Tag;
+use CBOR\UnsignedIntegerObject;
+use DateTimeImmutable;
+use DateTimeInterface;
+use InvalidArgumentException;
+use const STR_PAD_RIGHT;
+
+final class TimestampTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof UnsignedIntegerObject && ! $object instanceof NegativeIntegerObject && ! $object instanceof HalfPrecisionFloatObject && ! $object instanceof SinglePrecisionFloatObject && ! $object instanceof DoublePrecisionFloatObject) {
+ throw new InvalidArgumentException('This tag only accepts integer-based or float-based objects.');
+ }
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_EPOCH_DATETIME;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_EPOCH_DATETIME);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): DateTimeInterface
+ {
+ $object = $this->object;
+
+ switch (true) {
+ case $object instanceof UnsignedIntegerObject:
+ case $object instanceof NegativeIntegerObject:
+ $formatted = DateTimeImmutable::createFromFormat('U', $object->normalize());
+
+ break;
+ case $object instanceof HalfPrecisionFloatObject:
+ case $object instanceof SinglePrecisionFloatObject:
+ case $object instanceof DoublePrecisionFloatObject:
+ $value = (string) $object->normalize();
+ $parts = explode('.', $value);
+ if (isset($parts[1])) {
+ if (mb_strlen($parts[1], '8bit') > 6) {
+ $parts[1] = mb_substr($parts[1], 0, 6, '8bit');
+ } else {
+ $parts[1] = str_pad($parts[1], 6, '0', STR_PAD_RIGHT);
+ }
+ }
+ $formatted = DateTimeImmutable::createFromFormat('U.u', implode('.', $parts));
+
+ break;
+ default:
+ throw new InvalidArgumentException('Unable to normalize the object');
+ }
+
+ if ($formatted === false) {
+ throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object');
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+ switch (true) {
+ case $this->object instanceof UnsignedIntegerObject:
+ case $this->object instanceof NegativeIntegerObject:
+ case $this->object instanceof HalfPrecisionFloatObject:
+ case $this->object instanceof SinglePrecisionFloatObject:
+ case $this->object instanceof DoublePrecisionFloatObject:
+ return $this->normalize();
+ default:
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\ByteStringObject;
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthByteStringObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\Utils;
+use InvalidArgumentException;
+
+/**
+ * @final
+ */
+class UnsignedBigIntegerTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Byte String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_UNSIGNED_BIG_NUM;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_UNSIGNED_BIG_NUM);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): string
+ {
+ /** @var ByteStringObject|IndefiniteLengthByteStringObject $object */
+ $object = $this->object;
+
+ return Utils::hexToString($object->normalize());
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ if ($ignoreTags) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ if (! $this->object instanceof ByteStringObject) {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+
+ return Utils::hexToString($this->object->getValue());
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR\Tag;
+
+use CBOR\CBORObject;
+use CBOR\IndefiniteLengthTextStringObject;
+use CBOR\Normalizable;
+use CBOR\Tag;
+use CBOR\TextStringObject;
+use InvalidArgumentException;
+
+final class UriTag extends Tag implements Normalizable
+{
+ public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
+ {
+ if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
+ throw new InvalidArgumentException('This tag only accepts a Text String object.');
+ }
+
+ parent::__construct($additionalInformation, $data, $object);
+ }
+
+ public static function getTagId(): int
+ {
+ return self::TAG_URI;
+ }
+
+ public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
+ {
+ return new self($additionalInformation, $data, $object);
+ }
+
+ public static function create(CBORObject $object): Tag
+ {
+ [$ai, $data] = self::determineComponents(self::TAG_URI);
+
+ return new self($ai, $data, $object);
+ }
+
+ public function normalize(): string
+ {
+ /** @var TextStringObject|IndefiniteLengthTextStringObject $object */
+ $object = $this->object;
+
+ return $object->normalize();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false)
+ {
+ return $this->object->getNormalizedData($ignoreTags);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use Tag instead
+ */
+abstract class TagObject extends Tag
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+final class TextStringObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING;
+
+ /**
+ * @var string|null
+ */
+ private $length;
+
+ /**
+ * @var string
+ */
+ private $data;
+
+ public function __construct(string $data)
+ {
+ [$additionalInformation, $length] = LengthCalculator::getLengthOfString($data);
+
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ $this->length = $length;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->length !== null) {
+ $result .= $this->length;
+ }
+
+ return $result . $this->data;
+ }
+
+ public static function create(string $data): self
+ {
+ return new self($data);
+ }
+
+ public function getValue(): string
+ {
+ return $this->data;
+ }
+
+ public function getLength(): int
+ {
+ return mb_strlen($this->data, 'utf8');
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ return $this->data;
+ }
+
+ public function normalize()
+ {
+ return $this->data;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+/**
+ * @deprecated Will be removed in v3.0. Please use IndefiniteLengthTextStringObject instead
+ */
+final class TextStringWithChunkObject extends IndefiniteLengthTextStringObject
+{
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use Brick\Math\BigInteger;
+use InvalidArgumentException;
+use const STR_PAD_LEFT;
+
+final class UnsignedIntegerObject extends AbstractCBORObject implements Normalizable
+{
+ private const MAJOR_TYPE = self::MAJOR_TYPE_UNSIGNED_INTEGER;
+
+ /**
+ * @var string|null
+ */
+ private $data;
+
+ public function __construct(int $additionalInformation, ?string $data)
+ {
+ parent::__construct(self::MAJOR_TYPE, $additionalInformation);
+ $this->data = $data;
+ }
+
+ public function __toString(): string
+ {
+ $result = parent::__toString();
+ if ($this->data !== null) {
+ $result .= $this->data;
+ }
+
+ return $result;
+ }
+
+ public static function createObjectForValue(int $additionalInformation, ?string $data): self
+ {
+ return new self($additionalInformation, $data);
+ }
+
+ public static function create(int $value): self
+ {
+ return self::createFromString((string) $value);
+ }
+
+ public static function createFromHex(string $value): self
+ {
+ $integer = BigInteger::fromBase($value, 16);
+
+ return self::createBigInteger($integer);
+ }
+
+ public static function createFromString(string $value): self
+ {
+ $integer = BigInteger::of($value);
+
+ return self::createBigInteger($integer);
+ }
+
+ public function getMajorType(): int
+ {
+ return self::MAJOR_TYPE;
+ }
+
+ public function getValue(): string
+ {
+ if ($this->data === null) {
+ return (string) $this->additionalInformation;
+ }
+
+ $integer = BigInteger::fromBase(bin2hex($this->data), 16);
+
+ return $integer->toBase(10);
+ }
+
+ public function normalize(): string
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * @deprecated The method will be removed on v3.0. Please rely on the CBOR\Normalizable interface
+ */
+ public function getNormalizedData(bool $ignoreTags = false): string
+ {
+ return $this->getValue();
+ }
+
+ private static function createBigInteger(BigInteger $integer): self
+ {
+ if ($integer->isLessThan(BigInteger::zero())) {
+ throw new InvalidArgumentException('The value must be a positive integer.');
+ }
+
+ switch (true) {
+ case $integer->isLessThan(BigInteger::of(24)):
+ $ai = $integer->toInt();
+ $data = null;
+ break;
+ case $integer->isLessThan(BigInteger::fromBase('FF', 16)):
+ $ai = 24;
+ $data = self::hex2bin(str_pad($integer->toBase(16), 2, '0', STR_PAD_LEFT));
+ break;
+ case $integer->isLessThan(BigInteger::fromBase('FFFF', 16)):
+ $ai = 25;
+ $data = self::hex2bin(str_pad($integer->toBase(16), 4, '0', STR_PAD_LEFT));
+ break;
+ case $integer->isLessThan(BigInteger::fromBase('FFFFFFFF', 16)):
+ $ai = 26;
+ $data = self::hex2bin(str_pad($integer->toBase(16), 8, '0', STR_PAD_LEFT));
+ break;
+ default:
+ throw new InvalidArgumentException(
+ 'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'
+ );
+ }
+
+ return new self($ai, $data);
+ }
+
+ private static function hex2bin(string $data): string
+ {
+ $result = hex2bin($data);
+ if ($result === false) {
+ throw new InvalidArgumentException('Unable to convert the data');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2020 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace CBOR;
+
+use Brick\Math\BigInteger;
+use InvalidArgumentException;
+use function is_string;
+
+/**
+ * @internal
+ */
+abstract class Utils
+{
+ public static function binToInt(string $value): int
+ {
+ return self::binToBigInteger($value)->toInt();
+ }
+
+ public static function binToBigInteger(string $value): BigInteger
+ {
+ return self::hexToBigInteger(bin2hex($value));
+ }
+
+ public static function hexToInt(string $value): int
+ {
+ return self::hexToBigInteger($value)->toInt();
+ }
+
+ public static function hexToBigInteger(string $value): BigInteger
+ {
+ return BigInteger::fromBase($value, 16);
+ }
+
+ public static function hexToString(string $value): string
+ {
+ return BigInteger::fromBase(bin2hex($value), 16)->toBase(10);
+ }
+
+ public static function decode(string $data): string
+ {
+ $decoded = base64_decode(strtr($data, '-_', '+/'), true);
+ if ($decoded === false) {
+ throw new InvalidArgumentException('Invalid data provided');
+ }
+
+ return $decoded;
+ }
+
+ /**
+ * @param mixed|null $data
+ */
+ public static function assertString($data, ?string $message = null): void
+ {
+ if (! is_string($data)) {
+ throw new InvalidArgumentException($message ?? '');
+ }
+ }
+}
--- /dev/null
+Copyright (c) 2020-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php80;
+
+/**
+ * @author Ion Bazan <ion.bazan@gmail.com>
+ * @author Nico Oelgart <nicoswd@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Php80
+{
+ public static function fdiv(float $dividend, float $divisor): float
+ {
+ return @($dividend / $divisor);
+ }
+
+ public static function get_debug_type($value): string
+ {
+ switch (true) {
+ case null === $value: return 'null';
+ case \is_bool($value): return 'bool';
+ case \is_string($value): return 'string';
+ case \is_array($value): return 'array';
+ case \is_int($value): return 'int';
+ case \is_float($value): return 'float';
+ case \is_object($value): break;
+ case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
+ default:
+ if (null === $type = @get_resource_type($value)) {
+ return 'unknown';
+ }
+
+ if ('Unknown' === $type) {
+ $type = 'closed';
+ }
+
+ return "resource ($type)";
+ }
+
+ $class = \get_class($value);
+
+ if (false === strpos($class, '@')) {
+ return $class;
+ }
+
+ return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
+ }
+
+ public static function get_resource_id($res): int
+ {
+ if (!\is_resource($res) && null === @get_resource_type($res)) {
+ throw new \TypeError(\sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
+ }
+
+ return (int) $res;
+ }
+
+ public static function preg_last_error_msg(): string
+ {
+ switch (preg_last_error()) {
+ case \PREG_INTERNAL_ERROR:
+ return 'Internal error';
+ case \PREG_BAD_UTF8_ERROR:
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
+ case \PREG_BAD_UTF8_OFFSET_ERROR:
+ return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
+ case \PREG_BACKTRACK_LIMIT_ERROR:
+ return 'Backtrack limit exhausted';
+ case \PREG_RECURSION_LIMIT_ERROR:
+ return 'Recursion limit exhausted';
+ case \PREG_JIT_STACKLIMIT_ERROR:
+ return 'JIT stack limit exhausted';
+ case \PREG_NO_ERROR:
+ return 'No error';
+ default:
+ return 'Unknown error';
+ }
+ }
+
+ public static function str_contains(string $haystack, string $needle): bool
+ {
+ return '' === $needle || false !== strpos($haystack, $needle);
+ }
+
+ public static function str_starts_with(string $haystack, string $needle): bool
+ {
+ return 0 === strncmp($haystack, $needle, \strlen($needle));
+ }
+
+ public static function str_ends_with(string $haystack, string $needle): bool
+ {
+ if ('' === $needle || $needle === $haystack) {
+ return true;
+ }
+
+ if ('' === $haystack) {
+ return false;
+ }
+
+ $needleLength = \strlen($needle);
+
+ return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php80;
+
+/**
+ * @author Fedonyuk Anton <info@ensostudio.ru>
+ *
+ * @internal
+ */
+class PhpToken implements \Stringable
+{
+ /**
+ * @var int
+ */
+ public $id;
+
+ /**
+ * @var string
+ */
+ public $text;
+
+ /**
+ * @var -1|positive-int
+ */
+ public $line;
+
+ /**
+ * @var int
+ */
+ public $pos;
+
+ /**
+ * @param -1|positive-int $line
+ */
+ public function __construct(int $id, string $text, int $line = -1, int $position = -1)
+ {
+ $this->id = $id;
+ $this->text = $text;
+ $this->line = $line;
+ $this->pos = $position;
+ }
+
+ public function getTokenName(): ?string
+ {
+ if ('UNKNOWN' === $name = token_name($this->id)) {
+ $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
+ }
+
+ return $name;
+ }
+
+ /**
+ * @param int|string|array $kind
+ */
+ public function is($kind): bool
+ {
+ foreach ((array) $kind as $value) {
+ if (\in_array($value, [$this->id, $this->text], true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function isIgnorable(): bool
+ {
+ return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true);
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->text;
+ }
+
+ /**
+ * @return list<static>
+ */
+ public static function tokenize(string $code, int $flags = 0): array
+ {
+ $line = 1;
+ $position = 0;
+ $tokens = token_get_all($code, $flags);
+ foreach ($tokens as $index => $token) {
+ if (\is_string($token)) {
+ $id = \ord($token);
+ $text = $token;
+ } else {
+ [$id, $text, $line] = $token;
+ }
+ $tokens[$index] = new static($id, $text, $line, $position);
+ $position += \strlen($text);
+ }
+
+ return $tokens;
+ }
+}
--- /dev/null
+Symfony Polyfill / Php80
+========================
+
+This component provides features added to PHP 8.0 core:
+
+- [`Stringable`](https://php.net/stringable) interface
+- [`fdiv`](https://php.net/fdiv)
+- [`ValueError`](https://php.net/valueerror) class
+- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class
+- `FILTER_VALIDATE_BOOL` constant
+- [`get_debug_type`](https://php.net/get_debug_type)
+- [`PhpToken`](https://php.net/phptoken) class
+- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
+- [`str_contains`](https://php.net/str_contains)
+- [`str_starts_with`](https://php.net/str_starts_with)
+- [`str_ends_with`](https://php.net/str_ends_with)
+- [`get_resource_id`](https://php.net/get_resource_id)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class Attribute
+{
+ public const TARGET_CLASS = 1;
+ public const TARGET_FUNCTION = 2;
+ public const TARGET_METHOD = 4;
+ public const TARGET_PROPERTY = 8;
+ public const TARGET_CLASS_CONSTANT = 16;
+ public const TARGET_PARAMETER = 32;
+ public const TARGET_ALL = 63;
+ public const IS_REPEATABLE = 64;
+
+ /** @var int */
+ public $flags;
+
+ public function __construct(int $flags = self::TARGET_ALL)
+ {
+ $this->flags = $flags;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) {
+ class PhpToken extends Symfony\Polyfill\Php80\PhpToken
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ interface Stringable
+ {
+ /**
+ * @return string
+ */
+ public function __toString();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ class UnhandledMatchError extends Error
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ class ValueError extends Error
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php80 as p;
+
+if (\PHP_VERSION_ID >= 80000) {
+ return;
+}
+
+if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
+ define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
+}
+
+if (!function_exists('fdiv')) {
+ function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
+}
+if (!function_exists('preg_last_error_msg')) {
+ function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
+}
+if (!function_exists('str_contains')) {
+ function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('str_starts_with')) {
+ function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('str_ends_with')) {
+ function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('get_debug_type')) {
+ function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
+}
+if (!function_exists('get_resource_id')) {
+ function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
+}
--- /dev/null
+{
+ "name": "symfony/polyfill-php80",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
--- /dev/null
+CHANGELOG
+=========
+
+5.2.0
+-----
+
+ * added `Process::setOptions()` to set `Process` specific options
+ * added option `create_new_console` to allow a subprocess to continue
+ to run after the main script exited, both on Linux and on Windows
+
+5.1.0
+-----
+
+ * added `Process::getStartTime()` to retrieve the start time of the process as float
+
+5.0.0
+-----
+
+ * removed `Process::inheritEnvironmentVariables()`
+ * removed `PhpProcess::setPhpBinary()`
+ * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell
+ * removed `Process::setCommandLine()`
+
+4.4.0
+-----
+
+ * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited.
+ * added `Process::getLastOutputTime()` method
+
+4.2.0
+-----
+
+ * added the `Process::fromShellCommandline()` to run commands in a shell wrapper
+ * deprecated passing a command as string when creating a `Process` instance
+ * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
+ * added the `Process::waitUntil()` method to wait for the process only for a
+ specific output, then continue the normal execution of your application
+
+4.1.0
+-----
+
+ * added the `Process::isTtySupported()` method that allows to check for TTY support
+ * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary
+ * added the `ProcessSignaledException` class to properly catch signaled process errors
+
+4.0.0
+-----
+
+ * environment variables will always be inherited
+ * added a second `array $env = []` argument to the `start()`, `run()`,
+ `mustRun()`, and `restart()` methods of the `Process` class
+ * added a second `array $env = []` argument to the `start()` method of the
+ `PhpProcess` class
+ * the `ProcessUtils::escapeArgument()` method has been removed
+ * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`
+ methods of the `Process` class have been removed
+ * support for passing `proc_open()` options has been removed
+ * removed the `ProcessBuilder` class, use the `Process` class instead
+ * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class
+ * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not
+ supported anymore
+
+3.4.0
+-----
+
+ * deprecated the ProcessBuilder class
+ * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)
+
+3.3.0
+-----
+
+ * added command line arrays in the `Process` class
+ * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
+ * deprecated the `ProcessUtils::escapeArgument()` method
+ * deprecated not inheriting environment variables
+ * deprecated configuring `proc_open()` options
+ * deprecated configuring enhanced Windows compatibility
+ * deprecated configuring enhanced sigchild compatibility
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+ the exit code returned by the process
+ * added ProcessBuilder
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal <soullivaneuh@gmail.com>
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ public const TYPE_GENERAL = 1;
+ public const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ExecutableFinder
+{
+ private const CMD_BUILTINS = [
+ 'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
+ 'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
+ 'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
+ 'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
+ 'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
+ ];
+
+ private $suffixes = [];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ */
+ public function addSuffix(string $suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null
+ */
+ public function find(string $name, ?string $default = null, array $extraDirs = [])
+ {
+ // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes
+ if ('\\' === \DIRECTORY_SEPARATOR && \in_array(strtolower($name), self::CMD_BUILTINS, true)) {
+ return $name;
+ }
+
+ $dirs = array_merge(
+ explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+
+ $suffixes = [];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = $this->suffixes;
+ $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);
+ }
+ $suffixes = '' !== pathinfo($name, PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if ('' === $dir) {
+ $dir = '.';
+ }
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+
+ if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) {
+ return $dir;
+ }
+ }
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) {
+ return $default;
+ }
+
+ $execResult = exec('command -v -- '.escapeshellarg($name));
+
+ if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) {
+ return $executablePath;
+ }
+
+ return $default;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @implements \IteratorAggregate<int, string>
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(?callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('"%s" is closed.', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ /**
+ * @return \Traversable<int, string>
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
--- /dev/null
+Copyright (c) 2004-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @return string|false
+ */
+ public function find(bool $includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php) && !$php = $this->executableFinder->find($php)) {
+ return false;
+ }
+
+ if (@is_dir($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return \PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php) || @is_dir($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php) && !@is_dir($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) {
+ return $php;
+ }
+
+ $dirs = [\PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('<?php echo "foo"; ?>');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
+ {
+ throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(?callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ if (\is_resource($pipe)) {
+ fclose($pipe);
+ }
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ */
+ protected function hasSystemCallBeenInterrupted(): bool
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write(): ?array
+ {
+ if (!isset($this->pipes[0])) {
+ return null;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!\is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return null;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ while (true) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError(int $type, string $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ public const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ */
+ public function getDescriptors(): array;
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles(): array;
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array;
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ */
+ public function areOpen(): bool;
+
+ /**
+ * Returns if pipes are able to read output.
+ */
+ public function haveReadSupport(): bool;
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __sleep(): array
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors(): array
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles(): array
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = @fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport(): bool
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen(): bool
+ {
+ return (bool) $this->pipes;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ if (file_exists($file.'.lock')) {
+ continue 2;
+ }
+ restore_error_handler();
+ throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
+ }
+ if (!flock($h, \LOCK_EX | \LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], \LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], \LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __sleep(): array
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors(): array
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles(): array
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep((int) (Process::TIMEOUT_PRECISION * 1E6));
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], \LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport(): bool
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen(): bool
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], \LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @implements \IteratorAggregate<string, string>
+ */
+class Process implements \IteratorAggregate
+{
+ public const ERR = 'err';
+ public const OUT = 'out';
+
+ public const STATUS_READY = 'ready';
+ public const STATUS_STARTED = 'started';
+ public const STATUS_TERMINATED = 'terminated';
+
+ public const STDIN = 0;
+ public const STDOUT = 1;
+ public const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ public const TIMEOUT_PRECISION = 0.2;
+
+ public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env = [];
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty = false;
+ private $pty;
+ private $options = ['suppress_errors' => true, 'bypass_shell' => true];
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+ private $cachedExitCode;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws LogicException When proc_open is not installed
+ */
+ public function __construct(array $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/51800
+ // @see : https://bugs.php.net/50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @return static
+ *
+ * @throws LogicException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if ($this->options['create_new_console'] ?? false) {
+ $this->processPipes->close();
+ } else {
+ $this->stop(0);
+ }
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws ProcessTimedOutException When process timed out
+ * @throws ProcessSignaledException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(?callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @return $this
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(?callable $callback = null, array $env = []): self
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(?callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running.');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if ($this->env) {
+ $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
+ }
+
+ $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ } else {
+ $commandline = $this->replacePlaceholders($commandline, $env);
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
+
+ if (!$this->process) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return static
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(?callable $callback = null, array $env = []): self
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running.');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws ProcessTimedOutException When process timed out
+ * @throws ProcessSignaledException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(?callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal(int $signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output cannot be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @return \Generator<string, string>
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator(int $flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return null;
+ }
+
+ return self::$exitCodes[$exitcode] ?? 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int|null The exit-code of the process or null if it's not running
+ */
+ public function stop(float $timeout = 10, ?int $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, \SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, \SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the last output time in seconds.
+ */
+ public function getLastOutputTime(): ?float
+ {
+ return $this->lastOutputTime;
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Gets the process timeout in seconds (max. runtime).
+ *
+ * @return float|null
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout in seconds (max. time since last output).
+ *
+ * @return float|null
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout(?float $timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @return $this
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout(?float $timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout cannot be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty(bool $tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @return $this
+ */
+ public function setPty(bool $bool)
+ {
+ $this->pty = $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory(string $cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * @param array<string|\Stringable> $env The new environment variables
+ *
+ * @return $this
+ */
+ public function setEnv(array $env)
+ {
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input cannot be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * @throws LogicException in case process is not started
+ */
+ public function getStartTime(): float
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException('Start time is only available after process start.');
+ }
+
+ return $this->starttime;
+ }
+
+ /**
+ * Defines options to pass to the underlying proc_open().
+ *
+ * @see https://php.net/proc_open for the options supported by PHP.
+ *
+ * Enabling the "create_new_console" option allows a subprocess to continue
+ * to run after the main process exited, on both Windows and *nix
+ */
+ public function setOptions(array $options)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Setting options while the process is running is not possible.');
+ }
+
+ $defaultOptions = $this->options;
+ $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];
+
+ foreach ($options as $key => $value) {
+ if (!\in_array($key, $existingOptions)) {
+ $this->options = $defaultOptions;
+ throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
+ }
+ $this->options[$key] = $value;
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure
+ */
+ protected function buildCallback(?callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback): bool {
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out): bool {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus(bool $blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call.
+ // Subsequent calls return -1 as the process is discarded. This workaround caches the first
+ // retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior.
+ if (\PHP_VERSION_ID < 80300) {
+ if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) {
+ $this->cachedExitCode = $this->processInformation['exitcode'];
+ }
+
+ if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) {
+ $this->processInformation['exitcode'] = $this->cachedExitCode;
+ }
+ }
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(\INFO_GENERAL);
+
+ return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if ($this->process) {
+ proc_close($this->process);
+ $this->process = null;
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Cannot send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env): string
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (str_contains($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ static $comSpec;
+
+ if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {
+ // Escape according to CommandLineToArgvW rules
+ $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec) .'"';
+ }
+
+ $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (str_contains($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[()%!^"<>&|\s[\]=;*?\'$]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function replacePlaceholders(string $commandline, array $env)
+ {
+ return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
+ if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
+ throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
+ }
+
+ return $this->escapeArgument($env[$matches[1]]);
+ }, $commandline);
+ }
+
+ private function getDefaultEnv(): array
+ {
+ $env = getenv();
+ $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;
+
+ return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň <martin.hason@gmail.com>
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput(string $caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (\is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
--- /dev/null
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Sponsor
+-------
+
+The Process component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2].
+
+As the creator of Symfony, SensioLabs supports companies using Symfony, with an
+offering encompassing consultancy, expertise, services, training, and technical
+assistance to ensure the success of web application development projects.
+
+Help Symfony by [sponsoring][3] its development!
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
+
+[1]: https://symfony.com/backers
+[2]: https://sensiolabs.com
+[3]: https://symfony.com/sponsor
--- /dev/null
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Executes commands in sub-processes",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2018 TheCodingMachine
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
--- /dev/null
+[](https://packagist.org/packages/thecodingmachine/safe)
+[](https://packagist.org/packages/thecodingmachine/safe)
+[](https://packagist.org/packages/thecodingmachine/safe)
+[](https://packagist.org/packages/thecodingmachine/safe)
+[](https://travis-ci.org/thecodingmachine/safe)
+[](https://github.com/thecodingmachine/safe/actions)
+[](https://codecov.io/gh/thecodingmachine/safe)
+
+Safe PHP
+========
+
+**Work in progress**
+
+A set of core PHP functions rewritten to throw exceptions instead of returning `false` when an error is encountered.
+
+## The problem
+
+Most PHP core functions were written before exception handling was added to the language. Therefore, most PHP functions
+do not throw exceptions. Instead, they return `false` in case of error.
+
+But most of us are too lazy to check explicitly for every single return of every core PHP function.
+
+```php
+// This code is incorrect. Twice.
+// "file_get_contents" can return false if the file does not exists
+// "json_decode" can return false if the file content is not valid JSON
+$content = file_get_contents('foobar.json');
+$foobar = json_decode($content);
+```
+
+The correct version of this code would be:
+
+```php
+$content = file_get_contents('foobar.json');
+if ($content === false) {
+ throw new FileLoadingException('Could not load file foobar.json');
+}
+$foobar = json_decode($content);
+if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new FileLoadingException('foobar.json does not contain valid JSON: '.json_last_error_msg());
+}
+```
+
+Obviously, while this snippet is correct, it is less easy to read.
+
+## The solution
+
+Enter *thecodingmachine/safe* aka Safe-PHP.
+
+Safe-PHP redeclares all core PHP functions. The new PHP functions act exactly as the old ones, except they
+throw exceptions properly when an error is encountered. The "safe" functions have the same name as the core PHP
+functions, except they are in the `Safe` namespace.
+
+```php
+use function Safe\file_get_contents;
+use function Safe\json_decode;
+
+// This code is both safe and simple!
+$content = file_get_contents('foobar.json');
+$foobar = json_decode($content);
+```
+
+All PHP functions that can return `false` on error are part of Safe.
+In addition, Safe also provide 2 'Safe' classes: `Safe\DateTime` and `Safe\DateTimeImmutable` whose methods will throw exceptions instead of returning false.
+
+## PHPStan integration
+
+> Yeah... but I must explicitly think about importing the "safe" variant of the function, for each and every file of my application.
+> I'm sure I will forget some "use function" statements!
+
+Fear not! thecodingmachine/safe comes with a PHPStan rule.
+
+Never heard of [PHPStan](https://github.com/phpstan/phpstan) before?
+Check it out, it's an amazing code analyzer for PHP.
+
+Simply install the Safe rule in your PHPStan setup (explained in the "Installation" section) and PHPStan will let you know each time you are using an "unsafe" function.
+
+The code below will trigger this warning:
+
+```php
+$content = file_get_contents('foobar.json');
+```
+
+> Function file_get_contents is unsafe to use. It can return FALSE instead of throwing an exception. Please add 'use function Safe\\file_get_contents;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library.
+
+## Installation
+
+Use composer to install Safe-PHP:
+
+```bash
+$ composer require thecodingmachine/safe
+```
+
+*Highly recommended*: install PHPStan and PHPStan extension:
+
+```bash
+$ composer require --dev thecodingmachine/phpstan-safe-rule
+```
+
+Now, edit your `phpstan.neon` file and add these rules:
+
+```yml
+includes:
+ - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
+```
+
+## Automated refactoring
+
+You have a large legacy codebase and want to use "Safe-PHP" functions throughout your project? PHPStan will help you
+find these functions but changing the namespace of the functions one function at a time might be a tedious task.
+
+Fortunately, Safe comes bundled with a "Rector" configuration file. [Rector](https://github.com/rectorphp/rector) is a command-line
+tool that performs instant refactoring of your application.
+
+Run
+
+```bash
+$ composer require --dev rector/rector:^0.7
+```
+
+to install `rector/rector`.
+
+Run
+
+```bash
+vendor/bin/rector process src/ --config vendor/thecodingmachine/safe/rector-migrate-0.7.php
+```
+
+to run `rector/rector`.
+
+*Note:* do not forget to replace "src/" with the path to your source directory.
+
+**Important:** the refactoring only performs a "dumb" replacement of functions. It will not modify the way
+"false" return values are handled. So if your code was already performing error handling, you will have to deal
+with it manually.
+
+Especially, you should look for error handling that was already performed, like:
+
+```php
+if (!mkdir($dirPath)) {
+ // Do something on error
+}
+```
+
+This code will be refactored by Rector to:
+
+```php
+if (!\Safe\mkdir($dirPath)) {
+ // Do something on error
+}
+```
+
+You should then (manually) refactor it to:
+
+```php
+try {
+ \Safe\mkdir($dirPath));
+} catch (\Safe\FilesystemException $e) {
+ // Do something on error
+}
+```
+
+## Performance impact
+
+Safe is loading 1000+ functions from ~85 files on each request. Yet, the performance impact of this loading is quite low.
+
+In case you worry, using Safe will "cost" you ~700µs on each request. The [performance section](performance/README.md)
+contains more information regarding the way we tested the performance impact of Safe.
+
+## Learn more
+
+Read [the release article on TheCodingMachine's blog](https://thecodingmachine.io/introducing-safe-php) if you want to
+learn more about what triggered the development of Safe-PHP.
+
+## Contributing
+
+The files that contain all the functions are auto-generated from the PHP doc.
+Read the [CONTRIBUTING.md](CONTRIBUTING.md) file to learn how to regenerate these files and to contribute to this library.
--- /dev/null
+{
+ "name": "thecodingmachine/safe",
+ "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "Safe\\": [
+ "lib/",
+ "deprecated/",
+ "generated/"
+ ]
+ },
+ "files": [
+ "deprecated/apc.php",
+ "deprecated/libevent.php",
+ "deprecated/mssql.php",
+ "deprecated/stats.php",
+ "lib/special_cases.php",
+ "generated/apache.php",
+ "generated/apcu.php",
+ "generated/array.php",
+ "generated/bzip2.php",
+ "generated/calendar.php",
+ "generated/classobj.php",
+ "generated/com.php",
+ "generated/cubrid.php",
+ "generated/curl.php",
+ "generated/datetime.php",
+ "generated/dir.php",
+ "generated/eio.php",
+ "generated/errorfunc.php",
+ "generated/exec.php",
+ "generated/fileinfo.php",
+ "generated/filesystem.php",
+ "generated/filter.php",
+ "generated/fpm.php",
+ "generated/ftp.php",
+ "generated/funchand.php",
+ "generated/gmp.php",
+ "generated/gnupg.php",
+ "generated/hash.php",
+ "generated/ibase.php",
+ "generated/ibmDb2.php",
+ "generated/iconv.php",
+ "generated/image.php",
+ "generated/imap.php",
+ "generated/info.php",
+ "generated/ingres-ii.php",
+ "generated/inotify.php",
+ "generated/json.php",
+ "generated/ldap.php",
+ "generated/libxml.php",
+ "generated/lzf.php",
+ "generated/mailparse.php",
+ "generated/mbstring.php",
+ "generated/misc.php",
+ "generated/msql.php",
+ "generated/mysql.php",
+ "generated/mysqli.php",
+ "generated/mysqlndMs.php",
+ "generated/mysqlndQc.php",
+ "generated/network.php",
+ "generated/oci8.php",
+ "generated/opcache.php",
+ "generated/openssl.php",
+ "generated/outcontrol.php",
+ "generated/password.php",
+ "generated/pcntl.php",
+ "generated/pcre.php",
+ "generated/pdf.php",
+ "generated/pgsql.php",
+ "generated/posix.php",
+ "generated/ps.php",
+ "generated/pspell.php",
+ "generated/readline.php",
+ "generated/rpminfo.php",
+ "generated/rrd.php",
+ "generated/sem.php",
+ "generated/session.php",
+ "generated/shmop.php",
+ "generated/simplexml.php",
+ "generated/sockets.php",
+ "generated/sodium.php",
+ "generated/solr.php",
+ "generated/spl.php",
+ "generated/sqlsrv.php",
+ "generated/ssdeep.php",
+ "generated/ssh2.php",
+ "generated/stream.php",
+ "generated/strings.php",
+ "generated/swoole.php",
+ "generated/uodbc.php",
+ "generated/uopz.php",
+ "generated/url.php",
+ "generated/var.php",
+ "generated/xdiff.php",
+ "generated/xml.php",
+ "generated/xmlrpc.php",
+ "generated/yaml.php",
+ "generated/yaz.php",
+ "generated/zip.php",
+ "generated/zlib.php"
+ ]
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12",
+ "thecodingmachine/phpstan-strict-rules": "^0.12",
+ "squizlabs/php_codesniffer": "^3.2"
+ },
+ "scripts": {
+ "phpstan": "phpstan analyse lib -c phpstan.neon --level=max --no-progress -vvv",
+ "cs-fix": "phpcbf",
+ "cs-check": "phpcs"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ApcException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class LibeventException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MssqlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class StatsException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ApcException;
+
+/**
+ * Retrieves cached information and meta-data from APC's data store.
+ *
+ * @param string $cache_type If cache_type is "user",
+ * information about the user cache will be returned.
+ *
+ * If cache_type is "filehits",
+ * information about which files have been served from the bytecode cache
+ * for the current request will be returned. This feature must be enabled at
+ * compile time using --enable-filehits.
+ *
+ * If an invalid or no cache_type is specified, information about
+ * the system cache (cached files) will be returned.
+ * @param bool $limited If limited is TRUE, the
+ * return value will exclude the individual list of cache entries. This
+ * is useful when trying to optimize calls for statistics gathering.
+ * @return array Array of cached data (and meta-data)
+ * @throws ApcException
+ *
+ */
+function apc_cache_info(string $cache_type = '', bool $limited = false): array
+{
+ error_clear_last();
+ $result = \apc_cache_info($cache_type, $limited);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * apc_cas updates an already existing integer value if the
+ * old parameter matches the currently stored value
+ * with the value of the new parameter.
+ *
+ * @param string $key The key of the value being updated.
+ * @param int $old The old value (the value currently stored).
+ * @param int $new The new value to update to.
+ * @throws ApcException
+ *
+ */
+function apc_cas(string $key, int $old, int $new): void
+{
+ error_clear_last();
+ $result = \apc_cas($key, $old, $new);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Stores a file in the bytecode cache, bypassing all filters.
+ *
+ * @param string $filename Full or relative path to a PHP file that will be compiled and stored in
+ * the bytecode cache.
+ * @param bool $atomic
+ * @return mixed Returns TRUE on success.
+ * @throws ApcException
+ *
+ */
+function apc_compile_file(string $filename, bool $atomic = true)
+{
+ error_clear_last();
+ $result = \apc_compile_file($filename, $atomic);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Decreases a stored integer value.
+ *
+ * @param string $key The key of the value being decreased.
+ * @param int $step The step, or value to decrease.
+ * @param bool|null $success Optionally pass the success or fail boolean value to
+ * this referenced variable.
+ * @return int Returns the current value of key's value on success
+ * @throws ApcException
+ *
+ */
+function apc_dec(string $key, int $step = 1, ?bool &$success = null): int
+{
+ error_clear_last();
+ $result = \apc_dec($key, $step, $success);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * define is notoriously slow. Since the main benefit of
+ * APC is to increase the performance of scripts/applications, this mechanism
+ * is provided to streamline the process of mass constant definition. However,
+ * this function does not perform as well as anticipated.
+ *
+ * For a better-performing solution, try the
+ * hidef extension from PECL.
+ *
+ * @param string $key The key serves as the name of the constant set
+ * being stored. This key is used to retrieve the
+ * stored constants in apc_load_constants.
+ * @param array $constants An associative array of constant_name => value
+ * pairs. The constant_name must follow the normal
+ * constant naming rules.
+ * value must evaluate to a scalar value.
+ * @param bool $case_sensitive The default behaviour for constants is to be declared case-sensitive;
+ * i.e. CONSTANT and Constant
+ * represent different values. If this parameter evaluates to FALSE the
+ * constants will be declared as case-insensitive symbols.
+ * @throws ApcException
+ *
+ */
+function apc_define_constants(string $key, array $constants, bool $case_sensitive = true): void
+{
+ error_clear_last();
+ $result = \apc_define_constants($key, $constants, $case_sensitive);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deletes the given files from the opcode cache.
+ *
+ * @param mixed $keys The files to be deleted. Accepts a string,
+ * array of strings, or an APCIterator
+ * object.
+ * @return mixed Returns TRUE on success.
+ * Or if keys is an array, then
+ * an empty array is returned on success, or an array of failed files
+ * is returned.
+ * @throws ApcException
+ *
+ */
+function apc_delete_file($keys)
+{
+ error_clear_last();
+ $result = \apc_delete_file($keys);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Removes a stored variable from the cache.
+ *
+ * @param string|string[]|\APCIterator $key The key used to store the value (with
+ * apc_store).
+ * @throws ApcException
+ *
+ */
+function apc_delete($key): void
+{
+ error_clear_last();
+ $result = \apc_delete($key);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Increases a stored number.
+ *
+ * @param string $key The key of the value being increased.
+ * @param int $step The step, or value to increase.
+ * @param bool|null $success Optionally pass the success or fail boolean value to
+ * this referenced variable.
+ * @return int Returns the current value of key's value on success
+ * @throws ApcException
+ *
+ */
+function apc_inc(string $key, int $step = 1, ?bool &$success = null): int
+{
+ error_clear_last();
+ $result = \apc_inc($key, $step, $success);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Loads a set of constants from the cache.
+ *
+ * @param string $key The name of the constant set (that was stored with
+ * apc_define_constants) to be retrieved.
+ * @param bool $case_sensitive The default behaviour for constants is to be declared case-sensitive;
+ * i.e. CONSTANT and Constant
+ * represent different values. If this parameter evaluates to FALSE the
+ * constants will be declared as case-insensitive symbols.
+ * @throws ApcException
+ *
+ */
+function apc_load_constants(string $key, bool $case_sensitive = true): void
+{
+ error_clear_last();
+ $result = \apc_load_constants($key, $case_sensitive);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieves APC's Shared Memory Allocation information.
+ *
+ * @param bool $limited When set to FALSE (default) apc_sma_info will
+ * return a detailed information about each segment.
+ * @return array Array of Shared Memory Allocation data; FALSE on failure.
+ * @throws ApcException
+ *
+ */
+function apc_sma_info(bool $limited = false): array
+{
+ error_clear_last();
+ $result = \apc_sma_info($limited);
+ if ($result === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+return [
+ 'apc_cache_info',
+ 'apc_cas',
+ 'apc_compile_file',
+ 'apc_dec',
+ 'apc_define_constants',
+ 'apc_delete',
+ 'apc_delete_file',
+ 'apc_inc',
+ 'apc_load_constants',
+ 'apc_sma_info',
+ 'event_add',
+ 'event_base_loopbreak',
+ 'event_base_loopexit',
+ 'event_base_new',
+ 'event_base_priority_init',
+ 'event_base_reinit',
+ 'event_base_set',
+ 'event_buffer_base_set',
+ 'event_buffer_disable',
+ 'event_buffer_enable',
+ 'event_buffer_new',
+ 'event_buffer_priority_set',
+ 'event_buffer_set_callback',
+ 'event_buffer_write',
+ 'event_del',
+ 'event_new',
+ 'event_priority_set',
+ 'event_set',
+ 'event_timer_set',
+ 'imagepsencodefont',
+ 'imagepsextendfont',
+ 'imagepsfreefont',
+ 'imagepsslantfont',
+ 'mssql_bind',
+ 'mssql_close',
+ 'mssql_connect',
+ 'mssql_data_seek',
+ 'mssql_field_length',
+ 'mssql_field_name',
+ 'mssql_field_seek',
+ 'mssql_field_type',
+ 'mssql_free_result',
+ 'mssql_free_statement',
+ 'mssql_init',
+ 'mssql_pconnect',
+ 'mssql_query',
+ 'mssql_select_db',
+ 'stats_covariance',
+ 'stats_standard_deviation',
+ 'stats_stat_correlation',
+ 'stats_stat_innerproduct',
+ 'stats_variance',
+];
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\LibeventException;
+
+/**
+ * event_add schedules the execution of the event
+ * when the event specified in event_set occurs or in at least the time
+ * specified by the timeout argument. If
+ * timeout was not specified, not timeout is set. The
+ * event must be already initalized by event_set
+ * and event_base_set functions. If the
+ * event already has a timeout set, it is replaced by
+ * the new one.
+ *
+ * @param resource $event Valid event resource.
+ * @param int $timeout Optional timeout (in microseconds).
+ * @throws LibeventException
+ *
+ */
+function event_add($event, int $timeout = -1): void
+{
+ error_clear_last();
+ $result = \event_add($event, $timeout);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Abort the active event loop immediately. The behaviour is similar to
+ * break statement.
+ *
+ * @param resource $event_base Valid event base resource.
+ * @throws LibeventException
+ *
+ */
+function event_base_loopbreak($event_base): void
+{
+ error_clear_last();
+ $result = \event_base_loopbreak($event_base);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The next event loop iteration after the given timer expires will complete
+ * normally, then exit without blocking for events again.
+ *
+ * @param resource $event_base Valid event base resource.
+ * @param int $timeout Optional timeout parameter (in microseconds).
+ * @throws LibeventException
+ *
+ */
+function event_base_loopexit($event_base, int $timeout = -1): void
+{
+ error_clear_last();
+ $result = \event_base_loopexit($event_base, $timeout);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns new event base, which can be used later in event_base_set,
+ * event_base_loop and other functions.
+ *
+ * @return resource event_base_new returns valid event base resource on
+ * success.
+ * @throws LibeventException
+ *
+ */
+function event_base_new()
+{
+ error_clear_last();
+ $result = \event_base_new();
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the number of different event priority levels.
+ *
+ * By default all events are scheduled with the same priority
+ * (npriorities/2).
+ * Using event_base_priority_init you can change the number
+ * of event priority levels and then set a desired priority for each event.
+ *
+ * @param resource $event_base Valid event base resource.
+ * @param int $npriorities The number of event priority levels.
+ * @throws LibeventException
+ *
+ */
+function event_base_priority_init($event_base, int $npriorities): void
+{
+ error_clear_last();
+ $result = \event_base_priority_init($event_base, $npriorities);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Some event mechanisms do not survive across fork. The
+ * event_base needs to be reinitialized with this
+ * function.
+ *
+ * @param resource $event_base Valid event base resource that needs to be re-initialized.
+ * @throws LibeventException
+ *
+ */
+function event_base_reinit($event_base): void
+{
+ error_clear_last();
+ $result = \event_base_reinit($event_base);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Associates the event_base with the
+ * event.
+ *
+ * @param resource $event Valid event resource.
+ * @param resource $event_base Valid event base resource.
+ * @throws LibeventException
+ *
+ */
+function event_base_set($event, $event_base): void
+{
+ error_clear_last();
+ $result = \event_base_set($event, $event_base);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Assign the specified bevent to the
+ * event_base.
+ *
+ * @param resource $bevent Valid buffered event resource.
+ * @param resource $event_base Valid event base resource.
+ * @throws LibeventException
+ *
+ */
+function event_buffer_base_set($bevent, $event_base): void
+{
+ error_clear_last();
+ $result = \event_buffer_base_set($bevent, $event_base);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Disables the specified buffered event.
+ *
+ * @param resource $bevent Valid buffered event resource.
+ * @param int $events Any combination of EV_READ and
+ * EV_WRITE.
+ * @throws LibeventException
+ *
+ */
+function event_buffer_disable($bevent, int $events): void
+{
+ error_clear_last();
+ $result = \event_buffer_disable($bevent, $events);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Enables the specified buffered event.
+ *
+ * @param resource $bevent Valid buffered event resource.
+ * @param int $events Any combination of EV_READ and
+ * EV_WRITE.
+ * @throws LibeventException
+ *
+ */
+function event_buffer_enable($bevent, int $events): void
+{
+ error_clear_last();
+ $result = \event_buffer_enable($bevent, $events);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Libevent provides an abstraction layer on top of the regular event API.
+ * Using buffered event you don't need to deal with the I/O manually, instead
+ * it provides input and output buffers that get filled and drained
+ * automatically.
+ *
+ * @param resource $stream Valid PHP stream resource. Must be castable to file descriptor.
+ * @param mixed $readcb Callback to invoke where there is data to read, or NULL if
+ * no callback is desired.
+ * @param mixed $writecb Callback to invoke where the descriptor is ready for writing,
+ * or NULL if no callback is desired.
+ * @param mixed $errorcb Callback to invoke where there is an error on the descriptor, cannot be
+ * NULL.
+ * @param mixed $arg An argument that will be passed to each of the callbacks (optional).
+ * @return resource event_buffer_new returns new buffered event resource
+ * on success.
+ * @throws LibeventException
+ *
+ */
+function event_buffer_new($stream, $readcb, $writecb, $errorcb, $arg = null)
+{
+ error_clear_last();
+ if ($arg !== null) {
+ $result = \event_buffer_new($stream, $readcb, $writecb, $errorcb, $arg);
+ } else {
+ $result = \event_buffer_new($stream, $readcb, $writecb, $errorcb);
+ }
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Assign a priority to the bevent.
+ *
+ * @param resource $bevent Valid buffered event resource.
+ * @param int $priority Priority level. Cannot be less than zero and cannot exceed maximum
+ * priority level of the event base (see event_base_priority_init).
+ * @throws LibeventException
+ *
+ */
+function event_buffer_priority_set($bevent, int $priority): void
+{
+ error_clear_last();
+ $result = \event_buffer_priority_set($bevent, $priority);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets or changes existing callbacks for the buffered event.
+ *
+ * @param resource $event Valid buffered event resource.
+ * @param mixed $readcb Callback to invoke where there is data to read, or NULL if
+ * no callback is desired.
+ * @param mixed $writecb Callback to invoke where the descriptor is ready for writing,
+ * or NULL if no callback is desired.
+ * @param mixed $errorcb Callback to invoke where there is an error on the descriptor, cannot be
+ * NULL.
+ * @param mixed $arg An argument that will be passed to each of the callbacks (optional).
+ * @throws LibeventException
+ *
+ */
+function event_buffer_set_callback($event, $readcb, $writecb, $errorcb, $arg = null): void
+{
+ error_clear_last();
+ if ($arg !== null) {
+ $result = \event_buffer_set_callback($event, $readcb, $writecb, $errorcb, $arg);
+ } else {
+ $result = \event_buffer_set_callback($event, $readcb, $writecb, $errorcb);
+ }
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Writes data to the specified buffered event. The data is appended to the
+ * output buffer and written to the descriptor when it becomes available for
+ * writing.
+ *
+ * @param resource $bevent Valid buffered event resource.
+ * @param string $data The data to be written.
+ * @param int $data_size Optional size parameter. event_buffer_write writes
+ * all the data by default.
+ * @throws LibeventException
+ *
+ */
+function event_buffer_write($bevent, string $data, int $data_size = -1): void
+{
+ error_clear_last();
+ $result = \event_buffer_write($bevent, $data, $data_size);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Cancels the event.
+ *
+ * @param resource $event Valid event resource.
+ * @throws LibeventException
+ *
+ */
+function event_del($event): void
+{
+ error_clear_last();
+ $result = \event_del($event);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates and returns a new event resource.
+ *
+ * @return resource event_new returns a new event resource on success.
+ * @throws LibeventException
+ *
+ */
+function event_new()
+{
+ error_clear_last();
+ $result = \event_new();
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Assign a priority to the event.
+ *
+ * @param resource $event Valid event resource.
+ * @param int $priority Priority level. Cannot be less than zero and cannot exceed maximum
+ * priority level of the event base (see
+ * event_base_priority_init).
+ * @throws LibeventException
+ *
+ */
+function event_priority_set($event, int $priority): void
+{
+ error_clear_last();
+ $result = \event_priority_set($event, $priority);
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prepares the event to be used in event_add. The event
+ * is prepared to call the function specified by the callback
+ * on the events specified in parameter events, which
+ * is a set of the following flags: EV_TIMEOUT,
+ * EV_SIGNAL, EV_READ,
+ * EV_WRITE and EV_PERSIST.
+ *
+ * If EV_SIGNAL bit is set in parameter events,
+ * the fd is interpreted as signal number.
+ *
+ * After initializing the event, use event_base_set to
+ * associate the event with its event base.
+ *
+ * In case of matching event, these three arguments are passed to the
+ * callback function:
+ *
+ *
+ * fd
+ *
+ *
+ * Signal number or resource indicating the stream.
+ *
+ *
+ *
+ *
+ * events
+ *
+ *
+ * A flag indicating the event. Consists of the following flags:
+ * EV_TIMEOUT, EV_SIGNAL,
+ * EV_READ, EV_WRITE
+ * and EV_PERSIST.
+ *
+ *
+ *
+ *
+ * arg
+ *
+ *
+ * Optional parameter, previously passed to event_set
+ * as arg.
+ *
+ *
+ *
+ *
+ *
+ * @param resource $event Valid event resource.
+ * @param mixed $fd Valid PHP stream resource. The stream must be castable to file
+ * descriptor, so you most likely won't be able to use any of filtered
+ * streams.
+ * @param int $events A set of flags indicating the desired event, can be
+ * EV_READ and/or EV_WRITE.
+ * The additional flag EV_PERSIST makes the event
+ * to persist until event_del is called, otherwise
+ * the callback is invoked only once.
+ * @param mixed $callback Callback function to be called when the matching event occurs.
+ * @param mixed $arg Optional callback parameter.
+ * @throws LibeventException
+ *
+ */
+function event_set($event, $fd, int $events, $callback, $arg = null): void
+{
+ error_clear_last();
+ if ($arg !== null) {
+ $result = \event_set($event, $fd, $events, $callback, $arg);
+ } else {
+ $result = \event_set($event, $fd, $events, $callback);
+ }
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prepares the timer event to be used in event_add. The
+ * event is prepared to call the function specified by the
+ * callback when the event timeout elapses.
+ *
+ * After initializing the event, use event_base_set to
+ * associate the event with its event base.
+ *
+ * In case of matching event, these three arguments are passed to the
+ * callback function:
+ *
+ *
+ * fd
+ *
+ *
+ * Signal number or resource indicating the stream.
+ *
+ *
+ *
+ *
+ * events
+ *
+ *
+ * A flag indicating the event. This will always be
+ * EV_TIMEOUT for timer events.
+ *
+ *
+ *
+ *
+ * arg
+ *
+ *
+ * Optional parameter, previously passed to
+ * event_timer_set as arg.
+ *
+ *
+ *
+ *
+ *
+ * @param resource $event Valid event resource.
+ * @param callable $callback Callback function to be called when the matching event occurs.
+ * @param mixed $arg Optional callback parameter.
+ * @throws LibeventException
+ *
+ */
+function event_timer_set($event, callable $callback, $arg = null): void
+{
+ error_clear_last();
+ if ($arg !== null) {
+ $result = \event_timer_set($event, $callback, $arg);
+ } else {
+ $result = \event_timer_set($event, $callback);
+ }
+ if ($result === false) {
+ throw LibeventException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MssqlException;
+
+/**
+ * Binds a parameter to a stored procedure or a remote stored procedure.
+ *
+ * @param resource $stmt Statement resource, obtained with mssql_init.
+ * @param string $param_name The parameter name, as a string.
+ *
+ * You have to include the @ character, like in the
+ * T-SQL syntax. See the explanation included in
+ * mssql_execute.
+ * @param mixed $var The PHP variable you'll bind the MSSQL parameter to. It is passed by
+ * reference, to retrieve OUTPUT and RETVAL values after
+ * the procedure execution.
+ * @param int $type One of: SQLTEXT,
+ * SQLVARCHAR, SQLCHAR,
+ * SQLINT1, SQLINT2,
+ * SQLINT4, SQLBIT,
+ * SQLFLT4, SQLFLT8,
+ * SQLFLTN.
+ * @param bool $is_output Whether the value is an OUTPUT parameter or not. If it's an OUTPUT
+ * parameter and you don't mention it, it will be treated as a normal
+ * input parameter and no error will be thrown.
+ * @param bool $is_null Whether the parameter is NULL or not. Passing the NULL value as
+ * var will not do the job.
+ * @param int $maxlen Used with char/varchar values. You have to indicate the length of the
+ * data so if the parameter is a varchar(50), the type must be
+ * SQLVARCHAR and this value 50.
+ * @throws MssqlException
+ *
+ */
+function mssql_bind($stmt, string $param_name, &$var, int $type, bool $is_output = false, bool $is_null = false, int $maxlen = -1): void
+{
+ error_clear_last();
+ $result = \mssql_bind($stmt, $param_name, $var, $type, $is_output, $is_null, $maxlen);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the link to a MS SQL Server database that's associated with the
+ * specified link identifier. If the link identifier isn't specified, the
+ * last opened link is assumed.
+ *
+ * Note that this isn't usually necessary, as non-persistent open
+ * links are automatically closed at the end of the script's
+ * execution.
+ *
+ * @param resource $link_identifier A MS SQL link identifier, returned by
+ * mssql_connect.
+ *
+ * This function will not close persistent links generated by
+ * mssql_pconnect.
+ * @throws MssqlException
+ *
+ */
+function mssql_close($link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \mssql_close($link_identifier);
+ } else {
+ $result = \mssql_close();
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * mssql_connect establishes a connection to a
+ * MS SQL server.
+ *
+ * The link to the server will be closed as soon as the execution of
+ * the script ends, unless it's closed earlier by explicitly calling
+ * mssql_close.
+ *
+ * @param string $servername The MS SQL server. It can also include a port number, e.g.
+ * hostname:port (Linux), or
+ * hostname,port (Windows).
+ * @param string $username The username.
+ * @param string $password The password.
+ * @param bool $new_link If a second call is made to mssql_connect with the
+ * same arguments, no new link will be established, but instead, the link
+ * identifier of the already opened link will be returned. This parameter
+ * modifies this behavior and makes mssql_connect
+ * always open a new link, even if mssql_connect was
+ * called before with the same parameters.
+ * @return resource Returns a MS SQL link identifier on success.
+ * @throws MssqlException
+ *
+ */
+function mssql_connect(string $servername = null, string $username = null, string $password = null, bool $new_link = false)
+{
+ error_clear_last();
+ if ($new_link !== false) {
+ $result = \mssql_connect($servername, $username, $password, $new_link);
+ } elseif ($password !== null) {
+ $result = \mssql_connect($servername, $username, $password);
+ } elseif ($username !== null) {
+ $result = \mssql_connect($servername, $username);
+ } elseif ($servername !== null) {
+ $result = \mssql_connect($servername);
+ } else {
+ $result = \mssql_connect();
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mssql_data_seek moves the internal row
+ * pointer of the MS SQL result associated with the specified result
+ * identifier to point to the specified row number, first row being
+ * number 0. The next call to mssql_fetch_row
+ * would return that row.
+ *
+ * @param resource $result_identifier The result resource that is being evaluated.
+ * @param int $row_number The desired row number of the new result pointer.
+ * @throws MssqlException
+ *
+ */
+function mssql_data_seek($result_identifier, int $row_number): void
+{
+ error_clear_last();
+ $result = \mssql_data_seek($result_identifier, $row_number);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the length of field no. offset in
+ * result.
+ *
+ * @param resource $result The result resource that is being evaluated. This result comes from a
+ * call to mssql_query.
+ * @param int $offset The field offset, starts at 0. If omitted, the current field is used.
+ * @return int The length of the specified field index on success.
+ * @throws MssqlException
+ *
+ */
+function mssql_field_length($result, int $offset = -1): int
+{
+ error_clear_last();
+ $result = \mssql_field_length($result, $offset);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the name of field no. offset in
+ * result.
+ *
+ * @param resource $result The result resource that is being evaluated. This result comes from a
+ * call to mssql_query.
+ * @param int $offset The field offset, starts at 0. If omitted, the current field is used.
+ * @return string The name of the specified field index on success.
+ * @throws MssqlException
+ *
+ */
+function mssql_field_name($result, int $offset = -1): string
+{
+ error_clear_last();
+ $result = \mssql_field_name($result, $offset);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Seeks to the specified field offset. If the next call to
+ * mssql_fetch_field won't include a field
+ * offset, this field would be returned.
+ *
+ * @param resource $result The result resource that is being evaluated. This result comes from a
+ * call to mssql_query.
+ * @param int $field_offset The field offset, starts at 0.
+ * @throws MssqlException
+ *
+ */
+function mssql_field_seek($result, int $field_offset): void
+{
+ error_clear_last();
+ $result = \mssql_field_seek($result, $field_offset);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the type of field no. offset in
+ * result.
+ *
+ * @param resource $result The result resource that is being evaluated. This result comes from a
+ * call to mssql_query.
+ * @param int $offset The field offset, starts at 0. If omitted, the current field is used.
+ * @return string The type of the specified field index on success.
+ * @throws MssqlException
+ *
+ */
+function mssql_field_type($result, int $offset = -1): string
+{
+ error_clear_last();
+ $result = \mssql_field_type($result, $offset);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mssql_free_result only needs to be called
+ * if you are worried about using too much memory while your script
+ * is running. All result memory will automatically be freed when
+ * the script ends. You may call mssql_free_result
+ * with the result identifier as an argument and the associated
+ * result memory will be freed.
+ *
+ * @param resource $result The result resource that is being freed. This result comes from a
+ * call to mssql_query.
+ * @throws MssqlException
+ *
+ */
+function mssql_free_result($result): void
+{
+ error_clear_last();
+ $result = \mssql_free_result($result);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * mssql_free_statement only needs to be called
+ * if you are worried about using too much memory while your script
+ * is running. All statement memory will automatically be freed when
+ * the script ends. You may call mssql_free_statement
+ * with the statement identifier as an argument and the associated
+ * statement memory will be freed.
+ *
+ * @param resource $stmt Statement resource, obtained with mssql_init.
+ * @throws MssqlException
+ *
+ */
+function mssql_free_statement($stmt): void
+{
+ error_clear_last();
+ $result = \mssql_free_statement($stmt);
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Initializes a stored procedure or a remote stored procedure.
+ *
+ * @param string $sp_name Stored procedure name, like ownew.sp_name or
+ * otherdb.owner.sp_name.
+ * @param resource $link_identifier A MS SQL link identifier, returned by
+ * mssql_connect.
+ * @return resource Returns a resource identifier "statement", used in subsequent calls to
+ * mssql_bind and mssql_executes.
+ * @throws MssqlException
+ *
+ */
+function mssql_init(string $sp_name, $link_identifier = null)
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \mssql_init($sp_name, $link_identifier);
+ } else {
+ $result = \mssql_init($sp_name);
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mssql_pconnect acts very much like
+ * mssql_connect with two major differences.
+ *
+ * First, when connecting, the function would first try to find a
+ * (persistent) link that's already open with the same host,
+ * username and password. If one is found, an identifier for it
+ * will be returned instead of opening a new connection.
+ *
+ * Second, the connection to the SQL server will not be closed when
+ * the execution of the script ends. Instead, the link will remain
+ * open for future use (mssql_close will not
+ * close links established by mssql_pconnect).
+ *
+ * This type of links is therefore called 'persistent'.
+ *
+ * @param string $servername The MS SQL server. It can also include a port number. e.g.
+ * hostname:port.
+ * @param string $username The username.
+ * @param string $password The password.
+ * @param bool $new_link If a second call is made to mssql_pconnect with
+ * the same arguments, no new link will be established, but instead, the
+ * link identifier of the already opened link will be returned. This
+ * parameter modifies this behavior and makes
+ * mssql_pconnect always open a new link, even if
+ * mssql_pconnect was called before with the same
+ * parameters.
+ * @return resource Returns a positive MS SQL persistent link identifier on success.
+ * @throws MssqlException
+ *
+ */
+function mssql_pconnect(string $servername = null, string $username = null, string $password = null, bool $new_link = false)
+{
+ error_clear_last();
+ if ($new_link !== false) {
+ $result = \mssql_pconnect($servername, $username, $password, $new_link);
+ } elseif ($password !== null) {
+ $result = \mssql_pconnect($servername, $username, $password);
+ } elseif ($username !== null) {
+ $result = \mssql_pconnect($servername, $username);
+ } elseif ($servername !== null) {
+ $result = \mssql_pconnect($servername);
+ } else {
+ $result = \mssql_pconnect();
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mssql_query sends a query to the currently active
+ * database on the server that's associated with the specified link
+ * identifier.
+ *
+ * @param string $query An SQL query.
+ * @param resource $link_identifier A MS SQL link identifier, returned by
+ * mssql_connect or
+ * mssql_pconnect.
+ *
+ * If the link identifier isn't specified, the last opened link is
+ * assumed. If no link is open, the function tries to establish a link
+ * as if mssql_connect was called, and use it.
+ * @param int $batch_size The number of records to batch in the buffer.
+ * @return mixed Returns a MS SQL result resource on success, TRUE if no rows were
+ * returned.
+ * @throws MssqlException
+ *
+ */
+function mssql_query(string $query, $link_identifier = null, int $batch_size = 0)
+{
+ error_clear_last();
+ if ($batch_size !== 0) {
+ $result = \mssql_query($query, $link_identifier, $batch_size);
+ } elseif ($link_identifier !== null) {
+ $result = \mssql_query($query, $link_identifier);
+ } else {
+ $result = \mssql_query($query);
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mssql_select_db sets the current active
+ * database on the server that's associated with the specified link
+ * identifier.
+ *
+ * Every subsequent call to mssql_query will be
+ * made on the active database.
+ *
+ * @param string $database_name The database name.
+ *
+ * To escape the name of a database that contains spaces, hyphens ("-"),
+ * or any other exceptional characters, the database name must be
+ * enclosed in brackets, as is shown in the example, below. This
+ * technique must also be applied when selecting a database name that is
+ * also a reserved word (such as primary).
+ * @param resource $link_identifier A MS SQL link identifier, returned by
+ * mssql_connect or
+ * mssql_pconnect.
+ *
+ * If no link identifier is specified, the last opened link is assumed.
+ * If no link is open, the function will try to establish a link as if
+ * mssql_connect was called, and use it.
+ * @throws MssqlException
+ *
+ */
+function mssql_select_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \mssql_select_db($database_name, $link_identifier);
+ } else {
+ $result = \mssql_select_db($database_name);
+ }
+ if ($result === false) {
+ throw MssqlException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\StatsException;
+
+/**
+ * Returns the covariance of a and b.
+ *
+ * @param array $a The first array
+ * @param array $b The second array
+ * @return float Returns the covariance of a and b.
+ * @throws StatsException
+ *
+ */
+function stats_covariance(array $a, array $b): float
+{
+ error_clear_last();
+ $result = \stats_covariance($a, $b);
+ if ($result === false) {
+ throw StatsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the standard deviation of the values in a.
+ *
+ * @param array $a The array of data to find the standard deviation for. Note that all
+ * values of the array will be cast to float.
+ * @param bool $sample Indicates if a represents a sample of the
+ * population; defaults to FALSE.
+ * @return float Returns the standard deviation on success; FALSE on failure.
+ * @throws StatsException
+ *
+ */
+function stats_standard_deviation(array $a, bool $sample = false): float
+{
+ error_clear_last();
+ $result = \stats_standard_deviation($a, $sample);
+ if ($result === false) {
+ throw StatsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the Pearson correlation coefficient between arr1 and arr2.
+ *
+ * @param array $arr1 The first array
+ * @param array $arr2 The second array
+ * @return float Returns the Pearson correlation coefficient between arr1 and arr2.
+ * @throws StatsException
+ *
+ */
+function stats_stat_correlation(array $arr1, array $arr2): float
+{
+ error_clear_last();
+ $result = \stats_stat_correlation($arr1, $arr2);
+ if ($result === false) {
+ throw StatsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the inner product of arr1 and arr2.
+ *
+ * @param array $arr1 The first array
+ * @param array $arr2 The second array
+ * @return float Returns the inner product of arr1 and arr2.
+ * @throws StatsException
+ *
+ */
+function stats_stat_innerproduct(array $arr1, array $arr2): float
+{
+ error_clear_last();
+ $result = \stats_stat_innerproduct($arr1, $arr2);
+ if ($result === false) {
+ throw StatsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the variance of the values in a.
+ *
+ * @param array $a The array of data to find the standard deviation for. Note that all
+ * values of the array will be cast to float.
+ * @param bool $sample Indicates if a represents a sample of the
+ * population; defaults to FALSE.
+ * @return float Returns the variance on success; FALSE on failure.
+ * @throws StatsException
+ *
+ */
+function stats_variance(array $a, bool $sample = false): float
+{
+ error_clear_last();
+ $result = \stats_variance($a, $sample);
+ if ($result === false) {
+ throw StatsException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ApacheException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ApcuException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ArrayException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class Bzip2Exception extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class CalendarException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ClassobjException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ComException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class CubridException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class DatetimeException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class DirException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class EioException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ErrorfuncException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ExecException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FileinfoException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FilesystemException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FilterException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FpmException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FtpException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class FunchandException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class GmpException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class GnupgException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class HashException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class IbaseException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class IbmDb2Exception extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class IconvException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ImageException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ImapException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class InfoException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class IngresiiException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class InotifyException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class LdapException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class LibxmlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class LzfException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MailparseException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MbstringException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MiscException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MsqlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MysqlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MysqliException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MysqlndMsException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class MysqlndQcException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class NetworkException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class Oci8Exception extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class OpcacheException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class OutcontrolException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PasswordException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PcntlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PdfException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PgsqlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PosixException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PsException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class PspellException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ReadlineException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class RpminfoException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class RrdException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SemException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SessionException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ShmopException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SimplexmlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SocketsException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SodiumException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SolrException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SplException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SqlsrvException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SsdeepException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class Ssh2Exception extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class StreamException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class StringsException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class SwooleException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class UodbcException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class UopzException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class UrlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class VarException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class XdiffException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class XmlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class XmlrpcException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class YamlException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class YazException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ZipException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+namespace Safe\Exceptions;
+
+class ZlibException extends \ErrorException implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $error = error_get_last();
+ return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ApacheException;
+
+/**
+ * Fetch the Apache version.
+ *
+ * @return string Returns the Apache version on success.
+ * @throws ApacheException
+ *
+ */
+function apache_get_version(): string
+{
+ error_clear_last();
+ $result = \apache_get_version();
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieve an Apache environment variable specified by
+ * variable.
+ *
+ * This function requires Apache 2 otherwise it's undefined.
+ *
+ * @param string $variable The Apache environment variable
+ * @param bool $walk_to_top Whether to get the top-level variable available to all Apache layers.
+ * @return string The value of the Apache environment variable on success
+ * @throws ApacheException
+ *
+ */
+function apache_getenv(string $variable, bool $walk_to_top = false): string
+{
+ error_clear_last();
+ $result = \apache_getenv($variable, $walk_to_top);
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Fetches all HTTP request headers from the current request. Works in the
+ * Apache, FastCGI, CLI, FPM and NSAPI server module
+ * in Netscape/iPlanet/SunONE webservers.
+ *
+ * @return array An associative array of all the HTTP headers in the current request.
+ * @throws ApacheException
+ *
+ */
+function apache_request_headers(): array
+{
+ error_clear_last();
+ $result = \apache_request_headers();
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * apache_reset_timeout resets the Apache write timer,
+ * which defaults to 300 seconds. With set_time_limit(0);
+ * ignore_user_abort(true) and periodic
+ * apache_reset_timeout calls, Apache can theoretically
+ * run forever.
+ *
+ * This function requires Apache 1.
+ *
+ * @throws ApacheException
+ *
+ */
+function apache_reset_timeout(): void
+{
+ error_clear_last();
+ $result = \apache_reset_timeout();
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetch all HTTP response headers. Works in the
+ * Apache, FastCGI, CLI, FPM and NSAPI server module
+ * in Netscape/iPlanet/SunONE webservers.
+ *
+ * @return array An array of all Apache response headers on success.
+ * @throws ApacheException
+ *
+ */
+function apache_response_headers(): array
+{
+ error_clear_last();
+ $result = \apache_response_headers();
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * apache_setenv sets the value of the Apache
+ * environment variable specified by
+ * variable.
+ *
+ * @param string $variable The environment variable that's being set.
+ * @param string $value The new variable value.
+ * @param bool $walk_to_top Whether to set the top-level variable available to all Apache layers.
+ * @throws ApacheException
+ *
+ */
+function apache_setenv(string $variable, string $value, bool $walk_to_top = false): void
+{
+ error_clear_last();
+ $result = \apache_setenv($variable, $value, $walk_to_top);
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetches all HTTP headers from the current request.
+ *
+ * This function is an alias for apache_request_headers.
+ * Please read the apache_request_headers
+ * documentation for more information on how this function works.
+ *
+ * @return array An associative array of all the HTTP headers in the current request.
+ * @throws ApacheException
+ *
+ */
+function getallheaders(): array
+{
+ error_clear_last();
+ $result = \getallheaders();
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * virtual is an Apache-specific function which
+ * is similar to <!--#include virtual...--> in
+ * mod_include.
+ * It performs an Apache sub-request. It is useful for including
+ * CGI scripts or .shtml files, or anything else that you would
+ * parse through Apache. Note that for a CGI script, the script
+ * must generate valid CGI headers. At the minimum that means it
+ * must generate a Content-Type header.
+ *
+ * To run the sub-request, all buffers are terminated and flushed to the
+ * browser, pending headers are sent too.
+ *
+ * @param string $filename The file that the virtual command will be performed on.
+ * @throws ApacheException
+ *
+ */
+function virtual(string $filename): void
+{
+ error_clear_last();
+ $result = \virtual($filename);
+ if ($result === false) {
+ throw ApacheException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ApcuException;
+
+/**
+ * Retrieves cached information and meta-data from APC's data store.
+ *
+ * @param bool $limited If limited is TRUE, the
+ * return value will exclude the individual list of cache entries. This
+ * is useful when trying to optimize calls for statistics gathering.
+ * @return array Array of cached data (and meta-data)
+ * @throws ApcuException
+ *
+ */
+function apcu_cache_info(bool $limited = false): array
+{
+ error_clear_last();
+ $result = \apcu_cache_info($limited);
+ if ($result === false) {
+ throw ApcuException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * apcu_cas updates an already existing integer value if the
+ * old parameter matches the currently stored value
+ * with the value of the new parameter.
+ *
+ * @param string $key The key of the value being updated.
+ * @param int $old The old value (the value currently stored).
+ * @param int $new The new value to update to.
+ * @throws ApcuException
+ *
+ */
+function apcu_cas(string $key, int $old, int $new): void
+{
+ error_clear_last();
+ $result = \apcu_cas($key, $old, $new);
+ if ($result === false) {
+ throw ApcuException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Decreases a stored integer value.
+ *
+ * @param string $key The key of the value being decreased.
+ * @param int $step The step, or value to decrease.
+ * @param bool|null $success Optionally pass the success or fail boolean value to
+ * this referenced variable.
+ * @param int $ttl TTL to use if the operation inserts a new value (rather than decrementing an existing one).
+ * @return int Returns the current value of key's value on success
+ * @throws ApcuException
+ *
+ */
+function apcu_dec(string $key, int $step = 1, ?bool &$success = null, int $ttl = 0): int
+{
+ error_clear_last();
+ $result = \apcu_dec($key, $step, $success, $ttl);
+ if ($result === false) {
+ throw ApcuException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Increases a stored number.
+ *
+ * @param string $key The key of the value being increased.
+ * @param int $step The step, or value to increase.
+ * @param bool|null $success Optionally pass the success or fail boolean value to
+ * this referenced variable.
+ * @param int $ttl TTL to use if the operation inserts a new value (rather than incrementing an existing one).
+ * @return int Returns the current value of key's value on success
+ * @throws ApcuException
+ *
+ */
+function apcu_inc(string $key, int $step = 1, ?bool &$success = null, int $ttl = 0): int
+{
+ error_clear_last();
+ $result = \apcu_inc($key, $step, $success, $ttl);
+ if ($result === false) {
+ throw ApcuException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves APCu Shared Memory Allocation information.
+ *
+ * @param bool $limited When set to FALSE (default) apcu_sma_info will
+ * return a detailed information about each segment.
+ * @return array Array of Shared Memory Allocation data; FALSE on failure.
+ * @throws ApcuException
+ *
+ */
+function apcu_sma_info(bool $limited = false): array
+{
+ error_clear_last();
+ $result = \apcu_sma_info($limited);
+ if ($result === false) {
+ throw ApcuException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ArrayException;
+
+/**
+ * Creates an array by using the values from the
+ * keys array as keys and the values from the
+ * values array as the corresponding values.
+ *
+ * @param array $keys Array of keys to be used. Illegal values for key will be
+ * converted to string.
+ * @param array $values Array of values to be used
+ * @return array Returns the combined array, FALSE if the number of elements
+ * for each array isn't equal.
+ * @throws ArrayException
+ *
+ */
+function array_combine(array $keys, array $values): array
+{
+ error_clear_last();
+ $result = \array_combine($keys, $values);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * array_flip returns an array in flip
+ * order, i.e. keys from array become values and values
+ * from array become keys.
+ *
+ * Note that the values of array need to be valid
+ * keys, i.e. they need to be either integer or
+ * string. A warning will be emitted if a value has the wrong
+ * type, and the key/value pair in question will not be included
+ * in the result.
+ *
+ * If a value has several occurrences, the latest key will be
+ * used as its value, and all others will be lost.
+ *
+ * @param array $array An array of key/value pairs to be flipped.
+ * @return array Returns the flipped array on success.
+ * @throws ArrayException
+ *
+ */
+function array_flip(array $array): array
+{
+ error_clear_last();
+ $result = \array_flip($array);
+ if ($result === null) {
+ throw ArrayException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * array_replace_recursive replaces the values of
+ * array1 with the same values from all the following
+ * arrays. If a key from the first array exists in the second array, its value
+ * will be replaced by the value from the second array. If the key exists in the
+ * second array, and not the first, it will be created in the first array.
+ * If a key only exists in the first array, it will be left as is.
+ * If several arrays are passed for replacement, they will be processed
+ * in order, the later array overwriting the previous values.
+ *
+ * array_replace_recursive is recursive : it will recurse into
+ * arrays and apply the same process to the inner value.
+ *
+ * When the value in the first array is scalar, it will be replaced
+ * by the value in the second array, may it be scalar or array.
+ * When the value in the first array and the second array
+ * are both arrays, array_replace_recursive will replace
+ * their respective value recursively.
+ *
+ * @param array $array1 The array in which elements are replaced.
+ * @param array $params Optional. Arrays from which elements will be extracted.
+ * @return array Returns an array.
+ * @throws ArrayException
+ *
+ */
+function array_replace_recursive(array $array1, array ...$params): array
+{
+ error_clear_last();
+ if ($params !== []) {
+ $result = \array_replace_recursive($array1, ...$params);
+ } else {
+ $result = \array_replace_recursive($array1);
+ }
+ if ($result === null) {
+ throw ArrayException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * array_replace replaces the values of
+ * array1 with values having the same keys in each of the following
+ * arrays. If a key from the first array exists in the second array, its value
+ * will be replaced by the value from the second array. If the key exists in the
+ * second array, and not the first, it will be created in the first array.
+ * If a key only exists in the first array, it will be left as is.
+ * If several arrays are passed for replacement, they will be processed
+ * in order, the later arrays overwriting the previous values.
+ *
+ * array_replace is not recursive : it will replace
+ * values in the first array by whatever type is in the second array.
+ *
+ * @param array $array1 The array in which elements are replaced.
+ * @param array $params Arrays from which elements will be extracted.
+ * Values from later arrays overwrite the previous values.
+ * @return array Returns an array.
+ * @throws ArrayException
+ *
+ */
+function array_replace(array $array1, array ...$params): array
+{
+ error_clear_last();
+ if ($params !== []) {
+ $result = \array_replace($array1, ...$params);
+ } else {
+ $result = \array_replace($array1);
+ }
+ if ($result === null) {
+ throw ArrayException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Applies the user-defined callback function to each
+ * element of the array. This function will recurse
+ * into deeper arrays.
+ *
+ * @param array $array The input array.
+ * @param callable $callback Typically, callback takes on two parameters.
+ * The array parameter's value being the first, and
+ * the key/index second.
+ *
+ * If callback needs to be working with the
+ * actual values of the array, specify the first parameter of
+ * callback as a
+ * reference. Then,
+ * any changes made to those elements will be made in the
+ * original array itself.
+ * @param mixed $userdata If the optional userdata parameter is supplied,
+ * it will be passed as the third parameter to the
+ * callback.
+ * @throws ArrayException
+ *
+ */
+function array_walk_recursive(array &$array, callable $callback, $userdata = null): void
+{
+ error_clear_last();
+ $result = \array_walk_recursive($array, $callback, $userdata);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sorts an array such that array indices maintain their
+ * correlation with the array elements they are associated with.
+ *
+ * This is used mainly when sorting associative arrays where the actual
+ * element order is significant.
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags You may modify the behavior of the sort using the optional parameter
+ * sort_flags, for details see
+ * sort.
+ * @throws ArrayException
+ *
+ */
+function arsort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \arsort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sorts an array such that array indices maintain
+ * their correlation with the array elements they are associated
+ * with. This is used mainly when sorting associative arrays where
+ * the actual element order is significant.
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags You may modify the behavior of the sort using the optional
+ * parameter sort_flags, for details
+ * see sort.
+ * @throws ArrayException
+ *
+ */
+function asort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \asort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sorts an array by key in reverse order, maintaining key to data
+ * correlations. This is useful mainly for associative arrays.
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags You may modify the behavior of the sort using the optional parameter
+ * sort_flags, for details see
+ * sort.
+ * @throws ArrayException
+ *
+ */
+function krsort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \krsort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sorts an array by key, maintaining key to data correlations. This is
+ * useful mainly for associative arrays.
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags You may modify the behavior of the sort using the optional
+ * parameter sort_flags, for details
+ * see sort.
+ * @throws ArrayException
+ *
+ */
+function ksort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \ksort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * natcasesort is a case insensitive version of
+ * natsort.
+ *
+ * This function implements a sort algorithm that orders
+ * alphanumeric strings in the way a human being would while maintaining
+ * key/value associations. This is described as a "natural ordering".
+ *
+ * @param array $array The input array.
+ * @throws ArrayException
+ *
+ */
+function natcasesort(array &$array): void
+{
+ error_clear_last();
+ $result = \natcasesort($array);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function implements a sort algorithm that orders alphanumeric strings
+ * in the way a human being would while maintaining key/value associations.
+ * This is described as a "natural ordering". An example of the difference
+ * between this algorithm and the regular computer string sorting algorithms
+ * (used in sort) can be seen in the example below.
+ *
+ * @param array $array The input array.
+ * @throws ArrayException
+ *
+ */
+function natsort(array &$array): void
+{
+ error_clear_last();
+ $result = \natsort($array);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sorts an array in reverse order (highest to lowest).
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags You may modify the behavior of the sort using the optional
+ * parameter sort_flags, for details see
+ * sort.
+ * @throws ArrayException
+ *
+ */
+function rsort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \rsort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function shuffles (randomizes the order of the elements in) an array.
+ * It uses a pseudo random number generator that is not suitable for
+ * cryptographic purposes.
+ *
+ * @param array $array The array.
+ * @throws ArrayException
+ *
+ */
+function shuffle(array &$array): void
+{
+ error_clear_last();
+ $result = \shuffle($array);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sorts an array. Elements will be arranged from
+ * lowest to highest when this function has completed.
+ *
+ * @param array $array The input array.
+ * @param int $sort_flags The optional second parameter sort_flags
+ * may be used to modify the sorting behavior using these values:
+ *
+ * Sorting type flags:
+ *
+ *
+ * SORT_REGULAR - compare items normally;
+ * the details are described in the comparison operators section
+ *
+ *
+ * SORT_NUMERIC - compare items numerically
+ *
+ *
+ * SORT_STRING - compare items as strings
+ *
+ *
+ *
+ * SORT_LOCALE_STRING - compare items as
+ * strings, based on the current locale. It uses the locale,
+ * which can be changed using setlocale
+ *
+ *
+ *
+ *
+ * SORT_NATURAL - compare items as strings
+ * using "natural ordering" like natsort
+ *
+ *
+ *
+ *
+ * SORT_FLAG_CASE - can be combined
+ * (bitwise OR) with
+ * SORT_STRING or
+ * SORT_NATURAL to sort strings case-insensitively
+ *
+ *
+ *
+ * @throws ArrayException
+ *
+ */
+function sort(array &$array, int $sort_flags = SORT_REGULAR): void
+{
+ error_clear_last();
+ $result = \sort($array, $sort_flags);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sorts an array such that array indices maintain their
+ * correlation with the array elements they are associated with, using a
+ * user-defined comparison function.
+ *
+ * This is used mainly when sorting associative arrays where the actual
+ * element order is significant.
+ *
+ * @param array $array The input array.
+ * @param callable $value_compare_func See usort and uksort for
+ * examples of user-defined comparison functions.
+ * @throws ArrayException
+ *
+ */
+function uasort(array &$array, callable $value_compare_func): void
+{
+ error_clear_last();
+ $result = \uasort($array, $value_compare_func);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * uksort will sort the keys of an array using a
+ * user-supplied comparison function. If the array you wish to sort
+ * needs to be sorted by some non-trivial criteria, you should use
+ * this function.
+ *
+ * @param array $array The input array.
+ * @param callable $key_compare_func The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
+ * Note that before PHP 7.0.0 this integer had to be in the range from -2147483648 to 2147483647.
+ * @throws ArrayException
+ *
+ */
+function uksort(array &$array, callable $key_compare_func): void
+{
+ error_clear_last();
+ $result = \uksort($array, $key_compare_func);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function will sort an array by its values using a user-supplied
+ * comparison function. If the array you wish to sort needs to be sorted by
+ * some non-trivial criteria, you should use this function.
+ *
+ * @param array $array The input array.
+ * @param callable $value_compare_func The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
+ * Note that before PHP 7.0.0 this integer had to be in the range from -2147483648 to 2147483647.
+ *
+ * Returning non-integer values from the comparison
+ * function, such as float, will result in an internal cast to
+ * integer of the callback's return value. So values such as
+ * 0.99 and 0.1 will both be cast to an integer value of 0, which will
+ * compare such values as equal.
+ * @throws ArrayException
+ *
+ */
+function usort(array &$array, callable $value_compare_func): void
+{
+ error_clear_last();
+ $result = \usort($array, $value_compare_func);
+ if ($result === false) {
+ throw ArrayException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\Bzip2Exception;
+
+/**
+ * Closes the given bzip2 file pointer.
+ *
+ * @param resource $bz The file pointer. It must be valid and must point to a file
+ * successfully opened by bzopen.
+ * @throws Bzip2Exception
+ *
+ */
+function bzclose($bz): void
+{
+ error_clear_last();
+ $result = \bzclose($bz);
+ if ($result === false) {
+ throw Bzip2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Forces a write of all buffered bzip2 data for the file pointer
+ * bz.
+ *
+ * @param resource $bz The file pointer. It must be valid and must point to a file
+ * successfully opened by bzopen.
+ * @throws Bzip2Exception
+ *
+ */
+function bzflush($bz): void
+{
+ error_clear_last();
+ $result = \bzflush($bz);
+ if ($result === false) {
+ throw Bzip2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * bzread reads from the given bzip2 file pointer.
+ *
+ * Reading stops when length (uncompressed) bytes have
+ * been read or EOF is reached, whichever comes first.
+ *
+ * @param resource $bz The file pointer. It must be valid and must point to a file
+ * successfully opened by bzopen.
+ * @param int $length If not specified, bzread will read 1024
+ * (uncompressed) bytes at a time. A maximum of 8192
+ * uncompressed bytes will be read at a time.
+ * @return string Returns the uncompressed data.
+ * @throws Bzip2Exception
+ *
+ */
+function bzread($bz, int $length = 1024): string
+{
+ error_clear_last();
+ $result = \bzread($bz, $length);
+ if ($result === false) {
+ throw Bzip2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * bzwrite writes a string into the given bzip2 file
+ * stream.
+ *
+ * @param resource $bz The file pointer. It must be valid and must point to a file
+ * successfully opened by bzopen.
+ * @param string $data The written data.
+ * @param int $length If supplied, writing will stop after length
+ * (uncompressed) bytes have been written or the end of
+ * data is reached, whichever comes first.
+ * @return int Returns the number of bytes written.
+ * @throws Bzip2Exception
+ *
+ */
+function bzwrite($bz, string $data, int $length = null): int
+{
+ error_clear_last();
+ if ($length !== null) {
+ $result = \bzwrite($bz, $data, $length);
+ } else {
+ $result = \bzwrite($bz, $data);
+ }
+ if ($result === false) {
+ throw Bzip2Exception::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\CalendarException;
+
+/**
+ * This function will return a Unix timestamp corresponding to the
+ * Julian Day given in jday or FALSE if
+ * jday is outside of the allowed range. The time returned is
+ * UTC.
+ *
+ * @param int $jday A julian day number between 2440588 and 106751993607888
+ * on 64bit systems, or between 2440588 and 2465443 on 32bit systems.
+ * @return int The unix timestamp for the start (midnight, not noon) of the given Julian day.
+ * @throws CalendarException
+ *
+ */
+function jdtounix(int $jday): int
+{
+ error_clear_last();
+ $result = \jdtounix($jday);
+ if ($result === false) {
+ throw CalendarException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ClassobjException;
+
+/**
+ * Creates an alias named alias
+ * based on the user defined class original.
+ * The aliased class is exactly the same as the original class.
+ *
+ * @param string $original The original class.
+ * @param string $alias The alias name for the class.
+ * @param bool $autoload Whether to autoload if the original class is not found.
+ * @throws ClassobjException
+ *
+ */
+function class_alias(string $original, string $alias, bool $autoload = true): void
+{
+ error_clear_last();
+ $result = \class_alias($original, $alias, $autoload);
+ if ($result === false) {
+ throw ClassobjException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ComException;
+
+/**
+ * Instructs COM to sink events generated by
+ * comobject into the PHP object
+ * sinkobject.
+ *
+ * Be careful how you use this feature; if you are doing something similar
+ * to the example below, then it doesn't really make sense to run it in a
+ * web server context.
+ *
+ * @param object $comobject
+ * @param object $sinkobject sinkobject should be an instance of a class with
+ * methods named after those of the desired dispinterface; you may use
+ * com_print_typeinfo to help generate a template class
+ * for this purpose.
+ * @param mixed $sinkinterface PHP will attempt to use the default dispinterface type specified by
+ * the typelibrary associated with comobject, but
+ * you may override this choice by setting
+ * sinkinterface to the name of the dispinterface
+ * that you want to use.
+ * @throws ComException
+ *
+ */
+function com_event_sink(object $comobject, object $sinkobject, $sinkinterface = null): void
+{
+ error_clear_last();
+ if ($sinkinterface !== null) {
+ $result = \com_event_sink($comobject, $sinkobject, $sinkinterface);
+ } else {
+ $result = \com_event_sink($comobject, $sinkobject);
+ }
+ if ($result === false) {
+ throw ComException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Loads a type-library and registers its constants in the engine, as though
+ * they were defined using define.
+ *
+ * Note that it is much more efficient to use the configuration setting to pre-load and
+ * register the constants, although not so flexible.
+ *
+ * If you have turned on , then
+ * PHP will attempt to automatically register the constants associated with a
+ * COM object when you instantiate it. This depends on the interfaces
+ * provided by the COM object itself, and may not always be possible.
+ *
+ * @param string $typelib_name typelib_name can be one of the following:
+ *
+ *
+ *
+ * The filename of a .tlb file or the executable module
+ * that contains the type library.
+ *
+ *
+ *
+ *
+ * The type library GUID, followed by its version number, for example
+ * {00000200-0000-0010-8000-00AA006D2EA4},2,0.
+ *
+ *
+ *
+ *
+ * The type library name, e.g. Microsoft OLE DB ActiveX Data
+ * Objects 1.0 Library.
+ *
+ *
+ *
+ * PHP will attempt to resolve the type library in this order, as the
+ * process gets more and more expensive as you progress down the list;
+ * searching for the type library by name is handled by physically
+ * enumerating the registry until we find a match.
+ *
+ * The filename of a .tlb file or the executable module
+ * that contains the type library.
+ *
+ * The type library GUID, followed by its version number, for example
+ * {00000200-0000-0010-8000-00AA006D2EA4},2,0.
+ *
+ * The type library name, e.g. Microsoft OLE DB ActiveX Data
+ * Objects 1.0 Library.
+ * @param bool $case_sensitive The case_sensitive behaves inversely to
+ * the parameter $case_insensitive in the define
+ * function.
+ * @throws ComException
+ *
+ */
+function com_load_typelib(string $typelib_name, bool $case_sensitive = true): void
+{
+ error_clear_last();
+ $result = \com_load_typelib($typelib_name, $case_sensitive);
+ if ($result === false) {
+ throw ComException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The purpose of this function is to help generate a skeleton class for use
+ * as an event sink. You may also use it to generate a dump of any COM
+ * object, provided that it supports enough of the introspection interfaces,
+ * and that you know the name of the interface you want to display.
+ *
+ * @param object $comobject comobject should be either an instance of a COM
+ * object, or be the name of a typelibrary (which will be resolved according
+ * to the rules set out in com_load_typelib).
+ * @param string $dispinterface The name of an IDispatch descendant interface that you want to display.
+ * @param bool $wantsink If set to TRUE, the corresponding sink interface will be displayed
+ * instead.
+ * @throws ComException
+ *
+ */
+function com_print_typeinfo(object $comobject, string $dispinterface = null, bool $wantsink = false): void
+{
+ error_clear_last();
+ $result = \com_print_typeinfo($comobject, $dispinterface, $wantsink);
+ if ($result === false) {
+ throw ComException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\CubridException;
+
+/**
+ * This function frees the memory occupied by the result data. It returns
+ * TRUE on success. Note that it can only frees the
+ * client fetch buffer now, and if you want free all memory, use function
+ * cubrid_close_request.
+ *
+ * @param resource $req_identifier This is the request identifier.
+ * @throws CubridException
+ *
+ */
+function cubrid_free_result($req_identifier): void
+{
+ error_clear_last();
+ $result = \cubrid_free_result($req_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function returns the current CUBRID connection charset and is similar
+ * to the CUBRID MySQL compatible function
+ * cubrid_client_encoding.
+ *
+ * @param resource $conn_identifier The CUBRID connection.
+ * @return string A string that represents the CUBRID connection charset; on success.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_get_charset($conn_identifier): string
+{
+ error_clear_last();
+ $result = \cubrid_get_charset($conn_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns a string that represents the client library version.
+ *
+ * @return string A string that represents the client library version; on success.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_get_client_info(): string
+{
+ error_clear_last();
+ $result = \cubrid_get_client_info();
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns the CUBRID database parameters or it returns FALSE on
+ * failure. It returns an associative array with the values for the following
+ * parameters:
+ *
+ *
+ * PARAM_ISOLATION_LEVEL
+ * PARAM_LOCK_TIMEOUT
+ * PARAM_MAX_STRING_LENGTH
+ * PARAM_AUTO_COMMIT
+ *
+ *
+ *
+ * Database parameters
+ *
+ *
+ *
+ * Parameter
+ * Description
+ *
+ *
+ *
+ *
+ * PARAM_ISOLATION_LEVEL
+ * The transaction isolation level.
+ *
+ *
+ * LOCK_TIMEOUT
+ * CUBRID provides the lock timeout feature, which sets the waiting
+ * time (in seconds) for the lock until the transaction lock setting is
+ * allowed. The default value of the lock_timeout_in_secs parameter is
+ * -1, which means the application client will wait indefinitely until
+ * the transaction lock is allowed.
+ *
+ *
+ *
+ * PARAM_AUTO_COMMIT
+ * In CUBRID PHP, auto-commit mode is disabled by default for
+ * transaction management. It can be set by using
+ * cubrid_set_autocommit.
+ *
+ *
+ *
+ *
+ *
+ *
+ * The following table shows the isolation levels from 1 to 6. It consists of
+ * table schema (row) and isolation level:
+ *
+ * Levels of Isolation Supported by CUBRID
+ *
+ *
+ *
+ * Name
+ * Description
+ *
+ *
+ *
+ *
+ * SERIALIZABLE (6)
+ * In this isolation level, problems concerning concurrency (e.g.
+ * dirty read, non-repeatable read, phantom read, etc.) do not
+ * occur.
+ *
+ *
+ * REPEATABLE READ CLASS with REPEATABLE READ INSTANCES (5)
+ * Another transaction T2 cannot update the schema of table A while
+ * transaction T1 is viewing table A.
+ * Transaction T1 may experience phantom read for the record R that was
+ * inserted by another transaction T2 when it is repeatedly retrieving a
+ * specific record.
+ *
+ *
+ * REPEATABLE READ CLASS with READ COMMITTED INSTANCES (or CURSOR STABILITY) (4)
+ * Another transaction T2 cannot update the schema of table A while
+ * transaction T1 is viewing table A.
+ * Transaction T1 may experience R read (non-repeatable read) that was
+ * updated and committed by another transaction T2 when it is repeatedly
+ * retrieving the record R.
+ *
+ *
+ * REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES (3)
+ * Default isolation level. Another transaction T2 cannot update
+ * the schema of table A while transaction T1 is viewing table A.
+ * Transaction T1 may experience R' read (dirty read) for the record that
+ * was updated but not committed by another transaction T2.
+ *
+ *
+ * READ COMMITTED CLASS with READ COMMITTED INSTANCES (2)
+ * Transaction T1 may experience A' read (non-repeatable read) for
+ * the table that was updated and committed by another transaction T2
+ * while it is viewing table A repeatedly. Transaction T1 may experience
+ * R' read (non-repeatable read) for the record that was updated and
+ * committed by another transaction T2 while it is retrieving the record
+ * R repeatedly.
+ *
+ *
+ * READ COMMITTED CLASS with READ UNCOMMITTED INSTANCES (1)
+ * Transaction T1 may experience A' read (non-repeatable read) for
+ * the table that was updated and committed by another transaction T2
+ * while it is repeatedly viewing table A. Transaction T1 may experience
+ * R' read (dirty read) for the record that was updated but not committed
+ * by another transaction T2.
+ *
+ *
+ *
+ *
+ *
+ * @param resource $conn_identifier The CUBRID connection. If the connection identifier is not specified,
+ * the last link opened by cubrid_connect is assumed.
+ * @return array An associative array with CUBRID database parameters; on success.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_get_db_parameter($conn_identifier): array
+{
+ error_clear_last();
+ $result = \cubrid_get_db_parameter($conn_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns a string that represents the CUBRID server version.
+ *
+ * @param resource $conn_identifier The CUBRID connection.
+ * @return string A string that represents the CUBRID server version; on success.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_get_server_info($conn_identifier): string
+{
+ error_clear_last();
+ $result = \cubrid_get_server_info($conn_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_insert_id function retrieves the ID
+ * generated for the AUTO_INCREMENT column which is updated by the previous
+ * INSERT query. It returns 0 if the previous query does not generate new
+ * rows.
+ *
+ * @param resource $conn_identifier The connection identifier previously obtained by a call to
+ * cubrid_connect.
+ * @return string A string representing the ID generated for an AUTO_INCREMENT column by the
+ * previous query, on success.
+ *
+ * 0, if the previous query does not generate new rows.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_insert_id($conn_identifier = null): string
+{
+ error_clear_last();
+ if ($conn_identifier !== null) {
+ $result = \cubrid_insert_id($conn_identifier);
+ } else {
+ $result = \cubrid_insert_id();
+ }
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_lob2_new function is used to create a lob object (both BLOB and CLOB).
+ * This function should be used before you bind a lob object.
+ *
+ * @param resource $conn_identifier Connection identifier. If the connection identifier is not specified,
+ * the last connection opened by cubrid_connect or
+ * cubrid_connect_with_url is assumed.
+ * @param string $type It may be "BLOB" or "CLOB", it won't be case-sensitive. The default value is "BLOB".
+ * @return resource Lob identifier when it is successful.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_lob2_new($conn_identifier = null, string $type = "BLOB")
+{
+ error_clear_last();
+ if ($type !== "BLOB") {
+ $result = \cubrid_lob2_new($conn_identifier, $type);
+ } elseif ($conn_identifier !== null) {
+ $result = \cubrid_lob2_new($conn_identifier);
+ } else {
+ $result = \cubrid_lob2_new();
+ }
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_lob2_size function is used to get the size of a lob object.
+ *
+ * @param resource $lob_identifier Lob identifier as a result of cubrid_lob2_new or get from the result set.
+ * @return int It will return the size of the LOB object when it processes successfully.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_lob2_size($lob_identifier): int
+{
+ error_clear_last();
+ $result = \cubrid_lob2_size($lob_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_lob2_size64 function is used to get the
+ * size of a lob object. If the size of a lob object is larger than an
+ * integer data can be stored, you can use this function and it will return
+ * the size as a string.
+ *
+ * @param resource $lob_identifier Lob identifier as a result of cubrid_lob2_new or get from the result set.
+ * @return string It will return the size of the LOB object as a string when it processes successfully.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_lob2_size64($lob_identifier): string
+{
+ error_clear_last();
+ $result = \cubrid_lob2_size64($lob_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_lob2_tell function is used to tell the cursor position of the LOB object.
+ *
+ * @param resource $lob_identifier Lob identifier as a result of cubrid_lob2_new or get from the result set.
+ * @return int It will return the cursor position on the LOB object when it processes successfully.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_lob2_tell($lob_identifier): int
+{
+ error_clear_last();
+ $result = \cubrid_lob2_tell($lob_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_lob2_tell64 function is used to tell the
+ * cursor position of the LOB object. If the size of a lob object is larger
+ * than an integer data can be stored, you can use this function and it will
+ * return the position information as a string.
+ *
+ * @param resource $lob_identifier Lob identifier as a result of cubrid_lob2_new or get from the result set.
+ * @return string It will return the cursor position on the LOB object as a string when it processes successfully.
+ *
+ * FALSE on failure.
+ * @throws CubridException
+ *
+ */
+function cubrid_lob2_tell64($lob_identifier): string
+{
+ error_clear_last();
+ $result = \cubrid_lob2_tell64($lob_identifier);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The cubrid_set_db_parameter function is used to set
+ * the CUBRID database parameters. It can set the following CUBRID database
+ * parameters:
+ *
+ *
+ * PARAM_ISOLATION_LEVEL
+ * PARAM_LOCK_TIMEOUT
+ *
+ *
+ * @param resource $conn_identifier The CUBRID connection. If the connection identifier is not specified,
+ * the last link opened by cubrid_connect is assumed.
+ * @param int $param_type Database parameter type.
+ * @param int $param_value Isolation level value (1-6) or lock timeout (in seconds) value.
+ * @throws CubridException
+ *
+ */
+function cubrid_set_db_parameter($conn_identifier, int $param_type, int $param_value): void
+{
+ error_clear_last();
+ $result = \cubrid_set_db_parameter($conn_identifier, $param_type, $param_value);
+ if ($result === false) {
+ throw CubridException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\CurlException;
+
+/**
+ * This function URL encodes the given string according to RFC 3986.
+ *
+ * @param resource $ch A cURL handle returned by
+ * curl_init.
+ * @param string $str The string to be encoded.
+ * @return string Returns escaped string.
+ * @throws CurlException
+ *
+ */
+function curl_escape($ch, string $str): string
+{
+ error_clear_last();
+ $result = \curl_escape($ch, $str);
+ if ($result === false) {
+ throw CurlException::createFromCurlResource($ch);
+ }
+ return $result;
+}
+
+
+/**
+ * Execute the given cURL session.
+ *
+ * This function should be called after initializing a cURL session and all
+ * the options for the session are set.
+ *
+ * @param resource $ch A cURL handle returned by
+ * curl_init.
+ * @return bool|string Returns TRUE on success. However, if the CURLOPT_RETURNTRANSFER
+ * option is set, it will return
+ * the result on success, FALSE on failure.
+ * @throws CurlException
+ *
+ */
+function curl_exec($ch)
+{
+ error_clear_last();
+ $result = \curl_exec($ch);
+ if ($result === false) {
+ throw CurlException::createFromCurlResource($ch);
+ }
+ return $result;
+}
+
+
+/**
+ * Gets information about the last transfer.
+ *
+ * @param resource $ch A cURL handle returned by
+ * curl_init.
+ * @param int $opt This may be one of the following constants:
+ *
+ *
+ *
+ * CURLINFO_EFFECTIVE_URL - Last effective URL
+ *
+ *
+ *
+ *
+ * CURLINFO_HTTP_CODE - The last response code.
+ * As of PHP 5.5.0 and cURL 7.10.8, this is a legacy alias of
+ * CURLINFO_RESPONSE_CODE
+ *
+ *
+ *
+ *
+ * CURLINFO_FILETIME - Remote time of the retrieved document, with the CURLOPT_FILETIME enabled; if -1 is returned the time of the document is unknown
+ *
+ *
+ *
+ *
+ * CURLINFO_TOTAL_TIME - Total transaction time in seconds for last transfer
+ *
+ *
+ *
+ *
+ * CURLINFO_NAMELOOKUP_TIME - Time in seconds until name resolving was complete
+ *
+ *
+ *
+ *
+ * CURLINFO_CONNECT_TIME - Time in seconds it took to establish the connection
+ *
+ *
+ *
+ *
+ * CURLINFO_PRETRANSFER_TIME - Time in seconds from start until just before file transfer begins
+ *
+ *
+ *
+ *
+ * CURLINFO_STARTTRANSFER_TIME - Time in seconds until the first byte is about to be transferred
+ *
+ *
+ *
+ *
+ * CURLINFO_REDIRECT_COUNT - Number of redirects, with the CURLOPT_FOLLOWLOCATION option enabled
+ *
+ *
+ *
+ *
+ * CURLINFO_REDIRECT_TIME - Time in seconds of all redirection steps before final transaction was started, with the CURLOPT_FOLLOWLOCATION option enabled
+ *
+ *
+ *
+ *
+ * CURLINFO_REDIRECT_URL - With the CURLOPT_FOLLOWLOCATION option disabled: redirect URL found in the last transaction, that should be requested manually next. With the CURLOPT_FOLLOWLOCATION option enabled: this is empty. The redirect URL in this case is available in CURLINFO_EFFECTIVE_URL
+ *
+ *
+ *
+ *
+ * CURLINFO_PRIMARY_IP - IP address of the most recent connection
+ *
+ *
+ *
+ *
+ * CURLINFO_PRIMARY_PORT - Destination port of the most recent connection
+ *
+ *
+ *
+ *
+ * CURLINFO_LOCAL_IP - Local (source) IP address of the most recent connection
+ *
+ *
+ *
+ *
+ * CURLINFO_LOCAL_PORT - Local (source) port of the most recent connection
+ *
+ *
+ *
+ *
+ * CURLINFO_SIZE_UPLOAD - Total number of bytes uploaded
+ *
+ *
+ *
+ *
+ * CURLINFO_SIZE_DOWNLOAD - Total number of bytes downloaded
+ *
+ *
+ *
+ *
+ * CURLINFO_SPEED_DOWNLOAD - Average download speed
+ *
+ *
+ *
+ *
+ * CURLINFO_SPEED_UPLOAD - Average upload speed
+ *
+ *
+ *
+ *
+ * CURLINFO_HEADER_SIZE - Total size of all headers received
+ *
+ *
+ *
+ *
+ * CURLINFO_HEADER_OUT - The request string sent. For this to
+ * work, add the CURLINFO_HEADER_OUT option to the handle by calling
+ * curl_setopt
+ *
+ *
+ *
+ *
+ * CURLINFO_REQUEST_SIZE - Total size of issued requests, currently only for HTTP requests
+ *
+ *
+ *
+ *
+ * CURLINFO_SSL_VERIFYRESULT - Result of SSL certification verification requested by setting CURLOPT_SSL_VERIFYPEER
+ *
+ *
+ *
+ *
+ * CURLINFO_CONTENT_LENGTH_DOWNLOAD - Content length of download, read from Content-Length: field
+ *
+ *
+ *
+ *
+ * CURLINFO_CONTENT_LENGTH_UPLOAD - Specified size of upload
+ *
+ *
+ *
+ *
+ * CURLINFO_CONTENT_TYPE - Content-Type: of the requested document. NULL indicates server did not send valid Content-Type: header
+ *
+ *
+ *
+ *
+ * CURLINFO_PRIVATE - Private data associated with this cURL handle, previously set with the CURLOPT_PRIVATE option of curl_setopt
+ *
+ *
+ *
+ *
+ * CURLINFO_RESPONSE_CODE - The last response code
+ *
+ *
+ *
+ *
+ * CURLINFO_HTTP_CONNECTCODE - The CONNECT response code
+ *
+ *
+ *
+ *
+ * CURLINFO_HTTPAUTH_AVAIL - Bitmask indicating the authentication method(s) available according to the previous response
+ *
+ *
+ *
+ *
+ * CURLINFO_PROXYAUTH_AVAIL - Bitmask indicating the proxy authentication method(s) available according to the previous response
+ *
+ *
+ *
+ *
+ * CURLINFO_OS_ERRNO - Errno from a connect failure. The number is OS and system specific.
+ *
+ *
+ *
+ *
+ * CURLINFO_NUM_CONNECTS - Number of connections curl had to create to achieve the previous transfer
+ *
+ *
+ *
+ *
+ * CURLINFO_SSL_ENGINES - OpenSSL crypto-engines supported
+ *
+ *
+ *
+ *
+ * CURLINFO_COOKIELIST - All known cookies
+ *
+ *
+ *
+ *
+ * CURLINFO_FTP_ENTRY_PATH - Entry path in FTP server
+ *
+ *
+ *
+ *
+ * CURLINFO_APPCONNECT_TIME - Time in seconds it took from the start until the SSL/SSH connect/handshake to the remote host was completed
+ *
+ *
+ *
+ *
+ * CURLINFO_CERTINFO - TLS certificate chain
+ *
+ *
+ *
+ *
+ * CURLINFO_CONDITION_UNMET - Info on unmet time conditional
+ *
+ *
+ *
+ *
+ * CURLINFO_RTSP_CLIENT_CSEQ - Next RTSP client CSeq
+ *
+ *
+ *
+ *
+ * CURLINFO_RTSP_CSEQ_RECV - Recently received CSeq
+ *
+ *
+ *
+ *
+ * CURLINFO_RTSP_SERVER_CSEQ - Next RTSP server CSeq
+ *
+ *
+ *
+ *
+ * CURLINFO_RTSP_SESSION_ID - RTSP session ID
+ *
+ *
+ *
+ *
+ * CURLINFO_CONTENT_LENGTH_DOWNLOAD_T - The content-length of the download. This is the value read from the Content-Type: field. -1 if the size isn't known
+ *
+ *
+ *
+ *
+ * CURLINFO_CONTENT_LENGTH_UPLOAD_T - The specified size of the upload. -1 if the size isn't known
+ *
+ *
+ *
+ *
+ * CURLINFO_HTTP_VERSION - The version used in the last HTTP connection. The return value will be one of the defined CURL_HTTP_VERSION_* constants or 0 if the version can't be determined
+ *
+ *
+ *
+ *
+ * CURLINFO_PROTOCOL - The protocol used in the last HTTP connection. The returned value will be exactly one of the CURLPROTO_* values
+ *
+ *
+ *
+ *
+ * CURLINFO_PROXY_SSL_VERIFYRESULT - The result of the certificate verification that was requested (using the CURLOPT_PROXY_SSL_VERIFYPEER option). Only used for HTTPS proxies
+ *
+ *
+ *
+ *
+ * CURLINFO_SCHEME - The URL scheme used for the most recent connection
+ *
+ *
+ *
+ *
+ * CURLINFO_SIZE_DOWNLOAD_T - Total number of bytes that were downloaded. The number is only for the latest transfer and will be reset again for each new transfer
+ *
+ *
+ *
+ *
+ * CURLINFO_SIZE_UPLOAD_T - Total number of bytes that were uploaded
+ *
+ *
+ *
+ *
+ * CURLINFO_SPEED_DOWNLOAD_T - The average download speed in bytes/second that curl measured for the complete download
+ *
+ *
+ *
+ *
+ * CURLINFO_SPEED_UPLOAD_T - The average upload speed in bytes/second that curl measured for the complete upload
+ *
+ *
+ *
+ *
+ * CURLINFO_APPCONNECT_TIME_T - Time, in microseconds, it took from the start until the SSL/SSH connect/handshake to the remote host was completed
+ *
+ *
+ *
+ *
+ * CURLINFO_CONNECT_TIME_T - Total time taken, in microseconds, from the start until the connection to the remote host (or proxy) was completed
+ *
+ *
+ *
+ *
+ * CURLINFO_FILETIME_T - Remote time of the retrieved document (as Unix timestamp), an alternative to CURLINFO_FILETIME to allow systems with 32 bit long variables to extract dates outside of the 32bit timestamp range
+ *
+ *
+ *
+ *
+ * CURLINFO_NAMELOOKUP_TIME_T - Time in microseconds from the start until the name resolving was completed
+ *
+ *
+ *
+ *
+ * CURLINFO_PRETRANSFER_TIME_T - Time taken from the start until the file transfer is just about to begin, in microseconds
+ *
+ *
+ *
+ *
+ * CURLINFO_REDIRECT_TIME_T - Total time, in microseconds, it took for all redirection steps include name lookup, connect, pretransfer and transfer before final transaction was started
+ *
+ *
+ *
+ *
+ * CURLINFO_STARTTRANSFER_TIME_T - Time, in microseconds, it took from the start until the first byte is received
+ *
+ *
+ *
+ *
+ * CURLINFO_TOTAL_TIME_T - Total time in microseconds for the previous transfer, including name resolving, TCP connect etc.
+ *
+ *
+ *
+ * @return mixed If opt is given, returns its value.
+ * Otherwise, returns an associative array with the following elements
+ * (which correspond to opt):
+ *
+ *
+ *
+ * "url"
+ *
+ *
+ *
+ *
+ * "content_type"
+ *
+ *
+ *
+ *
+ * "http_code"
+ *
+ *
+ *
+ *
+ * "header_size"
+ *
+ *
+ *
+ *
+ * "request_size"
+ *
+ *
+ *
+ *
+ * "filetime"
+ *
+ *
+ *
+ *
+ * "ssl_verify_result"
+ *
+ *
+ *
+ *
+ * "redirect_count"
+ *
+ *
+ *
+ *
+ * "total_time"
+ *
+ *
+ *
+ *
+ * "namelookup_time"
+ *
+ *
+ *
+ *
+ * "connect_time"
+ *
+ *
+ *
+ *
+ * "pretransfer_time"
+ *
+ *
+ *
+ *
+ * "size_upload"
+ *
+ *
+ *
+ *
+ * "size_download"
+ *
+ *
+ *
+ *
+ * "speed_download"
+ *
+ *
+ *
+ *
+ * "speed_upload"
+ *
+ *
+ *
+ *
+ * "download_content_length"
+ *
+ *
+ *
+ *
+ * "upload_content_length"
+ *
+ *
+ *
+ *
+ * "starttransfer_time"
+ *
+ *
+ *
+ *
+ * "redirect_time"
+ *
+ *
+ *
+ *
+ * "certinfo"
+ *
+ *
+ *
+ *
+ * "primary_ip"
+ *
+ *
+ *
+ *
+ * "primary_port"
+ *
+ *
+ *
+ *
+ * "local_ip"
+ *
+ *
+ *
+ *
+ * "local_port"
+ *
+ *
+ *
+ *
+ * "redirect_url"
+ *
+ *
+ *
+ *
+ * "request_header" (This is only set if the CURLINFO_HEADER_OUT
+ * is set by a previous call to curl_setopt)
+ *
+ *
+ *
+ * Note that private data is not included in the associative array and must be retrieved individually with the CURLINFO_PRIVATE option.
+ * @throws CurlException
+ *
+ */
+function curl_getinfo($ch, int $opt = null)
+{
+ error_clear_last();
+ if ($opt !== null) {
+ $result = \curl_getinfo($ch, $opt);
+ } else {
+ $result = \curl_getinfo($ch);
+ }
+ if ($result === false) {
+ throw CurlException::createFromCurlResource($ch);
+ }
+ return $result;
+}
+
+
+/**
+ * Initializes a new session and return a cURL handle for use with the
+ * curl_setopt, curl_exec,
+ * and curl_close functions.
+ *
+ * @param string $url If provided, the CURLOPT_URL option will be set
+ * to its value. You can manually set this using the
+ * curl_setopt function.
+ *
+ * The file protocol is disabled by cURL if
+ * open_basedir is set.
+ * @return resource Returns a cURL handle on success, FALSE on errors.
+ * @throws CurlException
+ *
+ */
+function curl_init(string $url = null)
+{
+ error_clear_last();
+ $result = \curl_init($url);
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Return an integer containing the last multi curl error number.
+ *
+ * @param resource $mh A cURL multi handle returned by
+ * curl_multi_init.
+ * @return int Return an integer containing the last multi curl error number.
+ * @throws CurlException
+ *
+ */
+function curl_multi_errno($mh): int
+{
+ error_clear_last();
+ $result = \curl_multi_errno($mh);
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Ask the multi handle if there are any messages or information from the individual transfers.
+ * Messages may include information such as an error code from the transfer or just the fact
+ * that a transfer is completed.
+ *
+ * Repeated calls to this function will return a new result each time, until a FALSE is returned
+ * as a signal that there is no more to get at this point. The integer pointed to with
+ * msgs_in_queue will contain the number of remaining messages after this
+ * function was called.
+ *
+ * @param resource $mh A cURL multi handle returned by
+ * curl_multi_init.
+ * @param int|null $msgs_in_queue Number of messages that are still in the queue
+ * @return array On success, returns an associative array for the message, FALSE on failure.
+ *
+ *
+ * Contents of the returned array
+ *
+ *
+ *
+ * Key:
+ * Value:
+ *
+ *
+ *
+ *
+ * msg
+ * The CURLMSG_DONE constant. Other return values
+ * are currently not available.
+ *
+ *
+ * result
+ * One of the CURLE_* constants. If everything is
+ * OK, the CURLE_OK will be the result.
+ *
+ *
+ * handle
+ * Resource of type curl indicates the handle which it concerns.
+ *
+ *
+ *
+ *
+ * @throws CurlException
+ *
+ */
+function curl_multi_info_read($mh, ?int &$msgs_in_queue = null): array
+{
+ error_clear_last();
+ $result = \curl_multi_info_read($mh, $msgs_in_queue);
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Allows the processing of multiple cURL handles asynchronously.
+ *
+ * @return resource Returns a cURL multi handle resource on success, FALSE on failure.
+ * @throws CurlException
+ *
+ */
+function curl_multi_init()
+{
+ error_clear_last();
+ $result = \curl_multi_init();
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets an option on the given cURL session handle.
+ *
+ * @param resource $ch A cURL handle returned by
+ * curl_init.
+ * @param int $option The CURLOPT_XXX option to set.
+ * @param mixed $value The value to be set on option.
+ *
+ * value should be a bool for the
+ * following values of the option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ * Notes
+ *
+ *
+ *
+ *
+ * CURLOPT_AUTOREFERER
+ *
+ * TRUE to automatically set the Referer: field in
+ * requests where it follows a Location: redirect.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_BINARYTRANSFER
+ *
+ * TRUE to return the raw output when
+ * CURLOPT_RETURNTRANSFER is used.
+ *
+ *
+ * From PHP 5.1.3, this option has no effect: the raw output will
+ * always be returned when
+ * CURLOPT_RETURNTRANSFER is used.
+ *
+ *
+ *
+ * CURLOPT_COOKIESESSION
+ *
+ * TRUE to mark this as a new cookie "session". It will force libcurl
+ * to ignore all cookies it is about to load that are "session cookies"
+ * from the previous session. By default, libcurl always stores and
+ * loads all cookies, independent if they are session cookies or not.
+ * Session cookies are cookies without expiry date and they are meant
+ * to be alive and existing for this "session" only.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_CERTINFO
+ *
+ * TRUE to output SSL certification information to STDERR
+ * on secure transfers.
+ *
+ *
+ * Added in cURL 7.19.1.
+ * Available since PHP 5.3.2.
+ * Requires CURLOPT_VERBOSE to be on to have an effect.
+ *
+ *
+ *
+ * CURLOPT_CONNECT_ONLY
+ *
+ * TRUE tells the library to perform all the required proxy authentication
+ * and connection setup, but no data transfer. This option is implemented for
+ * HTTP, SMTP and POP3.
+ *
+ *
+ * Added in 7.15.2.
+ * Available since PHP 5.5.0.
+ *
+ *
+ *
+ * CURLOPT_CRLF
+ *
+ * TRUE to convert Unix newlines to CRLF newlines
+ * on transfers.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_DISALLOW_USERNAME_IN_URL
+ *
+ * TRUE to not allow URLs that include a username. Usernames are allowed by default (0).
+ *
+ *
+ * Added in cURL 7.61.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_DNS_SHUFFLE_ADDRESSES
+ *
+ * TRUE to shuffle the order of all returned addresses so that they will be used
+ * in a random order, when a name is resolved and more than one IP address is returned.
+ * This may cause IPv4 to be used before IPv6 or vice versa.
+ *
+ *
+ * Added in cURL 7.60.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_HAPROXYPROTOCOL
+ *
+ * TRUE to send an HAProxy PROXY protocol v1 header at the start of the connection.
+ * The default action is not to send this header.
+ *
+ *
+ * Added in cURL 7.60.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_SSH_COMPRESSION
+ *
+ * TRUE to enable built-in SSH compression. This is a request, not an order;
+ * the server may or may not do it.
+ *
+ *
+ * Added in cURL 7.56.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_DNS_USE_GLOBAL_CACHE
+ *
+ * TRUE to use a global DNS cache. This option is not thread-safe.
+ * It is conditionally enabled by default if PHP is built for non-threaded use
+ * (CLI, FCGI, Apache2-Prefork, etc.).
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FAILONERROR
+ *
+ * TRUE to fail verbosely if the HTTP code returned
+ * is greater than or equal to 400. The default behavior is to return
+ * the page normally, ignoring the code.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSL_FALSESTART
+ *
+ * TRUE to enable TLS false start.
+ *
+ *
+ * Added in cURL 7.42.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_FILETIME
+ *
+ * TRUE to attempt to retrieve the modification
+ * date of the remote document. This value can be retrieved using
+ * the CURLINFO_FILETIME option with
+ * curl_getinfo.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FOLLOWLOCATION
+ *
+ * TRUE to follow any
+ * "Location: " header that the server sends as
+ * part of the HTTP header (note this is recursive, PHP will follow as
+ * many "Location: " headers that it is sent,
+ * unless CURLOPT_MAXREDIRS is set).
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FORBID_REUSE
+ *
+ * TRUE to force the connection to explicitly
+ * close when it has finished processing, and not be pooled for reuse.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FRESH_CONNECT
+ *
+ * TRUE to force the use of a new connection
+ * instead of a cached one.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FTP_USE_EPRT
+ *
+ * TRUE to use EPRT (and LPRT) when doing active
+ * FTP downloads. Use FALSE to disable EPRT and LPRT and use PORT
+ * only.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FTP_USE_EPSV
+ *
+ * TRUE to first try an EPSV command for FTP
+ * transfers before reverting back to PASV. Set to FALSE
+ * to disable EPSV.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FTP_CREATE_MISSING_DIRS
+ *
+ * TRUE to create missing directories when an FTP operation
+ * encounters a path that currently doesn't exist.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FTPAPPEND
+ *
+ * TRUE to append to the remote file instead of
+ * overwriting it.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_TCP_NODELAY
+ *
+ * TRUE to disable TCP's Nagle algorithm, which tries to minimize
+ * the number of small packets on the network.
+ *
+ *
+ * Available since PHP 5.2.1 for versions compiled with libcurl 7.11.2 or
+ * greater.
+ *
+ *
+ *
+ * CURLOPT_FTPASCII
+ *
+ * An alias of
+ * CURLOPT_TRANSFERTEXT. Use that instead.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_FTPLISTONLY
+ *
+ * TRUE to only list the names of an FTP
+ * directory.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_HEADER
+ *
+ * TRUE to include the header in the output.
+ *
+ *
+ *
+ *
+ *
+ * CURLINFO_HEADER_OUT
+ *
+ * TRUE to track the handle's request string.
+ *
+ *
+ * Available since PHP 5.1.3. The CURLINFO_
+ * prefix is intentional.
+ *
+ *
+ *
+ * CURLOPT_HTTP09_ALLOWED
+ *
+ * Whether to allow HTTP/0.9 responses. Defaults to FALSE as of libcurl 7.66.0;
+ * formerly it defaulted to TRUE.
+ *
+ *
+ * Available since PHP 7.3.15 and 7.4.3, respectively, if built against libcurl >= 7.64.0
+ *
+ *
+ *
+ * CURLOPT_HTTPGET
+ *
+ * TRUE to reset the HTTP request method to GET.
+ * Since GET is the default, this is only necessary if the request
+ * method has been changed.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_HTTPPROXYTUNNEL
+ *
+ * TRUE to tunnel through a given HTTP proxy.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_HTTP_CONTENT_DECODING
+ *
+ * FALSE to get the raw HTTP response body.
+ *
+ *
+ * Available as of PHP 5.5.0 if built against libcurl >= 7.16.2.
+ *
+ *
+ *
+ * CURLOPT_KEEP_SENDING_ON_ERROR
+ *
+ * TRUE to keep sending the request body if the HTTP code returned is
+ * equal to or larger than 300. The default action would be to stop sending
+ * and close the stream or connection. Suitable for manual NTLM authentication.
+ * Most applications do not need this option.
+ *
+ *
+ * Available as of PHP 7.3.0 if built against libcurl >= 7.51.0.
+ *
+ *
+ *
+ * CURLOPT_MUTE
+ *
+ * TRUE to be completely silent with regards to
+ * the cURL functions.
+ *
+ *
+ * Removed in cURL 7.15.5 (You can use CURLOPT_RETURNTRANSFER instead)
+ *
+ *
+ *
+ * CURLOPT_NETRC
+ *
+ * TRUE to scan the ~/.netrc
+ * file to find a username and password for the remote site that
+ * a connection is being established with.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_NOBODY
+ *
+ * TRUE to exclude the body from the output.
+ * Request method is then set to HEAD. Changing this to FALSE does
+ * not change it to GET.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_NOPROGRESS
+ *
+ * TRUE to disable the progress meter for cURL transfers.
+ *
+ *
+ * PHP automatically sets this option to TRUE, this should only be
+ * changed for debugging purposes.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_NOSIGNAL
+ *
+ * TRUE to ignore any cURL function that causes a
+ * signal to be sent to the PHP process. This is turned on by default
+ * in multi-threaded SAPIs so timeout options can still be used.
+ *
+ *
+ * Added in cURL 7.10.
+ *
+ *
+ *
+ * CURLOPT_PATH_AS_IS
+ *
+ * TRUE to not handle dot dot sequences.
+ *
+ *
+ * Added in cURL 7.42.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_PIPEWAIT
+ *
+ * TRUE to wait for pipelining/multiplexing.
+ *
+ *
+ * Added in cURL 7.43.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_POST
+ *
+ * TRUE to do a regular HTTP POST. This POST is the
+ * normal application/x-www-form-urlencoded kind,
+ * most commonly used by HTML forms.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PUT
+ *
+ * TRUE to HTTP PUT a file. The file to PUT must
+ * be set with CURLOPT_INFILE and
+ * CURLOPT_INFILESIZE.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_RETURNTRANSFER
+ *
+ * TRUE to return the transfer as a string of the
+ * return value of curl_exec instead of outputting
+ * it directly.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SAFE_UPLOAD
+ *
+ * TRUE to disable support for the @ prefix for
+ * uploading files in CURLOPT_POSTFIELDS, which
+ * means that values starting with @ can be safely
+ * passed as fields. CURLFile may be used for
+ * uploads instead.
+ *
+ *
+ * Added in PHP 5.5.0 with FALSE as the default value. PHP 5.6.0
+ * changes the default value to TRUE. PHP 7 removes this option;
+ * the CURLFile interface must be used to upload files.
+ *
+ *
+ *
+ * CURLOPT_SASL_IR
+ *
+ * TRUE to enable sending the initial response in the first packet.
+ *
+ *
+ * Added in cURL 7.31.10. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_SSL_ENABLE_ALPN
+ *
+ * FALSE to disable ALPN in the SSL handshake (if the SSL backend
+ * libcurl is built to use supports it), which can be used to
+ * negotiate http2.
+ *
+ *
+ * Added in cURL 7.36.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_SSL_ENABLE_NPN
+ *
+ * FALSE to disable NPN in the SSL handshake (if the SSL backend
+ * libcurl is built to use supports it), which can be used to
+ * negotiate http2.
+ *
+ *
+ * Added in cURL 7.36.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_SSL_VERIFYPEER
+ *
+ * FALSE to stop cURL from verifying the peer's
+ * certificate. Alternate certificates to verify against can be
+ * specified with the CURLOPT_CAINFO option
+ * or a certificate directory can be specified with the
+ * CURLOPT_CAPATH option.
+ *
+ *
+ * TRUE by default as of cURL 7.10. Default bundle installed as of
+ * cURL 7.10.
+ *
+ *
+ *
+ * CURLOPT_SSL_VERIFYSTATUS
+ *
+ * TRUE to verify the certificate's status.
+ *
+ *
+ * Added in cURL 7.41.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSL_VERIFYPEER
+ *
+ * FALSE to stop cURL from verifying the peer's certificate.
+ * Alternate certificates to verify against can be
+ * specified with the CURLOPT_CAINFO option
+ * or a certificate directory can be specified with the
+ * CURLOPT_CAPATH option.
+ * When set to false, the peer certificate verification succeeds regardless.
+ *
+ *
+ * TRUE by default. Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_SUPPRESS_CONNECT_HEADERS
+ *
+ * TRUE to suppress proxy CONNECT response headers from the user callback functions
+ * CURLOPT_HEADERFUNCTION and CURLOPT_WRITEFUNCTION,
+ * when CURLOPT_HTTPPROXYTUNNEL is used and a CONNECT request is made.
+ *
+ *
+ * Added in cURL 7.54.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_TCP_FASTOPEN
+ *
+ * TRUE to enable TCP Fast Open.
+ *
+ *
+ * Added in cURL 7.49.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_TFTP_NO_OPTIONS
+ *
+ * TRUE to not send TFTP options requests.
+ *
+ *
+ * Added in cURL 7.48.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_TRANSFERTEXT
+ *
+ * TRUE to use ASCII mode for FTP transfers.
+ * For LDAP, it retrieves data in plain text instead of HTML. On
+ * Windows systems, it will not set STDOUT to binary
+ * mode.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_UNRESTRICTED_AUTH
+ *
+ * TRUE to keep sending the username and password
+ * when following locations (using
+ * CURLOPT_FOLLOWLOCATION), even when the
+ * hostname has changed.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_UPLOAD
+ *
+ * TRUE to prepare for an upload.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_VERBOSE
+ *
+ * TRUE to output verbose information. Writes
+ * output to STDERR, or the file specified using
+ * CURLOPT_STDERR.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * TRUE to disable the progress meter for cURL transfers.
+ *
+ *
+ * PHP automatically sets this option to TRUE, this should only be
+ * changed for debugging purposes.
+ *
+ *
+ *
+ * PHP automatically sets this option to TRUE, this should only be
+ * changed for debugging purposes.
+ *
+ * value should be an integer for the
+ * following values of the option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ * Notes
+ *
+ *
+ *
+ *
+ * CURLOPT_BUFFERSIZE
+ *
+ * The size of the buffer to use for each read. There is no guarantee
+ * this request will be fulfilled, however.
+ *
+ *
+ * Added in cURL 7.10.
+ *
+ *
+ *
+ * CURLOPT_CLOSEPOLICY
+ *
+ * One of the CURLCLOSEPOLICY_* values.
+ *
+ *
+ * This option is deprecated, as it was never implemented in cURL and
+ * never had any effect.
+ *
+ *
+ *
+ *
+ * Removed in PHP 5.6.0.
+ *
+ *
+ *
+ * CURLOPT_CONNECTTIMEOUT
+ *
+ * The number of seconds to wait while trying to connect. Use 0 to
+ * wait indefinitely.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_CONNECTTIMEOUT_MS
+ *
+ * The number of milliseconds to wait while trying to connect. Use 0 to
+ * wait indefinitely.
+ *
+ * If libcurl is built to use the standard system name resolver, that
+ * portion of the connect will still use full-second resolution for
+ * timeouts with a minimum timeout allowed of one second.
+ *
+ *
+ * Added in cURL 7.16.2. Available since PHP 5.2.3.
+ *
+ *
+ *
+ * CURLOPT_DNS_CACHE_TIMEOUT
+ *
+ * The number of seconds to keep DNS entries in memory. This
+ * option is set to 120 (2 minutes) by default.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_EXPECT_100_TIMEOUT_MS
+ *
+ * The timeout for Expect: 100-continue responses in milliseconds.
+ * Defaults to 1000 milliseconds.
+ *
+ *
+ * Added in cURL 7.36.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS
+ *
+ * Head start for ipv6 for the happy eyeballs algorithm. Happy eyeballs attempts
+ * to connect to both IPv4 and IPv6 addresses for dual-stack hosts,
+ * preferring IPv6 first for timeout milliseconds.
+ * Defaults to CURL_HET_DEFAULT, which is currently 200 milliseconds.
+ *
+ *
+ * Added in cURL 7.59.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_FTPSSLAUTH
+ *
+ * The FTP authentication method (when is activated):
+ * CURLFTPAUTH_SSL (try SSL first),
+ * CURLFTPAUTH_TLS (try TLS first), or
+ * CURLFTPAUTH_DEFAULT (let cURL decide).
+ *
+ *
+ * Added in cURL 7.12.2.
+ *
+ *
+ *
+ * CURLOPT_HEADEROPT
+ *
+ * How to deal with headers. One of the following constants:
+ *
+ * CURLHEADER_UNIFIED: the headers specified in
+ * CURLOPT_HTTPHEADER will be used in requests
+ * both to servers and proxies. With this option enabled,
+ * CURLOPT_PROXYHEADER will not have any effect.
+ *
+ *
+ * CURLHEADER_SEPARATE: makes
+ * CURLOPT_HTTPHEADER headers only get sent to
+ * a server and not to a proxy. Proxy headers must be set with
+ * CURLOPT_PROXYHEADER to get used. Note that if
+ * a non-CONNECT request is sent to a proxy, libcurl will send both
+ * server headers and proxy headers. When doing CONNECT, libcurl will
+ * send CURLOPT_PROXYHEADER headers only to the
+ * proxy and then CURLOPT_HTTPHEADER headers
+ * only to the server.
+ *
+ *
+ * Defaults to CURLHEADER_SEPARATE as of cURL
+ * 7.42.1, and CURLHEADER_UNIFIED before.
+ *
+ *
+ *
+ * Added in cURL 7.37.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_HTTP_VERSION
+ *
+ * CURL_HTTP_VERSION_NONE (default, lets CURL
+ * decide which version to use),
+ * CURL_HTTP_VERSION_1_0 (forces HTTP/1.0),
+ * CURL_HTTP_VERSION_1_1 (forces HTTP/1.1),
+ * CURL_HTTP_VERSION_2_0 (attempts HTTP 2),
+ * CURL_HTTP_VERSION_2 (alias of CURL_HTTP_VERSION_2_0),
+ * CURL_HTTP_VERSION_2TLS (attempts HTTP 2 over TLS (HTTPS) only) or
+ * CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE (issues non-TLS HTTP requests using HTTP/2 without HTTP/1.1 Upgrade).
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_HTTPAUTH
+ *
+ *
+ * The HTTP authentication method(s) to use. The options are:
+ * CURLAUTH_BASIC,
+ * CURLAUTH_DIGEST,
+ * CURLAUTH_GSSNEGOTIATE,
+ * CURLAUTH_NTLM,
+ * CURLAUTH_ANY, and
+ * CURLAUTH_ANYSAFE.
+ *
+ *
+ * The bitwise | (or) operator can be used to combine
+ * more than one method. If this is done, cURL will poll the server to see
+ * what methods it supports and pick the best one.
+ *
+ *
+ * CURLAUTH_ANY is an alias for
+ * CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM.
+ *
+ *
+ * CURLAUTH_ANYSAFE is an alias for
+ * CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM.
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_INFILESIZE
+ *
+ * The expected size, in bytes, of the file when uploading a file to
+ * a remote site. Note that using this option will not stop libcurl
+ * from sending more data, as exactly what is sent depends on
+ * CURLOPT_READFUNCTION.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_LOW_SPEED_LIMIT
+ *
+ * The transfer speed, in bytes per second, that the transfer should be
+ * below during the count of CURLOPT_LOW_SPEED_TIME
+ * seconds before PHP considers the transfer too slow and aborts.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_LOW_SPEED_TIME
+ *
+ * The number of seconds the transfer speed should be below
+ * CURLOPT_LOW_SPEED_LIMIT before PHP considers
+ * the transfer too slow and aborts.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_MAXCONNECTS
+ *
+ * The maximum amount of persistent connections that are allowed.
+ * When the limit is reached,
+ * CURLOPT_CLOSEPOLICY is used to determine
+ * which connection to close.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_MAXREDIRS
+ *
+ * The maximum amount of HTTP redirections to follow. Use this option
+ * alongside CURLOPT_FOLLOWLOCATION.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PORT
+ *
+ * An alternative port number to connect to.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_POSTREDIR
+ *
+ * A bitmask of 1 (301 Moved Permanently), 2 (302 Found)
+ * and 4 (303 See Other) if the HTTP POST method should be maintained
+ * when CURLOPT_FOLLOWLOCATION is set and a
+ * specific type of redirect occurs.
+ *
+ *
+ * Added in cURL 7.19.1. Available since PHP 5.3.2.
+ *
+ *
+ *
+ * CURLOPT_PROTOCOLS
+ *
+ *
+ * Bitmask of CURLPROTO_* values. If used, this bitmask
+ * limits what protocols libcurl may use in the transfer. This allows you to have
+ * a libcurl built to support a wide range of protocols but still limit specific
+ * transfers to only be allowed to use a subset of them. By default libcurl will
+ * accept all protocols it supports.
+ * See also CURLOPT_REDIR_PROTOCOLS.
+ *
+ *
+ * Valid protocol options are:
+ * CURLPROTO_HTTP,
+ * CURLPROTO_HTTPS,
+ * CURLPROTO_FTP,
+ * CURLPROTO_FTPS,
+ * CURLPROTO_SCP,
+ * CURLPROTO_SFTP,
+ * CURLPROTO_TELNET,
+ * CURLPROTO_LDAP,
+ * CURLPROTO_LDAPS,
+ * CURLPROTO_DICT,
+ * CURLPROTO_FILE,
+ * CURLPROTO_TFTP,
+ * CURLPROTO_ALL
+ *
+ *
+ *
+ * Added in cURL 7.19.4.
+ *
+ *
+ *
+ * CURLOPT_PROXYAUTH
+ *
+ * The HTTP authentication method(s) to use for the proxy connection.
+ * Use the same bitmasks as described in
+ * CURLOPT_HTTPAUTH. For proxy authentication,
+ * only CURLAUTH_BASIC and
+ * CURLAUTH_NTLM are currently supported.
+ *
+ *
+ * Added in cURL 7.10.7.
+ *
+ *
+ *
+ * CURLOPT_PROXYPORT
+ *
+ * The port number of the proxy to connect to. This port number can
+ * also be set in CURLOPT_PROXY.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PROXYTYPE
+ *
+ * Either CURLPROXY_HTTP (default),
+ * CURLPROXY_SOCKS4,
+ * CURLPROXY_SOCKS5,
+ * CURLPROXY_SOCKS4A or
+ * CURLPROXY_SOCKS5_HOSTNAME.
+ *
+ *
+ * Added in cURL 7.10.
+ *
+ *
+ *
+ * CURLOPT_REDIR_PROTOCOLS
+ *
+ * Bitmask of CURLPROTO_* values. If used, this bitmask
+ * limits what protocols libcurl may use in a transfer that it follows to in
+ * a redirect when CURLOPT_FOLLOWLOCATION is enabled.
+ * This allows you to limit specific transfers to only be allowed to use a subset
+ * of protocols in redirections. By default libcurl will allow all protocols
+ * except for FILE and SCP. This is a difference compared to pre-7.19.4 versions
+ * which unconditionally would follow to all protocols supported.
+ * See also CURLOPT_PROTOCOLS for protocol constant values.
+ *
+ *
+ * Added in cURL 7.19.4.
+ *
+ *
+ *
+ * CURLOPT_RESUME_FROM
+ *
+ * The offset, in bytes, to resume a transfer from.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SOCKS5_AUTH
+ *
+ *
+ * The SOCKS5 authentication method(s) to use. The options are:
+ * CURLAUTH_BASIC,
+ * CURLAUTH_GSSAPI,
+ * CURLAUTH_NONE.
+ *
+ *
+ * The bitwise | (or) operator can be used to combine
+ * more than one method. If this is done, cURL will poll the server to see
+ * what methods it supports and pick the best one.
+ *
+ *
+ * CURLAUTH_BASIC allows username/password authentication.
+ *
+ *
+ * CURLAUTH_GSSAPI allows GSS-API authentication.
+ *
+ *
+ * CURLAUTH_NONE allows no authentication.
+ *
+ *
+ * Defaults to CURLAUTH_BASIC|CURLAUTH_GSSAPI.
+ * Set the actual username and password with the CURLOPT_PROXYUSERPWD option.
+ *
+ *
+ *
+ * Available as of 7.3.0 and curl >= 7.55.0.
+ *
+ *
+ *
+ * CURLOPT_SSL_OPTIONS
+ *
+ * Set SSL behavior options, which is a bitmask of any of the following constants:
+ *
+ * CURLSSLOPT_ALLOW_BEAST: do not attempt to use
+ * any workarounds for a security flaw in the SSL3 and TLS1.0 protocols.
+ *
+ *
+ * CURLSSLOPT_NO_REVOKE: disable certificate
+ * revocation checks for those SSL backends where such behavior is
+ * present.
+ *
+ *
+ *
+ * Added in cURL 7.25.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_SSL_VERIFYHOST
+ *
+ * 1 to check the existence of a common name in the
+ * SSL peer certificate. 2 to check the existence of
+ * a common name and also verify that it matches the hostname
+ * provided. 0 to not check the names. In production environments the value of this option
+ * should be kept at 2 (default value).
+ *
+ *
+ * Support for value 1 removed in cURL 7.28.1.
+ *
+ *
+ *
+ * CURLOPT_SSLVERSION
+ *
+ * One of CURL_SSLVERSION_DEFAULT (0),
+ * CURL_SSLVERSION_TLSv1 (1),
+ * CURL_SSLVERSION_SSLv2 (2),
+ * CURL_SSLVERSION_SSLv3 (3),
+ * CURL_SSLVERSION_TLSv1_0 (4),
+ * CURL_SSLVERSION_TLSv1_1 (5) or
+ * CURL_SSLVERSION_TLSv1_2 (6).
+ * The maximum TLS version can be set by using one of the CURL_SSLVERSION_MAX_*
+ * constants. It is also possible to OR one of the CURL_SSLVERSION_*
+ * constants with one of the CURL_SSLVERSION_MAX_* constants.
+ * CURL_SSLVERSION_MAX_DEFAULT (the maximum version supported by the library),
+ * CURL_SSLVERSION_MAX_TLSv1_0,
+ * CURL_SSLVERSION_MAX_TLSv1_1,
+ * CURL_SSLVERSION_MAX_TLSv1_2, or
+ * CURL_SSLVERSION_MAX_TLSv1_3.
+ *
+ *
+ * Your best bet is to not set this and let it use the default.
+ * Setting it to 2 or 3 is very dangerous given the known
+ * vulnerabilities in SSLv2 and SSLv3.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSL_OPTIONS
+ *
+ * Set proxy SSL behavior options, which is a bitmask of any of the following constants:
+ *
+ * CURLSSLOPT_ALLOW_BEAST: do not attempt to use
+ * any workarounds for a security flaw in the SSL3 and TLS1.0 protocols.
+ *
+ *
+ * CURLSSLOPT_NO_REVOKE: disable certificate
+ * revocation checks for those SSL backends where such behavior is
+ * present. (curl >= 7.44.0)
+ *
+ *
+ * CURLSSLOPT_NO_PARTIALCHAIN: do not accept "partial"
+ * certificate chains, which it otherwise does by default. (curl >= 7.68.0)
+ *
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSL_VERIFYHOST
+ *
+ * Set to 2 to verify in the HTTPS proxy's certificate name fields against the proxy name.
+ * When set to 0 the connection succeeds regardless of the names used in the certificate.
+ * Use that ability with caution!
+ * 1 treated as a debug option in curl 7.28.0 and earlier.
+ * From curl 7.28.1 to 7.65.3 CURLE_BAD_FUNCTION_ARGUMENT is returned.
+ * From curl 7.66.0 onwards 1 and 2 is treated as the same value.
+ * In production environments the value of this option should be kept at 2 (default value).
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSLVERSION
+ *
+ * One of CURL_SSLVERSION_DEFAULT,
+ * CURL_SSLVERSION_TLSv1,
+ * CURL_SSLVERSION_TLSv1_0,
+ * CURL_SSLVERSION_TLSv1_1,
+ * CURL_SSLVERSION_TLSv1_2,
+ * CURL_SSLVERSION_TLSv1_3,
+ * CURL_SSLVERSION_MAX_DEFAULT,
+ * CURL_SSLVERSION_MAX_TLSv1_0,
+ * CURL_SSLVERSION_MAX_TLSv1_1,
+ * CURL_SSLVERSION_MAX_TLSv1_2,
+ * CURL_SSLVERSION_MAX_TLSv1_3 or
+ * CURL_SSLVERSION_SSLv3.
+ *
+ *
+ * Your best bet is to not set this and let it use the default CURL_SSLVERSION_DEFAULT
+ * which will attempt to figure out the remote SSL protocol version.
+ *
+ *
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_STREAM_WEIGHT
+ *
+ * Set the numerical stream weight (a number between 1 and 256).
+ *
+ *
+ * Added in cURL 7.46.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_TCP_KEEPALIVE
+ *
+ * If set to 1, TCP keepalive probes will be sent. The delay and
+ * frequency of these probes can be controlled by the CURLOPT_TCP_KEEPIDLE
+ * and CURLOPT_TCP_KEEPINTVL options, provided the operating system
+ * supports them. If set to 0 (default) keepalive probes are disabled.
+ *
+ *
+ * Added in cURL 7.25.0. Available since PHP 5.5.0.
+ *
+ *
+ *
+ * CURLOPT_TCP_KEEPIDLE
+ *
+ * Sets the delay, in seconds, that the operating system will wait while the connection is
+ * idle before sending keepalive probes, if CURLOPT_TCP_KEEPALIVE is
+ * enabled. Not all operating systems support this option.
+ * The default is 60.
+ *
+ *
+ * Added in cURL 7.25.0. Available since PHP 5.5.0.
+ *
+ *
+ *
+ * CURLOPT_TCP_KEEPINTVL
+ *
+ * Sets the interval, in seconds, that the operating system will wait between sending
+ * keepalive probes, if CURLOPT_TCP_KEEPALIVE is enabled.
+ * Not all operating systems support this option.
+ * The default is 60.
+ *
+ *
+ * Added in cURL 7.25.0. Available since PHP 5.5.0.
+ *
+ *
+ *
+ * CURLOPT_TIMECONDITION
+ *
+ * How CURLOPT_TIMEVALUE is treated.
+ * Use CURL_TIMECOND_IFMODSINCE to return the
+ * page only if it has been modified since the time specified in
+ * CURLOPT_TIMEVALUE. If it hasn't been modified,
+ * a "304 Not Modified" header will be returned
+ * assuming CURLOPT_HEADER is TRUE.
+ * Use CURL_TIMECOND_IFUNMODSINCE for the reverse
+ * effect. CURL_TIMECOND_IFMODSINCE is the
+ * default.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_TIMEOUT
+ *
+ * The maximum number of seconds to allow cURL functions to execute.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_TIMEOUT_MS
+ *
+ * The maximum number of milliseconds to allow cURL functions to
+ * execute.
+ *
+ * If libcurl is built to use the standard system name resolver, that
+ * portion of the connect will still use full-second resolution for
+ * timeouts with a minimum timeout allowed of one second.
+ *
+ *
+ * Added in cURL 7.16.2. Available since PHP 5.2.3.
+ *
+ *
+ *
+ * CURLOPT_TIMEVALUE
+ *
+ * The time in seconds since January 1st, 1970. The time will be used
+ * by CURLOPT_TIMECONDITION. By default,
+ * CURL_TIMECOND_IFMODSINCE is used.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_TIMEVALUE_LARGE
+ *
+ * The time in seconds since January 1st, 1970. The time will be used
+ * by CURLOPT_TIMECONDITION. Defaults to zero.
+ * The difference between this option and CURLOPT_TIMEVALUE
+ * is the type of the argument. On systems where 'long' is only 32 bit wide,
+ * this option has to be used to set dates beyond the year 2038.
+ *
+ *
+ * Added in cURL 7.59.0. Available since PHP 7.3.0.
+ *
+ *
+ *
+ * CURLOPT_MAX_RECV_SPEED_LARGE
+ *
+ * If a download exceeds this speed (counted in bytes per second) on
+ * cumulative average during the transfer, the transfer will pause to
+ * keep the average rate less than or equal to the parameter value.
+ * Defaults to unlimited speed.
+ *
+ *
+ * Added in cURL 7.15.5. Available since PHP 5.4.0.
+ *
+ *
+ *
+ * CURLOPT_MAX_SEND_SPEED_LARGE
+ *
+ * If an upload exceeds this speed (counted in bytes per second) on
+ * cumulative average during the transfer, the transfer will pause to
+ * keep the average rate less than or equal to the parameter value.
+ * Defaults to unlimited speed.
+ *
+ *
+ * Added in cURL 7.15.5. Available since PHP 5.4.0.
+ *
+ *
+ *
+ * CURLOPT_SSH_AUTH_TYPES
+ *
+ * A bitmask consisting of one or more of
+ * CURLSSH_AUTH_PUBLICKEY,
+ * CURLSSH_AUTH_PASSWORD,
+ * CURLSSH_AUTH_HOST,
+ * CURLSSH_AUTH_KEYBOARD. Set to
+ * CURLSSH_AUTH_ANY to let libcurl pick one.
+ *
+ *
+ * Added in cURL 7.16.1.
+ *
+ *
+ *
+ * CURLOPT_IPRESOLVE
+ *
+ * Allows an application to select what kind of IP addresses to use when
+ * resolving host names. This is only interesting when using host names that
+ * resolve addresses using more than one version of IP, possible values are
+ * CURL_IPRESOLVE_WHATEVER,
+ * CURL_IPRESOLVE_V4,
+ * CURL_IPRESOLVE_V6, by default
+ * CURL_IPRESOLVE_WHATEVER.
+ *
+ *
+ * Added in cURL 7.10.8.
+ *
+ *
+ *
+ * CURLOPT_FTP_FILEMETHOD
+ *
+ * Tell curl which method to use to reach a file on a FTP(S) server. Possible values are
+ * CURLFTPMETHOD_MULTICWD,
+ * CURLFTPMETHOD_NOCWD and
+ * CURLFTPMETHOD_SINGLECWD.
+ *
+ *
+ * Added in cURL 7.15.1. Available since PHP 5.3.0.
+ *
+ *
+ *
+ *
+ *
+ *
+ * This option is deprecated, as it was never implemented in cURL and
+ * never had any effect.
+ *
+ * The HTTP authentication method(s) to use. The options are:
+ * CURLAUTH_BASIC,
+ * CURLAUTH_DIGEST,
+ * CURLAUTH_GSSNEGOTIATE,
+ * CURLAUTH_NTLM,
+ * CURLAUTH_ANY, and
+ * CURLAUTH_ANYSAFE.
+ *
+ * The bitwise | (or) operator can be used to combine
+ * more than one method. If this is done, cURL will poll the server to see
+ * what methods it supports and pick the best one.
+ *
+ * CURLAUTH_ANY is an alias for
+ * CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM.
+ *
+ * CURLAUTH_ANYSAFE is an alias for
+ * CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM.
+ *
+ * Bitmask of CURLPROTO_* values. If used, this bitmask
+ * limits what protocols libcurl may use in the transfer. This allows you to have
+ * a libcurl built to support a wide range of protocols but still limit specific
+ * transfers to only be allowed to use a subset of them. By default libcurl will
+ * accept all protocols it supports.
+ * See also CURLOPT_REDIR_PROTOCOLS.
+ *
+ * Valid protocol options are:
+ * CURLPROTO_HTTP,
+ * CURLPROTO_HTTPS,
+ * CURLPROTO_FTP,
+ * CURLPROTO_FTPS,
+ * CURLPROTO_SCP,
+ * CURLPROTO_SFTP,
+ * CURLPROTO_TELNET,
+ * CURLPROTO_LDAP,
+ * CURLPROTO_LDAPS,
+ * CURLPROTO_DICT,
+ * CURLPROTO_FILE,
+ * CURLPROTO_TFTP,
+ * CURLPROTO_ALL
+ *
+ * The SOCKS5 authentication method(s) to use. The options are:
+ * CURLAUTH_BASIC,
+ * CURLAUTH_GSSAPI,
+ * CURLAUTH_NONE.
+ *
+ * The bitwise | (or) operator can be used to combine
+ * more than one method. If this is done, cURL will poll the server to see
+ * what methods it supports and pick the best one.
+ *
+ * CURLAUTH_BASIC allows username/password authentication.
+ *
+ * CURLAUTH_GSSAPI allows GSS-API authentication.
+ *
+ * CURLAUTH_NONE allows no authentication.
+ *
+ * Defaults to CURLAUTH_BASIC|CURLAUTH_GSSAPI.
+ * Set the actual username and password with the CURLOPT_PROXYUSERPWD option.
+ *
+ * Your best bet is to not set this and let it use the default.
+ * Setting it to 2 or 3 is very dangerous given the known
+ * vulnerabilities in SSLv2 and SSLv3.
+ *
+ * Your best bet is to not set this and let it use the default CURL_SSLVERSION_DEFAULT
+ * which will attempt to figure out the remote SSL protocol version.
+ *
+ * value should be a string for the
+ * following values of the option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ * Notes
+ *
+ *
+ *
+ *
+ * CURLOPT_ABSTRACT_UNIX_SOCKET
+ *
+ * Enables the use of an abstract Unix domain socket instead of
+ * establishing a TCP connection to a host and sets the path to
+ * the given string. This option shares the same semantics
+ * as CURLOPT_UNIX_SOCKET_PATH. These two options
+ * share the same storage and therefore only one of them can be set
+ * per handle.
+ *
+ *
+ * Available since PHP 7.3.0 and cURL 7.53.0
+ *
+ *
+ *
+ * CURLOPT_CAINFO
+ *
+ * The name of a file holding one or more certificates to verify the
+ * peer with. This only makes sense when used in combination with
+ * CURLOPT_SSL_VERIFYPEER.
+ *
+ *
+ * Might require an absolute path.
+ *
+ *
+ *
+ * CURLOPT_CAPATH
+ *
+ * A directory that holds multiple CA certificates. Use this option
+ * alongside CURLOPT_SSL_VERIFYPEER.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_COOKIE
+ *
+ * The contents of the "Cookie: " header to be
+ * used in the HTTP request.
+ * Note that multiple cookies are separated with a semicolon followed
+ * by a space (e.g., "fruit=apple; colour=red")
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_COOKIEFILE
+ *
+ * The name of the file containing the cookie data. The cookie file can
+ * be in Netscape format, or just plain HTTP-style headers dumped into
+ * a file.
+ * If the name is an empty string, no cookies are loaded, but cookie
+ * handling is still enabled.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_COOKIEJAR
+ *
+ * The name of a file to save all internal cookies to when the handle is closed,
+ * e.g. after a call to curl_close.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_COOKIELIST
+ *
+ * A cookie string (i.e. a single line in Netscape/Mozilla format, or a regular
+ * HTTP-style Set-Cookie header) adds that single cookie to the internal cookie store.
+ * "ALL" erases all cookies held in memory.
+ * "SESS" erases all session cookies held in memory.
+ * "FLUSH" writes all known cookies to the file specified by CURLOPT_COOKIEJAR.
+ * "RELOAD" loads all cookies from the files specified by CURLOPT_COOKIEFILE.
+ *
+ *
+ * Available since PHP 5.5.0 and cURL 7.14.1.
+ *
+ *
+ *
+ * CURLOPT_CUSTOMREQUEST
+ *
+ * A custom request method to use instead of
+ * "GET" or "HEAD" when doing
+ * a HTTP request. This is useful for doing
+ * "DELETE" or other, more obscure HTTP requests.
+ * Valid values are things like "GET",
+ * "POST", "CONNECT" and so on;
+ * i.e. Do not enter a whole HTTP request line here. For instance,
+ * entering "GET /index.html HTTP/1.0\r\n\r\n"
+ * would be incorrect.
+ *
+ *
+ * Don't do this without making sure the server supports the custom
+ * request method first.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_DEFAULT_PROTOCOL
+ *
+ * The default protocol to use if the URL is missing a scheme name.
+ *
+ *
+ * Added in cURL 7.45.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_DNS_INTERFACE
+ *
+ * Set the name of the network interface that the DNS resolver should bind to.
+ * This must be an interface name (not an address).
+ *
+ *
+ * Added in cURL 7.33.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_DNS_LOCAL_IP4
+ *
+ * Set the local IPv4 address that the resolver should bind to. The argument
+ * should contain a single numerical IPv4 address as a string.
+ *
+ *
+ * Added in cURL 7.33.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_DNS_LOCAL_IP6
+ *
+ * Set the local IPv6 address that the resolver should bind to. The argument
+ * should contain a single numerical IPv6 address as a string.
+ *
+ *
+ * Added in cURL 7.33.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_EGDSOCKET
+ *
+ * Like CURLOPT_RANDOM_FILE, except a filename
+ * to an Entropy Gathering Daemon socket.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_ENCODING
+ *
+ * The contents of the "Accept-Encoding: " header.
+ * This enables decoding of the response. Supported encodings are
+ * "identity", "deflate", and
+ * "gzip". If an empty string, "",
+ * is set, a header containing all supported encoding types is sent.
+ *
+ *
+ * Added in cURL 7.10.
+ *
+ *
+ *
+ * CURLOPT_FTPPORT
+ *
+ * The value which will be used to get the IP address to use
+ * for the FTP "PORT" instruction. The "PORT" instruction tells
+ * the remote server to connect to our specified IP address. The
+ * string may be a plain IP address, a hostname, a network
+ * interface name (under Unix), or just a plain '-' to use the
+ * systems default IP address.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_INTERFACE
+ *
+ * The name of the outgoing network interface to use. This can be an
+ * interface name, an IP address or a host name.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_KEYPASSWD
+ *
+ * The password required to use the CURLOPT_SSLKEY
+ * or CURLOPT_SSH_PRIVATE_KEYFILE private key.
+ *
+ *
+ * Added in cURL 7.16.1.
+ *
+ *
+ *
+ * CURLOPT_KRB4LEVEL
+ *
+ * The KRB4 (Kerberos 4) security level. Any of the following values
+ * (in order from least to most powerful) are valid:
+ * "clear",
+ * "safe",
+ * "confidential",
+ * "private"..
+ * If the string does not match one of these,
+ * "private" is used. Setting this option to NULL
+ * will disable KRB4 security. Currently KRB4 security only works
+ * with FTP transactions.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_LOGIN_OPTIONS
+ *
+ * Can be used to set protocol specific login options, such as the
+ * preferred authentication mechanism via "AUTH=NTLM" or "AUTH=*",
+ * and should be used in conjunction with the
+ * CURLOPT_USERNAME option.
+ *
+ *
+ * Added in cURL 7.34.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_PINNEDPUBLICKEY
+ *
+ * Set the pinned public key.
+ * The string can be the file name of your pinned public key. The file
+ * format expected is "PEM" or "DER". The string can also be any
+ * number of base64 encoded sha256 hashes preceded by "sha256//" and
+ * separated by ";".
+ *
+ *
+ * Added in cURL 7.39.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_POSTFIELDS
+ *
+ *
+ * The full data to post in a HTTP "POST" operation.
+ * To post a file, prepend a filename with @ and
+ * use the full path. The filetype can be explicitly specified by
+ * following the filename with the type in the format
+ * ';type=mimetype'. This parameter can either be
+ * passed as a urlencoded string like 'para1=val1&para2=val2&...'
+ * or as an array with the field name as key and field data as value.
+ * If value is an array, the
+ * Content-Type header will be set to
+ * multipart/form-data.
+ *
+ *
+ * As of PHP 5.2.0, value must be an array if
+ * files are passed to this option with the @ prefix.
+ *
+ *
+ * As of PHP 5.5.0, the @ prefix is deprecated and
+ * files can be sent using CURLFile. The
+ * @ prefix can be disabled for safe passing of
+ * values beginning with @ by setting the
+ * CURLOPT_SAFE_UPLOAD option to TRUE.
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PRIVATE
+ *
+ * Any data that should be associated with this cURL handle. This data
+ * can subsequently be retrieved with the
+ * CURLINFO_PRIVATE option of
+ * curl_getinfo. cURL does nothing with this data.
+ * When using a cURL multi handle, this private data is typically a
+ * unique key to identify a standard cURL handle.
+ *
+ *
+ * Added in cURL 7.10.3.
+ *
+ *
+ *
+ * CURLOPT_PRE_PROXY
+ *
+ * Set a string holding the host name or dotted numerical
+ * IP address to be used as the preproxy that curl connects to before
+ * it connects to the HTTP(S) proxy specified in the
+ * CURLOPT_PROXY option for the upcoming request.
+ * The preproxy can only be a SOCKS proxy and it should be prefixed with
+ * [scheme]:// to specify which kind of socks is used.
+ * A numerical IPv6 address must be written within [brackets].
+ * Setting the preproxy to an empty string explicitly disables the use of a preproxy.
+ * To specify port number in this string, append :[port]
+ * to the end of the host name. The proxy's port number may optionally be
+ * specified with the separate option CURLOPT_PROXYPORT.
+ * Defaults to using port 1080 for proxies if a port is not specified.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY
+ *
+ * The HTTP proxy to tunnel requests through.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PROXY_SERVICE_NAME
+ *
+ * The proxy authentication service name.
+ *
+ *
+ * Added in cURL 7.34.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_PROXY_CAINFO
+ *
+ * The path to proxy Certificate Authority (CA) bundle. Set the path as a
+ * string naming a file holding one or more certificates to
+ * verify the HTTPS proxy with.
+ * This option is for connecting to an HTTPS proxy, not an HTTPS server.
+ * Defaults set to the system path where libcurl's cacert bundle is assumed
+ * to be stored.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_CAPATH
+ *
+ * The directory holding multiple CA certificates to verify the HTTPS proxy with.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_CRLFILE
+ *
+ * Set the file name with the concatenation of CRL (Certificate Revocation List)
+ * in PEM format to use in the certificate validation that occurs during
+ * the SSL exchange.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_KEYPASSWD
+ *
+ * Set the string be used as the password required to use the
+ * CURLOPT_PROXY_SSLKEY private key. You never needed a
+ * passphrase to load a certificate but you need one to load your private key.
+ * This option is for connecting to an HTTPS proxy, not an HTTPS server.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_PINNEDPUBLICKEY
+ *
+ * Set the pinned public key for HTTPS proxy. The string can be the file name
+ * of your pinned public key. The file format expected is "PEM" or "DER".
+ * The string can also be any number of base64 encoded sha256 hashes preceded by
+ * "sha256//" and separated by ";"
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSLCERT
+ *
+ * The file name of your client certificate used to connect to the HTTPS proxy.
+ * The default format is "P12" on Secure Transport and "PEM" on other engines,
+ * and can be changed with CURLOPT_PROXY_SSLCERTTYPE.
+ * With NSS or Secure Transport, this can also be the nickname of the certificate
+ * you wish to authenticate with as it is named in the security database.
+ * If you want to use a file from the current directory, please precede it with
+ * "./" prefix, in order to avoid confusion with a nickname.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSLCERTTYPE
+ *
+ * The format of your client certificate used when connecting to an HTTPS proxy.
+ * Supported formats are "PEM" and "DER", except with Secure Transport.
+ * OpenSSL (versions 0.9.3 and later) and Secure Transport
+ * (on iOS 5 or later, or OS X 10.7 or later) also support "P12" for
+ * PKCS#12-encoded files. Defaults to "PEM".
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSL_CIPHER_LIST
+ *
+ * The list of ciphers to use for the connection to the HTTPS proxy.
+ * The list must be syntactically correct, it consists of one or more cipher
+ * strings separated by colons. Commas or spaces are also acceptable separators
+ * but colons are normally used, !, - and + can be used as operators.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_TLS13_CIPHERS
+ *
+ * The list of cipher suites to use for the TLS 1.3 connection to a proxy.
+ * The list must be syntactically correct, it consists of one or more
+ * cipher suite strings separated by colons. This option is currently used
+ * only when curl is built to use OpenSSL 1.1.1 or later.
+ * If you are using a different SSL backend you can try setting
+ * TLS 1.3 cipher suites by using the CURLOPT_PROXY_SSL_CIPHER_LIST option.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.61.0. Available when built with OpenSSL >= 1.1.1.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSLKEY
+ *
+ * The file name of your private key used for connecting to the HTTPS proxy.
+ * The default format is "PEM" and can be changed with
+ * CURLOPT_PROXY_SSLKEYTYPE.
+ * (iOS and Mac OS X only) This option is ignored if curl was built against Secure Transport.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0. Available if built TLS enabled.
+ *
+ *
+ *
+ * CURLOPT_PROXY_SSLKEYTYPE
+ *
+ * The format of your private key. Supported formats are "PEM", "DER" and "ENG".
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_TLSAUTH_PASSWORD
+ *
+ * The password to use for the TLS authentication method specified with the
+ * CURLOPT_PROXY_TLSAUTH_TYPE option. Requires that the
+ * CURLOPT_PROXY_TLSAUTH_USERNAME option to also be set.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_TLSAUTH_TYPE
+ *
+ * The method of the TLS authentication used for the HTTPS connection. Supported method is "SRP".
+ *
+ *
+ * Secure Remote Password (SRP) authentication for TLS provides mutual authentication
+ * if both sides have a shared secret. To use TLS-SRP, you must also set the
+ * CURLOPT_PROXY_TLSAUTH_USERNAME and
+ * CURLOPT_PROXY_TLSAUTH_PASSWORD options.
+ *
+ *
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXY_TLSAUTH_USERNAME
+ *
+ * Tusername to use for the HTTPS proxy TLS authentication method specified with the
+ * CURLOPT_PROXY_TLSAUTH_TYPE option. Requires that the
+ * CURLOPT_PROXY_TLSAUTH_PASSWORD option to also be set.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.52.0.
+ *
+ *
+ *
+ * CURLOPT_PROXYUSERPWD
+ *
+ * A username and password formatted as
+ * "[username]:[password]" to use for the
+ * connection to the proxy.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_RANDOM_FILE
+ *
+ * A filename to be used to seed the random number generator for SSL.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_RANGE
+ *
+ * Range(s) of data to retrieve in the format
+ * "X-Y" where X or Y are optional. HTTP transfers
+ * also support several intervals, separated with commas in the format
+ * "X-Y,N-M".
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_REFERER
+ *
+ * The contents of the "Referer: " header to be used
+ * in a HTTP request.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SERVICE_NAME
+ *
+ * The authentication service name.
+ *
+ *
+ * Added in cURL 7.43.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_SSH_HOST_PUBLIC_KEY_MD5
+ *
+ * A string containing 32 hexadecimal digits. The string should be the
+ * MD5 checksum of the remote host's public key, and libcurl will reject
+ * the connection to the host unless the md5sums match.
+ * This option is only for SCP and SFTP transfers.
+ *
+ *
+ * Added in cURL 7.17.1.
+ *
+ *
+ *
+ * CURLOPT_SSH_PUBLIC_KEYFILE
+ *
+ * The file name for your public key. If not used, libcurl defaults to
+ * $HOME/.ssh/id_dsa.pub if the HOME environment variable is set,
+ * and just "id_dsa.pub" in the current directory if HOME is not set.
+ *
+ *
+ * Added in cURL 7.16.1.
+ *
+ *
+ *
+ * CURLOPT_SSH_PRIVATE_KEYFILE
+ *
+ * The file name for your private key. If not used, libcurl defaults to
+ * $HOME/.ssh/id_dsa if the HOME environment variable is set,
+ * and just "id_dsa" in the current directory if HOME is not set.
+ * If the file is password-protected, set the password with
+ * CURLOPT_KEYPASSWD.
+ *
+ *
+ * Added in cURL 7.16.1.
+ *
+ *
+ *
+ * CURLOPT_SSL_CIPHER_LIST
+ *
+ * A list of ciphers to use for SSL. For example,
+ * RC4-SHA and TLSv1 are valid
+ * cipher lists.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLCERT
+ *
+ * The name of a file containing a PEM formatted certificate.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLCERTPASSWD
+ *
+ * The password required to use the
+ * CURLOPT_SSLCERT certificate.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLCERTTYPE
+ *
+ * The format of the certificate. Supported formats are
+ * "PEM" (default), "DER",
+ * and "ENG".
+ * As of OpenSSL 0.9.3, "P12" (for PKCS#12-encoded files)
+ * is also supported.
+ *
+ *
+ * Added in cURL 7.9.3.
+ *
+ *
+ *
+ * CURLOPT_SSLENGINE
+ *
+ * The identifier for the crypto engine of the private SSL key
+ * specified in CURLOPT_SSLKEY.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLENGINE_DEFAULT
+ *
+ * The identifier for the crypto engine used for asymmetric crypto
+ * operations.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLKEY
+ *
+ * The name of a file containing a private SSL key.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLKEYPASSWD
+ *
+ * The secret password needed to use the private SSL key specified in
+ * CURLOPT_SSLKEY.
+ *
+ *
+ * Since this option contains a sensitive password, remember to keep
+ * the PHP script it is contained within safe.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_SSLKEYTYPE
+ *
+ * The key type of the private SSL key specified in
+ * CURLOPT_SSLKEY. Supported key types are
+ * "PEM" (default), "DER",
+ * and "ENG".
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_TLS13_CIPHERS
+ *
+ * The list of cipher suites to use for the TLS 1.3 connection. The list must be
+ * syntactically correct, it consists of one or more cipher suite strings separated by colons.
+ * This option is currently used only when curl is built to use OpenSSL 1.1.1 or later.
+ * If you are using a different SSL backend you can try setting
+ * TLS 1.3 cipher suites by using the CURLOPT_SSL_CIPHER_LIST option.
+ *
+ *
+ * Available since PHP 7.3.0 and libcurl >= cURL 7.61.0. Available when built with OpenSSL >= 1.1.1.
+ *
+ *
+ *
+ * CURLOPT_UNIX_SOCKET_PATH
+ *
+ * Enables the use of Unix domain sockets as connection endpoint and
+ * sets the path to the given string.
+ *
+ *
+ * Added in cURL 7.40.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_URL
+ *
+ * The URL to fetch. This can also be set when initializing a
+ * session with curl_init.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_USERAGENT
+ *
+ * The contents of the "User-Agent: " header to be
+ * used in a HTTP request.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_USERNAME
+ *
+ * The user name to use in authentication.
+ *
+ *
+ * Added in cURL 7.19.1. Available since PHP 5.5.0.
+ *
+ *
+ *
+ * CURLOPT_USERPWD
+ *
+ * A username and password formatted as
+ * "[username]:[password]" to use for the
+ * connection.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_XOAUTH2_BEARER
+ *
+ * Specifies the OAuth 2.0 access token.
+ *
+ *
+ * Added in cURL 7.33.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ *
+ *
+ *
+ * A custom request method to use instead of
+ * "GET" or "HEAD" when doing
+ * a HTTP request. This is useful for doing
+ * "DELETE" or other, more obscure HTTP requests.
+ * Valid values are things like "GET",
+ * "POST", "CONNECT" and so on;
+ * i.e. Do not enter a whole HTTP request line here. For instance,
+ * entering "GET /index.html HTTP/1.0\r\n\r\n"
+ * would be incorrect.
+ *
+ *
+ * Don't do this without making sure the server supports the custom
+ * request method first.
+ *
+ *
+ *
+ * Don't do this without making sure the server supports the custom
+ * request method first.
+ *
+ * The default protocol to use if the URL is missing a scheme name.
+ *
+ * Set the name of the network interface that the DNS resolver should bind to.
+ * This must be an interface name (not an address).
+ *
+ * Set the local IPv4 address that the resolver should bind to. The argument
+ * should contain a single numerical IPv4 address as a string.
+ *
+ * Set the local IPv6 address that the resolver should bind to. The argument
+ * should contain a single numerical IPv6 address as a string.
+ *
+ * Secure Remote Password (SRP) authentication for TLS provides mutual authentication
+ * if both sides have a shared secret. To use TLS-SRP, you must also set the
+ * CURLOPT_PROXY_TLSAUTH_USERNAME and
+ * CURLOPT_PROXY_TLSAUTH_PASSWORD options.
+ *
+ * The secret password needed to use the private SSL key specified in
+ * CURLOPT_SSLKEY.
+ *
+ *
+ * Since this option contains a sensitive password, remember to keep
+ * the PHP script it is contained within safe.
+ *
+ *
+ *
+ * Since this option contains a sensitive password, remember to keep
+ * the PHP script it is contained within safe.
+ *
+ * value should be an array for the
+ * following values of the option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ * Notes
+ *
+ *
+ *
+ *
+ * CURLOPT_CONNECT_TO
+ *
+ * Connect to a specific host and port instead of the URL's host and port.
+ * Accepts an array of strings with the format
+ * HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT.
+ *
+ *
+ * Added in cURL 7.49.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_HTTP200ALIASES
+ *
+ * An array of HTTP 200 responses that will be treated as valid
+ * responses and not as errors.
+ *
+ *
+ * Added in cURL 7.10.3.
+ *
+ *
+ *
+ * CURLOPT_HTTPHEADER
+ *
+ * An array of HTTP header fields to set, in the format
+ *
+ * array('Content-type: text/plain', 'Content-length: 100')
+ *
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_POSTQUOTE
+ *
+ * An array of FTP commands to execute on the server after the FTP
+ * request has been performed.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_PROXYHEADER
+ *
+ * An array of custom HTTP headers to pass to proxies.
+ *
+ *
+ * Added in cURL 7.37.0. Available since PHP 7.0.7.
+ *
+ *
+ *
+ * CURLOPT_QUOTE
+ *
+ * An array of FTP commands to execute on the server prior to the FTP
+ * request.
+ *
+ *
+ *
+ *
+ *
+ * CURLOPT_RESOLVE
+ *
+ * Provide a custom address for a specific host and port pair. An array
+ * of hostname, port, and IP address strings, each element separated by
+ * a colon. In the format:
+ *
+ * array("example.com:80:127.0.0.1")
+ *
+ *
+ *
+ * Added in cURL 7.21.3. Available since PHP 5.5.0.
+ *
+ *
+ *
+ *
+ *
+ *
+ * value should be a stream resource (using
+ * fopen, for example) for the following values of the
+ * option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ *
+ *
+ *
+ *
+ * CURLOPT_FILE
+ *
+ * The file that the transfer should be written to. The default
+ * is STDOUT (the browser window).
+ *
+ *
+ *
+ * CURLOPT_INFILE
+ *
+ * The file that the transfer should be read from when uploading.
+ *
+ *
+ *
+ * CURLOPT_STDERR
+ *
+ * An alternative location to output errors to instead of
+ * STDERR.
+ *
+ *
+ *
+ * CURLOPT_WRITEHEADER
+ *
+ * The file that the header part of the transfer is written to.
+ *
+ *
+ *
+ *
+ *
+ *
+ * value should be the name of a valid function or a Closure
+ * for the following values of the option parameter:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ *
+ *
+ *
+ *
+ * CURLOPT_HEADERFUNCTION
+ *
+ * A callback accepting two parameters.
+ * The first is the cURL resource, the second is a
+ * string with the header data to be written. The header data must
+ * be written by this callback. Return the number of
+ * bytes written.
+ *
+ *
+ *
+ * CURLOPT_PASSWDFUNCTION
+ *
+ * A callback accepting three parameters.
+ * The first is the cURL resource, the second is a
+ * string containing a password prompt, and the third is the maximum
+ * password length. Return the string containing the password.
+ *
+ *
+ *
+ * CURLOPT_PROGRESSFUNCTION
+ *
+ *
+ * A callback accepting five parameters.
+ * The first is the cURL resource, the second is the total number of
+ * bytes expected to be downloaded in this transfer, the third is
+ * the number of bytes downloaded so far, the fourth is the total
+ * number of bytes expected to be uploaded in this transfer, and the
+ * fifth is the number of bytes uploaded so far.
+ *
+ *
+ *
+ * The callback is only called when the CURLOPT_NOPROGRESS
+ * option is set to FALSE.
+ *
+ *
+ *
+ * Return a non-zero value to abort the transfer. In which case, the
+ * transfer will set a CURLE_ABORTED_BY_CALLBACK
+ * error.
+ *
+ *
+ *
+ *
+ * CURLOPT_READFUNCTION
+ *
+ * A callback accepting three parameters.
+ * The first is the cURL resource, the second is a
+ * stream resource provided to cURL through the option
+ * CURLOPT_INFILE, and the third is the maximum
+ * amount of data to be read. The callback must return a string
+ * with a length equal or smaller than the amount of data requested,
+ * typically by reading it from the passed stream resource. It should
+ * return an empty string to signal EOF.
+ *
+ *
+ *
+ * CURLOPT_WRITEFUNCTION
+ *
+ * A callback accepting two parameters.
+ * The first is the cURL resource, and the second is a
+ * string with the data to be written. The data must be saved by
+ * this callback. It must return the exact number of bytes written
+ * or the transfer will be aborted with an error.
+ *
+ *
+ *
+ *
+ *
+ *
+ * A callback accepting five parameters.
+ * The first is the cURL resource, the second is the total number of
+ * bytes expected to be downloaded in this transfer, the third is
+ * the number of bytes downloaded so far, the fourth is the total
+ * number of bytes expected to be uploaded in this transfer, and the
+ * fifth is the number of bytes uploaded so far.
+ *
+ * The callback is only called when the CURLOPT_NOPROGRESS
+ * option is set to FALSE.
+ *
+ * Return a non-zero value to abort the transfer. In which case, the
+ * transfer will set a CURLE_ABORTED_BY_CALLBACK
+ * error.
+ *
+ * Other values:
+ *
+ *
+ *
+ *
+ * Option
+ * Set value to
+ *
+ *
+ *
+ *
+ * CURLOPT_SHARE
+ *
+ * A result of curl_share_init. Makes the cURL
+ * handle to use the data from the shared handle.
+ *
+ *
+ *
+ *
+ *
+ * @throws CurlException
+ *
+ */
+function curl_setopt($ch, int $option, $value): void
+{
+ error_clear_last();
+ $result = \curl_setopt($ch, $option, $value);
+ if ($result === false) {
+ throw CurlException::createFromCurlResource($ch);
+ }
+}
+
+
+/**
+ * Return an integer containing the last share curl error number.
+ *
+ * @param resource $sh A cURL share handle returned by curl_share_init.
+ * @return int Returns an integer containing the last share curl error number.
+ * @throws CurlException
+ *
+ */
+function curl_share_errno($sh): int
+{
+ error_clear_last();
+ $result = \curl_share_errno($sh);
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets an option on the given cURL share handle.
+ *
+ * @param resource $sh A cURL share handle returned by curl_share_init.
+ * @param int $option
+ *
+ *
+ *
+ * Option
+ * Description
+ *
+ *
+ *
+ *
+ * CURLSHOPT_SHARE
+ *
+ * Specifies a type of data that should be shared.
+ *
+ *
+ *
+ * CURLSHOPT_UNSHARE
+ *
+ * Specifies a type of data that will be no longer shared.
+ *
+ *
+ *
+ *
+ *
+ * @param string $value
+ *
+ *
+ *
+ * Value
+ * Description
+ *
+ *
+ *
+ *
+ * CURL_LOCK_DATA_COOKIE
+ *
+ * Shares cookie data.
+ *
+ *
+ *
+ * CURL_LOCK_DATA_DNS
+ *
+ * Shares DNS cache. Note that when you use cURL multi handles,
+ * all handles added to the same multi handle will share DNS cache
+ * by default.
+ *
+ *
+ *
+ * CURL_LOCK_DATA_SSL_SESSION
+ *
+ * Shares SSL session IDs, reducing the time spent on the SSL
+ * handshake when reconnecting to the same server. Note that SSL
+ * session IDs are reused within the same handle by default.
+ *
+ *
+ *
+ *
+ *
+ * @throws CurlException
+ *
+ */
+function curl_share_setopt($sh, int $option, string $value): void
+{
+ error_clear_last();
+ $result = \curl_share_setopt($sh, $option, $value);
+ if ($result === false) {
+ throw CurlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function decodes the given URL encoded string.
+ *
+ * @param resource $ch A cURL handle returned by
+ * curl_init.
+ * @param string $str The URL encoded string to be decoded.
+ * @return string Returns decoded string.
+ * @throws CurlException
+ *
+ */
+function curl_unescape($ch, string $str): string
+{
+ error_clear_last();
+ $result = \curl_unescape($ch, $str);
+ if ($result === false) {
+ throw CurlException::createFromCurlResource($ch);
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\DatetimeException;
+
+/**
+ * Returns associative array with detailed info about given date/time.
+ *
+ * @param string $format Format accepted by DateTime::createFromFormat.
+ * @param string $datetime String representing the date/time.
+ * @return array Returns associative array with detailed info about given date/time.
+ * @throws DatetimeException
+ *
+ */
+function date_parse_from_format(string $format, string $datetime): array
+{
+ error_clear_last();
+ $result = \date_parse_from_format($format, $datetime);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $datetime Date/time in format accepted by
+ * DateTimeImmutable::__construct.
+ * @return array Returns array with information about the parsed date/time
+ * on success.
+ * @throws DatetimeException
+ *
+ */
+function date_parse(string $datetime): array
+{
+ error_clear_last();
+ $result = \date_parse($datetime);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param int $timestamp Unix timestamp.
+ * @param float $latitude Latitude in degrees.
+ * @param float $longitude Longitude in degrees.
+ * @return array Returns array on success.
+ * The structure of the array is detailed in the following list:
+ *
+ *
+ *
+ * sunrise
+ *
+ *
+ * The timestamp of the sunrise (zenith angle = 90°35').
+ *
+ *
+ *
+ *
+ * sunset
+ *
+ *
+ * The timestamp of the sunset (zenith angle = 90°35').
+ *
+ *
+ *
+ *
+ * transit
+ *
+ *
+ * The timestamp when the sun is at its zenith, i.e. has reached its topmost
+ * point.
+ *
+ *
+ *
+ *
+ * civil_twilight_begin
+ *
+ *
+ * The start of the civil dawn (zenith angle = 96°). It ends at sunrise.
+ *
+ *
+ *
+ *
+ * civil_twilight_end
+ *
+ *
+ * The end of the civil dusk (zenith angle = 96°). It starts at sunset.
+ *
+ *
+ *
+ *
+ * nautical_twilight_begin
+ *
+ *
+ * The start of the nautical dawn (zenith angle = 102°). It ends at
+ * civil_twilight_begin.
+ *
+ *
+ *
+ *
+ * nautical_twilight_end
+ *
+ *
+ * The end of the nautical dusk (zenith angle = 102°). It starts at
+ * civil_twilight_end.
+ *
+ *
+ *
+ *
+ * astronomical_twilight_begin
+ *
+ *
+ * The start of the astronomical dawn (zenith angle = 108°). It ends at
+ * nautical_twilight_begin.
+ *
+ *
+ *
+ *
+ * astronomical_twilight_end
+ *
+ *
+ * The end of the astronomical dusk (zenith angle = 108°). It starts at
+ * nautical_twilight_end.
+ *
+ *
+ *
+ *
+ *
+ * The values of the array elements are either UNIX timestamps, FALSE if the
+ * sun is below the respective zenith for the whole day, or TRUE if the sun is
+ * above the respective zenith for the whole day.
+ * @throws DatetimeException
+ *
+ */
+function date_sun_info(int $timestamp, float $latitude, float $longitude): array
+{
+ error_clear_last();
+ $result = \date_sun_info($timestamp, $latitude, $longitude);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * date_sunrise returns the sunrise time for a given
+ * day (specified as a timestamp) and location.
+ *
+ * @param int $timestamp The timestamp of the day from which the sunrise
+ * time is taken.
+ * @param int $returnFormat
+ * returnFormat constants
+ *
+ *
+ *
+ * constant
+ * description
+ * example
+ *
+ *
+ *
+ *
+ * SUNFUNCS_RET_STRING
+ * returns the result as string
+ * 16:46
+ *
+ *
+ * SUNFUNCS_RET_DOUBLE
+ * returns the result as float
+ * 16.78243132
+ *
+ *
+ * SUNFUNCS_RET_TIMESTAMP
+ * returns the result as integer (timestamp)
+ * 1095034606
+ *
+ *
+ *
+ *
+ * @param float $latitude Defaults to North, pass in a negative value for South.
+ * See also: date.default_latitude
+ * @param float $longitude Defaults to East, pass in a negative value for West.
+ * See also: date.default_longitude
+ * @param float $zenith zenith is the angle between the center of the sun
+ * and a line perpendicular to earth's surface. It defaults to
+ * date.sunrise_zenith
+ *
+ * Common zenith angles
+ *
+ *
+ *
+ * Angle
+ * Description
+ *
+ *
+ *
+ *
+ * 90°50'
+ * Sunrise: the point where the sun becomes visible.
+ *
+ *
+ * 96°
+ * Civil twilight: conventionally used to signify the start of dawn.
+ *
+ *
+ * 102°
+ * Nautical twilight: the point at which the horizon starts being visible at sea.
+ *
+ *
+ * 108°
+ * Astronomical twilight: the point at which the sun starts being the source of any illumination.
+ *
+ *
+ *
+ *
+ * @param float $utcOffset Specified in hours.
+ * The utcOffset is ignored, if
+ * returnFormat is
+ * SUNFUNCS_RET_TIMESTAMP.
+ * @return mixed Returns the sunrise time in a specified returnFormat on
+ * success. One potential reason for failure is that the
+ * sun does not rise at all, which happens inside the polar circles for part of
+ * the year.
+ * @throws DatetimeException
+ *
+ */
+function date_sunrise(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, float $latitude = null, float $longitude = null, float $zenith = null, float $utcOffset = 0)
+{
+ error_clear_last();
+ if ($utcOffset !== 0) {
+ $result = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
+ } elseif ($zenith !== null) {
+ $result = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith);
+ } elseif ($longitude !== null) {
+ $result = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude);
+ } elseif ($latitude !== null) {
+ $result = \date_sunrise($timestamp, $returnFormat, $latitude);
+ } else {
+ $result = \date_sunrise($timestamp, $returnFormat);
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * date_sunset returns the sunset time for a given
+ * day (specified as a timestamp) and location.
+ *
+ * @param int $timestamp The timestamp of the day from which the sunset
+ * time is taken.
+ * @param int $returnFormat
+ * returnFormat constants
+ *
+ *
+ *
+ * constant
+ * description
+ * example
+ *
+ *
+ *
+ *
+ * SUNFUNCS_RET_STRING
+ * returns the result as string
+ * 16:46
+ *
+ *
+ * SUNFUNCS_RET_DOUBLE
+ * returns the result as float
+ * 16.78243132
+ *
+ *
+ * SUNFUNCS_RET_TIMESTAMP
+ * returns the result as integer (timestamp)
+ * 1095034606
+ *
+ *
+ *
+ *
+ * @param float $latitude Defaults to North, pass in a negative value for South.
+ * See also: date.default_latitude
+ * @param float $longitude Defaults to East, pass in a negative value for West.
+ * See also: date.default_longitude
+ * @param float $zenith zenith is the angle between the center of the sun
+ * and a line perpendicular to earth's surface. It defaults to
+ * date.sunset_zenith
+ *
+ * Common zenith angles
+ *
+ *
+ *
+ * Angle
+ * Description
+ *
+ *
+ *
+ *
+ * 90°50'
+ * Sunset: the point where the sun becomes invisible.
+ *
+ *
+ * 96°
+ * Civil twilight: conventionally used to signify the end of dusk.
+ *
+ *
+ * 102°
+ * Nautical twilight: the point at which the horizon ends being visible at sea.
+ *
+ *
+ * 108°
+ * Astronomical twilight: the point at which the sun ends being the source of any illumination.
+ *
+ *
+ *
+ *
+ * @param float $utcOffset Specified in hours.
+ * The utcOffset is ignored, if
+ * returnFormat is
+ * SUNFUNCS_RET_TIMESTAMP.
+ * @return mixed Returns the sunset time in a specified returnFormat on
+ * success. One potential reason for failure is that the
+ * sun does not set at all, which happens inside the polar circles for part of
+ * the year.
+ * @throws DatetimeException
+ *
+ */
+function date_sunset(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, float $latitude = null, float $longitude = null, float $zenith = null, float $utcOffset = 0)
+{
+ error_clear_last();
+ if ($utcOffset !== 0) {
+ $result = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
+ } elseif ($zenith !== null) {
+ $result = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith);
+ } elseif ($longitude !== null) {
+ $result = \date_sunset($timestamp, $returnFormat, $latitude, $longitude);
+ } elseif ($latitude !== null) {
+ $result = \date_sunset($timestamp, $returnFormat, $latitude);
+ } else {
+ $result = \date_sunset($timestamp, $returnFormat);
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns a string formatted according to the given format string using the
+ * given integer timestamp or the current time
+ * if no timestamp is given. In other words, timestamp
+ * is optional and defaults to the value of time.
+ *
+ * @param string $format Format accepted by DateTimeInterface::format.
+ * @param int $timestamp The optional timestamp parameter is an
+ * integer Unix timestamp that defaults to the current
+ * local time if a timestamp is not given. In other
+ * words, it defaults to the value of time.
+ * @return string Returns a formatted date string. If a non-numeric value is used for
+ * timestamp, FALSE is returned and an
+ * E_WARNING level error is emitted.
+ * @throws DatetimeException
+ *
+ */
+function date(string $format, int $timestamp = null): string
+{
+ error_clear_last();
+ if ($timestamp !== null) {
+ $result = \date($format, $timestamp);
+ } else {
+ $result = \date($format);
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Identical to the date function except that
+ * the time returned is Greenwich Mean Time (GMT).
+ *
+ * @param string $format The format of the outputted date string. See the formatting
+ * options for the date function.
+ * @param int $timestamp The optional timestamp parameter is an
+ * integer Unix timestamp that defaults to the current
+ * local time if a timestamp is not given. In other
+ * words, it defaults to the value of time.
+ * @return string Returns a formatted date string. If a non-numeric value is used for
+ * timestamp, FALSE is returned and an
+ * E_WARNING level error is emitted.
+ * @throws DatetimeException
+ *
+ */
+function gmdate(string $format, int $timestamp = null): string
+{
+ error_clear_last();
+ if ($timestamp !== null) {
+ $result = \gmdate($format, $timestamp);
+ } else {
+ $result = \gmdate($format);
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the Unix timestamp corresponding to the arguments
+ * given. This timestamp is a long integer containing the number of
+ * seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time
+ * specified.
+ *
+ * Arguments may be left out in order from right to left; any
+ * arguments thus omitted will be set to the current value according
+ * to the local date and time.
+ *
+ * @param int $hour The number of the hour relative to the start of the day determined by
+ * month, day and year.
+ * Negative values reference the hour before midnight of the day in question.
+ * Values greater than 23 reference the appropriate hour in the following day(s).
+ * @param int $minute The number of the minute relative to the start of the hour.
+ * Negative values reference the minute in the previous hour.
+ * Values greater than 59 reference the appropriate minute in the following hour(s).
+ * @param int $second The number of seconds relative to the start of the minute.
+ * Negative values reference the second in the previous minute.
+ * Values greater than 59 reference the appropriate second in the following minute(s).
+ * @param int $month The number of the month relative to the end of the previous year.
+ * Values 1 to 12 reference the normal calendar months of the year in question.
+ * Values less than 1 (including negative values) reference the months in the previous year in reverse order, so 0 is December, -1 is November, etc.
+ * Values greater than 12 reference the appropriate month in the following year(s).
+ * @param int $day The number of the day relative to the end of the previous month.
+ * Values 1 to 28, 29, 30 or 31 (depending upon the month) reference the normal days in the relevant month.
+ * Values less than 1 (including negative values) reference the days in the previous month, so 0 is the last day of the previous month, -1 is the day before that, etc.
+ * Values greater than the number of days in the relevant month reference the appropriate day in the following month(s).
+ * @param int $year The number of the year, may be a two or four digit value,
+ * with values between 0-69 mapping to 2000-2069 and 70-100 to
+ * 1970-2000. On systems where time_t is a 32bit signed integer, as
+ * most common today, the valid range for year
+ * is somewhere between 1901 and 2038. However, before PHP 5.1.0 this
+ * range was limited from 1970 to 2038 on some systems (e.g. Windows).
+ * @return int mktime returns the Unix timestamp of the arguments
+ * given.
+ * If the arguments are invalid, the function returns FALSE (before PHP 5.1
+ * it returned -1).
+ * @throws DatetimeException
+ *
+ */
+function mktime(int $hour = null, int $minute = null, int $second = null, int $month = null, int $day = null, int $year = null): int
+{
+ error_clear_last();
+ if ($year !== null) {
+ $result = \mktime($hour, $minute, $second, $month, $day, $year);
+ } elseif ($day !== null) {
+ $result = \mktime($hour, $minute, $second, $month, $day);
+ } elseif ($month !== null) {
+ $result = \mktime($hour, $minute, $second, $month);
+ } elseif ($second !== null) {
+ $result = \mktime($hour, $minute, $second);
+ } elseif ($minute !== null) {
+ $result = \mktime($hour, $minute);
+ } elseif ($hour !== null) {
+ $result = \mktime($hour);
+ } else {
+ $result = \mktime();
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * strptime returns an array with the
+ * date parsed.
+ *
+ * Month and weekday names and other language dependent strings respect the
+ * current locale set with setlocale (LC_TIME).
+ *
+ * @param string $date The string to parse (e.g. returned from strftime).
+ * @param string $format The format used in date (e.g. the same as
+ * used in strftime). Note that some of the format
+ * options available to strftime may not have any
+ * effect within strptime; the exact subset that are
+ * supported will vary based on the operating system and C library in
+ * use.
+ *
+ * For more information about the format options, read the
+ * strftime page.
+ * @return array Returns an array.
+ *
+ *
+ * The following parameters are returned in the array
+ *
+ *
+ *
+ * parameters
+ * Description
+ *
+ *
+ *
+ *
+ * "tm_sec"
+ * Seconds after the minute (0-61)
+ *
+ *
+ * "tm_min"
+ * Minutes after the hour (0-59)
+ *
+ *
+ * "tm_hour"
+ * Hour since midnight (0-23)
+ *
+ *
+ * "tm_mday"
+ * Day of the month (1-31)
+ *
+ *
+ * "tm_mon"
+ * Months since January (0-11)
+ *
+ *
+ * "tm_year"
+ * Years since 1900
+ *
+ *
+ * "tm_wday"
+ * Days since Sunday (0-6)
+ *
+ *
+ * "tm_yday"
+ * Days since January 1 (0-365)
+ *
+ *
+ * "unparsed"
+ * the date part which was not
+ * recognized using the specified format
+ *
+ *
+ *
+ *
+ * @throws DatetimeException
+ *
+ */
+function strptime(string $date, string $format): array
+{
+ error_clear_last();
+ $result = \strptime($date, $format);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Each parameter of this function uses the default time zone unless a
+ * time zone is specified in that parameter. Be careful not to use
+ * different time zones in each parameter unless that is intended.
+ * See date_default_timezone_get on the various
+ * ways to define the default time zone.
+ *
+ * @param string $datetime A date/time string. Valid formats are explained in Date and Time Formats.
+ * @param int $now The timestamp which is used as a base for the calculation of relative
+ * dates.
+ * @return int Returns a timestamp on success, FALSE otherwise. Previous to PHP 5.1.0,
+ * this function would return -1 on failure.
+ * @throws DatetimeException
+ *
+ */
+function strtotime(string $datetime, int $now = null): int
+{
+ error_clear_last();
+ if ($now !== null) {
+ $result = \strtotime($datetime, $now);
+ } else {
+ $result = \strtotime($datetime);
+ }
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $abbr Time zone abbreviation.
+ * @param int $utcOffset Offset from GMT in seconds. Defaults to -1 which means that first found
+ * time zone corresponding to abbr is returned.
+ * Otherwise exact offset is searched and only if not found then the first
+ * time zone with any offset is returned.
+ * @param int $isDST Daylight saving time indicator. Defaults to -1, which means that
+ * whether the time zone has daylight saving or not is not taken into
+ * consideration when searching. If this is set to 1, then the
+ * utcOffset is assumed to be an offset with
+ * daylight saving in effect; if 0, then utcOffset
+ * is assumed to be an offset without daylight saving in effect. If
+ * abbr doesn't exist then the time zone is
+ * searched solely by the utcOffset and
+ * isDST.
+ * @return string Returns time zone name on success.
+ * @throws DatetimeException
+ *
+ */
+function timezone_name_from_abbr(string $abbr, int $utcOffset = -1, int $isDST = -1): string
+{
+ error_clear_last();
+ $result = \timezone_name_from_abbr($abbr, $utcOffset, $isDST);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\DirException;
+
+/**
+ * Changes PHP's current directory to
+ * directory.
+ *
+ * @param string $directory The new current directory
+ * @throws DirException
+ *
+ */
+function chdir(string $directory): void
+{
+ error_clear_last();
+ $result = \chdir($directory);
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Changes the root directory of the current process to
+ * directory, and changes the current
+ * working directory to "/".
+ *
+ * This function is only available to GNU and BSD systems, and
+ * only when using the CLI, CGI or Embed SAPI. Also, this function
+ * requires root privileges.
+ *
+ * @param string $directory The path to change the root directory to.
+ * @throws DirException
+ *
+ */
+function chroot(string $directory): void
+{
+ error_clear_last();
+ $result = \chroot($directory);
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets the current working directory.
+ *
+ * @return string Returns the current working directory on success.
+ *
+ * On some Unix variants, getcwd will return
+ * FALSE if any one of the parent directories does not have the
+ * readable or search mode set, even if the current directory
+ * does. See chmod for more information on
+ * modes and permissions.
+ * @throws DirException
+ *
+ */
+function getcwd(): string
+{
+ error_clear_last();
+ $result = \getcwd();
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Opens up a directory handle to be used in subsequent
+ * closedir, readdir, and
+ * rewinddir calls.
+ *
+ * @param string $path The directory path that is to be opened
+ * @param resource $context For a description of the context parameter,
+ * refer to the streams section of
+ * the manual.
+ * @return resource Returns a directory handle resource on success
+ * @throws DirException
+ *
+ */
+function opendir(string $path, $context = null)
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \opendir($path, $context);
+ } else {
+ $result = \opendir($path);
+ }
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Resets the directory stream indicated by
+ * dir_handle to the beginning of the
+ * directory.
+ *
+ * @param resource $dir_handle The directory handle resource previously opened
+ * with opendir. If the directory handle is
+ * not specified, the last link opened by opendir
+ * is assumed.
+ * @throws DirException
+ *
+ */
+function rewinddir($dir_handle = null): void
+{
+ error_clear_last();
+ if ($dir_handle !== null) {
+ $result = \rewinddir($dir_handle);
+ } else {
+ $result = \rewinddir();
+ }
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns an array of files and directories from the
+ * directory.
+ *
+ * @param string $directory The directory that will be scanned.
+ * @param int $sorting_order By default, the sorted order is alphabetical in ascending order. If
+ * the optional sorting_order is set to
+ * SCANDIR_SORT_DESCENDING, then the sort order is
+ * alphabetical in descending order. If it is set to
+ * SCANDIR_SORT_NONE then the result is unsorted.
+ * @param resource $context For a description of the context parameter,
+ * refer to the streams section of
+ * the manual.
+ * @return array Returns an array of filenames on success. If directory is not a directory, then
+ * boolean FALSE is returned, and an error of level
+ * E_WARNING is generated.
+ * @throws DirException
+ *
+ */
+function scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, $context = null): array
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \scandir($directory, $sorting_order, $context);
+ } else {
+ $result = \scandir($directory, $sorting_order);
+ }
+ if ($result === false) {
+ throw DirException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\EioException;
+
+/**
+ * eio_busy artificially increases load taking
+ * delay seconds to execute. May be used for debugging,
+ * or benchmarking.
+ *
+ * @param int $delay Delay in seconds
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback This callback is called when all the group requests are done.
+ * @param mixed $data Arbitrary variable passed to callback.
+ * @return resource eio_busy returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_busy(int $delay, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_busy($delay, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_chmod changes file, or directory permissions. The
+ * new permissions are specified by mode.
+ *
+ * @param string $path Path to the target file or directory
+ * Avoid relative
+ * paths
+ * @param int $mode The new permissions. E.g. 0644.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_chmod returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_chmod(string $path, int $mode, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_chmod($path, $mode, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Changes file, or directory permissions.
+ *
+ * @param string $path Path to file or directory.
+ * Avoid relative
+ * paths
+ * @param int $uid User ID. Is ignored when equal to -1.
+ * @param int $gid Group ID. Is ignored when equal to -1.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_chown returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_chown(string $path, int $uid, int $gid = -1, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_chown($path, $uid, $gid, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_close closes file specified by
+ * fd.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_close returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_close($fd, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_close($fd, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_custom executes custom function specified by
+ * execute processing it just like any other eio_* call.
+ *
+ * @param callable $execute Specifies the request function that should match the following prototype:
+ *
+ *
+ * callback is event completion callback that should match the following
+ * prototype:
+ *
+ *
+ * data is the data passed to
+ * execute via data argument
+ * without modifications
+ * result value returned by execute
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_custom returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_custom(callable $execute, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ $result = \eio_custom($execute, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_dup2 duplicates file descriptor.
+ *
+ * @param mixed $fd Source stream, Socket resource, or numeric file descriptor
+ * @param mixed $fd2 Target stream, Socket resource, or numeric file descriptor
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_dup2 returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_dup2($fd, $fd2, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_dup2($fd, $fd2, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_event_loop polls libeio until all requests proceeded.
+ *
+ * @throws EioException
+ *
+ */
+function eio_event_loop(): void
+{
+ error_clear_last();
+ $result = \eio_event_loop();
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+}
+
+
+/**
+ * eio_fallocate allows the caller to directly manipulate the allocated disk space for the
+ * file specified by fd file descriptor for the byte
+ * range starting at offset and continuing for
+ * length bytes.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor, e.g. returned by eio_open.
+ * @param int $mode Currently only one flag is supported for mode:
+ * EIO_FALLOC_FL_KEEP_SIZE (the same as POSIX constant
+ * FALLOC_FL_KEEP_SIZE).
+ * @param int $offset Specifies start of the byte range.
+ * @param int $length Specifies length the byte range.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_fallocate returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fallocate($fd, int $mode, int $offset, int $length, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_fallocate($fd, $mode, $offset, $length, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_fchmod changes permissions for the file specified
+ * by fd file descriptor.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor, e.g. returned by eio_open.
+ * @param int $mode The new permissions. E.g. 0644.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_fchmod returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fchmod($fd, int $mode, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_fchmod($fd, $mode, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_fdatasync synchronizes a file's in-core state with storage device.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor, e.g. returned by eio_open.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_fdatasync returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fdatasync($fd, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_fdatasync($fd, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_fstat returns file status information in
+ * result argument of callback
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_busy returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fstat($fd, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ if ($data !== null) {
+ $result = \eio_fstat($fd, $pri, $callback, $data);
+ } else {
+ $result = \eio_fstat($fd, $pri, $callback);
+ }
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_fstatvfs returns file system statistics in
+ * result of callback.
+ *
+ * @param mixed $fd A file descriptor of a file within the mounted file system.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_fstatvfs returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fstatvfs($fd, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ if ($data !== null) {
+ $result = \eio_fstatvfs($fd, $pri, $callback, $data);
+ } else {
+ $result = \eio_fstatvfs($fd, $pri, $callback);
+ }
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Synchronize a file's in-core state with storage device
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_fsync returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_fsync($fd, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_fsync($fd, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_ftruncate causes a regular file referenced by
+ * fd file descriptor to be truncated to precisely
+ * length bytes.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor.
+ * @param int $offset Offset from beginning of the file
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_ftruncate returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_ftruncate($fd, int $offset = 0, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_ftruncate($fd, $offset, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_futime changes file last access and modification
+ * times.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor, e.g. returned by eio_open
+ * @param float $atime Access time
+ * @param float $mtime Modification time
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_futime returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_futime($fd, float $atime, float $mtime, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_futime($fd, $atime, $mtime, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_grp creates a request group.
+ *
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param string $data is custom data passed to the request.
+ * @return resource eio_grp returns request group resource on success.
+ * @throws EioException
+ *
+ */
+function eio_grp(callable $callback, string $data = null)
+{
+ error_clear_last();
+ $result = \eio_grp($callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_lstat returns file status information in
+ * result argument of callback
+ *
+ * @param string $path The file path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_lstat returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_lstat(string $path, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ $result = \eio_lstat($path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_mkdir creates directory with specified access
+ * mode.
+ *
+ * @param string $path Path for the new directory.
+ * @param int $mode Access mode, e.g. 0755
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_mkdir returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_mkdir(string $path, int $mode, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_mkdir($path, $mode, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_mknod creates ordinary or special(often) file.
+ *
+ * @param string $path Path for the new node(file).
+ * @param int $mode Specifies both the permissions to use and the type of node to be
+ * created. It should be a combination (using bitwise OR) of one of the
+ * file types listed below and the permissions for the new node(e.g. 0640).
+ *
+ * Possible file types are: EIO_S_IFREG(regular file),
+ * EIO_S_IFCHR(character file),
+ * EIO_S_IFBLK(block special file),
+ * EIO_S_IFIFO(FIFO - named pipe) and
+ * EIO_S_IFSOCK(UNIX domain socket).
+ *
+ * To specify permissions EIO_S_I* constants could be
+ * used.
+ * @param int $dev If the file type is EIO_S_IFCHR or
+ * EIO_S_IFBLK then dev specifies the major and minor
+ * numbers of the newly created device special file. Otherwise
+ * dev ignored. See mknod(2) man page for
+ * details.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_mknod returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_mknod(string $path, int $mode, int $dev, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_mknod($path, $mode, $dev, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_nop does nothing, except go through the whole
+ * request cycle. Could be useful in debugging.
+ *
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_nop returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_nop(int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_nop($pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_readahead populates the page cache with data from a file so that subsequent reads from
+ * that file will not block on disk I/O. See READAHEAD(2) man page for details.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor
+ * @param int $offset Starting point from which data is to be read.
+ * @param int $length Number of bytes to be read.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_readahead returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_readahead($fd, int $offset, int $length, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_readahead($fd, $offset, $length, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads through a whole directory(via the opendir, readdir and
+ * closedir system calls) and returns either the names or an array in
+ * result argument of callback
+ * function, depending on the flags argument.
+ *
+ * @param string $path Directory path.
+ * @param int $flags Combination of EIO_READDIR_* constants.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param string $data is custom data passed to the request.
+ * @return resource eio_readdir returns request resource on success. Sets result argument of
+ * callback function according to
+ * flags:
+ *
+ *
+ *
+ *
+ *
+ *
+ * EIO_READDIR_DENTS
+ * (integer)
+ *
+ *
+ *
+ * eio_readdir flag. If specified, the result argument of the callback
+ * becomes an array with the following keys:
+ * 'names' - array of directory names
+ * 'dents' - array of struct
+ * eio_dirent-like arrays having the following keys each:
+ * 'name' - the directory name;
+ * 'type' - one of EIO_DT_*
+ * constants;
+ * 'inode' - the inode number, if available, otherwise
+ * unspecified;
+ *
+ *
+ *
+ *
+ *
+ * EIO_READDIR_DIRS_FIRST
+ * (integer)
+ *
+ *
+ *
+ * When this flag is specified, the names will be returned in an order
+ * where likely directories come first, in optimal stat order.
+ *
+ *
+ *
+ *
+ *
+ * EIO_READDIR_STAT_ORDER
+ * (integer)
+ *
+ *
+ *
+ * When this flag is specified, then the names will be returned in an order
+ * suitable for stat'ing each one. When planning to
+ * stat all files in the given directory, the
+ * returned order will likely be
+ * fastest.
+ *
+ *
+ *
+ *
+ *
+ * EIO_READDIR_FOUND_UNKNOWN
+ * (integer)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Node types:
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_UNKNOWN
+ * (integer)
+ *
+ *
+ *
+ * Unknown node type(very common). Further stat needed.
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_FIFO
+ * (integer)
+ *
+ *
+ *
+ * FIFO node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_CHR
+ * (integer)
+ *
+ *
+ *
+ * Node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_MPC
+ * (integer)
+ *
+ *
+ *
+ * Multiplexed char device (v7+coherent) node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_DIR
+ * (integer)
+ *
+ *
+ *
+ * Directory node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_NAM
+ * (integer)
+ *
+ *
+ *
+ * Xenix special named file node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_BLK
+ * (integer)
+ *
+ *
+ *
+ * Node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_MPB
+ * (integer)
+ *
+ *
+ *
+ * Multiplexed block device (v7+coherent)
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_REG
+ * (integer)
+ *
+ *
+ *
+ * Node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_NWK
+ * (integer)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_CMP
+ * (integer)
+ *
+ *
+ *
+ * HP-UX network special node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_LNK
+ * (integer)
+ *
+ *
+ *
+ * Link node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_SOCK
+ * (integer)
+ *
+ *
+ *
+ * Socket node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_DOOR
+ * (integer)
+ *
+ *
+ *
+ * Solaris door node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_WHT
+ * (integer)
+ *
+ *
+ *
+ * Node type
+ *
+ *
+ *
+ *
+ *
+ * EIO_DT_MAX
+ * (integer)
+ *
+ *
+ *
+ * Highest node type value
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @throws EioException
+ *
+ */
+function eio_readdir(string $path, int $flags, int $pri, callable $callback, string $data = null)
+{
+ error_clear_last();
+ $result = \eio_readdir($path, $flags, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $path Source symbolic link path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param string $data is custom data passed to the request.
+ * @return resource eio_readlink returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_readlink(string $path, int $pri, callable $callback, string $data = null)
+{
+ error_clear_last();
+ $result = \eio_readlink($path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_rename renames or moves a file to new location.
+ *
+ * @param string $path Source path
+ * @param string $new_path Target path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_rename returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_rename(string $path, string $new_path, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_rename($path, $new_path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_rmdir removes a directory.
+ *
+ * @param string $path Directory path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_rmdir returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_rmdir(string $path, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_rmdir($path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_seek repositions the offset of the open file associated with
+ * stream, Socket resource, or file descriptor specified by fd to the argument offset according to the directive whence as follows:
+ *
+ * EIO_SEEK_SET - Set position equal to offset bytes.
+ * EIO_SEEK_CUR - Set position to current location plus offset.
+ * EIO_SEEK_END - Set position to end-of-file plus offset.
+ *
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor
+ * @param int $offset Starting point from which data is to be read.
+ * @param int $whence Number of bytes to be read.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_seek returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_seek($fd, int $offset, int $whence, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_seek($fd, $offset, $whence, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_sendfile copies data between one file descriptor
+ * and another. See SENDFILE(2) man page for details.
+ *
+ * @param mixed $out_fd Output stream, Socket resource, or file descriptor. Should be opened for writing.
+ * @param mixed $in_fd Input stream, Socket resource, or file descriptor. Should be opened for reading.
+ * @param int $offset Offset within the source file.
+ * @param int $length Number of bytes to copy.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param string $data is custom data passed to the request.
+ * @return resource eio_sendfile returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_sendfile($out_fd, $in_fd, int $offset, int $length, int $pri = null, callable $callback = null, string $data = null)
+{
+ error_clear_last();
+ if ($data !== null) {
+ $result = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri, $callback, $data);
+ } elseif ($callback !== null) {
+ $result = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri, $callback);
+ } elseif ($pri !== null) {
+ $result = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri);
+ } else {
+ $result = \eio_sendfile($out_fd, $in_fd, $offset, $length);
+ }
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_stat returns file status information in
+ * result argument of callback
+ *
+ * @param string $path The file path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_stat returns request resource on success. On success assigns result argument of
+ * callback to an array.
+ * @throws EioException
+ *
+ */
+function eio_stat(string $path, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ $result = \eio_stat($path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_statvfs returns file system statistics information in
+ * result argument of callback
+ *
+ * @param string $path Pathname of any file within the mounted file system
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_statvfs returns request resource on success. On success assigns result argument of
+ * callback to an array.
+ * @throws EioException
+ *
+ */
+function eio_statvfs(string $path, int $pri, callable $callback, $data = null)
+{
+ error_clear_last();
+ if ($data !== null) {
+ $result = \eio_statvfs($path, $pri, $callback, $data);
+ } else {
+ $result = \eio_statvfs($path, $pri, $callback);
+ }
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_symlink creates a symbolic link
+ * new_path to path.
+ *
+ * @param string $path Source path
+ * @param string $new_path Target path
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_symlink returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_symlink(string $path, string $new_path, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_symlink($path, $new_path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_sync_file_range permits fine control when synchronizing the open file referred to by the file
+ * descriptor fd with disk.
+ *
+ * @param mixed $fd File descriptor
+ * @param int $offset The starting byte of the file range to be synchronized
+ * @param int $nbytes Specifies the length of the range to be synchronized, in bytes. If
+ * nbytes is zero, then all bytes from offset through
+ * to the end of file are synchronized.
+ * @param int $flags A bit-mask. Can include any of the following values:
+ * EIO_SYNC_FILE_RANGE_WAIT_BEFORE,
+ * EIO_SYNC_FILE_RANGE_WRITE,
+ * EIO_SYNC_FILE_RANGE_WAIT_AFTER. These flags have
+ * the same meaning as their SYNC_FILE_RANGE_*
+ * counterparts(see SYNC_FILE_RANGE(2) man page).
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_sync_file_range returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_sync_file_range($fd, int $offset, int $nbytes, int $flags, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_sync_file_range($fd, $offset, $nbytes, $flags, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param int $pri
+ * @param callable $callback
+ * @param mixed $data
+ * @return resource eio_sync returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_sync(int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_sync($pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param mixed $fd File descriptor
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_syncfs returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_syncfs($fd, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_syncfs($fd, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_truncate causes the regular file named by path to be truncated to
+ * a size of precisely length bytes
+ *
+ * @param string $path File path
+ * @param int $offset Offset from beginning of the file.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_busy returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_truncate(string $path, int $offset = 0, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_truncate($path, $offset, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_unlink deletes a name from the file system.
+ *
+ * @param string $path Path to file
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_unlink returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_unlink(string $path, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_unlink($path, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $path Path to the file.
+ * @param float $atime Access time
+ * @param float $mtime Modification time
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_utime returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_utime(string $path, float $atime, float $mtime, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_utime($path, $atime, $mtime, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * eio_write writes up to length
+ * bytes from str at offset
+ * offset from the beginning of the file.
+ *
+ * @param mixed $fd Stream, Socket resource, or numeric file descriptor, e.g. returned by eio_open
+ * @param string $str Source string
+ * @param int $length Maximum number of bytes to write.
+ * @param int $offset Offset from the beginning of file.
+ * @param int $pri The request priority: EIO_PRI_DEFAULT, EIO_PRI_MIN, EIO_PRI_MAX, or NULL.
+ * If NULL passed, pri internally is set to
+ * EIO_PRI_DEFAULT.
+ * @param callable $callback
+ * callback function is called when the request is done.
+ * It should match the following prototype:
+ *
+ *
+ * data
+ * is custom data passed to the request.
+ *
+ *
+ * result
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ *
+ * req
+ * is optional request resource which can be used with functions like eio_get_last_error
+ *
+ *
+ *
+ * is custom data passed to the request.
+ *
+ * request-specific result value; basically, the value returned by corresponding
+ * system call.
+ *
+ * is optional request resource which can be used with functions like eio_get_last_error
+ * @param mixed $data is custom data passed to the request.
+ * @return resource eio_write returns request resource on success.
+ * @throws EioException
+ *
+ */
+function eio_write($fd, string $str, int $length = 0, int $offset = 0, int $pri = EIO_PRI_DEFAULT, callable $callback = null, $data = null)
+{
+ error_clear_last();
+ $result = \eio_write($fd, $str, $length, $offset, $pri, $callback, $data);
+ if ($result === false) {
+ throw EioException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ErrorfuncException;
+
+/**
+ * Sends an error message to the web server's error log or to a file.
+ *
+ * @param string $message The error message that should be logged.
+ * @param int $message_type Says where the error should go. The possible message types are as
+ * follows:
+ *
+ *
+ * error_log log types
+ *
+ *
+ *
+ * 0
+ *
+ * message is sent to PHP's system logger, using
+ * the Operating System's system logging mechanism or a file, depending
+ * on what the error_log
+ * configuration directive is set to. This is the default option.
+ *
+ *
+ *
+ * 1
+ *
+ * message is sent by email to the address in
+ * the destination parameter. This is the only
+ * message type where the fourth parameter,
+ * extra_headers is used.
+ *
+ *
+ *
+ * 2
+ *
+ * No longer an option.
+ *
+ *
+ *
+ * 3
+ *
+ * message is appended to the file
+ * destination. A newline is not automatically
+ * added to the end of the message string.
+ *
+ *
+ *
+ * 4
+ *
+ * message is sent directly to the SAPI logging
+ * handler.
+ *
+ *
+ *
+ *
+ *
+ * @param string $destination The destination. Its meaning depends on the
+ * message_type parameter as described above.
+ * @param string $extra_headers The extra headers. It's used when the message_type
+ * parameter is set to 1.
+ * This message type uses the same internal function as
+ * mail does.
+ * @throws ErrorfuncException
+ *
+ */
+function error_log(string $message, int $message_type = 0, string $destination = null, string $extra_headers = null): void
+{
+ error_clear_last();
+ if ($extra_headers !== null) {
+ $result = \error_log($message, $message_type, $destination, $extra_headers);
+ } elseif ($destination !== null) {
+ $result = \error_log($message, $message_type, $destination);
+ } else {
+ $result = \error_log($message, $message_type);
+ }
+ if ($result === false) {
+ throw ErrorfuncException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ExecException;
+
+/**
+ * proc_get_status fetches data about a
+ * process opened using proc_open.
+ *
+ * @param resource $process The proc_open resource that will
+ * be evaluated.
+ * @return array An array of collected information on success. The returned array contains the following elements:
+ *
+ *
+ *
+ *
+ * elementtypedescription
+ *
+ *
+ *
+ * command
+ * string
+ *
+ * The command string that was passed to proc_open.
+ *
+ *
+ *
+ * pid
+ * int
+ * process id
+ *
+ *
+ * running
+ * bool
+ *
+ * TRUE if the process is still running, FALSE if it has
+ * terminated.
+ *
+ *
+ *
+ * signaled
+ * bool
+ *
+ * TRUE if the child process has been terminated by
+ * an uncaught signal. Always set to FALSE on Windows.
+ *
+ *
+ *
+ * stopped
+ * bool
+ *
+ * TRUE if the child process has been stopped by a
+ * signal. Always set to FALSE on Windows.
+ *
+ *
+ *
+ * exitcode
+ * int
+ *
+ * The exit code returned by the process (which is only
+ * meaningful if running is FALSE).
+ * Only first call of this function return real value, next calls return
+ * -1.
+ *
+ *
+ *
+ * termsig
+ * int
+ *
+ * The number of the signal that caused the child process to terminate
+ * its execution (only meaningful if signaled is TRUE).
+ *
+ *
+ *
+ * stopsig
+ * int
+ *
+ * The number of the signal that caused the child process to stop its
+ * execution (only meaningful if stopped is TRUE).
+ *
+ *
+ *
+ *
+ *
+ * @throws ExecException
+ *
+ */
+function proc_get_status($process): array
+{
+ error_clear_last();
+ $result = \proc_get_status($process);
+ if ($result === false) {
+ throw ExecException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * proc_nice changes the priority of the current
+ * process by the amount specified in increment. A
+ * positive increment will lower the priority of the
+ * current process, whereas a negative increment
+ * will raise the priority.
+ *
+ * proc_nice is not related to
+ * proc_open and its associated functions in any way.
+ *
+ * @param int $increment The new priority value, the value of this may differ on platforms.
+ *
+ * On Unix, a low value, such as -20 means high priority
+ * wheras a positive value have a lower priority.
+ *
+ * For Windows the increment parameter have the
+ * following meanings:
+ * @throws ExecException
+ *
+ */
+function proc_nice(int $increment): void
+{
+ error_clear_last();
+ $result = \proc_nice($increment);
+ if ($result === false) {
+ throw ExecException::createFromPhpError();
+ }
+}
+
+
+/**
+ * system is just like the C version of the
+ * function in that it executes the given
+ * command and outputs the result.
+ *
+ * The system call also tries to automatically
+ * flush the web server's output buffer after each line of output if
+ * PHP is running as a server module.
+ *
+ * If you need to execute a command and have all the data from the
+ * command passed directly back without any interference, use the
+ * passthru function.
+ *
+ * @param string $command The command that will be executed.
+ * @param int $return_var If the return_var argument is present, then the
+ * return status of the executed command will be written to this
+ * variable.
+ * @return string Returns the last line of the command output on success.
+ * @throws ExecException
+ *
+ */
+function system(string $command, int &$return_var = null): string
+{
+ error_clear_last();
+ $result = \system($command, $return_var);
+ if ($result === false) {
+ throw ExecException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FileinfoException;
+
+/**
+ * This function closes the resource opened by finfo_open.
+ *
+ * @param resource $finfo Fileinfo resource returned by finfo_open.
+ * @throws FileinfoException
+ *
+ */
+function finfo_close($finfo): void
+{
+ error_clear_last();
+ $result = \finfo_close($finfo);
+ if ($result === false) {
+ throw FileinfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Procedural style
+ *
+ * Object oriented style (constructor):
+ *
+ * This function opens a magic database and returns its resource.
+ *
+ * @param int $options One or disjunction of more Fileinfo
+ * constants.
+ * @param string $magic_file Name of a magic database file, usually something like
+ * /path/to/magic.mime. If not specified, the
+ * MAGIC environment variable is used. If the
+ * environment variable isn't set, then PHP's bundled magic database will
+ * be used.
+ *
+ * Passing NULL or an empty string will be equivalent to the default
+ * value.
+ * @return resource (Procedural style only)
+ * Returns a magic database resource on success.
+ * @throws FileinfoException
+ *
+ */
+function finfo_open(int $options = FILEINFO_NONE, string $magic_file = "")
+{
+ error_clear_last();
+ $result = \finfo_open($options, $magic_file);
+ if ($result === false) {
+ throw FileinfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the MIME content type for a file as determined by using
+ * information from the magic.mime file.
+ *
+ * @param string $filename Path to the tested file.
+ * @return string Returns the content type in MIME format, like
+ * text/plain or application/octet-stream.
+ * @throws FileinfoException
+ *
+ */
+function mime_content_type(string $filename): string
+{
+ error_clear_last();
+ $result = \mime_content_type($filename);
+ if ($result === false) {
+ throw FileinfoException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FilesystemException;
+
+/**
+ * Attempts to change the group of the file filename
+ * to group.
+ *
+ * Only the superuser may change the group of a file arbitrarily; other users
+ * may change the group of a file to any group of which that user is a member.
+ *
+ * @param string $filename Path to the file.
+ * @param string|int $group A group name or number.
+ * @throws FilesystemException
+ *
+ */
+function chgrp(string $filename, $group): void
+{
+ error_clear_last();
+ $result = \chgrp($filename, $group);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to change the mode of the specified file to that given in
+ * mode.
+ *
+ * @param string $filename Path to the file.
+ * @param int $mode Note that mode is not automatically
+ * assumed to be an octal value, so to ensure the expected operation,
+ * you need to prefix mode with a zero (0).
+ * Strings such as "g+w" will not work properly.
+ *
+ *
+ *
+ *
+ * ]]>
+ *
+ *
+ *
+ * The mode parameter consists of three octal
+ * number components specifying access restrictions for the owner,
+ * the user group in which the owner is in, and to everybody else in
+ * this order. One component can be computed by adding up the needed
+ * permissions for that target user base. Number 1 means that you
+ * grant execute rights, number 2 means that you make the file
+ * writeable, number 4 means that you make the file readable. Add
+ * up these numbers to specify needed rights. You can also read more
+ * about modes on Unix systems with 'man 1 chmod'
+ * and 'man 2 chmod'.
+ *
+ *
+ *
+ *
+ */
+function chmod(string $filename, int $mode): void
+{
+ error_clear_last();
+ $result = \chmod($filename, $mode);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to change the owner of the file filename
+ * to user user. Only the superuser may change the
+ * owner of a file.
+ *
+ * @param string $filename Path to the file.
+ * @param string|int $user A user name or number.
+ * @throws FilesystemException
+ *
+ */
+function chown(string $filename, $user): void
+{
+ error_clear_last();
+ $result = \chown($filename, $user);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes a copy of the file source to
+ * dest.
+ *
+ * If you wish to move a file, use the rename function.
+ *
+ * @param string $source Path to the source file.
+ * @param string $dest The destination path. If dest is a URL, the
+ * copy operation may fail if the wrapper does not support overwriting of
+ * existing files.
+ *
+ * If the destination file already exists, it will be overwritten.
+ * @param resource $context A valid context resource created with
+ * stream_context_create.
+ * @throws FilesystemException
+ *
+ */
+function copy(string $source, string $dest, $context = null): void
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \copy($source, $dest, $context);
+ } else {
+ $result = \copy($source, $dest);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Given a string containing a directory, this function will return the
+ * number of bytes available on the corresponding filesystem or disk
+ * partition.
+ *
+ * @param string $directory A directory of the filesystem or disk partition.
+ *
+ * Given a file name instead of a directory, the behaviour of the
+ * function is unspecified and may differ between operating systems and
+ * PHP versions.
+ * @return float Returns the number of available bytes as a float.
+ * @throws FilesystemException
+ *
+ */
+function disk_free_space(string $directory): float
+{
+ error_clear_last();
+ $result = \disk_free_space($directory);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Given a string containing a directory, this function will return the total
+ * number of bytes on the corresponding filesystem or disk partition.
+ *
+ * @param string $directory A directory of the filesystem or disk partition.
+ * @return float Returns the total number of bytes as a float.
+ * @throws FilesystemException
+ *
+ */
+function disk_total_space(string $directory): float
+{
+ error_clear_last();
+ $result = \disk_total_space($directory);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The file pointed to by handle is closed.
+ *
+ * @param resource $handle The file pointer must be valid, and must point to a file successfully
+ * opened by fopen or fsockopen.
+ * @throws FilesystemException
+ *
+ */
+function fclose($handle): void
+{
+ error_clear_last();
+ $result = \fclose($handle);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function forces a write of all buffered output to the resource
+ * pointed to by the file handle.
+ *
+ * @param resource $handle The file pointer must be valid, and must point to
+ * a file successfully opened by fopen or
+ * fsockopen (and not yet closed by
+ * fclose).
+ * @throws FilesystemException
+ *
+ */
+function fflush($handle): void
+{
+ error_clear_last();
+ $result = \fflush($handle);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function is similar to file, except that
+ * file_get_contents returns the file in a
+ * string, starting at the specified offset
+ * up to maxlen bytes. On failure,
+ * file_get_contents will return FALSE.
+ *
+ * file_get_contents is the preferred way to read the
+ * contents of a file into a string. It will use memory mapping techniques if
+ * supported by your OS to enhance performance.
+ *
+ * @param string $filename Name of the file to read.
+ * @param bool $use_include_path The FILE_USE_INCLUDE_PATH constant can be used
+ * to trigger include path
+ * search.
+ * This is not possible if strict typing
+ * is enabled, since FILE_USE_INCLUDE_PATH is an
+ * int. Use TRUE instead.
+ * @param resource|null $context A valid context resource created with
+ * stream_context_create. If you don't need to use a
+ * custom context, you can skip this parameter by NULL.
+ * @param int $offset The offset where the reading starts on the original stream.
+ * Negative offsets count from the end of the stream.
+ *
+ * Seeking (offset) is not supported with remote files.
+ * Attempting to seek on non-local files may work with small offsets, but this
+ * is unpredictable because it works on the buffered stream.
+ * @param int $maxlen Maximum length of data read. The default is to read until end
+ * of file is reached. Note that this parameter is applied to the
+ * stream processed by the filters.
+ * @return string The function returns the read data.
+ * @throws FilesystemException
+ *
+ */
+function file_get_contents(string $filename, bool $use_include_path = false, $context = null, int $offset = 0, int $maxlen = null): string
+{
+ error_clear_last();
+ if ($maxlen !== null) {
+ $result = \file_get_contents($filename, $use_include_path, $context, $offset, $maxlen);
+ } elseif ($offset !== 0) {
+ $result = \file_get_contents($filename, $use_include_path, $context, $offset);
+ } elseif ($context !== null) {
+ $result = \file_get_contents($filename, $use_include_path, $context);
+ } else {
+ $result = \file_get_contents($filename, $use_include_path);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is identical to calling fopen,
+ * fwrite and fclose successively
+ * to write data to a file.
+ *
+ * If filename does not exist, the file is created.
+ * Otherwise, the existing file is overwritten, unless the
+ * FILE_APPEND flag is set.
+ *
+ * @param string $filename Path to the file where to write the data.
+ * @param mixed $data The data to write. Can be either a string, an
+ * array or a stream resource.
+ *
+ * If data is a stream resource, the
+ * remaining buffer of that stream will be copied to the specified file.
+ * This is similar with using stream_copy_to_stream.
+ *
+ * You can also specify the data parameter as a single
+ * dimension array. This is equivalent to
+ * file_put_contents($filename, implode('', $array)).
+ * @param int $flags The value of flags can be any combination of
+ * the following flags, joined with the binary OR (|)
+ * operator.
+ *
+ *
+ * Available flags
+ *
+ *
+ *
+ * Flag
+ * Description
+ *
+ *
+ *
+ *
+ *
+ * FILE_USE_INCLUDE_PATH
+ *
+ *
+ * Search for filename in the include directory.
+ * See include_path for more
+ * information.
+ *
+ *
+ *
+ *
+ * FILE_APPEND
+ *
+ *
+ * If file filename already exists, append
+ * the data to the file instead of overwriting it.
+ *
+ *
+ *
+ *
+ * LOCK_EX
+ *
+ *
+ * Acquire an exclusive lock on the file while proceeding to the
+ * writing. In other words, a flock call happens
+ * between the fopen call and the
+ * fwrite call. This is not identical to an
+ * fopen call with mode "x".
+ *
+ *
+ *
+ *
+ *
+ * @param resource $context A valid context resource created with
+ * stream_context_create.
+ * @return int This function returns the number of bytes that were written to the file.
+ * @throws FilesystemException
+ *
+ */
+function file_put_contents(string $filename, $data, int $flags = 0, $context = null): int
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \file_put_contents($filename, $data, $flags, $context);
+ } else {
+ $result = \file_put_contents($filename, $data, $flags);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads an entire file into an array.
+ *
+ * @param string $filename Path to the file.
+ * @param int $flags The optional parameter flags can be one, or
+ * more, of the following constants:
+ *
+ *
+ *
+ * FILE_USE_INCLUDE_PATH
+ *
+ *
+ *
+ * Search for the file in the include_path.
+ *
+ *
+ *
+ *
+ *
+ * FILE_IGNORE_NEW_LINES
+ *
+ *
+ *
+ * Omit newline at the end of each array element
+ *
+ *
+ *
+ *
+ *
+ * FILE_SKIP_EMPTY_LINES
+ *
+ *
+ *
+ * Skip empty lines
+ *
+ *
+ *
+ *
+ * @param resource $context
+ * @return array Returns the file in an array. Each element of the array corresponds to a
+ * line in the file, with the newline still attached. Upon failure,
+ * file returns FALSE.
+ * @throws FilesystemException
+ *
+ */
+function file(string $filename, int $flags = 0, $context = null): array
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \file($filename, $flags, $context);
+ } else {
+ $result = \file($filename, $flags);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the time the file was last accessed.
+ * The time is returned as a Unix timestamp.
+ * @throws FilesystemException
+ *
+ */
+function fileatime(string $filename): int
+{
+ error_clear_last();
+ $result = \fileatime($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the inode change time of a file.
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the time the file was last changed.
+ * The time is returned as a Unix timestamp.
+ * @throws FilesystemException
+ *
+ */
+function filectime(string $filename): int
+{
+ error_clear_last();
+ $result = \filectime($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the file inode.
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the inode number of the file.
+ * @throws FilesystemException
+ *
+ */
+function fileinode(string $filename): int
+{
+ error_clear_last();
+ $result = \fileinode($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns the time when the data blocks of a file were being
+ * written to, that is, the time when the content of the file was changed.
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the time the file was last modified.
+ * The time is returned as a Unix timestamp, which is
+ * suitable for the date function.
+ * @throws FilesystemException
+ *
+ */
+function filemtime(string $filename): int
+{
+ error_clear_last();
+ $result = \filemtime($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the file owner.
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the user ID of the owner of the file.
+ * The user ID is returned in numerical format, use
+ * posix_getpwuid to resolve it to a username.
+ * @throws FilesystemException
+ *
+ */
+function fileowner(string $filename): int
+{
+ error_clear_last();
+ $result = \fileowner($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the size for the given file.
+ *
+ * @param string $filename Path to the file.
+ * @return int Returns the size of the file in bytes, or FALSE (and generates an error
+ * of level E_WARNING) in case of an error.
+ * @throws FilesystemException
+ *
+ */
+function filesize(string $filename): int
+{
+ error_clear_last();
+ $result = \filesize($filename);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * flock allows you to perform a simple reader/writer
+ * model which can be used on virtually every platform (including most Unix
+ * derivatives and even Windows).
+ *
+ * On versions of PHP before 5.3.2, the lock is released also by
+ * fclose (which is also called automatically when script
+ * finished).
+ *
+ * PHP supports a portable way of locking complete files in an advisory way
+ * (which means all accessing programs have to use the same way of locking
+ * or it will not work). By default, this function will block until the
+ * requested lock is acquired; this may be controlled with the LOCK_NB option documented below.
+ *
+ * @param resource $handle A file system pointer resource
+ * that is typically created using fopen.
+ * @param int $operation operation is one of the following:
+ *
+ *
+ *
+ * LOCK_SH to acquire a shared lock (reader).
+ *
+ *
+ *
+ *
+ * LOCK_EX to acquire an exclusive lock (writer).
+ *
+ *
+ *
+ *
+ * LOCK_UN to release a lock (shared or exclusive).
+ *
+ *
+ *
+ *
+ * It is also possible to add LOCK_NB as a bitmask to one
+ * of the above operations, if flock should not
+ * block during the locking attempt.
+ * @param int|null $wouldblock The optional third argument is set to 1 if the lock would block
+ * (EWOULDBLOCK errno condition).
+ * @throws FilesystemException
+ *
+ */
+function flock($handle, int $operation, ?int &$wouldblock = null): void
+{
+ error_clear_last();
+ $result = \flock($handle, $operation, $wouldblock);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * fopen binds a named resource, specified by
+ * filename, to a stream.
+ *
+ * @param string $filename If filename is of the form "scheme://...", it
+ * is assumed to be a URL and PHP will search for a protocol handler
+ * (also known as a wrapper) for that scheme. If no wrappers for that
+ * protocol are registered, PHP will emit a notice to help you track
+ * potential problems in your script and then continue as though
+ * filename specifies a regular file.
+ *
+ * If PHP has decided that filename specifies
+ * a local file, then it will try to open a stream on that file.
+ * The file must be accessible to PHP, so you need to ensure that
+ * the file access permissions allow this access.
+ * If you have enabled
+ * open_basedir further
+ * restrictions may apply.
+ *
+ * If PHP has decided that filename specifies
+ * a registered protocol, and that protocol is registered as a
+ * network URL, PHP will check to make sure that
+ * allow_url_fopen is
+ * enabled. If it is switched off, PHP will emit a warning and
+ * the fopen call will fail.
+ *
+ * The list of supported protocols can be found in . Some protocols (also referred to as
+ * wrappers) support context
+ * and/or php.ini options. Refer to the specific page for the
+ * protocol in use for a list of options which can be set. (e.g.
+ * php.ini value user_agent used by the
+ * http wrapper).
+ *
+ * On the Windows platform, be careful to escape any backslashes
+ * used in the path to the file, or use forward slashes.
+ *
+ *
+ *
+ * ]]>
+ *
+ *
+ * @param string $mode The mode parameter specifies the type of access
+ * you require to the stream. It may be any of the following:
+ *
+ *
+ * A list of possible modes for fopen
+ * using mode
+ *
+ *
+ *
+ *
+ * mode
+ * Description
+ *
+ *
+ *
+ *
+ * 'r'
+ *
+ * Open for reading only; place the file pointer at the
+ * beginning of the file.
+ *
+ *
+ *
+ * 'r+'
+ *
+ * Open for reading and writing; place the file pointer at
+ * the beginning of the file.
+ *
+ *
+ *
+ * 'w'
+ *
+ * Open for writing only; place the file pointer at the
+ * beginning of the file and truncate the file to zero length.
+ * If the file does not exist, attempt to create it.
+ *
+ *
+ *
+ * 'w+'
+ *
+ * Open for reading and writing; place the file pointer at
+ * the beginning of the file and truncate the file to zero
+ * length. If the file does not exist, attempt to create it.
+ *
+ *
+ *
+ * 'a'
+ *
+ * Open for writing only; place the file pointer at the end of
+ * the file. If the file does not exist, attempt to create it.
+ * In this mode, fseek has no effect, writes are always appended.
+ *
+ *
+ *
+ * 'a+'
+ *
+ * Open for reading and writing; place the file pointer at
+ * the end of the file. If the file does not exist, attempt to
+ * create it. In this mode, fseek only affects
+ * the reading position, writes are always appended.
+ *
+ *
+ *
+ * 'x'
+ *
+ * Create and open for writing only; place the file pointer at the
+ * beginning of the file. If the file already exists, the
+ * fopen call will fail by returning FALSE and
+ * generating an error of level E_WARNING. If
+ * the file does not exist, attempt to create it. This is equivalent
+ * to specifying O_EXCL|O_CREAT flags for the
+ * underlying open(2) system call.
+ *
+ *
+ *
+ * 'x+'
+ *
+ * Create and open for reading and writing; otherwise it has the
+ * same behavior as 'x'.
+ *
+ *
+ *
+ * 'c'
+ *
+ * Open the file for writing only. If the file does not exist, it is
+ * created. If it exists, it is neither truncated (as opposed to
+ * 'w'), nor the call to this function fails (as is
+ * the case with 'x'). The file pointer is
+ * positioned on the beginning of the file. This may be useful if it's
+ * desired to get an advisory lock (see flock)
+ * before attempting to modify the file, as using
+ * 'w' could truncate the file before the lock
+ * was obtained (if truncation is desired,
+ * ftruncate can be used after the lock is
+ * requested).
+ *
+ *
+ *
+ * 'c+'
+ *
+ * Open the file for reading and writing; otherwise it has the same
+ * behavior as 'c'.
+ *
+ *
+ *
+ * 'e'
+ *
+ * Set close-on-exec flag on the opened file descriptor. Only
+ * available in PHP compiled on POSIX.1-2008 conform systems.
+ *
+ *
+ *
+ *
+ *
+ *
+ * Different operating system families have different line-ending
+ * conventions. When you write a text file and want to insert a line
+ * break, you need to use the correct line-ending character(s) for your
+ * operating system. Unix based systems use \n as the
+ * line ending character, Windows based systems use \r\n
+ * as the line ending characters and Macintosh based systems (Mac OS Classic) used
+ * \r as the line ending character.
+ *
+ * If you use the wrong line ending characters when writing your files, you
+ * might find that other applications that open those files will "look
+ * funny".
+ *
+ * Windows offers a text-mode translation flag ('t')
+ * which will transparently translate \n to
+ * \r\n when working with the file. In contrast, you
+ * can also use 'b' to force binary mode, which will not
+ * translate your data. To use these flags, specify either
+ * 'b' or 't' as the last character
+ * of the mode parameter.
+ *
+ * The default translation mode is 'b'.
+ * You can use the 't'
+ * mode if you are working with plain-text files and you use
+ * \n to delimit your line endings in your script, but
+ * expect your files to be readable with applications such as old versions of notepad. You
+ * should use the 'b' in all other cases.
+ *
+ * If you specify the 't' flag when working with binary files, you
+ * may experience strange problems with your data, including broken image
+ * files and strange problems with \r\n characters.
+ *
+ * For portability, it is also strongly recommended that
+ * you re-write code that uses or relies upon the 't'
+ * mode so that it uses the correct line endings and
+ * 'b' mode instead.
+ * @param bool $use_include_path The optional third use_include_path parameter
+ * can be set to '1' or TRUE if you want to search for the file in the
+ * include_path, too.
+ * @param resource $context
+ * @return resource Returns a file pointer resource on success
+ * @throws FilesystemException
+ *
+ */
+function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null)
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \fopen($filename, $mode, $use_include_path, $context);
+ } else {
+ $result = \fopen($filename, $mode, $use_include_path);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * fputcsv formats a line (passed as a
+ * fields array) as CSV and writes it (terminated by a
+ * newline) to the specified file handle.
+ *
+ * @param resource $handle The file pointer must be valid, and must point to
+ * a file successfully opened by fopen or
+ * fsockopen (and not yet closed by
+ * fclose).
+ * @param array $fields An array of strings.
+ * @param string $delimiter The optional delimiter parameter sets the field
+ * delimiter (one character only).
+ * @param string $enclosure The optional enclosure parameter sets the field
+ * enclosure (one character only).
+ * @param string $escape_char The optional escape_char parameter sets the
+ * escape character (at most one character).
+ * An empty string ("") disables the proprietary escape mechanism.
+ * @return int Returns the length of the written string.
+ * @throws FilesystemException
+ *
+ */
+function fputcsv($handle, array $fields, string $delimiter = ",", string $enclosure = '"', string $escape_char = "\\"): int
+{
+ error_clear_last();
+ $result = \fputcsv($handle, $fields, $delimiter, $enclosure, $escape_char);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * fread reads up to
+ * length bytes from the file pointer
+ * referenced by handle. Reading stops as soon as one
+ * of the following conditions is met:
+ *
+ *
+ *
+ * length bytes have been read
+ *
+ *
+ *
+ *
+ * EOF (end of file) is reached
+ *
+ *
+ *
+ *
+ * a packet becomes available or the
+ * socket timeout occurs (for network streams)
+ *
+ *
+ *
+ *
+ * if the stream is read buffered and it does not represent a plain file, at
+ * most one read of up to a number of bytes equal to the chunk size (usually
+ * 8192) is made; depending on the previously buffered data, the size of the
+ * returned data may be larger than the chunk size.
+ *
+ *
+ *
+ *
+ * @param resource $handle A file system pointer resource
+ * that is typically created using fopen.
+ * @param int $length Up to length number of bytes read.
+ * @return string Returns the read string.
+ * @throws FilesystemException
+ *
+ */
+function fread($handle, int $length): string
+{
+ error_clear_last();
+ $result = \fread($handle, $length);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Takes the filepointer, handle, and truncates the file to
+ * length, size.
+ *
+ * @param resource $handle The file pointer.
+ *
+ * The handle must be open for writing.
+ * @param int $size The size to truncate to.
+ *
+ * If size is larger than the file then the file
+ * is extended with null bytes.
+ *
+ * If size is smaller than the file then the file
+ * is truncated to that size.
+ * @throws FilesystemException
+ *
+ */
+function ftruncate($handle, int $size): void
+{
+ error_clear_last();
+ $result = \ftruncate($handle, $size);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $handle A file system pointer resource
+ * that is typically created using fopen.
+ * @param string $string The string that is to be written.
+ * @param int $length If the length argument is given, writing will
+ * stop after length bytes have been written or
+ * the end of string is reached, whichever comes
+ * first.
+ *
+ * Note that if the length argument is given,
+ * then the magic_quotes_runtime
+ * configuration option will be ignored and no slashes will be
+ * stripped from string.
+ * @return int
+ * @throws FilesystemException
+ *
+ */
+function fwrite($handle, string $string, int $length = null): int
+{
+ error_clear_last();
+ if ($length !== null) {
+ $result = \fwrite($handle, $string, $length);
+ } else {
+ $result = \fwrite($handle, $string);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The glob function searches for all the pathnames
+ * matching pattern according to the rules used by
+ * the libc glob() function, which is similar to the rules used by common
+ * shells.
+ *
+ * @param string $pattern The pattern. No tilde expansion or parameter substitution is done.
+ *
+ * Special characters:
+ *
+ *
+ *
+ * * - Matches zero or more characters.
+ *
+ *
+ *
+ *
+ * ? - Matches exactly one character (any character).
+ *
+ *
+ *
+ *
+ * [...] - Matches one character from a group of
+ * characters. If the first character is !,
+ * matches any character not in the group.
+ *
+ *
+ *
+ *
+ * \ - Escapes the following character,
+ * except when the GLOB_NOESCAPE flag is used.
+ *
+ *
+ *
+ * @param int $flags Valid flags:
+ *
+ *
+ *
+ * GLOB_MARK - Adds a slash (a backslash on Windows) to each directory returned
+ *
+ *
+ *
+ *
+ * GLOB_NOSORT - Return files as they appear in the
+ * directory (no sorting). When this flag is not used, the pathnames are
+ * sorted alphabetically
+ *
+ *
+ *
+ *
+ * GLOB_NOCHECK - Return the search pattern if no
+ * files matching it were found
+ *
+ *
+ *
+ *
+ * GLOB_NOESCAPE - Backslashes do not quote
+ * metacharacters
+ *
+ *
+ *
+ *
+ * GLOB_BRACE - Expands {a,b,c} to match 'a', 'b',
+ * or 'c'
+ *
+ *
+ *
+ *
+ * GLOB_ONLYDIR - Return only directory entries
+ * which match the pattern
+ *
+ *
+ *
+ *
+ * GLOB_ERR - Stop on read errors (like unreadable
+ * directories), by default errors are ignored.
+ *
+ *
+ *
+ * @return array Returns an array containing the matched files/directories, an empty array
+ * if no file matched.
+ * @throws FilesystemException
+ *
+ */
+function glob(string $pattern, int $flags = 0): array
+{
+ error_clear_last();
+ $result = \glob($pattern, $flags);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Attempts to change the group of the symlink filename
+ * to group.
+ *
+ * Only the superuser may change the group of a symlink arbitrarily; other
+ * users may change the group of a symlink to any group of which that user is
+ * a member.
+ *
+ * @param string $filename Path to the symlink.
+ * @param string|int $group The group specified by name or number.
+ * @throws FilesystemException
+ *
+ */
+function lchgrp(string $filename, $group): void
+{
+ error_clear_last();
+ $result = \lchgrp($filename, $group);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to change the owner of the symlink filename
+ * to user user.
+ *
+ * Only the superuser may change the owner of a symlink.
+ *
+ * @param string $filename Path to the file.
+ * @param string|int $user User name or number.
+ * @throws FilesystemException
+ *
+ */
+function lchown(string $filename, $user): void
+{
+ error_clear_last();
+ $result = \lchown($filename, $user);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * link creates a hard link.
+ *
+ * @param string $target Target of the link.
+ * @param string $link The link name.
+ * @throws FilesystemException
+ *
+ */
+function link(string $target, string $link): void
+{
+ error_clear_last();
+ $result = \link($target, $link);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to create the directory specified by pathname.
+ *
+ * @param string $pathname The directory path.
+ * @param int $mode The mode is 0777 by default, which means the widest possible
+ * access. For more information on modes, read the details
+ * on the chmod page.
+ *
+ * mode is ignored on Windows.
+ *
+ * Note that you probably want to specify the mode as an octal number,
+ * which means it should have a leading zero. The mode is also modified
+ * by the current umask, which you can change using
+ * umask.
+ * @param bool $recursive Allows the creation of nested directories specified in the
+ * pathname.
+ * @param resource $context
+ * @throws FilesystemException
+ *
+ */
+function mkdir(string $pathname, int $mode = 0777, bool $recursive = false, $context = null): void
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \mkdir($pathname, $mode, $recursive, $context);
+ } else {
+ $result = \mkdir($pathname, $mode, $recursive);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * parse_ini_file loads in the
+ * ini file specified in filename,
+ * and returns the settings in it in an associative array.
+ *
+ * The structure of the ini file is the same as the php.ini's.
+ *
+ * @param string $filename The filename of the ini file being parsed. If a relative path is used,
+ * it is evaluated relative to the current working directory, then the
+ * include_path.
+ * @param bool $process_sections By setting the process_sections
+ * parameter to TRUE, you get a multidimensional array, with
+ * the section names and settings included. The default
+ * for process_sections is FALSE
+ * @param int $scanner_mode Can either be INI_SCANNER_NORMAL (default) or
+ * INI_SCANNER_RAW. If INI_SCANNER_RAW
+ * is supplied, then option values will not be parsed.
+ *
+ *
+ * As of PHP 5.6.1 can also be specified as INI_SCANNER_TYPED.
+ * In this mode boolean, null and integer types are preserved when possible.
+ * String values "true", "on" and "yes"
+ * are converted to TRUE. "false", "off", "no"
+ * and "none" are considered FALSE. "null" is converted to NULL
+ * in typed mode. Also, all numeric strings are converted to integer type if it is possible.
+ * @return array The settings are returned as an associative array on success.
+ * @throws FilesystemException
+ *
+ */
+function parse_ini_file(string $filename, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
+{
+ error_clear_last();
+ $result = \parse_ini_file($filename, $process_sections, $scanner_mode);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * parse_ini_string returns the settings in string
+ * ini in an associative array.
+ *
+ * The structure of the ini string is the same as the php.ini's.
+ *
+ * @param string $ini The contents of the ini file being parsed.
+ * @param bool $process_sections By setting the process_sections
+ * parameter to TRUE, you get a multidimensional array, with
+ * the section names and settings included. The default
+ * for process_sections is FALSE
+ * @param int $scanner_mode Can either be INI_SCANNER_NORMAL (default) or
+ * INI_SCANNER_RAW. If INI_SCANNER_RAW
+ * is supplied, then option values will not be parsed.
+ *
+ *
+ * As of PHP 5.6.1 can also be specified as INI_SCANNER_TYPED.
+ * In this mode boolean, null and integer types are preserved when possible.
+ * String values "true", "on" and "yes"
+ * are converted to TRUE. "false", "off", "no"
+ * and "none" are considered FALSE. "null" is converted to NULL
+ * in typed mode. Also, all numeric strings are converted to integer type if it is possible.
+ * @return array The settings are returned as an associative array on success.
+ * @throws FilesystemException
+ *
+ */
+function parse_ini_string(string $ini, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
+{
+ error_clear_last();
+ $result = \parse_ini_string($ini, $process_sections, $scanner_mode);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads a file and writes it to the output buffer.
+ *
+ * @param string $filename The filename being read.
+ * @param bool $use_include_path You can use the optional second parameter and set it to TRUE, if
+ * you want to search for the file in the include_path, too.
+ * @param resource $context A context stream resource.
+ * @return int Returns the number of bytes read from the file on success
+ * @throws FilesystemException
+ *
+ */
+function readfile(string $filename, bool $use_include_path = false, $context = null): int
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \readfile($filename, $use_include_path, $context);
+ } else {
+ $result = \readfile($filename, $use_include_path);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * readlink does the same as the readlink C function.
+ *
+ * @param string $path The symbolic link path.
+ * @return string Returns the contents of the symbolic link path.
+ * @throws FilesystemException
+ *
+ */
+function readlink(string $path): string
+{
+ error_clear_last();
+ $result = \readlink($path);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * realpath expands all symbolic links and
+ * resolves references to /./, /../ and extra / characters in
+ * the input path and returns the canonicalized
+ * absolute pathname.
+ *
+ * @param string $path The path being checked.
+ *
+ *
+ * Whilst a path must be supplied, the value can be an empty string.
+ * In this case, the value is interpreted as the current directory.
+ *
+ *
+ *
+ * Whilst a path must be supplied, the value can be an empty string.
+ * In this case, the value is interpreted as the current directory.
+ * @return string Returns the canonicalized absolute pathname on success. The resulting path
+ * will have no symbolic link, /./ or /../ components. Trailing delimiters,
+ * such as \ and /, are also removed.
+ *
+ * realpath returns FALSE on failure, e.g. if
+ * the file does not exist.
+ * @throws FilesystemException
+ *
+ */
+function realpath(string $path): string
+{
+ error_clear_last();
+ $result = \realpath($path);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Attempts to rename oldname to
+ * newname, moving it between directories if necessary.
+ * If renaming a file and newname exists,
+ * it will be overwritten. If renaming a directory and
+ * newname exists,
+ * this function will emit a warning.
+ *
+ * @param string $oldname The old name.
+ *
+ * The wrapper used in oldname
+ * must match the wrapper used in
+ * newname.
+ * @param string $newname The new name.
+ * @param resource $context
+ * @throws FilesystemException
+ *
+ */
+function rename(string $oldname, string $newname, $context = null): void
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \rename($oldname, $newname, $context);
+ } else {
+ $result = \rename($oldname, $newname);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the file position indicator for handle
+ * to the beginning of the file stream.
+ *
+ * @param resource $handle The file pointer must be valid, and must point to a file
+ * successfully opened by fopen.
+ * @throws FilesystemException
+ *
+ */
+function rewind($handle): void
+{
+ error_clear_last();
+ $result = \rewind($handle);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to remove the directory named by dirname.
+ * The directory must be empty, and the relevant permissions must permit this.
+ * A E_WARNING level error will be generated on failure.
+ *
+ * @param string $dirname Path to the directory.
+ * @param resource $context
+ * @throws FilesystemException
+ *
+ */
+function rmdir(string $dirname, $context = null): void
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \rmdir($dirname, $context);
+ } else {
+ $result = \rmdir($dirname);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * symlink creates a symbolic link to the existing
+ * target with the specified name
+ * link.
+ *
+ * @param string $target Target of the link.
+ * @param string $link The link name.
+ * @throws FilesystemException
+ *
+ */
+function symlink(string $target, string $link): void
+{
+ error_clear_last();
+ $result = \symlink($target, $link);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a file with a unique filename, with access permission set to 0600, in the specified directory.
+ * If the directory does not exist or is not writable, tempnam may
+ * generate a file in the system's temporary directory, and return
+ * the full path to that file, including its name.
+ *
+ * @param string $dir The directory where the temporary filename will be created.
+ * @param string $prefix The prefix of the generated temporary filename.
+ * @return string Returns the new temporary filename (with path).
+ * @throws FilesystemException
+ *
+ */
+function tempnam(string $dir, string $prefix): string
+{
+ error_clear_last();
+ $result = \tempnam($dir, $prefix);
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Creates a temporary file with a unique name in read-write (w+) mode and
+ * returns a file handle.
+ *
+ * The file is automatically removed when closed (for example, by calling
+ * fclose, or when there are no remaining references to
+ * the file handle returned by tmpfile), or when the
+ * script ends.
+ *
+ * @return resource Returns a file handle, similar to the one returned by
+ * fopen, for the new file.
+ * @throws FilesystemException
+ *
+ */
+function tmpfile()
+{
+ error_clear_last();
+ $result = \tmpfile();
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Attempts to set the access and modification times of the file named in the
+ * filename parameter to the value given in
+ * time.
+ * Note that the access time is always modified, regardless of the number
+ * of parameters.
+ *
+ * If the file does not exist, it will be created.
+ *
+ * @param string $filename The name of the file being touched.
+ * @param int $time The touch time. If time is not supplied,
+ * the current system time is used.
+ * @param int $atime If present, the access time of the given filename is set to
+ * the value of atime. Otherwise, it is set to
+ * the value passed to the time parameter.
+ * If neither are present, the current system time is used.
+ * @throws FilesystemException
+ *
+ */
+function touch(string $filename, int $time = null, int $atime = null): void
+{
+ error_clear_last();
+ if ($atime !== null) {
+ $result = \touch($filename, $time, $atime);
+ } elseif ($time !== null) {
+ $result = \touch($filename, $time);
+ } else {
+ $result = \touch($filename);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deletes filename. Similar to the Unix C unlink()
+ * function. An E_WARNING level error will be generated on
+ * failure.
+ *
+ * @param string $filename Path to the file.
+ * @param resource $context
+ * @throws FilesystemException
+ *
+ */
+function unlink(string $filename, $context = null): void
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \unlink($filename, $context);
+ } else {
+ $result = \unlink($filename);
+ }
+ if ($result === false) {
+ throw FilesystemException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FilterException;
+
+/**
+ * This function is useful for retrieving many values without
+ * repetitively calling filter_input.
+ *
+ * @param int $type One of INPUT_GET, INPUT_POST,
+ * INPUT_COOKIE, INPUT_SERVER, or
+ * INPUT_ENV.
+ * @param int|array $definition An array defining the arguments. A valid key is a string
+ * containing a variable name and a valid value is either a filter type, or an array
+ * optionally specifying the filter, flags and options. If the value is an
+ * array, valid keys are filter which specifies the
+ * filter type,
+ * flags which specifies any flags that apply to the
+ * filter, and options which specifies any options that
+ * apply to the filter. See the example below for a better understanding.
+ *
+ * This parameter can be also an integer holding a filter constant. Then all values in the
+ * input array are filtered by this filter.
+ * @param bool $add_empty Add missing keys as NULL to the return value.
+ * @return mixed An array containing the values of the requested variables on success.
+ * If the input array designated by type is not populated,
+ * the function returns NULL if the FILTER_NULL_ON_FAILURE
+ * flag is not given, or FALSE otherwise. For other failures, FALSE is returned.
+ *
+ * An array value will be FALSE if the filter fails, or NULL if
+ * the variable is not set. Or if the flag FILTER_NULL_ON_FAILURE
+ * is used, it returns FALSE if the variable is not set and NULL if the filter
+ * fails. If the add_empty parameter is FALSE, no array
+ * element will be added for unset variables.
+ * @throws FilterException
+ *
+ */
+function filter_input_array(int $type, $definition = null, bool $add_empty = true)
+{
+ error_clear_last();
+ if ($add_empty !== true) {
+ $result = \filter_input_array($type, $definition, $add_empty);
+ } elseif ($definition !== null) {
+ $result = \filter_input_array($type, $definition);
+ } else {
+ $result = \filter_input_array($type);
+ }
+ if ($result === false) {
+ throw FilterException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is useful for retrieving many values without
+ * repetitively calling filter_var.
+ *
+ * @param array $data An array with string keys containing the data to filter.
+ * @param mixed $definition An array defining the arguments. A valid key is a string
+ * containing a variable name and a valid value is either a
+ * filter type, or an
+ * array optionally specifying the filter, flags and options.
+ * If the value is an array, valid keys are filter
+ * which specifies the filter type,
+ * flags which specifies any flags that apply to the
+ * filter, and options which specifies any options that
+ * apply to the filter. See the example below for a better understanding.
+ *
+ * This parameter can be also an integer holding a filter constant. Then all values in the
+ * input array are filtered by this filter.
+ * @param bool $add_empty Add missing keys as NULL to the return value.
+ * @return mixed An array containing the values of the requested variables on success. An array value will be FALSE if the filter fails, or NULL if
+ * the variable is not set.
+ * @throws FilterException
+ *
+ */
+function filter_var_array(array $data, $definition = null, bool $add_empty = true)
+{
+ error_clear_last();
+ if ($add_empty !== true) {
+ $result = \filter_var_array($data, $definition, $add_empty);
+ } elseif ($definition !== null) {
+ $result = \filter_var_array($data, $definition);
+ } else {
+ $result = \filter_var_array($data);
+ }
+ if ($result === false) {
+ throw FilterException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FpmException;
+
+/**
+ * This function flushes all response data to the client and finishes the
+ * request. This allows for time consuming tasks to be performed without
+ * leaving the connection to the client open.
+ *
+ * @throws FpmException
+ *
+ */
+function fastcgi_finish_request(): void
+{
+ error_clear_last();
+ $result = \fastcgi_finish_request();
+ if ($result === false) {
+ throw FpmException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FtpException;
+
+/**
+ * Sends an ALLO command to the remote FTP server to
+ * allocate space for a file to be uploaded.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param int $filesize The number of bytes to allocate.
+ * @param string $result A textual representation of the servers response will be returned by
+ * reference in result if a variable is provided.
+ * @throws FtpException
+ *
+ */
+function ftp_alloc($ftp_stream, int $filesize, string &$result = null): void
+{
+ error_clear_last();
+ $result = \ftp_alloc($ftp_stream, $filesize, $result);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $ftp
+ * @param string $remote_file
+ * @param string $local_file
+ * @param int $mode
+ * @throws FtpException
+ *
+ */
+function ftp_append($ftp, string $remote_file, string $local_file, int $mode = FTP_BINARY): void
+{
+ error_clear_last();
+ $result = \ftp_append($ftp, $remote_file, $local_file, $mode);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Changes to the parent directory.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @throws FtpException
+ *
+ */
+function ftp_cdup($ftp_stream): void
+{
+ error_clear_last();
+ $result = \ftp_cdup($ftp_stream);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Changes the current directory to the specified one.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $directory The target directory.
+ * @throws FtpException
+ *
+ */
+function ftp_chdir($ftp_stream, string $directory): void
+{
+ error_clear_last();
+ $result = \ftp_chdir($ftp_stream, $directory);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the permissions on the specified remote file to
+ * mode.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param int $mode The new permissions, given as an octal value.
+ * @param string $filename The remote file.
+ * @return int Returns the new file permissions on success.
+ * @throws FtpException
+ *
+ */
+function ftp_chmod($ftp_stream, int $mode, string $filename): int
+{
+ error_clear_last();
+ $result = \ftp_chmod($ftp_stream, $mode, $filename);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ftp_close closes the given link identifier
+ * and releases the resource.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @throws FtpException
+ *
+ */
+function ftp_close($ftp_stream): void
+{
+ error_clear_last();
+ $result = \ftp_close($ftp_stream);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_connect opens an FTP connection to the
+ * specified host.
+ *
+ * @param string $host The FTP server address. This parameter shouldn't have any trailing
+ * slashes and shouldn't be prefixed with ftp://.
+ * @param int $port This parameter specifies an alternate port to connect to. If it is
+ * omitted or set to zero, then the default FTP port, 21, will be used.
+ * @param int $timeout This parameter specifies the timeout in seconds for all subsequent network operations.
+ * If omitted, the default value is 90 seconds. The timeout can be changed and
+ * queried at any time with ftp_set_option and
+ * ftp_get_option.
+ * @return resource Returns a FTP stream on success.
+ * @throws FtpException
+ *
+ */
+function ftp_connect(string $host, int $port = 21, int $timeout = 90)
+{
+ error_clear_last();
+ $result = \ftp_connect($host, $port, $timeout);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ftp_delete deletes the file specified by
+ * path from the FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $path The file to delete.
+ * @throws FtpException
+ *
+ */
+function ftp_delete($ftp_stream, string $path): void
+{
+ error_clear_last();
+ $result = \ftp_delete($ftp_stream, $path);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_fget retrieves remote_file
+ * from the FTP server, and writes it to the given file pointer.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param resource $handle An open file pointer in which we store the data.
+ * @param string $remote_file The remote file path.
+ * @param int $mode The transfer mode. Must be either FTP_ASCII or
+ * FTP_BINARY.
+ * @param int $resumepos The position in the remote file to start downloading from.
+ * @throws FtpException
+ *
+ */
+function ftp_fget($ftp_stream, $handle, string $remote_file, int $mode = FTP_BINARY, int $resumepos = 0): void
+{
+ error_clear_last();
+ $result = \ftp_fget($ftp_stream, $handle, $remote_file, $mode, $resumepos);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_fput uploads the data from a file pointer
+ * to a remote file on the FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $remote_file The remote file path.
+ * @param resource $handle An open file pointer on the local file. Reading stops at end of file.
+ * @param int $mode The transfer mode. Must be either FTP_ASCII or
+ * FTP_BINARY.
+ * @param int $startpos The position in the remote file to start uploading to.
+ * @throws FtpException
+ *
+ */
+function ftp_fput($ftp_stream, string $remote_file, $handle, int $mode = FTP_BINARY, int $startpos = 0): void
+{
+ error_clear_last();
+ $result = \ftp_fput($ftp_stream, $remote_file, $handle, $mode, $startpos);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_get retrieves a remote file from the FTP server,
+ * and saves it into a local file.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $local_file The local file path (will be overwritten if the file already exists).
+ * @param string $remote_file The remote file path.
+ * @param int $mode The transfer mode. Must be either FTP_ASCII or
+ * FTP_BINARY.
+ * @param int $resumepos The position in the remote file to start downloading from.
+ * @throws FtpException
+ *
+ */
+function ftp_get($ftp_stream, string $local_file, string $remote_file, int $mode = FTP_BINARY, int $resumepos = 0): void
+{
+ error_clear_last();
+ $result = \ftp_get($ftp_stream, $local_file, $remote_file, $mode, $resumepos);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Logs in to the given FTP stream.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $username The username (USER).
+ * @param string $password The password (PASS).
+ * @throws FtpException
+ *
+ */
+function ftp_login($ftp_stream, string $username, string $password): void
+{
+ error_clear_last();
+ $result = \ftp_login($ftp_stream, $username, $password);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates the specified directory on the FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $directory The name of the directory that will be created.
+ * @return string Returns the newly created directory name on success.
+ * @throws FtpException
+ *
+ */
+function ftp_mkdir($ftp_stream, string $directory): string
+{
+ error_clear_last();
+ $result = \ftp_mkdir($ftp_stream, $directory);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $directory The directory to be listed.
+ * @return array Returns an array of arrays with file infos from the specified directory on success.
+ * @throws FtpException
+ *
+ */
+function ftp_mlsd($ftp_stream, string $directory): array
+{
+ error_clear_last();
+ $result = \ftp_mlsd($ftp_stream, $directory);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $directory The directory to be listed. This parameter can also include arguments, eg.
+ * ftp_nlist($conn_id, "-la /your/dir");
+ * Note that this parameter isn't escaped so there may be some issues with
+ * filenames containing spaces and other characters.
+ * @return array Returns an array of filenames from the specified directory on success.
+ * @throws FtpException
+ *
+ */
+function ftp_nlist($ftp_stream, string $directory): array
+{
+ error_clear_last();
+ $result = \ftp_nlist($ftp_stream, $directory);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ftp_pasv turns on or off passive mode. In
+ * passive mode, data connections are initiated by the client,
+ * rather than by the server.
+ * It may be needed if the client is behind firewall.
+ *
+ * Please note that ftp_pasv can only be called after a
+ * successful login or otherwise it will fail.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param bool $pasv If TRUE, the passive mode is turned on, else it's turned off.
+ * @throws FtpException
+ *
+ */
+function ftp_pasv($ftp_stream, bool $pasv): void
+{
+ error_clear_last();
+ $result = \ftp_pasv($ftp_stream, $pasv);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_put stores a local file on the FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $remote_file The remote file path.
+ * @param string $local_file The local file path.
+ * @param int $mode The transfer mode. Must be either FTP_ASCII or
+ * FTP_BINARY.
+ * @param int $startpos The position in the remote file to start uploading to.
+ * @throws FtpException
+ *
+ */
+function ftp_put($ftp_stream, string $remote_file, string $local_file, int $mode = FTP_BINARY, int $startpos = 0): void
+{
+ error_clear_last();
+ $result = \ftp_put($ftp_stream, $remote_file, $local_file, $mode, $startpos);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @return string Returns the current directory name.
+ * @throws FtpException
+ *
+ */
+function ftp_pwd($ftp_stream): string
+{
+ error_clear_last();
+ $result = \ftp_pwd($ftp_stream);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ftp_rename renames a file or a directory on the FTP
+ * server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $oldname The old file/directory name.
+ * @param string $newname The new name.
+ * @throws FtpException
+ *
+ */
+function ftp_rename($ftp_stream, string $oldname, string $newname): void
+{
+ error_clear_last();
+ $result = \ftp_rename($ftp_stream, $oldname, $newname);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Removes the specified directory on the FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $directory The directory to delete. This must be either an absolute or relative
+ * path to an empty directory.
+ * @throws FtpException
+ *
+ */
+function ftp_rmdir($ftp_stream, string $directory): void
+{
+ error_clear_last();
+ $result = \ftp_rmdir($ftp_stream, $directory);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_site sends the given SITE
+ * command to the FTP server.
+ *
+ * SITE commands are not standardized, and vary from server
+ * to server. They are useful for handling such things as file permissions and
+ * group membership.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @param string $command The SITE command. Note that this parameter isn't escaped so there may
+ * be some issues with filenames containing spaces and other characters.
+ * @throws FtpException
+ *
+ */
+function ftp_site($ftp_stream, string $command): void
+{
+ error_clear_last();
+ $result = \ftp_site($ftp_stream, $command);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ftp_ssl_connect opens an explicit SSL-FTP connection to the
+ * specified host. That implies that
+ * ftp_ssl_connect will succeed even if the server is not
+ * configured for SSL-FTP, or its certificate is invalid. Only when
+ * ftp_login is called, the client will send the
+ * appropriate AUTH FTP command, so ftp_login will fail in
+ * the mentioned cases.
+ *
+ * @param string $host The FTP server address. This parameter shouldn't have any trailing
+ * slashes and shouldn't be prefixed with ftp://.
+ * @param int $port This parameter specifies an alternate port to connect to. If it is
+ * omitted or set to zero, then the default FTP port, 21, will be used.
+ * @param int $timeout This parameter specifies the timeout for all subsequent network operations.
+ * If omitted, the default value is 90 seconds. The timeout can be changed and
+ * queried at any time with ftp_set_option and
+ * ftp_get_option.
+ * @return resource Returns a SSL-FTP stream on success.
+ * @throws FtpException
+ *
+ */
+function ftp_ssl_connect(string $host, int $port = 21, int $timeout = 90)
+{
+ error_clear_last();
+ $result = \ftp_ssl_connect($host, $port, $timeout);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the system type identifier of the remote FTP server.
+ *
+ * @param resource $ftp_stream The link identifier of the FTP connection.
+ * @return string Returns the remote system type.
+ * @throws FtpException
+ *
+ */
+function ftp_systype($ftp_stream): string
+{
+ error_clear_last();
+ $result = \ftp_systype($ftp_stream);
+ if ($result === false) {
+ throw FtpException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\FunchandException;
+
+/**
+ * Creates an anonymous function from the parameters passed, and
+ * returns a unique name for it.
+ *
+ * @param string $args The function arguments.
+ * @param string $code The function code.
+ * @return string Returns a unique function name as a string.
+ * @throws FunchandException
+ *
+ */
+function create_function(string $args, string $code): string
+{
+ error_clear_last();
+ $result = \create_function($args, $code);
+ if ($result === false) {
+ throw FunchandException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param callable(): void $function The function to register.
+ * @param mixed $params
+ * @throws FunchandException
+ *
+ */
+function register_tick_function(callable $function, ...$params): void
+{
+ error_clear_last();
+ if ($params !== []) {
+ $result = \register_tick_function($function, ...$params);
+ } else {
+ $result = \register_tick_function($function);
+ }
+ if ($result === false) {
+ throw FunchandException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+return [
+ 'apache_getenv',
+ 'apache_get_version',
+ 'apache_request_headers',
+ 'apache_reset_timeout',
+ 'apache_response_headers',
+ 'apache_setenv',
+ 'apcu_cache_info',
+ 'apcu_cas',
+ 'apcu_dec',
+ 'apcu_fetch',
+ 'apcu_inc',
+ 'apcu_sma_info',
+ 'apc_fetch',
+ 'array_combine',
+ 'array_flip',
+ 'array_replace',
+ 'array_replace_recursive',
+ 'array_walk_recursive',
+ 'arsort',
+ 'asort',
+ 'base64_decode',
+ 'bzclose',
+ 'bzflush',
+ 'bzread',
+ 'bzwrite',
+ 'chdir',
+ 'chgrp',
+ 'chmod',
+ 'chown',
+ 'chroot',
+ 'class_alias',
+ 'class_implements',
+ 'class_parents',
+ 'class_uses',
+ 'cli_set_process_title',
+ 'closelog',
+ 'com_event_sink',
+ 'com_load_typelib',
+ 'com_print_typeinfo',
+ 'convert_uudecode',
+ 'convert_uuencode',
+ 'copy',
+ 'create_function',
+ 'cubrid_free_result',
+ 'cubrid_get_charset',
+ 'cubrid_get_client_info',
+ 'cubrid_get_db_parameter',
+ 'cubrid_get_server_info',
+ 'cubrid_insert_id',
+ 'cubrid_lob2_new',
+ 'cubrid_lob2_size',
+ 'cubrid_lob2_size64',
+ 'cubrid_lob2_tell',
+ 'cubrid_lob2_tell64',
+ 'cubrid_set_db_parameter',
+ 'curl_escape',
+ 'curl_exec',
+ 'curl_getinfo',
+ 'curl_init',
+ 'curl_multi_errno',
+ 'curl_multi_info_read',
+ 'curl_multi_init',
+ 'curl_setopt',
+ 'curl_share_errno',
+ 'curl_share_setopt',
+ 'curl_unescape',
+ 'date',
+ 'date_parse',
+ 'date_parse_from_format',
+ 'date_sunrise',
+ 'date_sunset',
+ 'date_sun_info',
+ 'db2_autocommit',
+ 'db2_bind_param',
+ 'db2_client_info',
+ 'db2_close',
+ 'db2_commit',
+ 'db2_execute',
+ 'db2_free_result',
+ 'db2_free_stmt',
+ 'db2_get_option',
+ 'db2_pclose',
+ 'db2_rollback',
+ 'db2_server_info',
+ 'db2_set_option',
+ 'define',
+ 'deflate_add',
+ 'deflate_init',
+ 'disk_free_space',
+ 'disk_total_space',
+ 'dl',
+ 'dns_get_record',
+ 'eio_busy',
+ 'eio_chmod',
+ 'eio_chown',
+ 'eio_close',
+ 'eio_custom',
+ 'eio_dup2',
+ 'eio_event_loop',
+ 'eio_fallocate',
+ 'eio_fchmod',
+ 'eio_fdatasync',
+ 'eio_fstat',
+ 'eio_fstatvfs',
+ 'eio_fsync',
+ 'eio_ftruncate',
+ 'eio_futime',
+ 'eio_grp',
+ 'eio_lstat',
+ 'eio_mkdir',
+ 'eio_mknod',
+ 'eio_nop',
+ 'eio_readahead',
+ 'eio_readdir',
+ 'eio_readlink',
+ 'eio_rename',
+ 'eio_rmdir',
+ 'eio_seek',
+ 'eio_sendfile',
+ 'eio_stat',
+ 'eio_statvfs',
+ 'eio_symlink',
+ 'eio_sync',
+ 'eio_syncfs',
+ 'eio_sync_file_range',
+ 'eio_truncate',
+ 'eio_unlink',
+ 'eio_utime',
+ 'eio_write',
+ 'error_log',
+ 'fastcgi_finish_request',
+ 'fbird_blob_cancel',
+ 'fclose',
+ 'fflush',
+ 'file',
+ 'fileatime',
+ 'filectime',
+ 'fileinode',
+ 'filemtime',
+ 'fileowner',
+ 'filesize',
+ 'file_get_contents',
+ 'file_put_contents',
+ 'filter_input_array',
+ 'filter_var_array',
+ 'finfo_close',
+ 'finfo_open',
+ 'flock',
+ 'fopen',
+ 'fputcsv',
+ 'fread',
+ 'fsockopen',
+ 'ftp_alloc',
+ 'ftp_append',
+ 'ftp_cdup',
+ 'ftp_chdir',
+ 'ftp_chmod',
+ 'ftp_close',
+ 'ftp_connect',
+ 'ftp_delete',
+ 'ftp_fget',
+ 'ftp_fput',
+ 'ftp_get',
+ 'ftp_login',
+ 'ftp_mkdir',
+ 'ftp_mlsd',
+ 'ftp_nlist',
+ 'ftp_pasv',
+ 'ftp_put',
+ 'ftp_pwd',
+ 'ftp_rename',
+ 'ftp_rmdir',
+ 'ftp_site',
+ 'ftp_ssl_connect',
+ 'ftp_systype',
+ 'ftruncate',
+ 'fwrite',
+ 'getallheaders',
+ 'getcwd',
+ 'gethostname',
+ 'getimagesize',
+ 'getlastmod',
+ 'getmygid',
+ 'getmyinode',
+ 'getmypid',
+ 'getmyuid',
+ 'getopt',
+ 'getprotobyname',
+ 'getprotobynumber',
+ 'get_headers',
+ 'glob',
+ 'gmdate',
+ 'gmp_binomial',
+ 'gmp_export',
+ 'gmp_import',
+ 'gmp_random_seed',
+ 'gnupg_adddecryptkey',
+ 'gnupg_addencryptkey',
+ 'gnupg_addsignkey',
+ 'gnupg_cleardecryptkeys',
+ 'gnupg_clearencryptkeys',
+ 'gnupg_clearsignkeys',
+ 'gnupg_setarmor',
+ 'gnupg_setsignmode',
+ 'gzclose',
+ 'gzcompress',
+ 'gzdecode',
+ 'gzdeflate',
+ 'gzencode',
+ 'gzgets',
+ 'gzgetss',
+ 'gzinflate',
+ 'gzpassthru',
+ 'gzrewind',
+ 'gzuncompress',
+ 'hash_hkdf',
+ 'hash_update_file',
+ 'header_register_callback',
+ 'hex2bin',
+ 'highlight_file',
+ 'highlight_string',
+ 'ibase_add_user',
+ 'ibase_backup',
+ 'ibase_blob_cancel',
+ 'ibase_blob_create',
+ 'ibase_blob_get',
+ 'ibase_close',
+ 'ibase_commit',
+ 'ibase_commit_ret',
+ 'ibase_connect',
+ 'ibase_delete_user',
+ 'ibase_drop_db',
+ 'ibase_free_event_handler',
+ 'ibase_free_query',
+ 'ibase_free_result',
+ 'ibase_maintain_db',
+ 'ibase_modify_user',
+ 'ibase_name_result',
+ 'ibase_pconnect',
+ 'ibase_restore',
+ 'ibase_rollback',
+ 'ibase_rollback_ret',
+ 'ibase_service_attach',
+ 'ibase_service_detach',
+ 'iconv',
+ 'iconv_get_encoding',
+ 'iconv_set_encoding',
+ 'image2wbmp',
+ 'imageaffine',
+ 'imageaffinematrixconcat',
+ 'imageaffinematrixget',
+ 'imagealphablending',
+ 'imageantialias',
+ 'imagearc',
+ 'imagebmp',
+ 'imagechar',
+ 'imagecharup',
+ 'imagecolorat',
+ 'imagecolordeallocate',
+ 'imagecolormatch',
+ 'imageconvolution',
+ 'imagecopy',
+ 'imagecopymerge',
+ 'imagecopymergegray',
+ 'imagecopyresampled',
+ 'imagecopyresized',
+ 'imagecreate',
+ 'imagecreatefrombmp',
+ 'imagecreatefromgd',
+ 'imagecreatefromgd2',
+ 'imagecreatefromgd2part',
+ 'imagecreatefromgif',
+ 'imagecreatefromjpeg',
+ 'imagecreatefrompng',
+ 'imagecreatefromwbmp',
+ 'imagecreatefromwebp',
+ 'imagecreatefromxbm',
+ 'imagecreatefromxpm',
+ 'imagecreatetruecolor',
+ 'imagecrop',
+ 'imagecropauto',
+ 'imagedashedline',
+ 'imagedestroy',
+ 'imageellipse',
+ 'imagefill',
+ 'imagefilledarc',
+ 'imagefilledellipse',
+ 'imagefilledpolygon',
+ 'imagefilledrectangle',
+ 'imagefilltoborder',
+ 'imagefilter',
+ 'imageflip',
+ 'imagegammacorrect',
+ 'imagegd',
+ 'imagegd2',
+ 'imagegif',
+ 'imagegrabscreen',
+ 'imagegrabwindow',
+ 'imagejpeg',
+ 'imagelayereffect',
+ 'imageline',
+ 'imageloadfont',
+ 'imageopenpolygon',
+ 'imagepng',
+ 'imagepolygon',
+ 'imagerectangle',
+ 'imagerotate',
+ 'imagesavealpha',
+ 'imagescale',
+ 'imagesetbrush',
+ 'imagesetclip',
+ 'imagesetinterpolation',
+ 'imagesetpixel',
+ 'imagesetstyle',
+ 'imagesetthickness',
+ 'imagesettile',
+ 'imagestring',
+ 'imagestringup',
+ 'imagesx',
+ 'imagesy',
+ 'imagetruecolortopalette',
+ 'imagettfbbox',
+ 'imagettftext',
+ 'imagewbmp',
+ 'imagewebp',
+ 'imagexbm',
+ 'imap_append',
+ 'imap_check',
+ 'imap_clearflag_full',
+ 'imap_close',
+ 'imap_createmailbox',
+ 'imap_deletemailbox',
+ 'imap_fetchstructure',
+ 'imap_gc',
+ 'imap_headerinfo',
+ 'imap_mail',
+ 'imap_mailboxmsginfo',
+ 'imap_mail_compose',
+ 'imap_mail_copy',
+ 'imap_mail_move',
+ 'imap_mutf7_to_utf8',
+ 'imap_num_msg',
+ 'imap_open',
+ 'imap_renamemailbox',
+ 'imap_savebody',
+ 'imap_setacl',
+ 'imap_setflag_full',
+ 'imap_set_quota',
+ 'imap_sort',
+ 'imap_subscribe',
+ 'imap_thread',
+ 'imap_timeout',
+ 'imap_undelete',
+ 'imap_unsubscribe',
+ 'imap_utf8_to_mutf7',
+ 'inet_ntop',
+ 'inflate_add',
+ 'inflate_get_read_len',
+ 'inflate_get_status',
+ 'inflate_init',
+ 'ingres_autocommit',
+ 'ingres_close',
+ 'ingres_commit',
+ 'ingres_connect',
+ 'ingres_execute',
+ 'ingres_field_name',
+ 'ingres_field_type',
+ 'ingres_free_result',
+ 'ingres_pconnect',
+ 'ingres_result_seek',
+ 'ingres_rollback',
+ 'ingres_set_environment',
+ 'ini_get',
+ 'ini_set',
+ 'inotify_init',
+ 'inotify_rm_watch',
+ 'iptcembed',
+ 'iptcparse',
+ 'jdtounix',
+ 'jpeg2wbmp',
+ 'json_decode',
+ 'json_encode',
+ 'json_last_error_msg',
+ 'krsort',
+ 'ksort',
+ 'lchgrp',
+ 'lchown',
+ 'ldap_add',
+ 'ldap_add_ext',
+ 'ldap_bind',
+ 'ldap_bind_ext',
+ 'ldap_control_paged_result',
+ 'ldap_control_paged_result_response',
+ 'ldap_count_entries',
+ 'ldap_delete',
+ 'ldap_delete_ext',
+ 'ldap_exop',
+ 'ldap_exop_passwd',
+ 'ldap_exop_whoami',
+ 'ldap_explode_dn',
+ 'ldap_first_attribute',
+ 'ldap_first_entry',
+ 'ldap_free_result',
+ 'ldap_get_attributes',
+ 'ldap_get_dn',
+ 'ldap_get_entries',
+ 'ldap_get_option',
+ 'ldap_get_values',
+ 'ldap_get_values_len',
+ 'ldap_list',
+ 'ldap_modify_batch',
+ 'ldap_mod_add',
+ 'ldap_mod_add_ext',
+ 'ldap_mod_del',
+ 'ldap_mod_del_ext',
+ 'ldap_mod_replace',
+ 'ldap_mod_replace_ext',
+ 'ldap_next_attribute',
+ 'ldap_parse_exop',
+ 'ldap_parse_result',
+ 'ldap_read',
+ 'ldap_rename',
+ 'ldap_rename_ext',
+ 'ldap_sasl_bind',
+ 'ldap_search',
+ 'ldap_set_option',
+ 'ldap_unbind',
+ 'libxml_get_last_error',
+ 'libxml_set_external_entity_loader',
+ 'link',
+ 'lzf_compress',
+ 'lzf_decompress',
+ 'mailparse_msg_extract_part_file',
+ 'mailparse_msg_free',
+ 'mailparse_msg_parse',
+ 'mailparse_msg_parse_file',
+ 'mailparse_stream_encode',
+ 'mb_chr',
+ 'mb_detect_order',
+ 'mb_encoding_aliases',
+ 'mb_eregi_replace',
+ 'mb_ereg_replace',
+ 'mb_ereg_replace_callback',
+ 'mb_ereg_search_getregs',
+ 'mb_ereg_search_init',
+ 'mb_ereg_search_regs',
+ 'mb_ereg_search_setpos',
+ 'mb_http_output',
+ 'mb_internal_encoding',
+ 'mb_ord',
+ 'mb_parse_str',
+ 'mb_regex_encoding',
+ 'mb_send_mail',
+ 'mb_split',
+ 'mb_str_split',
+ 'md5_file',
+ 'metaphone',
+ 'mime_content_type',
+ 'mkdir',
+ 'mktime',
+ 'msg_queue_exists',
+ 'msg_receive',
+ 'msg_remove_queue',
+ 'msg_send',
+ 'msg_set_queue',
+ 'msql_affected_rows',
+ 'msql_close',
+ 'msql_connect',
+ 'msql_create_db',
+ 'msql_data_seek',
+ 'msql_db_query',
+ 'msql_drop_db',
+ 'msql_field_len',
+ 'msql_field_name',
+ 'msql_field_seek',
+ 'msql_field_table',
+ 'msql_field_type',
+ 'msql_free_result',
+ 'msql_pconnect',
+ 'msql_query',
+ 'msql_select_db',
+ 'mysqli_get_cache_stats',
+ 'mysqli_get_client_stats',
+ 'mysqlnd_ms_dump_servers',
+ 'mysqlnd_ms_fabric_select_global',
+ 'mysqlnd_ms_fabric_select_shard',
+ 'mysqlnd_ms_get_last_used_connection',
+ 'mysqlnd_qc_clear_cache',
+ 'mysqlnd_qc_set_is_select',
+ 'mysqlnd_qc_set_storage_handler',
+ 'mysql_close',
+ 'mysql_connect',
+ 'mysql_create_db',
+ 'mysql_data_seek',
+ 'mysql_db_name',
+ 'mysql_db_query',
+ 'mysql_drop_db',
+ 'mysql_fetch_lengths',
+ 'mysql_field_flags',
+ 'mysql_field_len',
+ 'mysql_field_name',
+ 'mysql_field_seek',
+ 'mysql_free_result',
+ 'mysql_get_host_info',
+ 'mysql_get_proto_info',
+ 'mysql_get_server_info',
+ 'mysql_info',
+ 'mysql_list_dbs',
+ 'mysql_list_fields',
+ 'mysql_list_processes',
+ 'mysql_list_tables',
+ 'mysql_num_fields',
+ 'mysql_num_rows',
+ 'mysql_query',
+ 'mysql_real_escape_string',
+ 'mysql_result',
+ 'mysql_select_db',
+ 'mysql_set_charset',
+ 'mysql_tablename',
+ 'mysql_thread_id',
+ 'mysql_unbuffered_query',
+ 'natcasesort',
+ 'natsort',
+ 'ob_end_clean',
+ 'ob_end_flush',
+ 'oci_bind_array_by_name',
+ 'oci_bind_by_name',
+ 'oci_cancel',
+ 'oci_close',
+ 'oci_commit',
+ 'oci_connect',
+ 'oci_define_by_name',
+ 'oci_execute',
+ 'oci_fetch_all',
+ 'oci_field_name',
+ 'oci_field_precision',
+ 'oci_field_scale',
+ 'oci_field_size',
+ 'oci_field_type',
+ 'oci_field_type_raw',
+ 'oci_free_descriptor',
+ 'oci_free_statement',
+ 'oci_new_collection',
+ 'oci_new_connect',
+ 'oci_new_cursor',
+ 'oci_new_descriptor',
+ 'oci_num_fields',
+ 'oci_num_rows',
+ 'oci_parse',
+ 'oci_pconnect',
+ 'oci_result',
+ 'oci_rollback',
+ 'oci_server_version',
+ 'oci_set_action',
+ 'oci_set_call_timeout',
+ 'oci_set_client_identifier',
+ 'oci_set_client_info',
+ 'oci_set_db_operation',
+ 'oci_set_edition',
+ 'oci_set_module_name',
+ 'oci_set_prefetch',
+ 'oci_statement_type',
+ 'oci_unregister_taf_callback',
+ 'odbc_autocommit',
+ 'odbc_binmode',
+ 'odbc_columnprivileges',
+ 'odbc_columns',
+ 'odbc_commit',
+ 'odbc_data_source',
+ 'odbc_exec',
+ 'odbc_execute',
+ 'odbc_fetch_into',
+ 'odbc_field_len',
+ 'odbc_field_name',
+ 'odbc_field_num',
+ 'odbc_field_scale',
+ 'odbc_field_type',
+ 'odbc_foreignkeys',
+ 'odbc_gettypeinfo',
+ 'odbc_longreadlen',
+ 'odbc_prepare',
+ 'odbc_primarykeys',
+ 'odbc_result',
+ 'odbc_result_all',
+ 'odbc_rollback',
+ 'odbc_setoption',
+ 'odbc_specialcolumns',
+ 'odbc_statistics',
+ 'odbc_tableprivileges',
+ 'odbc_tables',
+ 'opcache_compile_file',
+ 'opcache_get_status',
+ 'opendir',
+ 'openlog',
+ 'openssl_cipher_iv_length',
+ 'openssl_csr_export',
+ 'openssl_csr_export_to_file',
+ 'openssl_csr_get_subject',
+ 'openssl_csr_new',
+ 'openssl_csr_sign',
+ 'openssl_decrypt',
+ 'openssl_dh_compute_key',
+ 'openssl_digest',
+ 'openssl_encrypt',
+ 'openssl_open',
+ 'openssl_pbkdf2',
+ 'openssl_pkcs7_decrypt',
+ 'openssl_pkcs7_encrypt',
+ 'openssl_pkcs7_read',
+ 'openssl_pkcs7_sign',
+ 'openssl_pkcs12_export',
+ 'openssl_pkcs12_export_to_file',
+ 'openssl_pkcs12_read',
+ 'openssl_pkey_export',
+ 'openssl_pkey_export_to_file',
+ 'openssl_pkey_get_private',
+ 'openssl_pkey_get_public',
+ 'openssl_pkey_new',
+ 'openssl_private_decrypt',
+ 'openssl_private_encrypt',
+ 'openssl_public_decrypt',
+ 'openssl_public_encrypt',
+ 'openssl_random_pseudo_bytes',
+ 'openssl_seal',
+ 'openssl_sign',
+ 'openssl_x509_export',
+ 'openssl_x509_export_to_file',
+ 'openssl_x509_fingerprint',
+ 'openssl_x509_read',
+ 'output_add_rewrite_var',
+ 'output_reset_rewrite_vars',
+ 'pack',
+ 'parse_ini_file',
+ 'parse_ini_string',
+ 'parse_url',
+ 'password_hash',
+ 'pcntl_exec',
+ 'pcntl_getpriority',
+ 'pcntl_setpriority',
+ 'pcntl_signal_dispatch',
+ 'pcntl_sigprocmask',
+ 'pcntl_strerror',
+ 'PDF_activate_item',
+ 'PDF_add_locallink',
+ 'PDF_add_nameddest',
+ 'PDF_add_note',
+ 'PDF_add_pdflink',
+ 'PDF_add_thumbnail',
+ 'PDF_add_weblink',
+ 'PDF_attach_file',
+ 'PDF_begin_layer',
+ 'PDF_begin_page',
+ 'PDF_begin_page_ext',
+ 'PDF_circle',
+ 'PDF_clip',
+ 'PDF_close',
+ 'PDF_closepath',
+ 'PDF_closepath_fill_stroke',
+ 'PDF_closepath_stroke',
+ 'PDF_close_pdi',
+ 'PDF_close_pdi_page',
+ 'PDF_concat',
+ 'PDF_continue_text',
+ 'PDF_curveto',
+ 'PDF_delete',
+ 'PDF_end_layer',
+ 'PDF_end_page',
+ 'PDF_end_page_ext',
+ 'PDF_end_pattern',
+ 'PDF_end_template',
+ 'PDF_fill',
+ 'PDF_fill_stroke',
+ 'PDF_fit_image',
+ 'PDF_fit_pdi_page',
+ 'PDF_fit_textline',
+ 'PDF_initgraphics',
+ 'PDF_lineto',
+ 'PDF_makespotcolor',
+ 'PDF_moveto',
+ 'PDF_open_file',
+ 'PDF_place_image',
+ 'PDF_place_pdi_page',
+ 'PDF_rect',
+ 'PDF_restore',
+ 'PDF_rotate',
+ 'PDF_save',
+ 'PDF_scale',
+ 'PDF_setcolor',
+ 'PDF_setdash',
+ 'PDF_setdashpattern',
+ 'PDF_setflat',
+ 'PDF_setfont',
+ 'PDF_setgray',
+ 'PDF_setgray_fill',
+ 'PDF_setgray_stroke',
+ 'PDF_setlinejoin',
+ 'PDF_setlinewidth',
+ 'PDF_setmatrix',
+ 'PDF_setmiterlimit',
+ 'PDF_setrgbcolor',
+ 'PDF_setrgbcolor_fill',
+ 'PDF_setrgbcolor_stroke',
+ 'PDF_set_border_color',
+ 'PDF_set_border_dash',
+ 'PDF_set_border_style',
+ 'PDF_set_info',
+ 'PDF_set_layer_dependency',
+ 'PDF_set_parameter',
+ 'PDF_set_text_pos',
+ 'PDF_set_value',
+ 'PDF_show',
+ 'PDF_show_xy',
+ 'PDF_skew',
+ 'PDF_stroke',
+ 'pg_cancel_query',
+ 'pg_client_encoding',
+ 'pg_close',
+ 'pg_connect',
+ 'pg_connection_reset',
+ 'pg_convert',
+ 'pg_copy_from',
+ 'pg_copy_to',
+ 'pg_dbname',
+ 'pg_delete',
+ 'pg_end_copy',
+ 'pg_execute',
+ 'pg_field_name',
+ 'pg_field_table',
+ 'pg_field_type',
+ 'pg_flush',
+ 'pg_free_result',
+ 'pg_host',
+ 'pg_insert',
+ 'pg_last_error',
+ 'pg_last_notice',
+ 'pg_last_oid',
+ 'pg_lo_close',
+ 'pg_lo_export',
+ 'pg_lo_import',
+ 'pg_lo_open',
+ 'pg_lo_read',
+ 'pg_lo_read_all',
+ 'pg_lo_seek',
+ 'pg_lo_truncate',
+ 'pg_lo_unlink',
+ 'pg_lo_write',
+ 'pg_meta_data',
+ 'pg_options',
+ 'pg_parameter_status',
+ 'pg_pconnect',
+ 'pg_ping',
+ 'pg_port',
+ 'pg_prepare',
+ 'pg_put_line',
+ 'pg_query',
+ 'pg_query_params',
+ 'pg_result_error_field',
+ 'pg_result_seek',
+ 'pg_select',
+ 'pg_send_execute',
+ 'pg_send_prepare',
+ 'pg_send_query',
+ 'pg_send_query_params',
+ 'pg_socket',
+ 'pg_trace',
+ 'pg_tty',
+ 'pg_update',
+ 'pg_version',
+ 'phpcredits',
+ 'phpinfo',
+ 'png2wbmp',
+ 'posix_access',
+ 'posix_getgrnam',
+ 'posix_getpgid',
+ 'posix_initgroups',
+ 'posix_kill',
+ 'posix_mkfifo',
+ 'posix_mknod',
+ 'posix_setegid',
+ 'posix_seteuid',
+ 'posix_setgid',
+ 'posix_setpgid',
+ 'posix_setrlimit',
+ 'posix_setuid',
+ 'preg_match',
+ 'preg_match_all',
+ 'preg_replace',
+ 'preg_split',
+ 'proc_get_status',
+ 'proc_nice',
+ 'pspell_add_to_personal',
+ 'pspell_add_to_session',
+ 'pspell_clear_session',
+ 'pspell_config_create',
+ 'pspell_config_data_dir',
+ 'pspell_config_dict_dir',
+ 'pspell_config_ignore',
+ 'pspell_config_mode',
+ 'pspell_config_personal',
+ 'pspell_config_repl',
+ 'pspell_config_runtogether',
+ 'pspell_config_save_repl',
+ 'pspell_new',
+ 'pspell_new_config',
+ 'pspell_save_wordlist',
+ 'pspell_store_replacement',
+ 'ps_add_launchlink',
+ 'ps_add_locallink',
+ 'ps_add_note',
+ 'ps_add_pdflink',
+ 'ps_add_weblink',
+ 'ps_arc',
+ 'ps_arcn',
+ 'ps_begin_page',
+ 'ps_begin_pattern',
+ 'ps_begin_template',
+ 'ps_circle',
+ 'ps_clip',
+ 'ps_close',
+ 'ps_closepath',
+ 'ps_closepath_stroke',
+ 'ps_close_image',
+ 'ps_continue_text',
+ 'ps_curveto',
+ 'ps_delete',
+ 'ps_end_page',
+ 'ps_end_pattern',
+ 'ps_end_template',
+ 'ps_fill',
+ 'ps_fill_stroke',
+ 'ps_get_parameter',
+ 'ps_hyphenate',
+ 'ps_include_file',
+ 'ps_lineto',
+ 'ps_moveto',
+ 'ps_new',
+ 'ps_open_file',
+ 'ps_place_image',
+ 'ps_rect',
+ 'ps_restore',
+ 'ps_rotate',
+ 'ps_save',
+ 'ps_scale',
+ 'ps_setcolor',
+ 'ps_setdash',
+ 'ps_setflat',
+ 'ps_setfont',
+ 'ps_setgray',
+ 'ps_setlinecap',
+ 'ps_setlinejoin',
+ 'ps_setlinewidth',
+ 'ps_setmiterlimit',
+ 'ps_setoverprintmode',
+ 'ps_setpolydash',
+ 'ps_set_border_color',
+ 'ps_set_border_dash',
+ 'ps_set_border_style',
+ 'ps_set_info',
+ 'ps_set_parameter',
+ 'ps_set_text_pos',
+ 'ps_set_value',
+ 'ps_shading',
+ 'ps_shading_pattern',
+ 'ps_shfill',
+ 'ps_show',
+ 'ps_show2',
+ 'ps_show_xy',
+ 'ps_show_xy2',
+ 'ps_stroke',
+ 'ps_symbol',
+ 'ps_translate',
+ 'putenv',
+ 'readfile',
+ 'readgzfile',
+ 'readline_add_history',
+ 'readline_callback_handler_install',
+ 'readline_clear_history',
+ 'readline_completion_function',
+ 'readline_read_history',
+ 'readline_write_history',
+ 'readlink',
+ 'realpath',
+ 'register_tick_function',
+ 'rename',
+ 'rewind',
+ 'rewinddir',
+ 'rmdir',
+ 'rpmaddtag',
+ 'rrd_create',
+ 'rsort',
+ 'sapi_windows_cp_conv',
+ 'sapi_windows_cp_set',
+ 'sapi_windows_generate_ctrl_event',
+ 'sapi_windows_vt100_support',
+ 'scandir',
+ 'sem_acquire',
+ 'sem_get',
+ 'sem_release',
+ 'sem_remove',
+ 'session_abort',
+ 'session_decode',
+ 'session_destroy',
+ 'session_regenerate_id',
+ 'session_reset',
+ 'session_unset',
+ 'session_write_close',
+ 'settype',
+ 'set_include_path',
+ 'set_time_limit',
+ 'sha1_file',
+ 'shmop_delete',
+ 'shmop_read',
+ 'shmop_write',
+ 'shm_put_var',
+ 'shm_remove',
+ 'shm_remove_var',
+ 'shuffle',
+ 'simplexml_import_dom',
+ 'simplexml_load_file',
+ 'simplexml_load_string',
+ 'sleep',
+ 'socket_accept',
+ 'socket_addrinfo_bind',
+ 'socket_addrinfo_connect',
+ 'socket_bind',
+ 'socket_connect',
+ 'socket_create',
+ 'socket_create_listen',
+ 'socket_create_pair',
+ 'socket_export_stream',
+ 'socket_getpeername',
+ 'socket_getsockname',
+ 'socket_get_option',
+ 'socket_import_stream',
+ 'socket_listen',
+ 'socket_read',
+ 'socket_send',
+ 'socket_sendmsg',
+ 'socket_sendto',
+ 'socket_set_block',
+ 'socket_set_nonblock',
+ 'socket_set_option',
+ 'socket_shutdown',
+ 'socket_write',
+ 'socket_wsaprotocol_info_export',
+ 'socket_wsaprotocol_info_import',
+ 'socket_wsaprotocol_info_release',
+ 'sodium_crypto_pwhash',
+ 'sodium_crypto_pwhash_str',
+ 'solr_get_version',
+ 'sort',
+ 'soundex',
+ 'spl_autoload_register',
+ 'spl_autoload_unregister',
+ 'sprintf',
+ 'sqlsrv_begin_transaction',
+ 'sqlsrv_cancel',
+ 'sqlsrv_client_info',
+ 'sqlsrv_close',
+ 'sqlsrv_commit',
+ 'sqlsrv_configure',
+ 'sqlsrv_execute',
+ 'sqlsrv_free_stmt',
+ 'sqlsrv_get_field',
+ 'sqlsrv_next_result',
+ 'sqlsrv_num_fields',
+ 'sqlsrv_num_rows',
+ 'sqlsrv_prepare',
+ 'sqlsrv_query',
+ 'sqlsrv_rollback',
+ 'ssdeep_fuzzy_compare',
+ 'ssdeep_fuzzy_hash',
+ 'ssdeep_fuzzy_hash_filename',
+ 'ssh2_auth_agent',
+ 'ssh2_auth_hostbased_file',
+ 'ssh2_auth_password',
+ 'ssh2_auth_pubkey_file',
+ 'ssh2_connect',
+ 'ssh2_disconnect',
+ 'ssh2_exec',
+ 'ssh2_publickey_add',
+ 'ssh2_publickey_init',
+ 'ssh2_publickey_remove',
+ 'ssh2_scp_recv',
+ 'ssh2_scp_send',
+ 'ssh2_sftp',
+ 'ssh2_sftp_chmod',
+ 'ssh2_sftp_mkdir',
+ 'ssh2_sftp_rename',
+ 'ssh2_sftp_rmdir',
+ 'ssh2_sftp_symlink',
+ 'ssh2_sftp_unlink',
+ 'stream_context_set_params',
+ 'stream_copy_to_stream',
+ 'stream_filter_append',
+ 'stream_filter_prepend',
+ 'stream_filter_register',
+ 'stream_filter_remove',
+ 'stream_get_contents',
+ 'stream_isatty',
+ 'stream_resolve_include_path',
+ 'stream_set_blocking',
+ 'stream_set_timeout',
+ 'stream_socket_accept',
+ 'stream_socket_client',
+ 'stream_socket_pair',
+ 'stream_socket_server',
+ 'stream_socket_shutdown',
+ 'stream_supports_lock',
+ 'stream_wrapper_register',
+ 'stream_wrapper_restore',
+ 'stream_wrapper_unregister',
+ 'strptime',
+ 'strtotime',
+ 'substr',
+ 'swoole_async_write',
+ 'swoole_async_writefile',
+ 'swoole_event_defer',
+ 'swoole_event_del',
+ 'swoole_event_write',
+ 'symlink',
+ 'syslog',
+ 'system',
+ 'tempnam',
+ 'timezone_name_from_abbr',
+ 'time_nanosleep',
+ 'time_sleep_until',
+ 'tmpfile',
+ 'touch',
+ 'uasort',
+ 'uksort',
+ 'unlink',
+ 'unpack',
+ 'uopz_extend',
+ 'uopz_implement',
+ 'usort',
+ 'virtual',
+ 'vsprintf',
+ 'xdiff_file_bdiff',
+ 'xdiff_file_bpatch',
+ 'xdiff_file_diff',
+ 'xdiff_file_diff_binary',
+ 'xdiff_file_patch_binary',
+ 'xdiff_file_rabdiff',
+ 'xdiff_string_bpatch',
+ 'xdiff_string_patch',
+ 'xdiff_string_patch_binary',
+ 'xmlrpc_set_type',
+ 'xml_parser_create',
+ 'xml_parser_create_ns',
+ 'xml_set_object',
+ 'yaml_parse',
+ 'yaml_parse_file',
+ 'yaml_parse_url',
+ 'yaz_ccl_parse',
+ 'yaz_close',
+ 'yaz_connect',
+ 'yaz_database',
+ 'yaz_element',
+ 'yaz_present',
+ 'yaz_search',
+ 'yaz_wait',
+ 'zip_entry_close',
+ 'zip_entry_open',
+ 'zip_entry_read',
+ 'zlib_decode',
+];
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\GmpException;
+
+/**
+ * Calculates the binomial coefficient C(n, k).
+ *
+ * @param \GMP|string|int $n Either a GMP number resource in PHP 5.5 and earlier, a GMP object in PHP 5.6 and later, or a numeric string provided that it is possible to convert the latter to a number.
+ * @param int $k
+ * @return \GMP Returns the binomial coefficient C(n, k).
+ * @throws GmpException
+ *
+ */
+function gmp_binomial($n, int $k): \GMP
+{
+ error_clear_last();
+ $result = \gmp_binomial($n, $k);
+ if ($result === false) {
+ throw GmpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Export a GMP number to a binary string
+ *
+ * @param \GMP|string|int $gmpnumber The GMP number being exported
+ * @param int $word_size Default value is 1. The number of bytes in each chunk of binary data. This is mainly used in conjunction with the options parameter.
+ * @param int $options Default value is GMP_MSW_FIRST | GMP_NATIVE_ENDIAN.
+ * @return string Returns a string.
+ * @throws GmpException
+ *
+ */
+function gmp_export($gmpnumber, int $word_size = 1, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN): string
+{
+ error_clear_last();
+ $result = \gmp_export($gmpnumber, $word_size, $options);
+ if ($result === false) {
+ throw GmpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Import a GMP number from a binary string
+ *
+ * @param string $data The binary string being imported
+ * @param int $word_size Default value is 1. The number of bytes in each chunk of binary data. This is mainly used in conjunction with the options parameter.
+ * @param int $options Default value is GMP_MSW_FIRST | GMP_NATIVE_ENDIAN.
+ * @return \GMP Returns a GMP number.
+ * @throws GmpException
+ *
+ */
+function gmp_import(string $data, int $word_size = 1, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN): \GMP
+{
+ error_clear_last();
+ $result = \gmp_import($data, $word_size, $options);
+ if ($result === false) {
+ throw GmpException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param \GMP|string|int $seed The seed to be set for the gmp_random,
+ * gmp_random_bits, and
+ * gmp_random_range functions.
+ *
+ * Either a GMP number resource in PHP 5.5 and earlier, a GMP object in PHP 5.6 and later, or a numeric string provided that it is possible to convert the latter to a number.
+ * @throws GmpException
+ *
+ */
+function gmp_random_seed($seed): void
+{
+ error_clear_last();
+ $result = \gmp_random_seed($seed);
+ if ($result === false) {
+ throw GmpException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\GnupgException;
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @param string $fingerprint The fingerprint key.
+ * @param string $passphrase The pass phrase.
+ * @throws GnupgException
+ *
+ */
+function gnupg_adddecryptkey($identifier, string $fingerprint, string $passphrase): void
+{
+ error_clear_last();
+ $result = \gnupg_adddecryptkey($identifier, $fingerprint, $passphrase);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @param string $fingerprint The fingerprint key.
+ * @throws GnupgException
+ *
+ */
+function gnupg_addencryptkey($identifier, string $fingerprint): void
+{
+ error_clear_last();
+ $result = \gnupg_addencryptkey($identifier, $fingerprint);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @param string $fingerprint The fingerprint key.
+ * @param string $passphrase The pass phrase.
+ * @throws GnupgException
+ *
+ */
+function gnupg_addsignkey($identifier, string $fingerprint, string $passphrase = null): void
+{
+ error_clear_last();
+ if ($passphrase !== null) {
+ $result = \gnupg_addsignkey($identifier, $fingerprint, $passphrase);
+ } else {
+ $result = \gnupg_addsignkey($identifier, $fingerprint);
+ }
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @throws GnupgException
+ *
+ */
+function gnupg_cleardecryptkeys($identifier): void
+{
+ error_clear_last();
+ $result = \gnupg_cleardecryptkeys($identifier);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @throws GnupgException
+ *
+ */
+function gnupg_clearencryptkeys($identifier): void
+{
+ error_clear_last();
+ $result = \gnupg_clearencryptkeys($identifier);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @throws GnupgException
+ *
+ */
+function gnupg_clearsignkeys($identifier): void
+{
+ error_clear_last();
+ $result = \gnupg_clearsignkeys($identifier);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Toggle the armored output.
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @param int $armor Pass a non-zero integer-value to this function to enable armored-output
+ * (default).
+ * Pass 0 to disable armored output.
+ * @throws GnupgException
+ *
+ */
+function gnupg_setarmor($identifier, int $armor): void
+{
+ error_clear_last();
+ $result = \gnupg_setarmor($identifier, $armor);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the mode for signing.
+ *
+ * @param resource $identifier The gnupg identifier, from a call to
+ * gnupg_init or gnupg.
+ * @param int $signmode The mode for signing.
+ *
+ * signmode takes a constant indicating what type of
+ * signature should be produced. The possible values are
+ * GNUPG_SIG_MODE_NORMAL,
+ * GNUPG_SIG_MODE_DETACH and
+ * GNUPG_SIG_MODE_CLEAR.
+ * By default GNUPG_SIG_MODE_CLEAR is used.
+ * @throws GnupgException
+ *
+ */
+function gnupg_setsignmode($identifier, int $signmode): void
+{
+ error_clear_last();
+ $result = \gnupg_setsignmode($identifier, $signmode);
+ if ($result === false) {
+ throw GnupgException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\HashException;
+
+/**
+ *
+ *
+ * @param string $algo Name of selected hashing algorithm (i.e. "sha256", "sha512", "haval160,4", etc..)
+ * See hash_algos for a list of supported algorithms.
+ *
+ *
+ * Non-cryptographic hash functions are not allowed.
+ *
+ *
+ *
+ * Non-cryptographic hash functions are not allowed.
+ * @param string $ikm Input keying material (raw binary). Cannot be empty.
+ * @param int $length Desired output length in bytes.
+ * Cannot be greater than 255 times the chosen hash function size.
+ *
+ * If length is 0, the output length
+ * will default to the chosen hash function size.
+ * @param string $info Application/context-specific info string.
+ * @param string $salt Salt to use during derivation.
+ *
+ * While optional, adding random salt significantly improves the strength of HKDF.
+ * @return string Returns a string containing a raw binary representation of the derived key
+ * (also known as output keying material - OKM);.
+ * @throws HashException
+ *
+ */
+function hash_hkdf(string $algo, string $ikm, int $length = 0, string $info = '', string $salt = ''): string
+{
+ error_clear_last();
+ $result = \hash_hkdf($algo, $ikm, $length, $info, $salt);
+ if ($result === false) {
+ throw HashException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param \HashContext $hcontext Hashing context returned by hash_init.
+ * @param string $filename URL describing location of file to be hashed; Supports fopen wrappers.
+ * @param \HashContext|null $scontext Stream context as returned by stream_context_create.
+ * @throws HashException
+ *
+ */
+function hash_update_file(\HashContext $hcontext, string $filename, ?\HashContext $scontext = null): void
+{
+ error_clear_last();
+ $result = \hash_update_file($hcontext, $filename, $scontext);
+ if ($result === false) {
+ throw HashException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\IbaseException;
+
+/**
+ * This function will discard a BLOB if it has not yet been closed by
+ * fbird_blob_close.
+ *
+ * @param resource $blob_handle A BLOB handle opened with fbird_blob_create.
+ * @throws IbaseException
+ *
+ */
+function fbird_blob_cancel($blob_handle): void
+{
+ error_clear_last();
+ $result = \fbird_blob_cancel($blob_handle);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $service_handle The handle on the database server service.
+ * @param string $user_name The login name of the new database user.
+ * @param string $password The password of the new user.
+ * @param string $first_name The first name of the new database user.
+ * @param string $middle_name The middle name of the new database user.
+ * @param string $last_name The last name of the new database user.
+ * @throws IbaseException
+ *
+ */
+function ibase_add_user($service_handle, string $user_name, string $password, string $first_name = null, string $middle_name = null, string $last_name = null): void
+{
+ error_clear_last();
+ if ($last_name !== null) {
+ $result = \ibase_add_user($service_handle, $user_name, $password, $first_name, $middle_name, $last_name);
+ } elseif ($middle_name !== null) {
+ $result = \ibase_add_user($service_handle, $user_name, $password, $first_name, $middle_name);
+ } elseif ($first_name !== null) {
+ $result = \ibase_add_user($service_handle, $user_name, $password, $first_name);
+ } else {
+ $result = \ibase_add_user($service_handle, $user_name, $password);
+ }
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function passes the arguments to the (remote) database server. There it starts a new backup process. Therefore you
+ * won't get any responses.
+ *
+ * @param resource $service_handle A previously opened connection to the database server.
+ * @param string $source_db The absolute file path to the database on the database server. You can also use a database alias.
+ * @param string $dest_file The path to the backup file on the database server.
+ * @param int $options Additional options to pass to the database server for backup.
+ * The options parameter can be a combination
+ * of the following constants:
+ * IBASE_BKP_IGNORE_CHECKSUMS,
+ * IBASE_BKP_IGNORE_LIMBO,
+ * IBASE_BKP_METADATA_ONLY,
+ * IBASE_BKP_NO_GARBAGE_COLLECT,
+ * IBASE_BKP_OLD_DESCRIPTIONS,
+ * IBASE_BKP_NON_TRANSPORTABLE or
+ * IBASE_BKP_CONVERT.
+ * Read the section about for further information.
+ * @param bool $verbose Since the backup process is done on the database server, you don't have any chance
+ * to get its output. This argument is useless.
+ * @return mixed Returns TRUE on success.
+ *
+ * Since the backup process is done on the (remote) server, this function just passes the arguments to it.
+ * While the arguments are legal, you won't get FALSE.
+ * @throws IbaseException
+ *
+ */
+function ibase_backup($service_handle, string $source_db, string $dest_file, int $options = 0, bool $verbose = false)
+{
+ error_clear_last();
+ $result = \ibase_backup($service_handle, $source_db, $dest_file, $options, $verbose);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function will discard a BLOB if it has not yet been closed by
+ * ibase_blob_close.
+ *
+ * @param resource $blob_handle A BLOB handle opened with ibase_blob_create.
+ * @throws IbaseException
+ *
+ */
+function ibase_blob_cancel($blob_handle): void
+{
+ error_clear_last();
+ $result = \ibase_blob_cancel($blob_handle);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ibase_blob_create creates a new BLOB for filling with
+ * data.
+ *
+ * @param resource $link_identifier An InterBase link identifier. If omitted, the last opened link is
+ * assumed.
+ * @return resource Returns a BLOB handle for later use with
+ * ibase_blob_add.
+ * @throws IbaseException
+ *
+ */
+function ibase_blob_create($link_identifier = null)
+{
+ error_clear_last();
+ $result = \ibase_blob_create($link_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns at most len bytes from a BLOB
+ * that has been opened for reading by ibase_blob_open.
+ *
+ * @param resource $blob_handle A BLOB handle opened with ibase_blob_open.
+ * @param int $len Size of returned data.
+ * @return string Returns at most len bytes from the BLOB.
+ * @throws IbaseException
+ *
+ */
+function ibase_blob_get($blob_handle, int $len): string
+{
+ error_clear_last();
+ $result = \ibase_blob_get($blob_handle, $len);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Closes the link to an InterBase database that's associated with
+ * a connection id returned from ibase_connect.
+ * Default transaction on link is committed, other transactions are
+ * rolled back.
+ *
+ * @param resource $connection_id An InterBase link identifier returned from
+ * ibase_connect. If omitted, the last opened link
+ * is assumed.
+ * @throws IbaseException
+ *
+ */
+function ibase_close($connection_id = null): void
+{
+ error_clear_last();
+ $result = \ibase_close($connection_id);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Commits a transaction without closing it.
+ *
+ * @param resource $link_or_trans_identifier If called without an argument, this function commits the default
+ * transaction of the default link. If the argument is a connection
+ * identifier, the default transaction of the corresponding connection
+ * will be committed. If the argument is a transaction identifier, the
+ * corresponding transaction will be committed. The transaction context
+ * will be retained, so statements executed from within this transaction
+ * will not be invalidated.
+ * @throws IbaseException
+ *
+ */
+function ibase_commit_ret($link_or_trans_identifier = null): void
+{
+ error_clear_last();
+ $result = \ibase_commit_ret($link_or_trans_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Commits a transaction.
+ *
+ * @param resource $link_or_trans_identifier If called without an argument, this function commits the default
+ * transaction of the default link. If the argument is a connection
+ * identifier, the default transaction of the corresponding connection
+ * will be committed. If the argument is a transaction identifier, the
+ * corresponding transaction will be committed.
+ * @throws IbaseException
+ *
+ */
+function ibase_commit($link_or_trans_identifier = null): void
+{
+ error_clear_last();
+ $result = \ibase_commit($link_or_trans_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Establishes a connection to an Firebird/InterBase server.
+ *
+ * In case a second call is made to ibase_connect with
+ * the same arguments, no new link will be established, but instead, the link
+ * identifier of the already opened link will be returned. The link to the
+ * server will be closed as soon as the execution of the script ends, unless
+ * it's closed earlier by explicitly calling ibase_close.
+ *
+ * @param string $database The database argument has to be a valid path to
+ * database file on the server it resides on. If the server is not local,
+ * it must be prefixed with either 'hostname:' (TCP/IP), 'hostname/port:'
+ * (TCP/IP with interbase server on custom TCP port), '//hostname/'
+ * (NetBEUI), depending on the connection
+ * protocol used.
+ * @param string $username The user name. Can be set with the
+ * ibase.default_user php.ini directive.
+ * @param string $password The password for username. Can be set with the
+ * ibase.default_password php.ini directive.
+ * @param string $charset charset is the default character set for a
+ * database.
+ * @param int $buffers buffers is the number of database buffers to
+ * allocate for the server-side cache. If 0 or omitted, server chooses
+ * its own default.
+ * @param int $dialect dialect selects the default SQL dialect for any
+ * statement executed within a connection, and it defaults to the highest
+ * one supported by client libraries.
+ * @param string $role Functional only with InterBase 5 and up.
+ * @param int $sync
+ * @return resource Returns an Firebird/InterBase link identifier on success.
+ * @throws IbaseException
+ *
+ */
+function ibase_connect(string $database = null, string $username = null, string $password = null, string $charset = null, int $buffers = null, int $dialect = null, string $role = null, int $sync = null)
+{
+ error_clear_last();
+ if ($sync !== null) {
+ $result = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect, $role, $sync);
+ } elseif ($role !== null) {
+ $result = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect, $role);
+ } elseif ($dialect !== null) {
+ $result = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect);
+ } elseif ($buffers !== null) {
+ $result = \ibase_connect($database, $username, $password, $charset, $buffers);
+ } elseif ($charset !== null) {
+ $result = \ibase_connect($database, $username, $password, $charset);
+ } elseif ($password !== null) {
+ $result = \ibase_connect($database, $username, $password);
+ } elseif ($username !== null) {
+ $result = \ibase_connect($database, $username);
+ } elseif ($database !== null) {
+ $result = \ibase_connect($database);
+ } else {
+ $result = \ibase_connect();
+ }
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $service_handle The handle on the database server service.
+ * @param string $user_name The login name of the user you want to delete from the database.
+ * @throws IbaseException
+ *
+ */
+function ibase_delete_user($service_handle, string $user_name): void
+{
+ error_clear_last();
+ $result = \ibase_delete_user($service_handle, $user_name);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This functions drops a database that was opened by either ibase_connect
+ * or ibase_pconnect. The database is closed and deleted from the server.
+ *
+ * @param resource $connection An InterBase link identifier. If omitted, the last opened link is
+ * assumed.
+ * @throws IbaseException
+ *
+ */
+function ibase_drop_db($connection = null): void
+{
+ error_clear_last();
+ $result = \ibase_drop_db($connection);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function causes the registered event handler specified by
+ * event to be cancelled. The callback function will
+ * no longer be called for the events it was registered to handle.
+ *
+ * @param resource $event An event resource, created by
+ * ibase_set_event_handler.
+ * @throws IbaseException
+ *
+ */
+function ibase_free_event_handler($event): void
+{
+ error_clear_last();
+ $result = \ibase_free_event_handler($event);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees a prepared query.
+ *
+ * @param resource $query A query prepared with ibase_prepare.
+ * @throws IbaseException
+ *
+ */
+function ibase_free_query($query): void
+{
+ error_clear_last();
+ $result = \ibase_free_query($query);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees a result set.
+ *
+ * @param resource $result_identifier A result set created by ibase_query or
+ * ibase_execute.
+ * @throws IbaseException
+ *
+ */
+function ibase_free_result($result_identifier): void
+{
+ error_clear_last();
+ $result = \ibase_free_result($result_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $service_handle
+ * @param string $db
+ * @param int $action
+ * @param int $argument
+ * @throws IbaseException
+ *
+ */
+function ibase_maintain_db($service_handle, string $db, int $action, int $argument = 0): void
+{
+ error_clear_last();
+ $result = \ibase_maintain_db($service_handle, $db, $action, $argument);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $service_handle The handle on the database server service.
+ * @param string $user_name The login name of the database user to modify.
+ * @param string $password The user's new password.
+ * @param string $first_name The user's new first name.
+ * @param string $middle_name The user's new middle name.
+ * @param string $last_name The user's new last name.
+ * @throws IbaseException
+ *
+ */
+function ibase_modify_user($service_handle, string $user_name, string $password, string $first_name = null, string $middle_name = null, string $last_name = null): void
+{
+ error_clear_last();
+ if ($last_name !== null) {
+ $result = \ibase_modify_user($service_handle, $user_name, $password, $first_name, $middle_name, $last_name);
+ } elseif ($middle_name !== null) {
+ $result = \ibase_modify_user($service_handle, $user_name, $password, $first_name, $middle_name);
+ } elseif ($first_name !== null) {
+ $result = \ibase_modify_user($service_handle, $user_name, $password, $first_name);
+ } else {
+ $result = \ibase_modify_user($service_handle, $user_name, $password);
+ }
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function assigns a name to a result set. This name can be used later in
+ * UPDATE|DELETE ... WHERE CURRENT OF name statements.
+ *
+ * @param resource $result An InterBase result set.
+ * @param string $name The name to be assigned.
+ * @throws IbaseException
+ *
+ */
+function ibase_name_result($result, string $name): void
+{
+ error_clear_last();
+ $result = \ibase_name_result($result, $name);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Opens a persistent connection to an InterBase database.
+ *
+ * ibase_pconnect acts very much like
+ * ibase_connect with two major differences.
+ *
+ * First, when connecting, the function will first try to find a (persistent)
+ * link that's already opened with the same parameters. If one is found, an
+ * identifier for it will be returned instead of opening a new connection.
+ *
+ * Second, the connection to the InterBase server will not be closed when the
+ * execution of the script ends. Instead, the link will remain open for
+ * future use (ibase_close will not close links
+ * established by ibase_pconnect). This type of link is
+ * therefore called 'persistent'.
+ *
+ * @param string $database The database argument has to be a valid path to
+ * database file on the server it resides on. If the server is not local,
+ * it must be prefixed with either 'hostname:' (TCP/IP), '//hostname/'
+ * (NetBEUI) or 'hostname@' (IPX/SPX), depending on the connection
+ * protocol used.
+ * @param string $username The user name. Can be set with the
+ * ibase.default_user php.ini directive.
+ * @param string $password The password for username. Can be set with the
+ * ibase.default_password php.ini directive.
+ * @param string $charset charset is the default character set for a
+ * database.
+ * @param int $buffers buffers is the number of database buffers to
+ * allocate for the server-side cache. If 0 or omitted, server chooses
+ * its own default.
+ * @param int $dialect dialect selects the default SQL dialect for any
+ * statement executed within a connection, and it defaults to the highest
+ * one supported by client libraries. Functional only with InterBase 6
+ * and up.
+ * @param string $role Functional only with InterBase 5 and up.
+ * @param int $sync
+ * @return resource Returns an InterBase link identifier on success.
+ * @throws IbaseException
+ *
+ */
+function ibase_pconnect(string $database = null, string $username = null, string $password = null, string $charset = null, int $buffers = null, int $dialect = null, string $role = null, int $sync = null)
+{
+ error_clear_last();
+ if ($sync !== null) {
+ $result = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect, $role, $sync);
+ } elseif ($role !== null) {
+ $result = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect, $role);
+ } elseif ($dialect !== null) {
+ $result = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect);
+ } elseif ($buffers !== null) {
+ $result = \ibase_pconnect($database, $username, $password, $charset, $buffers);
+ } elseif ($charset !== null) {
+ $result = \ibase_pconnect($database, $username, $password, $charset);
+ } elseif ($password !== null) {
+ $result = \ibase_pconnect($database, $username, $password);
+ } elseif ($username !== null) {
+ $result = \ibase_pconnect($database, $username);
+ } elseif ($database !== null) {
+ $result = \ibase_pconnect($database);
+ } else {
+ $result = \ibase_pconnect();
+ }
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function passes the arguments to the (remote) database server. There it starts a new restore process. Therefore you
+ * won't get any responses.
+ *
+ * @param resource $service_handle A previously opened connection to the database server.
+ * @param string $source_file The absolute path on the server where the backup file is located.
+ * @param string $dest_db The path to create the new database on the server. You can also use database alias.
+ * @param int $options Additional options to pass to the database server for restore.
+ * The options parameter can be a combination
+ * of the following constants:
+ * IBASE_RES_DEACTIVATE_IDX,
+ * IBASE_RES_NO_SHADOW,
+ * IBASE_RES_NO_VALIDITY,
+ * IBASE_RES_ONE_AT_A_TIME,
+ * IBASE_RES_REPLACE,
+ * IBASE_RES_CREATE,
+ * IBASE_RES_USE_ALL_SPACE,
+ * IBASE_PRP_PAGE_BUFFERS,
+ * IBASE_PRP_SWEEP_INTERVAL,
+ * IBASE_RES_CREATE.
+ * Read the section about for further information.
+ * @param bool $verbose Since the restore process is done on the database server, you don't have any chance
+ * to get its output. This argument is useless.
+ * @return mixed Returns TRUE on success.
+ *
+ * Since the restore process is done on the (remote) server, this function just passes the arguments to it.
+ * While the arguments are legal, you won't get FALSE.
+ * @throws IbaseException
+ *
+ */
+function ibase_restore($service_handle, string $source_file, string $dest_db, int $options = 0, bool $verbose = false)
+{
+ error_clear_last();
+ $result = \ibase_restore($service_handle, $source_file, $dest_db, $options, $verbose);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Rolls back a transaction without closing it.
+ *
+ * @param resource $link_or_trans_identifier If called without an argument, this function rolls back the default
+ * transaction of the default link. If the argument is a connection
+ * identifier, the default transaction of the corresponding connection
+ * will be rolled back. If the argument is a transaction identifier, the
+ * corresponding transaction will be rolled back. The transaction context
+ * will be retained, so statements executed from within this transaction
+ * will not be invalidated.
+ * @throws IbaseException
+ *
+ */
+function ibase_rollback_ret($link_or_trans_identifier = null): void
+{
+ error_clear_last();
+ $result = \ibase_rollback_ret($link_or_trans_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Rolls back a transaction.
+ *
+ * @param resource $link_or_trans_identifier If called without an argument, this function rolls back the default
+ * transaction of the default link. If the argument is a connection
+ * identifier, the default transaction of the corresponding connection
+ * will be rolled back. If the argument is a transaction identifier, the
+ * corresponding transaction will be rolled back.
+ * @throws IbaseException
+ *
+ */
+function ibase_rollback($link_or_trans_identifier = null): void
+{
+ error_clear_last();
+ $result = \ibase_rollback($link_or_trans_identifier);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $host The name or ip address of the database host. You can define the port by adding
+ * '/' and port number. If no port is specified, port 3050 will be used.
+ * @param string $dba_username The name of any valid user.
+ * @param string $dba_password The user's password.
+ * @return resource Returns a Interbase / Firebird link identifier on success.
+ * @throws IbaseException
+ *
+ */
+function ibase_service_attach(string $host, string $dba_username, string $dba_password)
+{
+ error_clear_last();
+ $result = \ibase_service_attach($host, $dba_username, $dba_password);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $service_handle A previously created connection to the database server.
+ * @throws IbaseException
+ *
+ */
+function ibase_service_detach($service_handle): void
+{
+ error_clear_last();
+ $result = \ibase_service_detach($service_handle);
+ if ($result === false) {
+ throw IbaseException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\IbmDb2Exception;
+
+/**
+ * Sets or gets the AUTOCOMMIT behavior of the specified connection resource.
+ *
+ * @param resource $connection A valid database connection resource variable as returned from
+ * db2_connect or db2_pconnect.
+ * @param int $value One of the following constants:
+ *
+ *
+ * DB2_AUTOCOMMIT_OFF
+ *
+ *
+ * Turns AUTOCOMMIT off.
+ *
+ *
+ *
+ *
+ * DB2_AUTOCOMMIT_ON
+ *
+ *
+ * Turns AUTOCOMMIT on.
+ *
+ *
+ *
+ *
+ *
+ * Turns AUTOCOMMIT off.
+ *
+ * Turns AUTOCOMMIT on.
+ * @return mixed When db2_autocommit receives only the
+ * connection parameter, it returns the current state
+ * of AUTOCOMMIT for the requested connection as an integer value. A value of
+ * DB2_AUTOCOMMIT_OFF indicates that AUTOCOMMIT is off, while a value of DB2_AUTOCOMMIT_ON indicates that
+ * AUTOCOMMIT is on.
+ *
+ * When db2_autocommit receives both the
+ * connection parameter and
+ * autocommit parameter, it attempts to set the
+ * AUTOCOMMIT state of the requested connection to the corresponding state.
+ * Returns TRUE on success.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_autocommit($connection, int $value = null)
+{
+ error_clear_last();
+ if ($value !== null) {
+ $result = \db2_autocommit($connection, $value);
+ } else {
+ $result = \db2_autocommit($connection);
+ }
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Binds a PHP variable to an SQL statement parameter in a statement resource
+ * returned by db2_prepare. This function gives you more
+ * control over the parameter type, data type, precision, and scale for the
+ * parameter than simply passing the variable as part of the optional input
+ * array to db2_execute.
+ *
+ * @param resource $stmt A prepared statement returned from db2_prepare.
+ * @param int $parameter_number Specifies the 1-indexed position of the parameter in the prepared
+ * statement.
+ * @param string $variable_name A string specifying the name of the PHP variable to bind to the
+ * parameter specified by parameter_number.
+ * @param int $parameter_type A constant specifying whether the PHP variable should be bound to the
+ * SQL parameter as an input parameter (DB2_PARAM_IN),
+ * an output parameter (DB2_PARAM_OUT), or as a
+ * parameter that accepts input and returns output
+ * (DB2_PARAM_INOUT). To avoid memory overhead, you can
+ * also specify DB2_PARAM_FILE to bind the PHP variable
+ * to the name of a file that contains large object (BLOB, CLOB, or DBCLOB)
+ * data.
+ * @param int $data_type A constant specifying the SQL data type that the PHP variable should be
+ * bound as: one of DB2_BINARY,
+ * DB2_CHAR, DB2_DOUBLE, or
+ * DB2_LONG .
+ * @param int $precision Specifies the precision with which the variable should be bound to the
+ * database. This parameter can also be used for retrieving XML output values
+ * from stored procedures. A non-negative value specifies the maximum size of
+ * the XML data that will be retrieved from the database. If this parameter
+ * is not used, a default of 1MB will be assumed for retrieving the XML
+ * output value from the stored procedure.
+ * @param int $scale Specifies the scale with which the variable should be bound to the
+ * database.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_bind_param($stmt, int $parameter_number, string $variable_name, int $parameter_type = null, int $data_type = 0, int $precision = -1, int $scale = 0): void
+{
+ error_clear_last();
+ if ($scale !== 0) {
+ $result = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type, $precision, $scale);
+ } elseif ($precision !== -1) {
+ $result = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type, $precision);
+ } elseif ($data_type !== 0) {
+ $result = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type);
+ } elseif ($parameter_type !== null) {
+ $result = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type);
+ } else {
+ $result = \db2_bind_param($stmt, $parameter_number, $variable_name);
+ }
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function returns an object with read-only properties that return
+ * information about the DB2 database client. The following table lists
+ * the DB2 client properties:
+ *
+ * DB2 client properties
+ *
+ *
+ *
+ * Property name
+ * Return type
+ * Description
+ *
+ *
+ *
+ *
+ * APPL_CODEPAGE
+ * int
+ * The application code page.
+ *
+ *
+ * CONN_CODEPAGE
+ * int
+ * The code page for the current connection.
+ *
+ *
+ * DATA_SOURCE_NAME
+ * string
+ * The data source name (DSN) used to create the current connection
+ * to the database.
+ *
+ *
+ * DRIVER_NAME
+ * string
+ * The name of the library that implements the DB2 Call
+ * Level Interface (CLI) specification.
+ *
+ *
+ * DRIVER_ODBC_VER
+ * string
+ * The version of ODBC that the DB2 client supports. This returns a
+ * string "MM.mm" where MM is the major version and
+ * mm is the minor version. The DB2 client always
+ * returns "03.51".
+ *
+ *
+ *
+ * DRIVER_VER
+ * string
+ * The version of the client, in the form of a string "MM.mm.uuuu" where
+ * MM is the major version,
+ * mm is the minor version,
+ * and uuuu is the update. For example, "08.02.0001"
+ * represents major version 8, minor version 2, update 1.
+ *
+ *
+ *
+ * ODBC_SQL_CONFORMANCE
+ * string
+ *
+ * The level of ODBC SQL grammar supported by the client:
+ *
+ *
+ * MINIMUM
+ *
+ *
+ * Supports the minimum ODBC SQL grammar.
+ *
+ *
+ *
+ *
+ * CORE
+ *
+ *
+ * Supports the core ODBC SQL grammar.
+ *
+ *
+ *
+ *
+ * EXTENDED
+ *
+ *
+ * Supports extended ODBC SQL grammar.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ODBC_VER
+ * string
+ * The version of ODBC that the ODBC driver manager supports. This
+ * returns a string "MM.mm.rrrr" where MM is the major
+ * version, mm is the minor version, and
+ * rrrr is the release. The DB2 client always returns
+ * "03.01.0000".
+ *
+ *
+ *
+ *
+ *
+ *
+ * @param resource $connection Specifies an active DB2 client connection.
+ * @return object Returns an object on a successful call. Returns FALSE on failure.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_client_info($connection): object
+{
+ error_clear_last();
+ $result = \db2_client_info($connection);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function closes a DB2 client connection created with
+ * db2_connect and returns the corresponding
+ * resources to the database server.
+ *
+ * If you attempt to close a persistent DB2 client connection created with
+ * db2_pconnect, the close request is ignored and the
+ * persistent DB2 client connection remains available for the next caller.
+ *
+ * @param resource $connection Specifies an active DB2 client connection.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_close($connection): void
+{
+ error_clear_last();
+ $result = \db2_close($connection);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Commits an in-progress transaction on the specified connection resource and
+ * begins a new transaction. PHP applications normally default to AUTOCOMMIT
+ * mode, so db2_commit is not necessary unless AUTOCOMMIT
+ * has been turned off for the connection resource.
+ *
+ * @param resource $connection A valid database connection resource variable as returned from
+ * db2_connect or db2_pconnect.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_commit($connection): void
+{
+ error_clear_last();
+ $result = \db2_commit($connection);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * db2_execute executes an SQL statement that was
+ * prepared by db2_prepare.
+ *
+ * If the SQL statement returns a result set, for example, a SELECT statement
+ * or a CALL to a stored procedure that returns one or more result sets, you
+ * can retrieve a row as an array from the stmt resource
+ * using db2_fetch_assoc,
+ * db2_fetch_both, or
+ * db2_fetch_array. Alternatively, you can use
+ * db2_fetch_row to move the result set pointer to the
+ * next row and fetch a column at a time from that row with
+ * db2_result.
+ *
+ * Refer to db2_prepare for a brief discussion of the
+ * advantages of using db2_prepare and
+ * db2_execute rather than db2_exec.
+ *
+ * @param resource $stmt A prepared statement returned from db2_prepare.
+ * @param array $parameters An array of input parameters matching any parameter markers contained
+ * in the prepared statement.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_execute($stmt, array $parameters = null): void
+{
+ error_clear_last();
+ if ($parameters !== null) {
+ $result = \db2_execute($stmt, $parameters);
+ } else {
+ $result = \db2_execute($stmt);
+ }
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees the system and database resources that are associated with a result
+ * set. These resources are freed implicitly when a script finishes, but you
+ * can call db2_free_result to explicitly free the result
+ * set resources before the end of the script.
+ *
+ * @param resource $stmt A valid statement resource.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_free_result($stmt): void
+{
+ error_clear_last();
+ $result = \db2_free_result($stmt);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees the system and database resources that are associated with a statement
+ * resource. These resources are freed implicitly when a script finishes, but
+ * you can call db2_free_stmt to explicitly free the
+ * statement resources before the end of the script.
+ *
+ * @param resource $stmt A valid statement resource.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_free_stmt($stmt): void
+{
+ error_clear_last();
+ $result = \db2_free_stmt($stmt);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieves the value of a specified option value for a statement resource
+ * or a connection resource.
+ *
+ * @param resource $resource A valid statement resource as returned from
+ * db2_prepare or a valid connection resource as
+ * returned from db2_connect or
+ * db2_pconnect.
+ * @param string $option A valid statement or connection options. The following new options are available
+ * as of ibm_db2 version 1.6.0. They provide useful tracking information
+ * that can be set during execution with db2_get_option.
+ *
+ *
+ * Prior versions of ibm_db2 do not support these new options.
+ *
+ *
+ * When the value in each option is being set, some servers might not handle
+ * the entire length provided and might truncate the value.
+ *
+ *
+ * To ensure that the data specified in each option is converted correctly
+ * when transmitted to a host system, use only the characters A through Z,
+ * 0 through 9, and the underscore (_) or period (.).
+ *
+ *
+ *
+ *
+ * userid
+ *
+ *
+ * SQL_ATTR_INFO_USERID - A pointer to a null-terminated
+ * character string used to identify the client user ID sent to the host
+ * database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ *
+ *
+ *
+ *
+ *
+ * acctstr
+ *
+ *
+ * SQL_ATTR_INFO_ACCTSTR - A pointer to a null-terminated
+ * character string used to identify the client accounting string sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ * applname
+ *
+ *
+ * SQL_ATTR_INFO_APPLNAME - A pointer to a null-terminated
+ * character string used to identify the client application name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ * wrkstnname
+ *
+ *
+ * SQL_ATTR_INFO_WRKSTNNAME - A pointer to a null-terminated
+ * character string used to identify the client workstation name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Prior versions of ibm_db2 do not support these new options.
+ *
+ * When the value in each option is being set, some servers might not handle
+ * the entire length provided and might truncate the value.
+ *
+ * To ensure that the data specified in each option is converted correctly
+ * when transmitted to a host system, use only the characters A through Z,
+ * 0 through 9, and the underscore (_) or period (.).
+ *
+ * SQL_ATTR_INFO_USERID - A pointer to a null-terminated
+ * character string used to identify the client user ID sent to the host
+ * database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ * SQL_ATTR_INFO_ACCTSTR - A pointer to a null-terminated
+ * character string used to identify the client accounting string sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ * SQL_ATTR_INFO_APPLNAME - A pointer to a null-terminated
+ * character string used to identify the client application name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ * SQL_ATTR_INFO_WRKSTNNAME - A pointer to a null-terminated
+ * character string used to identify the client workstation name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ * @return string Returns the current setting of the connection attribute provided on success.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_get_option($resource, string $option): string
+{
+ error_clear_last();
+ $result = \db2_get_option($resource, $option);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function closes a DB2 client connection created with
+ * db2_pconnect and returns the corresponding resources
+ * to the database server.
+ *
+ *
+ * This function is only available on i5/OS in response to i5/OS system
+ * administration requests.
+ *
+ *
+ *
+ * If you have a persistent DB2 client connection created with
+ * db2_pconnect, you may use this function to close the
+ * connection. To avoid substantial connection performance penalties, this
+ * function should only be used in rare cases when the persistent connection
+ * has become unresponsive or the persistent connection will not be needed for
+ * a long period of time.
+ *
+ * @param resource $resource Specifies an active DB2 client connection.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_pclose($resource): void
+{
+ error_clear_last();
+ $result = \db2_pclose($resource);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Rolls back an in-progress transaction on the specified connection resource and
+ * begins a new transaction. PHP applications normally default to AUTOCOMMIT
+ * mode, so db2_rollback normally has no effect unless
+ * AUTOCOMMIT has been turned off for the connection resource.
+ *
+ * @param resource $connection A valid database connection resource variable as returned from
+ * db2_connect or db2_pconnect.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_rollback($connection): void
+{
+ error_clear_last();
+ $result = \db2_rollback($connection);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function returns an object with read-only properties that return
+ * information about the IBM DB2, Cloudscape, or Apache Derby database server.
+ * The following table lists the database server properties:
+ *
+ * Database server properties
+ *
+ *
+ *
+ * Property name
+ * Return type
+ * Description
+ *
+ *
+ *
+ *
+ * DBMS_NAME
+ * string
+ * The name of the database server to which you are
+ * connected. For DB2 servers this is a combination of
+ * DB2 followed by the operating system on which
+ * the database server is running.
+ *
+ *
+ * DBMS_VER
+ * string
+ * The version of the database server, in the form of a string
+ * "MM.mm.uuuu" where MM is the major version,
+ * mm is the minor version,
+ * and uuuu is the update. For example, "08.02.0001"
+ * represents major version 8, minor version 2, update 1.
+ *
+ *
+ *
+ * DB_CODEPAGE
+ * int
+ * The code page of the database to which you are connected.
+ *
+ *
+ * DB_NAME
+ * string
+ * The name of the database to which you are connected.
+ *
+ *
+ * DFT_ISOLATION
+ * string
+ *
+ * The default transaction isolation level supported by the
+ * server:
+ *
+ *
+ * UR
+ *
+ *
+ * Uncommitted read: changes are immediately visible by all
+ * concurrent transactions.
+ *
+ *
+ *
+ *
+ * CS
+ *
+ *
+ * Cursor stability: a row read by one transaction can be altered and
+ * committed by a second concurrent transaction.
+ *
+ *
+ *
+ *
+ * RS
+ *
+ *
+ * Read stability: a transaction can add or remove rows matching a
+ * search condition or a pending transaction.
+ *
+ *
+ *
+ *
+ * RR
+ *
+ *
+ * Repeatable read: data affected by pending transaction is not
+ * available to other transactions.
+ *
+ *
+ *
+ *
+ * NC
+ *
+ *
+ * No commit: any changes are visible at the end of a successful
+ * operation. Explicit commits and rollbacks are not allowed.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * IDENTIFIER_QUOTE_CHAR
+ * string
+ * The character used to delimit an identifier.
+ *
+ *
+ * INST_NAME
+ * string
+ * The instance on the database server that contains the
+ * database.
+ *
+ *
+ * ISOLATION_OPTION
+ * array
+ * An array of the isolation options supported by the
+ * database server. The isolation options are described in
+ * the DFT_ISOLATION property.
+ *
+ *
+ * KEYWORDS
+ * array
+ * An array of the keywords reserved by the database
+ * server.
+ *
+ *
+ * LIKE_ESCAPE_CLAUSE
+ * bool
+ * TRUE if the database server supports the
+ * use of % and _ wildcard
+ * characters. FALSE if the database server does not
+ * support these wildcard characters.
+ *
+ *
+ * MAX_COL_NAME_LEN
+ * int
+ * Maximum length of a column name supported by the database
+ * server, expressed in bytes.
+ *
+ *
+ * MAX_IDENTIFIER_LEN
+ * int
+ * Maximum length of an SQL identifier supported by the database
+ * server, expressed in characters.
+ *
+ *
+ * MAX_INDEX_SIZE
+ * int
+ * Maximum size of columns combined in an index supported by the
+ * database server, expressed in bytes.
+ *
+ *
+ * MAX_PROC_NAME_LEN
+ * int
+ * Maximum length of a procedure name supported by the database
+ * server, expressed in bytes.
+ *
+ *
+ * MAX_ROW_SIZE
+ * int
+ * Maximum length of a row in a base table supported by the
+ * database server, expressed in bytes.
+ *
+ *
+ * MAX_SCHEMA_NAME_LEN
+ * int
+ * Maximum length of a schema name supported by the database
+ * server, expressed in bytes.
+ *
+ *
+ * MAX_STATEMENT_LEN
+ * int
+ * Maximum length of an SQL statement supported by the database
+ * server, expressed in bytes.
+ *
+ *
+ * MAX_TABLE_NAME_LEN
+ * int
+ * Maximum length of a table name supported by the database
+ * server, expressed in bytes.
+ *
+ *
+ * NON_NULLABLE_COLUMNS
+ * bool
+ * TRUE if the database server supports columns that can be
+ * defined as NOT NULL, FALSE if the database server does not support
+ * columns defined as NOT NULL.
+ *
+ *
+ * PROCEDURES
+ * bool
+ * TRUE if the database server supports the use of the CALL
+ * statement to call stored procedures, FALSE if the database
+ * server does not support the CALL statement.
+ *
+ *
+ * SPECIAL_CHARS
+ * string
+ * A string containing all of the characters other than
+ * a-Z, 0-9, and underscore that can be used in an identifier name.
+ *
+ *
+ * SQL_CONFORMANCE
+ * string
+ *
+ * The level of conformance to the ANSI/ISO SQL-92 specification
+ * offered by the database server:
+ *
+ *
+ * ENTRY
+ *
+ *
+ * Entry-level SQL-92 compliance.
+ *
+ *
+ *
+ *
+ * FIPS127
+ *
+ *
+ * FIPS-127-2 transitional compliance.
+ *
+ *
+ *
+ *
+ * FULL
+ *
+ *
+ * Full level SQL-92 compliance.
+ *
+ *
+ *
+ *
+ * INTERMEDIATE
+ *
+ *
+ * Intermediate level SQL-92 compliance.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @param resource $connection Specifies an active DB2 client connection.
+ * @return object Returns an object on a successful call. Returns FALSE on failure.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_server_info($connection): object
+{
+ error_clear_last();
+ $result = \db2_server_info($connection);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets options for a statement resource or a connection resource. You
+ * cannot set options for result set resources.
+ *
+ * @param resource $resource A valid statement resource as returned from
+ * db2_prepare or a valid connection resource as
+ * returned from db2_connect or
+ * db2_pconnect.
+ * @param array $options An associative array containing valid statement or connection
+ * options. This parameter can be used to change autocommit values,
+ * cursor types (scrollable or forward), and to specify the case of
+ * the column names (lower, upper, or natural) that will appear in a
+ * result set.
+ *
+ *
+ * autocommit
+ *
+ *
+ * Passing DB2_AUTOCOMMIT_ON turns
+ * autocommit on for the specified connection resource.
+ *
+ *
+ * Passing DB2_AUTOCOMMIT_OFF turns
+ * autocommit off for the specified connection resource.
+ *
+ *
+ *
+ *
+ * cursor
+ *
+ *
+ * Passing DB2_FORWARD_ONLY specifies a
+ * forward-only cursor for a statement resource. This is the
+ * default cursor type, and is supported by all database
+ * servers.
+ *
+ *
+ * Passing DB2_SCROLLABLE specifies a
+ * scrollable cursor for a statement resource. Scrollable
+ * cursors enable result set rows to be accessed in
+ * non-sequential order, but are only supported by
+ * IBM DB2 Universal Database databases.
+ *
+ *
+ *
+ *
+ * binmode
+ *
+ *
+ * Passing DB2_BINARY specifies that
+ * binary data will be returned as is. This is the default
+ * mode. This is the equivalent of setting
+ * ibm_db2.binmode=1 in php.ini.
+ *
+ *
+ * Passing DB2_CONVERT specifies that
+ * binary data will be converted to hexadecimal encoding,
+ * and will be returned as such. This is the equivalent of
+ * setting ibm_db2.binmode=2 in php.ini.
+ *
+ *
+ * Passing DB2_PASSTHRU specifies that
+ * binary data will be converted to NULL. This is the
+ * equivalent of setting ibm_db2.binmode=3
+ * in php.ini.
+ *
+ *
+ *
+ *
+ * db2_attr_case
+ *
+ *
+ * Passing DB2_CASE_LOWER specifies that
+ * column names of the result set are returned in lower case.
+ *
+ *
+ * Passing DB2_CASE_UPPER specifies that
+ * column names of the result set are returned in upper case.
+ *
+ *
+ * Passing DB2_CASE_NATURAL specifies that
+ * column names of the result set are returned in natural
+ * case.
+ *
+ *
+ *
+ *
+ * deferred_prepare
+ *
+ *
+ * Passing DB2_DEFERRED_PREPARE_ON turns deferred
+ * prepare on for the specified statement resource.
+ *
+ *
+ * Passing DB2_DEFERRED_PREPARE_OFF turns deferred
+ * prepare off for the specified statement resource.
+ *
+ *
+ *
+ *
+ *
+ * Passing DB2_AUTOCOMMIT_ON turns
+ * autocommit on for the specified connection resource.
+ *
+ * Passing DB2_AUTOCOMMIT_OFF turns
+ * autocommit off for the specified connection resource.
+ *
+ * Passing DB2_FORWARD_ONLY specifies a
+ * forward-only cursor for a statement resource. This is the
+ * default cursor type, and is supported by all database
+ * servers.
+ *
+ * Passing DB2_SCROLLABLE specifies a
+ * scrollable cursor for a statement resource. Scrollable
+ * cursors enable result set rows to be accessed in
+ * non-sequential order, but are only supported by
+ * IBM DB2 Universal Database databases.
+ *
+ * Passing DB2_BINARY specifies that
+ * binary data will be returned as is. This is the default
+ * mode. This is the equivalent of setting
+ * ibm_db2.binmode=1 in php.ini.
+ *
+ * Passing DB2_CONVERT specifies that
+ * binary data will be converted to hexadecimal encoding,
+ * and will be returned as such. This is the equivalent of
+ * setting ibm_db2.binmode=2 in php.ini.
+ *
+ * Passing DB2_PASSTHRU specifies that
+ * binary data will be converted to NULL. This is the
+ * equivalent of setting ibm_db2.binmode=3
+ * in php.ini.
+ *
+ * Passing DB2_CASE_LOWER specifies that
+ * column names of the result set are returned in lower case.
+ *
+ * Passing DB2_CASE_UPPER specifies that
+ * column names of the result set are returned in upper case.
+ *
+ * Passing DB2_CASE_NATURAL specifies that
+ * column names of the result set are returned in natural
+ * case.
+ *
+ * Passing DB2_DEFERRED_PREPARE_ON turns deferred
+ * prepare on for the specified statement resource.
+ *
+ * Passing DB2_DEFERRED_PREPARE_OFF turns deferred
+ * prepare off for the specified statement resource.
+ *
+ * The following new i5/OS options are available in ibm_db2 version 1.5.1
+ * and later. These options apply only when running PHP and ibm_db2 natively on i5 systems.
+ *
+ *
+ * i5_fetch_only
+ *
+ *
+ * DB2_I5_FETCH_ON - Cursors are read-only
+ * and cannot be used for positioned updates or deletes. This
+ * is the default unless SQL_ATTR_FOR_FETCH_ONLY
+ * environment has been set to SQL_FALSE.
+ *
+ *
+ * DB2_I5_FETCH_OFF - Cursors can be used
+ * for positioned updates and deletes.
+ *
+ *
+ *
+ *
+ *
+ * DB2_I5_FETCH_ON - Cursors are read-only
+ * and cannot be used for positioned updates or deletes. This
+ * is the default unless SQL_ATTR_FOR_FETCH_ONLY
+ * environment has been set to SQL_FALSE.
+ *
+ * DB2_I5_FETCH_OFF - Cursors can be used
+ * for positioned updates and deletes.
+ *
+ * The following new option is available in ibm_db2 version 1.8.0 and later.
+ *
+ *
+ * rowcount
+ *
+ *
+ * DB2_ROWCOUNT_PREFETCH_ON - Client can request
+ * the full row count prior to fetching, which means that
+ * db2_num_rows returns the number of rows selected
+ * even when a ROLLFORWARD_ONLY cursor is used.
+ *
+ *
+ * DB2_ROWCOUNT_PREFETCH_OFF - Client cannot request
+ * the full row count prior to fetching.
+ *
+ *
+ *
+ *
+ *
+ * DB2_ROWCOUNT_PREFETCH_ON - Client can request
+ * the full row count prior to fetching, which means that
+ * db2_num_rows returns the number of rows selected
+ * even when a ROLLFORWARD_ONLY cursor is used.
+ *
+ * DB2_ROWCOUNT_PREFETCH_OFF - Client cannot request
+ * the full row count prior to fetching.
+ *
+ * The following new options are available in ibm_db2 version 1.7.0 and later.
+ *
+ *
+ * trusted_user
+ *
+ *
+ * To switch the user to a trusted user, pass the User ID (String)
+ * of the trusted user as the value of this key. This option can
+ * be set on a connection resource only. To use this option, trusted
+ * context must be enabled on the connection resource.
+ *
+ *
+ *
+ *
+ * trusted_password
+ *
+ *
+ * The password (String) that corresponds to the user specified
+ * by the trusted_user key.
+ *
+ *
+ *
+ *
+ *
+ * To switch the user to a trusted user, pass the User ID (String)
+ * of the trusted user as the value of this key. This option can
+ * be set on a connection resource only. To use this option, trusted
+ * context must be enabled on the connection resource.
+ *
+ * The password (String) that corresponds to the user specified
+ * by the trusted_user key.
+ *
+ * The following new options are available in ibm_db2 version 1.6.0 and later.
+ * These options provide useful tracking information that can be accessed during
+ * execution with db2_get_option.
+ *
+ *
+ * When the value in each option is being set, some servers might not handle
+ * the entire length provided and might truncate the value.
+ *
+ *
+ * To ensure that the data specified in each option is converted correctly
+ * when transmitted to a host system, use only the characters A through Z,
+ * 0 through 9, and the underscore (_) or period (.).
+ *
+ *
+ *
+ *
+ * userid
+ *
+ *
+ * SQL_ATTR_INFO_USERID - A pointer to a null-terminated
+ * character string used to identify the client user ID sent to the host
+ * database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ *
+ *
+ *
+ *
+ *
+ * acctstr
+ *
+ *
+ * SQL_ATTR_INFO_ACCTSTR - A pointer to a null-terminated
+ * character string used to identify the client accounting string sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ * applname
+ *
+ *
+ * SQL_ATTR_INFO_APPLNAME - A pointer to a null-terminated
+ * character string used to identify the client application name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ * wrkstnname
+ *
+ *
+ * SQL_ATTR_INFO_WRKSTNNAME - A pointer to a null-terminated
+ * character string used to identify the client workstation name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * When the value in each option is being set, some servers might not handle
+ * the entire length provided and might truncate the value.
+ *
+ * To ensure that the data specified in each option is converted correctly
+ * when transmitted to a host system, use only the characters A through Z,
+ * 0 through 9, and the underscore (_) or period (.).
+ *
+ * SQL_ATTR_INFO_USERID - A pointer to a null-terminated
+ * character string used to identify the client user ID sent to the host
+ * database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 16 characters.
+ * This user-id is not to be confused with the authentication user-id, it is for
+ * identification purposes only and is not used for any authorization.
+ *
+ * SQL_ATTR_INFO_ACCTSTR - A pointer to a null-terminated
+ * character string used to identify the client accounting string sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 200 characters.
+ *
+ * SQL_ATTR_INFO_APPLNAME - A pointer to a null-terminated
+ * character string used to identify the client application name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 32 characters.
+ *
+ * SQL_ATTR_INFO_WRKSTNNAME - A pointer to a null-terminated
+ * character string used to identify the client workstation name sent to the
+ * host database server when using DB2 Connect.
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ *
+ *
+ *
+ * DB2 for z/OS and OS/390 servers support up to a length of 18 characters.
+ * @param int $type Passing DB2_AUTOCOMMIT_ON turns
+ * autocommit on for the specified connection resource.
+ *
+ * Passing DB2_AUTOCOMMIT_OFF turns
+ * autocommit off for the specified connection resource.
+ * @throws IbmDb2Exception
+ *
+ */
+function db2_set_option($resource, array $options, int $type): void
+{
+ error_clear_last();
+ $result = \db2_set_option($resource, $options, $type);
+ if ($result === false) {
+ throw IbmDb2Exception::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\IconvException;
+
+/**
+ * Retrieve internal configuration variables of iconv extension.
+ *
+ * @param string $type The value of the optional type can be:
+ *
+ * all
+ * input_encoding
+ * output_encoding
+ * internal_encoding
+ *
+ * @return mixed Returns the current value of the internal configuration variable if
+ * successful.
+ *
+ * If type is omitted or set to "all",
+ * iconv_get_encoding returns an array that
+ * stores all these variables.
+ * @throws IconvException
+ *
+ */
+function iconv_get_encoding(string $type = "all")
+{
+ error_clear_last();
+ $result = \iconv_get_encoding($type);
+ if ($result === false) {
+ throw IconvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Changes the value of the internal configuration variable specified by
+ * type to charset.
+ *
+ * @param string $type The value of type can be any one of these:
+ *
+ * input_encoding
+ * output_encoding
+ * internal_encoding
+ *
+ * @param string $charset The character set.
+ * @throws IconvException
+ *
+ */
+function iconv_set_encoding(string $type, string $charset): void
+{
+ error_clear_last();
+ $result = \iconv_set_encoding($type, $charset);
+ if ($result === false) {
+ throw IconvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Performs a character set conversion on the string
+ * str from in_charset
+ * to out_charset.
+ *
+ * @param string $in_charset The input charset.
+ * @param string $out_charset The output charset.
+ *
+ * If you append the string //TRANSLIT to
+ * out_charset transliteration is activated. This
+ * means that when a character can't be represented in the target charset,
+ * it can be approximated through one or several similarly looking
+ * characters. If you append the string //IGNORE,
+ * characters that cannot be represented in the target charset are silently
+ * discarded. Otherwise, E_NOTICE is generated and the function
+ * will return FALSE.
+ *
+ * If and how //TRANSLIT works exactly depends on the
+ * system's iconv() implementation (cf. ICONV_IMPL).
+ * Some implementations are known to ignore //TRANSLIT,
+ * so the conversion is likely to fail for characters which are illegal for
+ * the out_charset.
+ * @param string $str The string to be converted.
+ * @return string Returns the converted string.
+ * @throws IconvException
+ *
+ */
+function iconv(string $in_charset, string $out_charset, string $str): string
+{
+ error_clear_last();
+ $result = \iconv($in_charset, $out_charset, $str);
+ if ($result === false) {
+ throw IconvException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ImageException;
+
+/**
+ * The getimagesize function will determine the
+ * size of any supported given image file and return the dimensions along with
+ * the file type and a height/width text string to be used inside a
+ * normal HTML IMG tag and the
+ * correspondent HTTP content type.
+ *
+ * getimagesize can also return some more information
+ * in imageinfo parameter.
+ *
+ * @param string $filename This parameter specifies the file you wish to retrieve information
+ * about. It can reference a local file or (configuration permitting) a
+ * remote file using one of the supported streams.
+ * @param array $imageinfo This optional parameter allows you to extract some extended
+ * information from the image file. Currently, this will return the
+ * different JPG APP markers as an associative array.
+ * Some programs use these APP markers to embed text information in
+ * images. A very common one is to embed
+ * IPTC information in the APP13 marker.
+ * You can use the iptcparse function to parse the
+ * binary APP13 marker into something readable.
+ *
+ * The imageinfo only supports
+ * JFIF files.
+ * @return array Returns an array with up to 7 elements. Not all image types will include
+ * the channels and bits elements.
+ *
+ * Index 0 and 1 contains respectively the width and the height of the image.
+ *
+ * Index 2 is one of the IMAGETYPE_XXX constants indicating
+ * the type of the image.
+ *
+ * Index 3 is a text string with the correct
+ * height="yyy" width="xxx" string that can be used
+ * directly in an IMG tag.
+ *
+ * mime is the correspondant MIME type of the image.
+ * This information can be used to deliver images with the correct HTTP
+ * Content-type header:
+ *
+ * getimagesize and MIME types
+ *
+ *
+ * ]]>
+ *
+ *
+ *
+ * channels will be 3 for RGB pictures and 4 for CMYK
+ * pictures.
+ *
+ * bits is the number of bits for each color.
+ *
+ * For some image types, the presence of channels and
+ * bits values can be a bit
+ * confusing. As an example, GIF always uses 3 channels
+ * per pixel, but the number of bits per pixel cannot be calculated for an
+ * animated GIF with a global color table.
+ *
+ * On failure, FALSE is returned.
+ * @throws ImageException
+ *
+ */
+function getimagesize(string $filename, array &$imageinfo = null): array
+{
+ error_clear_last();
+ $result = \getimagesize($filename, $imageinfo);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * image2wbmp outputs or save a WBMP
+ * version of the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param string|null $filename Path to the saved file. If not given, the raw image stream will be
+ * output directly.
+ * @param int $foreground You can set the foreground color with this parameter by setting an
+ * identifier obtained from imagecolorallocate.
+ * The default foreground color is black.
+ * @throws ImageException
+ *
+ */
+function image2wbmp($image, ?string $filename = null, int $foreground = null): void
+{
+ error_clear_last();
+ if ($foreground !== null) {
+ $result = \image2wbmp($image, $filename, $foreground);
+ } elseif ($filename !== null) {
+ $result = \image2wbmp($image, $filename);
+ } else {
+ $result = \image2wbmp($image);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $affine Array with keys 0 to 5.
+ * @param array $clip Array with keys "x", "y", "width" and "height".
+ * @return resource Return affined image resource on success.
+ * @throws ImageException
+ *
+ */
+function imageaffine($image, array $affine, array $clip = null)
+{
+ error_clear_last();
+ if ($clip !== null) {
+ $result = \imageaffine($image, $affine, $clip);
+ } else {
+ $result = \imageaffine($image, $affine);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the concatenation of two affine transformation matrices,
+ * what is useful if multiple transformations should be applied to the same
+ * image in one go.
+ *
+ * @param array $m1 An affine transformation matrix (an array with keys
+ * 0 to 5 and float values).
+ * @param array $m2 An affine transformation matrix (an array with keys
+ * 0 to 5 and float values).
+ * @return array{0:float,1:float,2:float,3:float,4:float,5:float} An affine transformation matrix (an array with keys
+ * 0 to 5 and float values).
+ * @throws ImageException
+ *
+ */
+function imageaffinematrixconcat(array $m1, array $m2): array
+{
+ error_clear_last();
+ $result = \imageaffinematrixconcat($m1, $m2);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns an affine transformation matrix.
+ *
+ * @param int $type One of the IMG_AFFINE_* constants.
+ * @param array|float $options If type is IMG_AFFINE_TRANSLATE
+ * or IMG_AFFINE_SCALE,
+ * options has to be an array with keys x
+ * and y, both having float values.
+ *
+ * If type is IMG_AFFINE_ROTATE,
+ * IMG_AFFINE_SHEAR_HORIZONTAL or IMG_AFFINE_SHEAR_VERTICAL,
+ * options has to be a float specifying the angle.
+ * @return array{0:float,1:float,2:float,3:float,4:float,5:float} An affine transformation matrix (an array with keys
+ * 0 to 5 and float values).
+ * @throws ImageException
+ *
+ */
+function imageaffinematrixget(int $type, $options = null): array
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \imageaffinematrixget($type, $options);
+ } else {
+ $result = \imageaffinematrixget($type);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagealphablending allows for two different
+ * modes of drawing on truecolor images. In blending mode, the
+ * alpha channel component of the color supplied to all drawing function,
+ * such as imagesetpixel determines how much of the
+ * underlying color should be allowed to shine through. As a result, gd
+ * automatically blends the existing color at that point with the drawing color,
+ * and stores the result in the image. The resulting pixel is opaque. In
+ * non-blending mode, the drawing color is copied literally with its alpha channel
+ * information, replacing the destination pixel. Blending mode is not available
+ * when drawing on palette images.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param bool $blendmode Whether to enable the blending mode or not. On true color images
+ * the default value is TRUE otherwise the default value is FALSE
+ * @throws ImageException
+ *
+ */
+function imagealphablending($image, bool $blendmode): void
+{
+ error_clear_last();
+ $result = \imagealphablending($image, $blendmode);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Activate the fast drawing antialiased methods for lines and wired polygons.
+ * It does not support alpha components. It works using a direct blend
+ * operation. It works only with truecolor images.
+ *
+ * Thickness and styled are not supported.
+ *
+ * Using antialiased primitives with transparent background color can end with
+ * some unexpected results. The blend method uses the background color as any
+ * other colors. The lack of alpha component support does not allow an alpha
+ * based antialiasing method.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param bool $enabled Whether to enable antialiasing or not.
+ * @throws ImageException
+ *
+ */
+function imageantialias($image, bool $enabled): void
+{
+ error_clear_last();
+ $result = \imageantialias($image, $enabled);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagearc draws an arc of circle centered at the given
+ * coordinates.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $cx x-coordinate of the center.
+ * @param int $cy y-coordinate of the center.
+ * @param int $width The arc width.
+ * @param int $height The arc height.
+ * @param int $start The arc start angle, in degrees.
+ * @param int $end The arc end angle, in degrees.
+ * 0° is located at the three-o'clock position, and the arc is drawn
+ * clockwise.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagearc($image, int $cx, int $cy, int $width, int $height, int $start, int $end, int $color): void
+{
+ error_clear_last();
+ $result = \imagearc($image, $cx, $cy, $width, $height, $start, $end, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs or saves a BMP version of the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ *
+ * NULL is invalid if the compressed arguments is
+ * not used.
+ * @param bool $compressed Whether the BMP should be compressed with run-length encoding (RLE), or not.
+ * @throws ImageException
+ *
+ */
+function imagebmp($image, $to = null, bool $compressed = true): void
+{
+ error_clear_last();
+ $result = \imagebmp($image, $to, $compressed);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagechar draws the first character of
+ * c in the image identified by
+ * image with its upper-left at
+ * x,y (top left is 0,
+ * 0) with the color color.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $font Can be 1, 2, 3, 4, 5 for built-in
+ * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your
+ * own font identifiers registered with imageloadfont.
+ * @param int $x x-coordinate of the start.
+ * @param int $y y-coordinate of the start.
+ * @param string $c The character to draw.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagechar($image, int $font, int $x, int $y, string $c, int $color): void
+{
+ error_clear_last();
+ $result = \imagechar($image, $font, $x, $y, $c, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws the character c vertically at the specified
+ * coordinate on the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $font Can be 1, 2, 3, 4, 5 for built-in
+ * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your
+ * own font identifiers registered with imageloadfont.
+ * @param int $x x-coordinate of the start.
+ * @param int $y y-coordinate of the start.
+ * @param string $c The character to draw.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagecharup($image, int $font, int $x, int $y, string $c, int $color): void
+{
+ error_clear_last();
+ $result = \imagecharup($image, $font, $x, $y, $c, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the index of the color of the pixel at the
+ * specified location in the image specified by image.
+ *
+ * If the image is a
+ * truecolor image, this function returns the RGB value of that pixel as
+ * integer. Use bitshifting and masking to access the distinct red, green and blue
+ * component values:
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x x-coordinate of the point.
+ * @param int $y y-coordinate of the point.
+ * @return int Returns the index of the color.
+ * @throws ImageException
+ *
+ */
+function imagecolorat($image, int $x, int $y): int
+{
+ error_clear_last();
+ $result = \imagecolorat($image, $x, $y);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * De-allocates a color previously allocated with
+ * imagecolorallocate or
+ * imagecolorallocatealpha.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $color The color identifier.
+ * @throws ImageException
+ *
+ */
+function imagecolordeallocate($image, int $color): void
+{
+ error_clear_last();
+ $result = \imagecolordeallocate($image, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes the colors of the palette version of an image more closely match the true color version.
+ *
+ * @param resource $image1 A truecolor image resource.
+ * @param resource $image2 A palette image resource pointing to an image that has the same
+ * size as image1.
+ * @throws ImageException
+ *
+ */
+function imagecolormatch($image1, $image2): void
+{
+ error_clear_last();
+ $result = \imagecolormatch($image1, $image2);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Applies a convolution matrix on the image, using the given coefficient and
+ * offset.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $matrix A 3x3 matrix: an array of three arrays of three floats.
+ * @param float $div The divisor of the result of the convolution, used for normalization.
+ * @param float $offset Color offset.
+ * @throws ImageException
+ *
+ */
+function imageconvolution($image, array $matrix, float $div, float $offset): void
+{
+ error_clear_last();
+ $result = \imageconvolution($image, $matrix, $div, $offset);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Copy a part of src_im onto
+ * dst_im starting at the x,y coordinates
+ * src_x, src_y with
+ * a width of src_w and a height of
+ * src_h. The portion defined will be copied
+ * onto the x,y coordinates, dst_x and
+ * dst_y.
+ *
+ * @param resource $dst_im Destination image resource.
+ * @param resource $src_im Source image resource.
+ * @param int $dst_x x-coordinate of destination point.
+ * @param int $dst_y y-coordinate of destination point.
+ * @param int $src_x x-coordinate of source point.
+ * @param int $src_y y-coordinate of source point.
+ * @param int $src_w Source width.
+ * @param int $src_h Source height.
+ * @throws ImageException
+ *
+ */
+function imagecopy($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h): void
+{
+ error_clear_last();
+ $result = \imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Copy a part of src_im onto
+ * dst_im starting at the x,y coordinates
+ * src_x, src_y with
+ * a width of src_w and a height of
+ * src_h. The portion defined will be copied
+ * onto the x,y coordinates, dst_x and
+ * dst_y.
+ *
+ * @param resource $dst_im Destination image resource.
+ * @param resource $src_im Source image resource.
+ * @param int $dst_x x-coordinate of destination point.
+ * @param int $dst_y y-coordinate of destination point.
+ * @param int $src_x x-coordinate of source point.
+ * @param int $src_y y-coordinate of source point.
+ * @param int $src_w Source width.
+ * @param int $src_h Source height.
+ * @param int $pct The two images will be merged according to pct
+ * which can range from 0 to 100. When pct = 0,
+ * no action is taken, when 100 this function behaves identically
+ * to imagecopy for pallete images, except for
+ * ignoring alpha components, while it implements alpha transparency
+ * for true colour images.
+ * @throws ImageException
+ *
+ */
+function imagecopymerge($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h, int $pct): void
+{
+ error_clear_last();
+ $result = \imagecopymerge($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagecopymergegray copy a part of src_im onto
+ * dst_im starting at the x,y coordinates
+ * src_x, src_y with
+ * a width of src_w and a height of
+ * src_h. The portion defined will be copied
+ * onto the x,y coordinates, dst_x and
+ * dst_y.
+ *
+ * This function is identical to imagecopymerge except
+ * that when merging it preserves the hue of the source by converting
+ * the destination pixels to gray scale before the copy operation.
+ *
+ * @param resource $dst_im Destination image resource.
+ * @param resource $src_im Source image resource.
+ * @param int $dst_x x-coordinate of destination point.
+ * @param int $dst_y y-coordinate of destination point.
+ * @param int $src_x x-coordinate of source point.
+ * @param int $src_y y-coordinate of source point.
+ * @param int $src_w Source width.
+ * @param int $src_h Source height.
+ * @param int $pct The src_im will be changed to grayscale according
+ * to pct where 0 is fully grayscale and 100 is
+ * unchanged. When pct = 100 this function behaves
+ * identically to imagecopy for pallete images, except for
+ * ignoring alpha components, while
+ * it implements alpha transparency for true colour images.
+ * @throws ImageException
+ *
+ */
+function imagecopymergegray($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h, int $pct): void
+{
+ error_clear_last();
+ $result = \imagecopymergegray($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagecopyresampled copies a rectangular
+ * portion of one image to another image, smoothly interpolating pixel
+ * values so that, in particular, reducing the size of an image still
+ * retains a great deal of clarity.
+ *
+ * In other words, imagecopyresampled will take a
+ * rectangular area from src_image of width
+ * src_w and height src_h at
+ * position (src_x,src_y)
+ * and place it in a rectangular area of dst_image
+ * of width dst_w and height dst_h
+ * at position (dst_x,dst_y).
+ *
+ * If the source and destination coordinates and width and heights
+ * differ, appropriate stretching or shrinking of the image fragment
+ * will be performed. The coordinates refer to the upper left
+ * corner. This function can be used to copy regions within the
+ * same image (if dst_image is the same as
+ * src_image) but if the regions overlap the
+ * results will be unpredictable.
+ *
+ * @param resource $dst_image Destination image resource.
+ * @param resource $src_image Source image resource.
+ * @param int $dst_x x-coordinate of destination point.
+ * @param int $dst_y y-coordinate of destination point.
+ * @param int $src_x x-coordinate of source point.
+ * @param int $src_y y-coordinate of source point.
+ * @param int $dst_w Destination width.
+ * @param int $dst_h Destination height.
+ * @param int $src_w Source width.
+ * @param int $src_h Source height.
+ * @throws ImageException
+ *
+ */
+function imagecopyresampled($dst_image, $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_w, int $dst_h, int $src_w, int $src_h): void
+{
+ error_clear_last();
+ $result = \imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagecopyresized copies a rectangular
+ * portion of one image to another image.
+ * dst_image is the destination image,
+ * src_image is the source image identifier.
+ *
+ * In other words, imagecopyresized will take a
+ * rectangular area from src_image of width
+ * src_w and height src_h at
+ * position (src_x,src_y)
+ * and place it in a rectangular area of dst_image
+ * of width dst_w and height dst_h
+ * at position (dst_x,dst_y).
+ *
+ * If the source and destination coordinates and width and heights
+ * differ, appropriate stretching or shrinking of the image fragment
+ * will be performed. The coordinates refer to the upper left
+ * corner. This function can be used to copy regions within the
+ * same image (if dst_image is the same as
+ * src_image) but if the regions overlap the
+ * results will be unpredictable.
+ *
+ * @param resource $dst_image Destination image resource.
+ * @param resource $src_image Source image resource.
+ * @param int $dst_x x-coordinate of destination point.
+ * @param int $dst_y y-coordinate of destination point.
+ * @param int $src_x x-coordinate of source point.
+ * @param int $src_y y-coordinate of source point.
+ * @param int $dst_w Destination width.
+ * @param int $dst_h Destination height.
+ * @param int $src_w Source width.
+ * @param int $src_h Source height.
+ * @throws ImageException
+ *
+ */
+function imagecopyresized($dst_image, $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_w, int $dst_h, int $src_w, int $src_h): void
+{
+ error_clear_last();
+ $result = \imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagecreate returns an image identifier
+ * representing a blank image of specified size.
+ *
+ * In general, we recommend the use of
+ * imagecreatetruecolor instead of
+ * imagecreate so that image processing occurs on the
+ * highest quality image possible. If you want to output a palette image, then
+ * imagetruecolortopalette should be called immediately
+ * before saving the image with imagepng or
+ * imagegif.
+ *
+ * @param int $width The image width.
+ * @param int $height The image height.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreate(int $width, int $height)
+{
+ error_clear_last();
+ $result = \imagecreate($width, $height);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefrombmp returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the BMP image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefrombmp(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefrombmp($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a new image from GD file or URL.
+ *
+ * @param string $filename Path to the GD file.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromgd(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromgd($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a new image from GD2 file or URL.
+ *
+ * @param string $filename Path to the GD2 image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromgd2(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromgd2($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a new image from a given part of GD2 file or URL.
+ *
+ * @param string $filename Path to the GD2 image.
+ * @param int $srcX x-coordinate of source point.
+ * @param int $srcY y-coordinate of source point.
+ * @param int $width Source width.
+ * @param int $height Source height.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromgd2part(string $filename, int $srcX, int $srcY, int $width, int $height)
+{
+ error_clear_last();
+ $result = \imagecreatefromgd2part($filename, $srcX, $srcY, $width, $height);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromgif returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the GIF image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromgif(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromgif($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromjpeg returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the JPEG image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromjpeg(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromjpeg($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefrompng returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the PNG image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefrompng(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefrompng($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromwbmp returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the WBMP image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromwbmp(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromwbmp($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromwebp returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the WebP image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromwebp(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromwebp($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromxbm returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the XBM image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromxbm(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromxbm($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatefromxpm returns an image identifier
+ * representing the image obtained from the given filename.
+ *
+ * @param string $filename Path to the XPM image.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatefromxpm(string $filename)
+{
+ error_clear_last();
+ $result = \imagecreatefromxpm($filename);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagecreatetruecolor returns an image identifier
+ * representing a black image of the specified size.
+ *
+ * @param int $width Image width.
+ * @param int $height Image height.
+ * @return resource Returns an image resource identifier on success, FALSE on errors.
+ * @throws ImageException
+ *
+ */
+function imagecreatetruecolor(int $width, int $height)
+{
+ error_clear_last();
+ $result = \imagecreatetruecolor($width, $height);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Crops an image to the given rectangular area and returns the resulting image.
+ * The given image is not modified.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $rect The cropping rectangle as array with keys
+ * x, y, width and
+ * height.
+ * @return resource Return cropped image resource on success.
+ * @throws ImageException
+ *
+ */
+function imagecrop($image, array $rect)
+{
+ error_clear_last();
+ $result = \imagecrop($image, $rect);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Automatically crops an image according to the given
+ * mode.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $mode One of the following constants:
+ * @param float $threshold
+ * @param int $color
+ * @return resource Returns a cropped image resource on success.
+ * If the complete image was cropped, imagecrop returns FALSE.
+ * @throws ImageException
+ *
+ */
+function imagecropauto($image, int $mode = IMG_CROP_DEFAULT, float $threshold = .5, int $color = -1)
+{
+ error_clear_last();
+ $result = \imagecropauto($image, $mode, $threshold, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is deprecated. Use combination of
+ * imagesetstyle and imageline
+ * instead.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x1 Upper left x coordinate.
+ * @param int $y1 Upper left y coordinate 0, 0 is the top left corner of the image.
+ * @param int $x2 Bottom right x coordinate.
+ * @param int $y2 Bottom right y coordinate.
+ * @param int $color The fill color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagedashedline($image, int $x1, int $y1, int $x2, int $y2, int $color): void
+{
+ error_clear_last();
+ $result = \imagedashedline($image, $x1, $y1, $x2, $y2, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagedestroy frees any memory associated
+ * with image image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @throws ImageException
+ *
+ */
+function imagedestroy($image): void
+{
+ error_clear_last();
+ $result = \imagedestroy($image);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws an ellipse centered at the specified coordinates.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $cx x-coordinate of the center.
+ * @param int $cy y-coordinate of the center.
+ * @param int $width The ellipse width.
+ * @param int $height The ellipse height.
+ * @param int $color The color of the ellipse. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imageellipse($image, int $cx, int $cy, int $width, int $height, int $color): void
+{
+ error_clear_last();
+ $result = \imageellipse($image, $cx, $cy, $width, $height, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Performs a flood fill starting at the given coordinate (top left is 0, 0)
+ * with the given color in the
+ * image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x x-coordinate of start point.
+ * @param int $y y-coordinate of start point.
+ * @param int $color The fill color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagefill($image, int $x, int $y, int $color): void
+{
+ error_clear_last();
+ $result = \imagefill($image, $x, $y, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a partial arc centered at the specified coordinate in the
+ * given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $cx x-coordinate of the center.
+ * @param int $cy y-coordinate of the center.
+ * @param int $width The arc width.
+ * @param int $height The arc height.
+ * @param int $start The arc start angle, in degrees.
+ * @param int $end The arc end angle, in degrees.
+ * 0° is located at the three-o'clock position, and the arc is drawn
+ * clockwise.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @param int $style A bitwise OR of the following possibilities:
+ *
+ * IMG_ARC_PIE
+ * IMG_ARC_CHORD
+ * IMG_ARC_NOFILL
+ * IMG_ARC_EDGED
+ *
+ * IMG_ARC_PIE and IMG_ARC_CHORD are
+ * mutually exclusive; IMG_ARC_CHORD just
+ * connects the starting and ending angles with a straight line, while
+ * IMG_ARC_PIE produces a rounded edge.
+ * IMG_ARC_NOFILL indicates that the arc
+ * or chord should be outlined, not filled. IMG_ARC_EDGED,
+ * used together with IMG_ARC_NOFILL, indicates that the
+ * beginning and ending angles should be connected to the center - this is a
+ * good way to outline (rather than fill) a 'pie slice'.
+ * @throws ImageException
+ *
+ */
+function imagefilledarc($image, int $cx, int $cy, int $width, int $height, int $start, int $end, int $color, int $style): void
+{
+ error_clear_last();
+ $result = \imagefilledarc($image, $cx, $cy, $width, $height, $start, $end, $color, $style);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws an ellipse centered at the specified coordinate on the given
+ * image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $cx x-coordinate of the center.
+ * @param int $cy y-coordinate of the center.
+ * @param int $width The ellipse width.
+ * @param int $height The ellipse height.
+ * @param int $color The fill color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagefilledellipse($image, int $cx, int $cy, int $width, int $height, int $color): void
+{
+ error_clear_last();
+ $result = \imagefilledellipse($image, $cx, $cy, $width, $height, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagefilledpolygon creates a filled polygon
+ * in the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $points An array containing the x and y
+ * coordinates of the polygons vertices consecutively.
+ * @param int $num_points Total number of points (vertices), which must be at least 3.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagefilledpolygon($image, array $points, int $num_points, int $color): void
+{
+ error_clear_last();
+ $result = \imagefilledpolygon($image, $points, $num_points, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a rectangle filled with color in the given
+ * image starting at point 1 and ending at point 2.
+ * 0, 0 is the top left corner of the image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x1 x-coordinate for point 1.
+ * @param int $y1 y-coordinate for point 1.
+ * @param int $x2 x-coordinate for point 2.
+ * @param int $y2 y-coordinate for point 2.
+ * @param int $color The fill color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagefilledrectangle($image, int $x1, int $y1, int $x2, int $y2, int $color): void
+{
+ error_clear_last();
+ $result = \imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagefilltoborder performs a flood fill
+ * whose border color is defined by border.
+ * The starting point for the fill is x,
+ * y (top left is 0, 0) and the region is
+ * filled with color color.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x x-coordinate of start.
+ * @param int $y y-coordinate of start.
+ * @param int $border The border color. A color identifier created with imagecolorallocate.
+ * @param int $color The fill color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagefilltoborder($image, int $x, int $y, int $border, int $color): void
+{
+ error_clear_last();
+ $result = \imagefilltoborder($image, $x, $y, $border, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagefilter applies the given filter
+ * filtertype on the image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $filtertype filtertype can be one of the following:
+ *
+ *
+ *
+ * IMG_FILTER_NEGATE: Reverses all colors of
+ * the image.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_GRAYSCALE: Converts the image into
+ * grayscale by changing the red, green and blue components to their
+ * weighted sum using the same coefficients as the REC.601 luma (Y')
+ * calculation. The alpha components are retained. For palette images the
+ * result may differ due to palette limitations.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_BRIGHTNESS: Changes the brightness
+ * of the image. Use arg1 to set the level of
+ * brightness. The range for the brightness is -255 to 255.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_CONTRAST: Changes the contrast of
+ * the image. Use arg1 to set the level of
+ * contrast.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_COLORIZE: Like
+ * IMG_FILTER_GRAYSCALE, except you can specify the
+ * color. Use arg1, arg2 and
+ * arg3 in the form of
+ * red, green,
+ * blue and arg4 for the
+ * alpha channel. The range for each color is 0 to 255.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_EDGEDETECT: Uses edge detection to
+ * highlight the edges in the image.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_EMBOSS: Embosses the image.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_GAUSSIAN_BLUR: Blurs the image using
+ * the Gaussian method.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SELECTIVE_BLUR: Blurs the image.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_MEAN_REMOVAL: Uses mean removal to
+ * achieve a "sketchy" effect.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SMOOTH: Makes the image smoother.
+ * Use arg1 to set the level of smoothness.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_PIXELATE: Applies pixelation effect
+ * to the image, use arg1 to set the block size
+ * and arg2 to set the pixelation effect mode.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SCATTER: Applies scatter effect
+ * to the image, use arg1 and
+ * arg2 to define the effect strength and
+ * additionally arg3 to only apply the
+ * on select pixel colors.
+ *
+ *
+ *
+ * @param int $arg1
+ *
+ *
+ * IMG_FILTER_BRIGHTNESS: Brightness level.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_CONTRAST: Contrast level.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_COLORIZE: Value of red component.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SMOOTH: Smoothness level.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_PIXELATE: Block size in pixels.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SCATTER: Effect substraction level.
+ * This must not be higher or equal to the addition level set with
+ * arg2.
+ *
+ *
+ *
+ * @param int $arg2
+ *
+ *
+ * IMG_FILTER_COLORIZE: Value of green component.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_PIXELATE: Whether to use advanced pixelation
+ * effect or not (defaults to FALSE).
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SCATTER: Effect addition level.
+ *
+ *
+ *
+ * @param int $arg3
+ *
+ *
+ * IMG_FILTER_COLORIZE: Value of blue component.
+ *
+ *
+ *
+ *
+ * IMG_FILTER_SCATTER: Optional array indexed color values
+ * to apply effect at.
+ *
+ *
+ *
+ * @param int $arg4
+ *
+ *
+ * IMG_FILTER_COLORIZE: Alpha channel, A value
+ * between 0 and 127. 0 indicates completely opaque while 127 indicates
+ * completely transparent.
+ *
+ *
+ *
+ * @throws ImageException
+ *
+ */
+function imagefilter($image, int $filtertype, int $arg1 = null, int $arg2 = null, int $arg3 = null, int $arg4 = null): void
+{
+ error_clear_last();
+ if ($arg4 !== null) {
+ $result = \imagefilter($image, $filtertype, $arg1, $arg2, $arg3, $arg4);
+ } elseif ($arg3 !== null) {
+ $result = \imagefilter($image, $filtertype, $arg1, $arg2, $arg3);
+ } elseif ($arg2 !== null) {
+ $result = \imagefilter($image, $filtertype, $arg1, $arg2);
+ } elseif ($arg1 !== null) {
+ $result = \imagefilter($image, $filtertype, $arg1);
+ } else {
+ $result = \imagefilter($image, $filtertype);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Flips the image image using the given
+ * mode.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $mode Flip mode, this can be one of the IMG_FLIP_* constants:
+ *
+ *
+ *
+ *
+ *
+ * Constant
+ * Meaning
+ *
+ *
+ *
+ *
+ * IMG_FLIP_HORIZONTAL
+ *
+ * Flips the image horizontally.
+ *
+ *
+ *
+ * IMG_FLIP_VERTICAL
+ *
+ * Flips the image vertically.
+ *
+ *
+ *
+ * IMG_FLIP_BOTH
+ *
+ * Flips the image both horizontally and vertically.
+ *
+ *
+ *
+ *
+ *
+ * @throws ImageException
+ *
+ */
+function imageflip($image, int $mode): void
+{
+ error_clear_last();
+ $result = \imageflip($image, $mode);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Applies gamma correction to the given gd image
+ * given an input and an output gamma.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param float $inputgamma The input gamma.
+ * @param float $outputgamma The output gamma.
+ * @throws ImageException
+ *
+ */
+function imagegammacorrect($image, float $inputgamma, float $outputgamma): void
+{
+ error_clear_last();
+ $result = \imagegammacorrect($image, $inputgamma, $outputgamma);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs a GD image to the given to.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @throws ImageException
+ *
+ */
+function imagegd($image, $to = null): void
+{
+ error_clear_last();
+ $result = \imagegd($image, $to);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs a GD2 image to the given to.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @param int $chunk_size Chunk size.
+ * @param int $type Either IMG_GD2_RAW or
+ * IMG_GD2_COMPRESSED. Default is
+ * IMG_GD2_RAW.
+ * @throws ImageException
+ *
+ */
+function imagegd2($image, $to = null, int $chunk_size = 128, int $type = IMG_GD2_RAW): void
+{
+ error_clear_last();
+ $result = \imagegd2($image, $to, $chunk_size, $type);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagegif creates the GIF
+ * file in to from the image image. The
+ * image argument is the return from the
+ * imagecreate or imagecreatefrom*
+ * function.
+ *
+ * The image format will be GIF87a unless the
+ * image has been made transparent with
+ * imagecolortransparent, in which case the
+ * image format will be GIF89a.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @throws ImageException
+ *
+ */
+function imagegif($image, $to = null): void
+{
+ error_clear_last();
+ $result = \imagegif($image, $to);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Grabs a screenshot of the whole screen.
+ *
+ * @return resource Returns an image resource identifier on success, FALSE on failure.
+ * @throws ImageException
+ *
+ */
+function imagegrabscreen()
+{
+ error_clear_last();
+ $result = \imagegrabscreen();
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Grabs a window or its client area using a windows handle (HWND property in COM instance)
+ *
+ * @param int $window_handle The HWND window ID.
+ * @param int $client_area Include the client area of the application window.
+ * @return resource Returns an image resource identifier on success, FALSE on failure.
+ * @throws ImageException
+ *
+ */
+function imagegrabwindow(int $window_handle, int $client_area = 0)
+{
+ error_clear_last();
+ $result = \imagegrabwindow($window_handle, $client_area);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagejpeg creates a JPEG file from
+ * the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @param int $quality quality is optional, and ranges from 0 (worst
+ * quality, smaller file) to 100 (best quality, biggest file). The
+ * default (-1) uses the default IJG quality value (about 75).
+ * @throws ImageException
+ *
+ */
+function imagejpeg($image, $to = null, int $quality = -1): void
+{
+ error_clear_last();
+ $result = \imagejpeg($image, $to, $quality);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the alpha blending flag to use layering effects.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $effect One of the following constants:
+ *
+ *
+ * IMG_EFFECT_REPLACE
+ *
+ *
+ * Use pixel replacement (equivalent of passing TRUE to
+ * imagealphablending)
+ *
+ *
+ *
+ *
+ * IMG_EFFECT_ALPHABLEND
+ *
+ *
+ * Use normal pixel blending (equivalent of passing FALSE to
+ * imagealphablending)
+ *
+ *
+ *
+ *
+ * IMG_EFFECT_NORMAL
+ *
+ *
+ * Same as IMG_EFFECT_ALPHABLEND.
+ *
+ *
+ *
+ *
+ * IMG_EFFECT_OVERLAY
+ *
+ *
+ * Overlay has the effect that black background pixels will remain
+ * black, white background pixels will remain white, but grey
+ * background pixels will take the colour of the foreground pixel.
+ *
+ *
+ *
+ *
+ * IMG_EFFECT_MULTIPLY
+ *
+ *
+ * Overlays with a multiply effect.
+ *
+ *
+ *
+ *
+ * @throws ImageException
+ *
+ */
+function imagelayereffect($image, int $effect): void
+{
+ error_clear_last();
+ $result = \imagelayereffect($image, $effect);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a line between the two given points.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x1 x-coordinate for first point.
+ * @param int $y1 y-coordinate for first point.
+ * @param int $x2 x-coordinate for second point.
+ * @param int $y2 y-coordinate for second point.
+ * @param int $color The line color. A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imageline($image, int $x1, int $y1, int $x2, int $y2, int $color): void
+{
+ error_clear_last();
+ $result = \imageline($image, $x1, $y1, $x2, $y2, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imageloadfont loads a user-defined bitmap and returns
+ * its identifier.
+ *
+ * @param string $file The font file format is currently binary and architecture
+ * dependent. This means you should generate the font files on the
+ * same type of CPU as the machine you are running PHP on.
+ *
+ *
+ * Font file format
+ *
+ *
+ *
+ * byte position
+ * C data type
+ * description
+ *
+ *
+ *
+ *
+ * byte 0-3
+ * int
+ * number of characters in the font
+ *
+ *
+ * byte 4-7
+ * int
+ *
+ * value of first character in the font (often 32 for space)
+ *
+ *
+ *
+ * byte 8-11
+ * int
+ * pixel width of each character
+ *
+ *
+ * byte 12-15
+ * int
+ * pixel height of each character
+ *
+ *
+ * byte 16-
+ * char
+ *
+ * array with character data, one byte per pixel in each
+ * character, for a total of (nchars*width*height) bytes.
+ *
+ *
+ *
+ *
+ *
+ * @return int The font identifier which is always bigger than 5 to avoid conflicts with
+ * built-in fontss.
+ * @throws ImageException
+ *
+ */
+function imageloadfont(string $file): int
+{
+ error_clear_last();
+ $result = \imageloadfont($file);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imageopenpolygon draws an open polygon on the given
+ * image. Contrary to imagepolygon,
+ * no line is drawn between the last and the first point.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $points An array containing the polygon's vertices, e.g.:
+ *
+ *
+ *
+ *
+ * points[0]
+ * = x0
+ *
+ *
+ * points[1]
+ * = y0
+ *
+ *
+ * points[2]
+ * = x1
+ *
+ *
+ * points[3]
+ * = y1
+ *
+ *
+ *
+ *
+ * @param int $num_points Total number of points (vertices), which must be at least 3.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imageopenpolygon($image, array $points, int $num_points, int $color): void
+{
+ error_clear_last();
+ $result = \imageopenpolygon($image, $points, $num_points, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs or saves a PNG image from the given
+ * image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ *
+ * NULL is invalid if the quality and
+ * filters arguments are not used.
+ * @param int $quality Compression level: from 0 (no compression) to 9.
+ * The default (-1) uses the zlib compression default.
+ * For more information see the zlib manual.
+ * @param int $filters Allows reducing the PNG file size. It is a bitmask field which may be
+ * set to any combination of the PNG_FILTER_XXX
+ * constants. PNG_NO_FILTER or
+ * PNG_ALL_FILTERS may also be used to respectively
+ * disable or activate all filters.
+ * The default value (-1) disables filtering.
+ * @throws ImageException
+ *
+ */
+function imagepng($image, $to = null, int $quality = -1, int $filters = -1): void
+{
+ error_clear_last();
+ $result = \imagepng($image, $to, $quality, $filters);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagepolygon creates a polygon in the given
+ * image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $points An array containing the polygon's vertices, e.g.:
+ *
+ *
+ *
+ *
+ * points[0]
+ * = x0
+ *
+ *
+ * points[1]
+ * = y0
+ *
+ *
+ * points[2]
+ * = x1
+ *
+ *
+ * points[3]
+ * = y1
+ *
+ *
+ *
+ *
+ * @param int $num_points Total number of points (vertices), which must be at least 3.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagepolygon($image, array $points, int $num_points, int $color): void
+{
+ error_clear_last();
+ $result = \imagepolygon($image, $points, $num_points, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagerectangle creates a rectangle starting at
+ * the specified coordinates.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x1 Upper left x coordinate.
+ * @param int $y1 Upper left y coordinate
+ * 0, 0 is the top left corner of the image.
+ * @param int $x2 Bottom right x coordinate.
+ * @param int $y2 Bottom right y coordinate.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagerectangle($image, int $x1, int $y1, int $x2, int $y2, int $color): void
+{
+ error_clear_last();
+ $result = \imagerectangle($image, $x1, $y1, $x2, $y2, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Rotates the image image using the given
+ * angle in degrees.
+ *
+ * The center of rotation is the center of the image, and the rotated
+ * image may have different dimensions than the original image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param float $angle Rotation angle, in degrees. The rotation angle is interpreted as the
+ * number of degrees to rotate the image anticlockwise.
+ * @param int $bgd_color Specifies the color of the uncovered zone after the rotation
+ * @param int $dummy This parameter is unused.
+ * @return resource Returns an image resource for the rotated image.
+ * @throws ImageException
+ *
+ */
+function imagerotate($image, float $angle, int $bgd_color, int $dummy = 0)
+{
+ error_clear_last();
+ $result = \imagerotate($image, $angle, $bgd_color, $dummy);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagesavealpha sets the flag which determines whether to retain
+ * full alpha channel information (as opposed to single-color transparency)
+ * when saving PNG images.
+ *
+ * Alphablending has to be disabled (imagealphablending($im, false))
+ * to retain the alpha-channel in the first place.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param bool $saveflag Whether to save the alpha channel or not. Defaults to FALSE.
+ * @throws ImageException
+ *
+ */
+function imagesavealpha($image, bool $saveflag): void
+{
+ error_clear_last();
+ $result = \imagesavealpha($image, $saveflag);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagescale scales an image using the given
+ * interpolation algorithm.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $new_width The width to scale the image to.
+ * @param int $new_height The height to scale the image to. If omitted or negative, the aspect
+ * ratio will be preserved.
+ * @param int $mode One of IMG_NEAREST_NEIGHBOUR,
+ * IMG_BILINEAR_FIXED,
+ * IMG_BICUBIC,
+ * IMG_BICUBIC_FIXED or anything else (will use two
+ * pass).
+ *
+ *
+ * IMG_WEIGHTED4 is not yet supported.
+ *
+ *
+ * @return resource Return the scaled image resource on success.
+ * @throws ImageException
+ *
+ */
+function imagescale($image, int $new_width, int $new_height = -1, int $mode = IMG_BILINEAR_FIXED)
+{
+ error_clear_last();
+ $result = \imagescale($image, $new_width, $new_height, $mode);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagesetbrush sets the brush image to be
+ * used by all line drawing functions (such as imageline
+ * and imagepolygon) when drawing with the special
+ * colors IMG_COLOR_BRUSHED or
+ * IMG_COLOR_STYLEDBRUSHED.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param resource $brush An image resource.
+ * @throws ImageException
+ *
+ */
+function imagesetbrush($image, $brush): void
+{
+ error_clear_last();
+ $result = \imagesetbrush($image, $brush);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagesetclip sets the current clipping rectangle, i.e.
+ * the area beyond which no pixels will be drawn.
+ *
+ * @param resource $im An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x1 The x-coordinate of the upper left corner.
+ * @param int $y1 The y-coordinate of the upper left corner.
+ * @param int $x2 The x-coordinate of the lower right corner.
+ * @param int $y2 The y-coordinate of the lower right corner.
+ * @throws ImageException
+ *
+ */
+function imagesetclip($im, int $x1, int $y1, int $x2, int $y2): void
+{
+ error_clear_last();
+ $result = \imagesetclip($im, $x1, $y1, $x2, $y2);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the interpolation method, setting an interpolation method affects the rendering
+ * of various functions in GD, such as the imagerotate function.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $method The interpolation method, which can be one of the following:
+ *
+ *
+ *
+ * IMG_BELL: Bell filter.
+ *
+ *
+ *
+ *
+ * IMG_BESSEL: Bessel filter.
+ *
+ *
+ *
+ *
+ * IMG_BICUBIC: Bicubic interpolation.
+ *
+ *
+ *
+ *
+ * IMG_BICUBIC_FIXED: Fixed point implementation of the bicubic interpolation.
+ *
+ *
+ *
+ *
+ * IMG_BILINEAR_FIXED: Fixed point implementation of the bilinear interpolation (default (also on image creation)).
+ *
+ *
+ *
+ *
+ * IMG_BLACKMAN: Blackman window function.
+ *
+ *
+ *
+ *
+ * IMG_BOX: Box blur filter.
+ *
+ *
+ *
+ *
+ * IMG_BSPLINE: Spline interpolation.
+ *
+ *
+ *
+ *
+ * IMG_CATMULLROM: Cubic Hermite spline interpolation.
+ *
+ *
+ *
+ *
+ * IMG_GAUSSIAN: Gaussian function.
+ *
+ *
+ *
+ *
+ * IMG_GENERALIZED_CUBIC: Generalized cubic spline fractal interpolation.
+ *
+ *
+ *
+ *
+ * IMG_HERMITE: Hermite interpolation.
+ *
+ *
+ *
+ *
+ * IMG_HAMMING: Hamming filter.
+ *
+ *
+ *
+ *
+ * IMG_HANNING: Hanning filter.
+ *
+ *
+ *
+ *
+ * IMG_MITCHELL: Mitchell filter.
+ *
+ *
+ *
+ *
+ * IMG_POWER: Power interpolation.
+ *
+ *
+ *
+ *
+ * IMG_QUADRATIC: Inverse quadratic interpolation.
+ *
+ *
+ *
+ *
+ * IMG_SINC: Sinc function.
+ *
+ *
+ *
+ *
+ * IMG_NEAREST_NEIGHBOUR: Nearest neighbour interpolation.
+ *
+ *
+ *
+ *
+ * IMG_WEIGHTED4: Weighting filter.
+ *
+ *
+ *
+ *
+ * IMG_TRIANGLE: Triangle interpolation.
+ *
+ *
+ *
+ * @throws ImageException
+ *
+ */
+function imagesetinterpolation($image, int $method = IMG_BILINEAR_FIXED): void
+{
+ error_clear_last();
+ $result = \imagesetinterpolation($image, $method);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagesetpixel draws a pixel at the specified
+ * coordinate.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $x x-coordinate.
+ * @param int $y y-coordinate.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagesetpixel($image, int $x, int $y, int $color): void
+{
+ error_clear_last();
+ $result = \imagesetpixel($image, $x, $y, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagesetstyle sets the style to be used by all
+ * line drawing functions (such as imageline
+ * and imagepolygon) when drawing with the special
+ * color IMG_COLOR_STYLED or lines of images with color
+ * IMG_COLOR_STYLEDBRUSHED.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param array $style An array of pixel colors. You can use the
+ * IMG_COLOR_TRANSPARENT constant to add a
+ * transparent pixel.
+ * Note that style must not be an empty array.
+ * @throws ImageException
+ *
+ */
+function imagesetstyle($image, array $style): void
+{
+ error_clear_last();
+ $result = \imagesetstyle($image, $style);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagesetthickness sets the thickness of the lines
+ * drawn when drawing rectangles, polygons, arcs etc. to
+ * thickness pixels.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $thickness Thickness, in pixels.
+ * @throws ImageException
+ *
+ */
+function imagesetthickness($image, int $thickness): void
+{
+ error_clear_last();
+ $result = \imagesetthickness($image, $thickness);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * imagesettile sets the tile image to be
+ * used by all region filling functions (such as imagefill
+ * and imagefilledpolygon) when filling with the special
+ * color IMG_COLOR_TILED.
+ *
+ * A tile is an image used to fill an area with a repeated pattern. Any
+ * GD image can be used as a tile, and by setting the transparent color index of the tile
+ * image with imagecolortransparent, a tile allows certain parts
+ * of the underlying area to shine through can be created.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param resource $tile The image resource to be used as a tile.
+ * @throws ImageException
+ *
+ */
+function imagesettile($image, $tile): void
+{
+ error_clear_last();
+ $result = \imagesettile($image, $tile);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a string at the given coordinates.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $font Can be 1, 2, 3, 4, 5 for built-in
+ * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your
+ * own font identifiers registered with imageloadfont.
+ * @param int $x x-coordinate of the upper left corner.
+ * @param int $y y-coordinate of the upper left corner.
+ * @param string $string The string to be written.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagestring($image, int $font, int $x, int $y, string $string, int $color): void
+{
+ error_clear_last();
+ $result = \imagestring($image, $font, $x, $y, $string, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a string vertically at the given
+ * coordinates.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param int $font Can be 1, 2, 3, 4, 5 for built-in
+ * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your
+ * own font identifiers registered with imageloadfont.
+ * @param int $x x-coordinate of the bottom left corner.
+ * @param int $y y-coordinate of the bottom left corner.
+ * @param string $string The string to be written.
+ * @param int $color A color identifier created with imagecolorallocate.
+ * @throws ImageException
+ *
+ */
+function imagestringup($image, int $font, int $x, int $y, string $string, int $color): void
+{
+ error_clear_last();
+ $result = \imagestringup($image, $font, $x, $y, $string, $color);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the width of the given image resource.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @return int Return the width of the images.
+ * @throws ImageException
+ *
+ */
+function imagesx($image): int
+{
+ error_clear_last();
+ $result = \imagesx($image);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the height of the given image resource.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @return int Return the height of the images.
+ * @throws ImageException
+ *
+ */
+function imagesy($image): int
+{
+ error_clear_last();
+ $result = \imagesy($image);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagetruecolortopalette converts a truecolor image
+ * to a palette image. The code for this function was originally drawn from
+ * the Independent JPEG Group library code, which is excellent. The code
+ * has been modified to preserve as much alpha channel information as
+ * possible in the resulting palette, in addition to preserving colors as
+ * well as possible. This does not work as well as might be hoped. It is
+ * usually best to simply produce a truecolor output image instead, which
+ * guarantees the highest output quality.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param bool $dither Indicates if the image should be dithered - if it is TRUE then
+ * dithering will be used which will result in a more speckled image but
+ * with better color approximation.
+ * @param int $ncolors Sets the maximum number of colors that should be retained in the palette.
+ * @throws ImageException
+ *
+ */
+function imagetruecolortopalette($image, bool $dither, int $ncolors): void
+{
+ error_clear_last();
+ $result = \imagetruecolortopalette($image, $dither, $ncolors);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function calculates and returns the bounding box in pixels
+ * for a TrueType text.
+ *
+ * @param float $size The font size in points.
+ * @param float $angle Angle in degrees in which text will be measured.
+ * @param string $fontfile The path to the TrueType font you wish to use.
+ *
+ * Depending on which version of the GD library PHP is using, when
+ * fontfile does not begin with a leading
+ * / then .ttf will be appended
+ * to the filename and the library will attempt to search for that
+ * filename along a library-defined font path.
+ *
+ * When using versions of the GD library lower than 2.0.18, a space character,
+ * rather than a semicolon, was used as the 'path separator' for different font files.
+ * Unintentional use of this feature will result in the warning message:
+ * Warning: Could not find/open font. For these affected versions, the
+ * only solution is moving the font to a path which does not contain spaces.
+ *
+ * In many cases where a font resides in the same directory as the script using it
+ * the following trick will alleviate any include problems.
+ *
+ *
+ * ]]>
+ *
+ *
+ * Note that open_basedir does
+ * not apply to fontfile.
+ * @param string $text The string to be measured.
+ * @return array imagettfbbox returns an array with 8
+ * elements representing four points making the bounding box of the
+ * text on success and FALSE on error.
+ *
+ *
+ *
+ *
+ * key
+ * contents
+ *
+ *
+ *
+ *
+ * 0
+ * lower left corner, X position
+ *
+ *
+ * 1
+ * lower left corner, Y position
+ *
+ *
+ * 2
+ * lower right corner, X position
+ *
+ *
+ * 3
+ * lower right corner, Y position
+ *
+ *
+ * 4
+ * upper right corner, X position
+ *
+ *
+ * 5
+ * upper right corner, Y position
+ *
+ *
+ * 6
+ * upper left corner, X position
+ *
+ *
+ * 7
+ * upper left corner, Y position
+ *
+ *
+ *
+ *
+ *
+ * The points are relative to the text regardless of the
+ * angle, so "upper left" means in the top left-hand
+ * corner seeing the text horizontally.
+ * @throws ImageException
+ *
+ */
+function imagettfbbox(float $size, float $angle, string $fontfile, string $text): array
+{
+ error_clear_last();
+ $result = \imagettfbbox($size, $angle, $fontfile, $text);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Writes the given text into the image using TrueType
+ * fonts.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param float $size The font size in points.
+ * @param float $angle The angle in degrees, with 0 degrees being left-to-right reading text.
+ * Higher values represent a counter-clockwise rotation. For example, a
+ * value of 90 would result in bottom-to-top reading text.
+ * @param int $x The coordinates given by x and
+ * y will define the basepoint of the first
+ * character (roughly the lower-left corner of the character). This
+ * is different from the imagestring, where
+ * x and y define the
+ * upper-left corner of the first character. For example, "top left"
+ * is 0, 0.
+ * @param int $y The y-ordinate. This sets the position of the fonts baseline, not the
+ * very bottom of the character.
+ * @param int $color The color index. Using the negative of a color index has the effect of
+ * turning off antialiasing. See imagecolorallocate.
+ * @param string $fontfile The path to the TrueType font you wish to use.
+ *
+ * Depending on which version of the GD library PHP is using, when
+ * fontfile does not begin with a leading
+ * / then .ttf will be appended
+ * to the filename and the library will attempt to search for that
+ * filename along a library-defined font path.
+ *
+ * When using versions of the GD library lower than 2.0.18, a space character,
+ * rather than a semicolon, was used as the 'path separator' for different font files.
+ * Unintentional use of this feature will result in the warning message:
+ * Warning: Could not find/open font. For these affected versions, the
+ * only solution is moving the font to a path which does not contain spaces.
+ *
+ * In many cases where a font resides in the same directory as the script using it
+ * the following trick will alleviate any include problems.
+ *
+ *
+ * ]]>
+ *
+ *
+ * Note that open_basedir does
+ * not apply to fontfile.
+ * @param string $text The text string in UTF-8 encoding.
+ *
+ * May include decimal numeric character references (of the form:
+ * &#8364;) to access characters in a font beyond position 127.
+ * The hexadecimal format (like &#xA9;) is supported.
+ * Strings in UTF-8 encoding can be passed directly.
+ *
+ * Named entities, such as &copy;, are not supported. Consider using
+ * html_entity_decode
+ * to decode these named entities into UTF-8 strings.
+ *
+ * If a character is used in the string which is not supported by the
+ * font, a hollow rectangle will replace the character.
+ * @return array Returns an array with 8 elements representing four points making the
+ * bounding box of the text. The order of the points is lower left, lower
+ * right, upper right, upper left. The points are relative to the text
+ * regardless of the angle, so "upper left" means in the top left-hand
+ * corner when you see the text horizontally.
+ * @throws ImageException
+ *
+ */
+function imagettftext($image, float $size, float $angle, int $x, int $y, int $color, string $fontfile, string $text): array
+{
+ error_clear_last();
+ $result = \imagettftext($image, $size, $angle, $x, $y, $color, $fontfile, $text);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * imagewbmp outputs or save a WBMP
+ * version of the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @param int $foreground You can set the foreground color with this parameter by setting an
+ * identifier obtained from imagecolorallocate.
+ * The default foreground color is black.
+ * @throws ImageException
+ *
+ */
+function imagewbmp($image, $to = null, int $foreground = null): void
+{
+ error_clear_last();
+ if ($foreground !== null) {
+ $result = \imagewbmp($image, $to, $foreground);
+ } else {
+ $result = \imagewbmp($image, $to);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs or saves a WebP version of the given image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly.
+ * @param int $quality quality ranges from 0 (worst
+ * quality, smaller file) to 100 (best quality, biggest file).
+ * @throws ImageException
+ *
+ */
+function imagewebp($image, $to = null, int $quality = 80): void
+{
+ error_clear_last();
+ $result = \imagewebp($image, $to, $quality);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs or save an XBM version of the given
+ * image.
+ *
+ * @param resource $image An image resource, returned by one of the image creation functions,
+ * such as imagecreatetruecolor.
+ * @param string|null $filename The path to save the file to, given as string. If NULL, the raw image stream will be output directly.
+ *
+ * The filename (without the .xbm extension) is also
+ * used for the C identifiers of the XBM, whereby non
+ * alphanumeric characters of the current locale are substituted by
+ * underscores. If filename is set to NULL,
+ * image is used to build the C identifiers.
+ * @param int $foreground You can set the foreground color with this parameter by setting an
+ * identifier obtained from imagecolorallocate.
+ * The default foreground color is black. All other colors are treated as
+ * background.
+ * @throws ImageException
+ *
+ */
+function imagexbm($image, ?string $filename, int $foreground = null): void
+{
+ error_clear_last();
+ if ($foreground !== null) {
+ $result = \imagexbm($image, $filename, $foreground);
+ } else {
+ $result = \imagexbm($image, $filename);
+ }
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Embeds binary IPTC data into a JPEG image.
+ *
+ * @param string $iptcdata The data to be written.
+ * @param string $jpeg_file_name Path to the JPEG image.
+ * @param int $spool Spool flag. If the spool flag is less than 2 then the JPEG will be
+ * returned as a string. Otherwise the JPEG will be printed to STDOUT.
+ * @return string|bool If spool is less than 2, the JPEG will be returned. Otherwise returns TRUE on success.
+ * @throws ImageException
+ *
+ */
+function iptcembed(string $iptcdata, string $jpeg_file_name, int $spool = 0)
+{
+ error_clear_last();
+ $result = \iptcembed($iptcdata, $jpeg_file_name, $spool);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Parses an IPTC block into its single tags.
+ *
+ * @param string $iptcblock A binary IPTC block.
+ * @return array Returns an array using the tagmarker as an index and the value as the
+ * value. It returns FALSE on error or if no IPTC data was found.
+ * @throws ImageException
+ *
+ */
+function iptcparse(string $iptcblock): array
+{
+ error_clear_last();
+ $result = \iptcparse($iptcblock);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Converts a JPEG file into a WBMP file.
+ *
+ * @param string $jpegname Path to JPEG file.
+ * @param string $wbmpname Path to destination WBMP file.
+ * @param int $dest_height Destination image height.
+ * @param int $dest_width Destination image width.
+ * @param int $threshold Threshold value, between 0 and 8 (inclusive).
+ * @throws ImageException
+ *
+ */
+function jpeg2wbmp(string $jpegname, string $wbmpname, int $dest_height, int $dest_width, int $threshold): void
+{
+ error_clear_last();
+ $result = \jpeg2wbmp($jpegname, $wbmpname, $dest_height, $dest_width, $threshold);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Converts a PNG file into a WBMP file.
+ *
+ * @param string $pngname Path to PNG file.
+ * @param string $wbmpname Path to destination WBMP file.
+ * @param int $dest_height Destination image height.
+ * @param int $dest_width Destination image width.
+ * @param int $threshold Threshold value, between 0 and 8 (inclusive).
+ * @throws ImageException
+ *
+ */
+function png2wbmp(string $pngname, string $wbmpname, int $dest_height, int $dest_width, int $threshold): void
+{
+ error_clear_last();
+ $result = \png2wbmp($pngname, $wbmpname, $dest_height, $dest_width, $threshold);
+ if ($result === false) {
+ throw ImageException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ImapException;
+
+/**
+ * Appends a string message to the specified mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @param string $message The message to be append, as a string
+ *
+ * When talking to the Cyrus IMAP server, you must use "\r\n" as
+ * your end-of-line terminator instead of "\n" or the operation will
+ * fail
+ * @param string $options If provided, the options will also be written
+ * to the mailbox
+ * @param string $internal_date If this parameter is set, it will set the INTERNALDATE on the appended message. The parameter should be a date string that conforms to the rfc2060 specifications for a date_time value.
+ * @throws ImapException
+ *
+ */
+function imap_append($imap_stream, string $mailbox, string $message, string $options = null, string $internal_date = null): void
+{
+ error_clear_last();
+ $result = \imap_append($imap_stream, $mailbox, $message, $options, $internal_date);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Checks information about the current mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @return \stdClass Returns the information in an object with following properties:
+ *
+ *
+ *
+ * Date - current system time formatted according to RFC2822
+ *
+ *
+ *
+ *
+ * Driver - protocol used to access this mailbox:
+ * POP3, IMAP, NNTP
+ *
+ *
+ *
+ *
+ * Mailbox - the mailbox name
+ *
+ *
+ *
+ *
+ * Nmsgs - number of messages in the mailbox
+ *
+ *
+ *
+ *
+ * Recent - number of recent messages in the mailbox
+ *
+ *
+ *
+ *
+ * Returns FALSE on failure.
+ * @throws ImapException
+ *
+ */
+function imap_check($imap_stream): \stdClass
+{
+ error_clear_last();
+ $result = \imap_check($imap_stream);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function causes a store to delete the specified
+ * flag to the flags set for the
+ * messages in the specified sequence.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $sequence A sequence of message numbers. You can enumerate desired messages
+ * with the X,Y syntax, or retrieve all messages
+ * within an interval with the X:Y syntax
+ * @param string $flag The flags which you can unset are "\\Seen", "\\Answered", "\\Flagged",
+ * "\\Deleted", and "\\Draft" (as defined by RFC2060)
+ * @param int $options options are a bit mask and may contain
+ * the single option:
+ *
+ *
+ *
+ * ST_UID - The sequence argument contains UIDs
+ * instead of sequence numbers
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_clearflag_full($imap_stream, string $sequence, string $flag, int $options = 0): void
+{
+ error_clear_last();
+ $result = \imap_clearflag_full($imap_stream, $sequence, $flag, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the imap stream.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $flag If set to CL_EXPUNGE, the function will silently
+ * expunge the mailbox before closing, removing all messages marked for
+ * deletion. You can achieve the same thing by using
+ * imap_expunge
+ * @throws ImapException
+ *
+ */
+function imap_close($imap_stream, int $flag = 0): void
+{
+ error_clear_last();
+ $result = \imap_close($imap_stream, $flag);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a new mailbox specified by mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information. Names containing international characters should be
+ * encoded by imap_utf7_encode
+ * @throws ImapException
+ *
+ */
+function imap_createmailbox($imap_stream, string $mailbox): void
+{
+ error_clear_last();
+ $result = \imap_createmailbox($imap_stream, $mailbox);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deletes the specified mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @throws ImapException
+ *
+ */
+function imap_deletemailbox($imap_stream, string $mailbox): void
+{
+ error_clear_last();
+ $result = \imap_deletemailbox($imap_stream, $mailbox);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetches all the structured information for a given message.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $msg_number The message number
+ * @param int $options This optional parameter only has a single option,
+ * FT_UID, which tells the function to treat the
+ * msg_number argument as a
+ * UID.
+ * @return \stdClass Returns an object with properties listed in the table below.
+ *
+ *
+ *
+ * Returned Object for imap_fetchstructure
+ *
+ *
+ *
+ *
+ * type
+ * Primary body type
+ *
+ *
+ * encoding
+ * Body transfer encoding
+ *
+ *
+ * ifsubtype
+ * TRUE if there is a subtype string
+ *
+ *
+ * subtype
+ * MIME subtype
+ *
+ *
+ * ifdescription
+ * TRUE if there is a description string
+ *
+ *
+ * description
+ * Content description string
+ *
+ *
+ * ifid
+ * TRUE if there is an identification string
+ *
+ *
+ * id
+ * Identification string
+ *
+ *
+ * lines
+ * Number of lines
+ *
+ *
+ * bytes
+ * Number of bytes
+ *
+ *
+ * ifdisposition
+ * TRUE if there is a disposition string
+ *
+ *
+ * disposition
+ * Disposition string
+ *
+ *
+ * ifdparameters
+ * TRUE if the dparameters array exists
+ *
+ *
+ * dparameters
+ * An array of objects where each object has an
+ * "attribute" and a "value"
+ * property corresponding to the parameters on the
+ * Content-disposition MIME
+ * header.
+ *
+ *
+ * ifparameters
+ * TRUE if the parameters array exists
+ *
+ *
+ * parameters
+ * An array of objects where each object has an
+ * "attribute" and a "value"
+ * property.
+ *
+ *
+ * parts
+ * An array of objects identical in structure to the top-level
+ * object, each of which corresponds to a MIME body
+ * part.
+ *
+ *
+ *
+ *
+ *
+ *
+ * Primary body type (value may vary with used library, use of constants is recommended)
+ *
+ *
+ * ValueTypeConstant
+ *
+ *
+ * 0textTYPETEXT
+ * 1multipartTYPEMULTIPART
+ * 2messageTYPEMESSAGE
+ * 3applicationTYPEAPPLICATION
+ * 4audioTYPEAUDIO
+ * 5imageTYPEIMAGE
+ * 6videoTYPEVIDEO
+ * 7modelTYPEMODEL
+ * 8otherTYPEOTHER
+ *
+ *
+ *
+ *
+ *
+ * Transfer encodings (value may vary with used library, use of constants is recommended)
+ *
+ *
+ * ValueTypeConstant
+ *
+ *
+ * 07bitENC7BIT
+ * 18bitENC8BIT
+ * 2BinaryENCBINARY
+ * 3Base64ENCBASE64
+ * 4Quoted-PrintableENCQUOTEDPRINTABLE
+ * 5otherENCOTHER
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_fetchstructure($imap_stream, int $msg_number, int $options = 0): \stdClass
+{
+ error_clear_last();
+ $result = \imap_fetchstructure($imap_stream, $msg_number, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Purges the cache of entries of a specific type.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $caches Specifies the cache to purge. It may one or a combination
+ * of the following constants:
+ * IMAP_GC_ELT (message cache elements),
+ * IMAP_GC_ENV (envelope and bodies),
+ * IMAP_GC_TEXTS (texts).
+ * @throws ImapException
+ *
+ */
+function imap_gc($imap_stream, int $caches): void
+{
+ error_clear_last();
+ $result = \imap_gc($imap_stream, $caches);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets information about the given message number by reading its headers.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $msg_number The message number
+ * @param int $fromlength Number of characters for the fetchfrom property.
+ * Must be greater than or equal to zero.
+ * @param int $subjectlength Number of characters for the fetchsubject property
+ * Must be greater than or equal to zero.
+ * @param string $defaulthost
+ * @return \stdClass Returns FALSE on error or, if successful, the information in an object with following properties:
+ *
+ *
+ *
+ * toaddress - full to: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * to - an array of objects from the To: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * fromaddress - full from: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * from - an array of objects from the From: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * ccaddress - full cc: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * cc - an array of objects from the Cc: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * bccaddress - full bcc: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * bcc - an array of objects from the Bcc: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * reply_toaddress - full Reply-To: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * reply_to - an array of objects from the Reply-To: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * senderaddress - full sender: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * sender - an array of objects from the Sender: line, with the following
+ * properties: personal, adl,
+ * mailbox, and host
+ *
+ *
+ *
+ *
+ * return_pathaddress - full Return-Path: line, up to 1024 characters
+ *
+ *
+ *
+ *
+ * return_path - an array of objects from the Return-Path: line, with the
+ * following properties: personal,
+ * adl, mailbox, and
+ * host
+ *
+ *
+ *
+ *
+ * remail -
+ *
+ *
+ *
+ *
+ * date - The message date as found in its headers
+ *
+ *
+ *
+ *
+ * Date - Same as date
+ *
+ *
+ *
+ *
+ * subject - The message subject
+ *
+ *
+ *
+ *
+ * Subject - Same as subject
+ *
+ *
+ *
+ *
+ * in_reply_to -
+ *
+ *
+ *
+ *
+ * message_id -
+ *
+ *
+ *
+ *
+ * newsgroups -
+ *
+ *
+ *
+ *
+ * followup_to -
+ *
+ *
+ *
+ *
+ * references -
+ *
+ *
+ *
+ *
+ * Recent - R if recent and seen, N
+ * if recent and not seen, ' ' if not recent.
+ *
+ *
+ *
+ *
+ * Unseen - U if not seen AND not recent, ' ' if seen
+ * OR not seen and recent
+ *
+ *
+ *
+ *
+ * Flagged - F if flagged, ' ' if not flagged
+ *
+ *
+ *
+ *
+ * Answered - A if answered, ' ' if unanswered
+ *
+ *
+ *
+ *
+ * Deleted - D if deleted, ' ' if not deleted
+ *
+ *
+ *
+ *
+ * Draft - X if draft, ' ' if not draft
+ *
+ *
+ *
+ *
+ * Msgno - The message number
+ *
+ *
+ *
+ *
+ * MailDate -
+ *
+ *
+ *
+ *
+ * Size - The message size
+ *
+ *
+ *
+ *
+ * udate - mail message date in Unix time
+ *
+ *
+ *
+ *
+ * fetchfrom - from line formatted to fit fromlength
+ * characters
+ *
+ *
+ *
+ *
+ * fetchsubject - subject line formatted to fit
+ * subjectlength characters
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_headerinfo($imap_stream, int $msg_number, int $fromlength = 0, int $subjectlength = 0, string $defaulthost = null): \stdClass
+{
+ error_clear_last();
+ $result = \imap_headerinfo($imap_stream, $msg_number, $fromlength, $subjectlength, $defaulthost);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a MIME message based on the given envelope
+ * and body sections.
+ *
+ * @param array $envelope An associative array of header fields. Valid keys are: "remail",
+ * "return_path", "date", "from", "reply_to", "in_reply_to", "subject",
+ * "to", "cc", "bcc" and "message_id", which set the respective message headers to the given string.
+ * To set additional headers, the key "custom_headers" is supported, which expects
+ * an array of those headers, e.g. ["User-Agent: My Mail Client"].
+ * @param array $body An indexed array of bodies. The first body is the main body of the message;
+ * only if it has a type of TYPEMULTIPART, further bodies
+ * are processed; these bodies constitute the bodies of the parts.
+ *
+ *
+ * Body Array Structure
+ *
+ *
+ *
+ * Key
+ * Type
+ * Description
+ *
+ *
+ *
+ *
+ * type
+ * int
+ *
+ * The MIME type.
+ * One of TYPETEXT (default), TYPEMULTIPART,
+ * TYPEMESSAGE, TYPEAPPLICATION,
+ * TYPEAUDIO, TYPEIMAGE,
+ * TYPEMODEL or TYPEOTHER.
+ *
+ *
+ *
+ * encoding
+ * int
+ *
+ * The Content-Transfer-Encoding.
+ * One of ENC7BIT (default), ENC8BIT,
+ * ENCBINARY, ENCBASE64,
+ * ENCQUOTEDPRINTABLE or ENCOTHER.
+ *
+ *
+ *
+ * charset
+ * string
+ * The charset parameter of the MIME type.
+ *
+ *
+ * type.parameters
+ * array
+ * An associative array of Content-Type parameter names and their values.
+ *
+ *
+ * subtype
+ * string
+ * The MIME subtype, e.g. 'jpeg' for TYPEIMAGE.
+ *
+ *
+ * id
+ * string
+ * The Content-ID.
+ *
+ *
+ * description
+ * string
+ * The Content-Description.
+ *
+ *
+ * disposition.type
+ * string
+ * The Content-Disposition, e.g. 'attachment'.
+ *
+ *
+ * disposition
+ * array
+ * An associative array of Content-Disposition parameter names and values.
+ *
+ *
+ * contents.data
+ * string
+ * The payload.
+ *
+ *
+ * lines
+ * int
+ * The size of the payload in lines.
+ *
+ *
+ * bytes
+ * int
+ * The size of the payload in bytes.
+ *
+ *
+ * md5
+ * string
+ * The MD5 checksum of the payload.
+ *
+ *
+ *
+ *
+ * @return string Returns the MIME message as string.
+ * @throws ImapException
+ *
+ */
+function imap_mail_compose(array $envelope, array $body): string
+{
+ error_clear_last();
+ $result = \imap_mail_compose($envelope, $body);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Copies mail messages specified by msglist
+ * to specified mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $msglist msglist is a range not just message
+ * numbers (as described in RFC2060).
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @param int $options options is a bitmask of one or more of
+ *
+ *
+ *
+ * CP_UID - the sequence numbers contain UIDS
+ *
+ *
+ *
+ *
+ * CP_MOVE - Delete the messages from
+ * the current mailbox after copying
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_mail_copy($imap_stream, string $msglist, string $mailbox, int $options = 0): void
+{
+ error_clear_last();
+ $result = \imap_mail_copy($imap_stream, $msglist, $mailbox, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Moves mail messages specified by msglist to the
+ * specified mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $msglist msglist is a range not just message numbers
+ * (as described in RFC2060).
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @param int $options options is a bitmask and may contain the single option:
+ *
+ *
+ *
+ * CP_UID - the sequence numbers contain UIDS
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_mail_move($imap_stream, string $msglist, string $mailbox, int $options = 0): void
+{
+ error_clear_last();
+ $result = \imap_mail_move($imap_stream, $msglist, $mailbox, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function allows sending of emails with correct handling of
+ * Cc and Bcc receivers.
+ *
+ * The parameters to, cc
+ * and bcc are all strings and are all parsed
+ * as RFC822 address lists.
+ *
+ * @param string $to The receiver
+ * @param string $subject The mail subject
+ * @param string $message The mail body, see imap_mail_compose
+ * @param string $additional_headers As string with additional headers to be set on the mail
+ * @param string $cc
+ * @param string $bcc The receivers specified in bcc will get the
+ * mail, but are excluded from the headers.
+ * @param string $rpath Use this parameter to specify return path upon mail delivery failure.
+ * This is useful when using PHP as a mail client for multiple users.
+ * @throws ImapException
+ *
+ */
+function imap_mail(string $to, string $subject, string $message, string $additional_headers = null, string $cc = null, string $bcc = null, string $rpath = null): void
+{
+ error_clear_last();
+ $result = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc, $rpath);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Checks the current mailbox status on the server. It is similar to
+ * imap_status, but will additionally sum up the size of
+ * all messages in the mailbox, which will take some additional time to
+ * execute.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @return \stdClass Returns the information in an object with following properties:
+ *
+ * Mailbox properties
+ *
+ *
+ *
+ * Date
+ * date of last change (current datetime)
+ *
+ *
+ * Driver
+ * driver
+ *
+ *
+ * Mailbox
+ * name of the mailbox
+ *
+ *
+ * Nmsgs
+ * number of messages
+ *
+ *
+ * Recent
+ * number of recent messages
+ *
+ *
+ * Unread
+ * number of unread messages
+ *
+ *
+ * Deleted
+ * number of deleted messages
+ *
+ *
+ * Size
+ * mailbox size
+ *
+ *
+ *
+ *
+ *
+ * Returns FALSE on failure.
+ * @throws ImapException
+ *
+ */
+function imap_mailboxmsginfo($imap_stream): \stdClass
+{
+ error_clear_last();
+ $result = \imap_mailboxmsginfo($imap_stream);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Decode a modified UTF-7 (as specified in RFC 2060, section 5.1.3) string to UTF-8.
+ *
+ * @param string $in A string encoded in modified UTF-7.
+ * @return string Returns in converted to UTF-8.
+ * @throws ImapException
+ *
+ */
+function imap_mutf7_to_utf8(string $in): string
+{
+ error_clear_last();
+ $result = \imap_mutf7_to_utf8($in);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the number of messages in the current mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @return int Return the number of messages in the current mailbox, as an integer.
+ * @throws ImapException
+ *
+ */
+function imap_num_msg($imap_stream): int
+{
+ error_clear_last();
+ $result = \imap_num_msg($imap_stream);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Opens an IMAP stream to a mailbox.
+ *
+ * This function can also be used to open streams to POP3 and
+ * NNTP servers, but some functions and features are only
+ * available on IMAP servers.
+ *
+ * @param string $mailbox A mailbox name consists of a server and a mailbox path on this server.
+ * The special name INBOX stands for the current users
+ * personal mailbox. Mailbox names that contain international characters
+ * besides those in the printable ASCII space have to be encoded with
+ * imap_utf7_encode.
+ *
+ * The server part, which is enclosed in '{' and '}', consists of the servers
+ * name or ip address, an optional port (prefixed by ':'), and an optional
+ * protocol specification (prefixed by '/').
+ *
+ * The server part is mandatory in all mailbox
+ * parameters.
+ *
+ * All names which start with { are remote names, and are
+ * in the form "{" remote_system_name [":" port] [flags] "}"
+ * [mailbox_name] where:
+ *
+ *
+ *
+ * remote_system_name - Internet domain name or
+ * bracketed IP address of server.
+ *
+ *
+ *
+ *
+ * port - optional TCP port number, default is the
+ * default port for that service
+ *
+ *
+ *
+ *
+ * flags - optional flags, see following table.
+ *
+ *
+ *
+ *
+ * mailbox_name - remote mailbox name, default is INBOX
+ *
+ *
+ *
+ *
+ *
+ * Optional flags for names
+ *
+ *
+ *
+ * Flag
+ * Description
+ *
+ *
+ *
+ *
+ * /service=service
+ * mailbox access service, default is "imap"
+ *
+ *
+ * /user=user
+ * remote user name for login on the server
+ *
+ *
+ * /authuser=user
+ * remote authentication user; if specified this is the user name
+ * whose password is used (e.g. administrator)
+ *
+ *
+ * /anonymous
+ * remote access as anonymous user
+ *
+ *
+ * /debug
+ * record protocol telemetry in application's debug log
+ *
+ *
+ * /secure
+ * do not transmit a plaintext password over the network
+ *
+ *
+ * /imap, /imap2,
+ * /imap2bis, /imap4,
+ * /imap4rev1
+ * equivalent to /service=imap
+ *
+ *
+ * /pop3
+ * equivalent to /service=pop3
+ *
+ *
+ * /nntp
+ * equivalent to /service=nntp
+ *
+ *
+ * /norsh
+ * do not use rsh or ssh to establish a preauthenticated IMAP
+ * session
+ *
+ *
+ * /ssl
+ * use the Secure Socket Layer to encrypt the session
+ *
+ *
+ * /validate-cert
+ * validate certificates from TLS/SSL server (this is the default
+ * behavior)
+ *
+ *
+ * /novalidate-cert
+ * do not validate certificates from TLS/SSL server, needed if
+ * server uses self-signed certificates
+ *
+ *
+ * /tls
+ * force use of start-TLS to encrypt the session, and reject
+ * connection to servers that do not support it
+ *
+ *
+ * /notls
+ * do not do start-TLS to encrypt the session, even with servers
+ * that support it
+ *
+ *
+ * /readonly
+ * request read-only mailbox open (IMAP only; ignored on NNTP, and
+ * an error with SMTP and POP3)
+ *
+ *
+ *
+ *
+ * @param string $username The user name
+ * @param string $password The password associated with the username
+ * @param int $options The options are a bit mask with one or more of
+ * the following:
+ *
+ *
+ *
+ * OP_READONLY - Open mailbox read-only
+ *
+ *
+ *
+ *
+ * OP_ANONYMOUS - Don't use or update a
+ * .newsrc for news (NNTP only)
+ *
+ *
+ *
+ *
+ * OP_HALFOPEN - For IMAP
+ * and NNTP names, open a connection but
+ * don't open a mailbox.
+ *
+ *
+ *
+ *
+ * CL_EXPUNGE - Expunge mailbox automatically upon mailbox close
+ * (see also imap_delete and
+ * imap_expunge)
+ *
+ *
+ *
+ *
+ * OP_DEBUG - Debug protocol negotiations
+ *
+ *
+ *
+ *
+ * OP_SHORTCACHE - Short (elt-only) caching
+ *
+ *
+ *
+ *
+ * OP_SILENT - Don't pass up events (internal use)
+ *
+ *
+ *
+ *
+ * OP_PROTOTYPE - Return driver prototype
+ *
+ *
+ *
+ *
+ * OP_SECURE - Don't do non-secure authentication
+ *
+ *
+ *
+ * @param int $n_retries Number of maximum connect attempts
+ * @param array|null $params Connection parameters, the following (string) keys maybe used
+ * to set one or more connection parameters:
+ *
+ *
+ *
+ * DISABLE_AUTHENTICATOR - Disable authentication properties
+ *
+ *
+ *
+ * @return resource Returns an IMAP stream on success.
+ * @throws ImapException
+ *
+ */
+function imap_open(string $mailbox, string $username, string $password, int $options = 0, int $n_retries = 0, ?array $params = null)
+{
+ error_clear_last();
+ $result = \imap_open($mailbox, $username, $password, $options, $n_retries, $params);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function renames on old mailbox to new mailbox (see
+ * imap_open for the format of
+ * mbox names).
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $old_mbox The old mailbox name, see imap_open for more
+ * information
+ * @param string $new_mbox The new mailbox name, see imap_open for more
+ * information
+ * @throws ImapException
+ *
+ */
+function imap_renamemailbox($imap_stream, string $old_mbox, string $new_mbox): void
+{
+ error_clear_last();
+ $result = \imap_renamemailbox($imap_stream, $old_mbox, $new_mbox);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Saves a part or the whole body of the specified message.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string|resource $file The path to the saved file as a string, or a valid file descriptor
+ * returned by fopen.
+ * @param int $msg_number The message number
+ * @param string $part_number The part number. It is a string of integers delimited by period which
+ * index into a body part list as per the IMAP4 specification
+ * @param int $options A bitmask with one or more of the following:
+ *
+ *
+ *
+ * FT_UID - The msg_number is a UID
+ *
+ *
+ *
+ *
+ * FT_PEEK - Do not set the \Seen flag if
+ * not already set
+ *
+ *
+ *
+ *
+ * FT_INTERNAL - The return string is in
+ * internal format, will not canonicalize to CRLF.
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_savebody($imap_stream, $file, int $msg_number, string $part_number = "", int $options = 0): void
+{
+ error_clear_last();
+ $result = \imap_savebody($imap_stream, $file, $msg_number, $part_number, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets an upper limit quota on a per mailbox basis.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $quota_root The mailbox to have a quota set. This should follow the IMAP standard
+ * format for a mailbox: user.name.
+ * @param int $quota_limit The maximum size (in KB) for the quota_root
+ * @throws ImapException
+ *
+ */
+function imap_set_quota($imap_stream, string $quota_root, int $quota_limit): void
+{
+ error_clear_last();
+ $result = \imap_set_quota($imap_stream, $quota_root, $quota_limit);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the ACL for a giving mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @param string $id The user to give the rights to.
+ * @param string $rights The rights to give to the user. Passing an empty string will delete
+ * acl.
+ * @throws ImapException
+ *
+ */
+function imap_setacl($imap_stream, string $mailbox, string $id, string $rights): void
+{
+ error_clear_last();
+ $result = \imap_setacl($imap_stream, $mailbox, $id, $rights);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Causes a store to add the specified flag to the
+ * flags set for the messages in the specified
+ * sequence.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $sequence A sequence of message numbers. You can enumerate desired messages
+ * with the X,Y syntax, or retrieve all messages
+ * within an interval with the X:Y syntax
+ * @param string $flag The flags which you can set are \Seen,
+ * \Answered, \Flagged,
+ * \Deleted, and \Draft as
+ * defined by RFC2060.
+ * @param int $options A bit mask that may contain the single option:
+ *
+ *
+ *
+ * ST_UID - The sequence argument contains UIDs
+ * instead of sequence numbers
+ *
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_setflag_full($imap_stream, string $sequence, string $flag, int $options = NIL): void
+{
+ error_clear_last();
+ $result = \imap_setflag_full($imap_stream, $sequence, $flag, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets and sorts message numbers by the given parameters.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $criteria Criteria can be one (and only one) of the following:
+ *
+ *
+ *
+ * SORTDATE - message Date
+ *
+ *
+ *
+ *
+ * SORTARRIVAL - arrival date
+ *
+ *
+ *
+ *
+ * SORTFROM - mailbox in first From address
+ *
+ *
+ *
+ *
+ * SORTSUBJECT - message subject
+ *
+ *
+ *
+ *
+ * SORTTO - mailbox in first To address
+ *
+ *
+ *
+ *
+ * SORTCC - mailbox in first cc address
+ *
+ *
+ *
+ *
+ * SORTSIZE - size of message in octets
+ *
+ *
+ *
+ * @param int $reverse Set this to 1 for reverse sorting
+ * @param int $options The options are a bitmask of one or more of the
+ * following:
+ *
+ *
+ *
+ * SE_UID - Return UIDs instead of sequence numbers
+ *
+ *
+ *
+ *
+ * SE_NOPREFETCH - Don't prefetch searched messages
+ *
+ *
+ *
+ * @param string $search_criteria IMAP2-format search criteria string. For details see
+ * imap_search.
+ * @param string $charset MIME character set to use when sorting strings.
+ * @return array Returns an array of message numbers sorted by the given
+ * parameters.
+ * @throws ImapException
+ *
+ */
+function imap_sort($imap_stream, int $criteria, int $reverse, int $options = 0, string $search_criteria = null, string $charset = null): array
+{
+ error_clear_last();
+ $result = \imap_sort($imap_stream, $criteria, $reverse, $options, $search_criteria, $charset);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Subscribe to a new mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @throws ImapException
+ *
+ */
+function imap_subscribe($imap_stream, string $mailbox): void
+{
+ error_clear_last();
+ $result = \imap_subscribe($imap_stream, $mailbox);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets a tree of a threaded message.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $options
+ * @return array imap_thread returns an associative array containing
+ * a tree of messages threaded by REFERENCES.
+ *
+ * Every message in the current mailbox will be represented by three entries
+ * in the resulting array:
+ *
+ *
+ * $thread["XX.num"] - current message number
+ *
+ *
+ * $thread["XX.next"]
+ *
+ *
+ * $thread["XX.branch"]
+ *
+ *
+ * @throws ImapException
+ *
+ */
+function imap_thread($imap_stream, int $options = SE_FREE): array
+{
+ error_clear_last();
+ $result = \imap_thread($imap_stream, $options);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets or fetches the imap timeout.
+ *
+ * @param int $timeout_type One of the following:
+ * IMAP_OPENTIMEOUT,
+ * IMAP_READTIMEOUT,
+ * IMAP_WRITETIMEOUT, or
+ * IMAP_CLOSETIMEOUT.
+ * @param int $timeout The timeout, in seconds.
+ * @return mixed If the timeout parameter is set, this function
+ * returns TRUE on success.
+ *
+ * If timeout is not provided or evaluates to -1,
+ * the current timeout value of timeout_type is
+ * returned as an integer.
+ * @throws ImapException
+ *
+ */
+function imap_timeout(int $timeout_type, int $timeout = -1)
+{
+ error_clear_last();
+ $result = \imap_timeout($timeout_type, $timeout);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Removes the deletion flag for a specified message, which is set by
+ * imap_delete or imap_mail_move.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param int $msg_number The message number
+ * @param int $flags
+ * @throws ImapException
+ *
+ */
+function imap_undelete($imap_stream, int $msg_number, int $flags = 0): void
+{
+ error_clear_last();
+ $result = \imap_undelete($imap_stream, $msg_number, $flags);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Unsubscribe from the specified mailbox.
+ *
+ * @param resource $imap_stream An IMAP stream returned by
+ * imap_open.
+ * @param string $mailbox The mailbox name, see imap_open for more
+ * information
+ * @throws ImapException
+ *
+ */
+function imap_unsubscribe($imap_stream, string $mailbox): void
+{
+ error_clear_last();
+ $result = \imap_unsubscribe($imap_stream, $mailbox);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Encode a UTF-8 string to modified UTF-7 (as specified in RFC 2060, section 5.1.3).
+ *
+ * @param string $in A UTF-8 encoded string.
+ * @return string Returns in converted to modified UTF-7.
+ * @throws ImapException
+ *
+ */
+function imap_utf8_to_mutf7(string $in): string
+{
+ error_clear_last();
+ $result = \imap_utf8_to_mutf7($in);
+ if ($result === false) {
+ throw ImapException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\InfoException;
+
+/**
+ * Sets the process title visible in tools such as top and
+ * ps. This function is available only in
+ * CLI mode.
+ *
+ * @param string $title The new title.
+ * @throws InfoException
+ *
+ */
+function cli_set_process_title(string $title): void
+{
+ error_clear_last();
+ $result = \cli_set_process_title($title);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Loads the PHP extension given by the parameter
+ * library.
+ *
+ * Use extension_loaded to test whether a given
+ * extension is already available or not. This works on both built-in
+ * extensions and dynamically loaded ones (either through php.ini or
+ * dl).
+ *
+ * @param string $library This parameter is only the filename of the
+ * extension to load which also depends on your platform. For example,
+ * the sockets extension (if compiled
+ * as a shared module, not the default!) would be called
+ * sockets.so on Unix platforms whereas it is called
+ * php_sockets.dll on the Windows platform.
+ *
+ * The directory where the extension is loaded from depends on your
+ * platform:
+ *
+ * Windows - If not explicitly set in the php.ini, the extension is
+ * loaded from C:\php5\ by default.
+ *
+ * Unix - If not explicitly set in the php.ini, the default extension
+ * directory depends on
+ *
+ *
+ *
+ * whether PHP has been built with --enable-debug
+ * or not
+ *
+ *
+ *
+ *
+ * whether PHP has been built with (experimental) ZTS (Zend Thread Safety)
+ * support or not
+ *
+ *
+ *
+ *
+ * the current internal ZEND_MODULE_API_NO (Zend
+ * internal module API number, which is basically the date on which a
+ * major module API change happened, e.g. 20010901)
+ *
+ *
+ *
+ * Taking into account the above, the directory then defaults to
+ * <install-dir>/lib/php/extensions/ <debug-or-not>-<zts-or-not>-ZEND_MODULE_API_NO,
+ * e.g.
+ * /usr/local/php/lib/php/extensions/debug-non-zts-20010901
+ * or
+ * /usr/local/php/lib/php/extensions/no-debug-zts-20010901.
+ * @throws InfoException
+ *
+ */
+function dl(string $library): void
+{
+ error_clear_last();
+ $result = \dl($library);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets the time of the last modification of the main script of execution.
+ *
+ * If you're interested in getting the last modification time
+ * of a different file, consider using filemtime.
+ *
+ * @return int Returns the time of the last modification of the current
+ * page. The value returned is a Unix timestamp, suitable for
+ * feeding to date.
+ * @throws InfoException
+ *
+ */
+function getlastmod(): int
+{
+ error_clear_last();
+ $result = \getlastmod();
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @return int Returns the group ID of the current script.
+ * @throws InfoException
+ *
+ */
+function getmygid(): int
+{
+ error_clear_last();
+ $result = \getmygid();
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the inode of the current script.
+ *
+ * @return int Returns the current script's inode as an integer.
+ * @throws InfoException
+ *
+ */
+function getmyinode(): int
+{
+ error_clear_last();
+ $result = \getmyinode();
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the current PHP process ID.
+ *
+ * @return int Returns the current PHP process ID.
+ * @throws InfoException
+ *
+ */
+function getmypid(): int
+{
+ error_clear_last();
+ $result = \getmypid();
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @return int Returns the user ID of the current script.
+ * @throws InfoException
+ *
+ */
+function getmyuid(): int
+{
+ error_clear_last();
+ $result = \getmyuid();
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Parses options passed to the script.
+ *
+ * @param string $options
+ * @param array $longopts
+ * @param int|null $optind
+ * @return array|array|array This function will return an array of option / argument pairs.
+ * @throws InfoException
+ *
+ */
+function getopt(string $options, array $longopts = null, ?int &$optind = null): array
+{
+ error_clear_last();
+ if ($optind !== null) {
+ $result = \getopt($options, $longopts, $optind);
+ } elseif ($longopts !== null) {
+ $result = \getopt($options, $longopts);
+ } else {
+ $result = \getopt($options);
+ }
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the value of the configuration option on success.
+ *
+ * @param string $varname The configuration option name.
+ * @return string Returns the value of the configuration option as a string on success, or an
+ * empty string for null values. Returns FALSE if the
+ * configuration option doesn't exist.
+ * @throws InfoException
+ *
+ */
+function ini_get(string $varname): string
+{
+ error_clear_last();
+ $result = \ini_get($varname);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the value of the given configuration option. The configuration option
+ * will keep this new value during the script's execution, and will be restored
+ * at the script's ending.
+ *
+ * @param string $varname Not all the available options can be changed using
+ * ini_set. There is a list of all available options
+ * in the appendix.
+ * @param string|int|float|bool $newvalue The new value for the option.
+ * @return string Returns the old value on success, FALSE on failure.
+ * @throws InfoException
+ *
+ */
+function ini_set(string $varname, $newvalue): string
+{
+ error_clear_last();
+ $result = \ini_set($varname, $newvalue);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function prints out the credits listing the PHP developers,
+ * modules, etc. It generates the appropriate HTML codes to insert
+ * the information in a page.
+ *
+ * @param int $flag To generate a custom credits page, you may want to use the
+ * flag parameter.
+ *
+ *
+ * Pre-defined phpcredits flags
+ *
+ *
+ *
+ * name
+ * description
+ *
+ *
+ *
+ *
+ * CREDITS_ALL
+ *
+ * All the credits, equivalent to using: CREDITS_DOCS +
+ * CREDITS_GENERAL + CREDITS_GROUP +
+ * CREDITS_MODULES + CREDITS_FULLPAGE.
+ * It generates a complete stand-alone HTML page with the appropriate tags.
+ *
+ *
+ *
+ * CREDITS_DOCS
+ * The credits for the documentation team
+ *
+ *
+ * CREDITS_FULLPAGE
+ *
+ * Usually used in combination with the other flags. Indicates
+ * that a complete stand-alone HTML page needs to be
+ * printed including the information indicated by the other
+ * flags.
+ *
+ *
+ *
+ * CREDITS_GENERAL
+ *
+ * General credits: Language design and concept, PHP authors
+ * and SAPI module.
+ *
+ *
+ *
+ * CREDITS_GROUP
+ * A list of the core developers
+ *
+ *
+ * CREDITS_MODULES
+ *
+ * A list of the extension modules for PHP, and their authors
+ *
+ *
+ *
+ * CREDITS_SAPI
+ *
+ * A list of the server API modules for PHP, and their authors
+ *
+ *
+ *
+ *
+ *
+ * @throws InfoException
+ *
+ */
+function phpcredits(int $flag = CREDITS_ALL): void
+{
+ error_clear_last();
+ $result = \phpcredits($flag);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Outputs a large amount of information about the current state of PHP.
+ * This includes information about PHP compilation options and extensions,
+ * the PHP version, server information and environment (if compiled as a
+ * module), the PHP environment, OS version information, paths, master and
+ * local values of configuration options, HTTP headers, and the PHP License.
+ *
+ * Because every system is setup differently, phpinfo is
+ * commonly used to check configuration settings and for available
+ * predefined variables
+ * on a given system.
+ *
+ * phpinfo is also a valuable debugging tool as it
+ * contains all EGPCS (Environment, GET, POST, Cookie, Server) data.
+ *
+ * @param int $what The output may be customized by passing one or more of the
+ * following constants bitwise values summed
+ * together in the optional what parameter.
+ * One can also combine the respective constants or bitwise values
+ * together with the bitwise or operator.
+ *
+ *
+ * phpinfo options
+ *
+ *
+ *
+ * Name (constant)
+ * Value
+ * Description
+ *
+ *
+ *
+ *
+ * INFO_GENERAL
+ * 1
+ *
+ * The configuration line, php.ini location, build date, Web
+ * Server, System and more.
+ *
+ *
+ *
+ * INFO_CREDITS
+ * 2
+ *
+ * PHP Credits. See also phpcredits.
+ *
+ *
+ *
+ * INFO_CONFIGURATION
+ * 4
+ *
+ * Current Local and Master values for PHP directives. See
+ * also ini_get.
+ *
+ *
+ *
+ * INFO_MODULES
+ * 8
+ *
+ * Loaded modules and their respective settings. See also
+ * get_loaded_extensions.
+ *
+ *
+ *
+ * INFO_ENVIRONMENT
+ * 16
+ *
+ * Environment Variable information that's also available in
+ * $_ENV.
+ *
+ *
+ *
+ * INFO_VARIABLES
+ * 32
+ *
+ * Shows all
+ * predefined variables from EGPCS (Environment, GET,
+ * POST, Cookie, Server).
+ *
+ *
+ *
+ * INFO_LICENSE
+ * 64
+ *
+ * PHP License information. See also the license FAQ.
+ *
+ *
+ *
+ * INFO_ALL
+ * -1
+ *
+ * Shows all of the above.
+ *
+ *
+ *
+ *
+ *
+ * @throws InfoException
+ *
+ */
+function phpinfo(int $what = INFO_ALL): void
+{
+ error_clear_last();
+ $result = \phpinfo($what);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds setting to the server environment. The
+ * environment variable will only exist for the duration of the current
+ * request. At the end of the request the environment is restored to its
+ * original state.
+ *
+ * @param string $setting The setting, like "FOO=BAR"
+ * @throws InfoException
+ *
+ */
+function putenv(string $setting): void
+{
+ error_clear_last();
+ $result = \putenv($setting);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the include_path
+ * configuration option for the duration of the script.
+ *
+ * @param string $new_include_path The new value for the include_path
+ * @return string Returns the old include_path on
+ * success.
+ * @throws InfoException
+ *
+ */
+function set_include_path(string $new_include_path): string
+{
+ error_clear_last();
+ $result = \set_include_path($new_include_path);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Set the number of seconds a script is allowed to run. If this is reached,
+ * the script returns a fatal error. The default limit is 30 seconds or, if
+ * it exists, the max_execution_time value defined in the
+ * php.ini.
+ *
+ * When called, set_time_limit restarts the timeout
+ * counter from zero. In other words, if the timeout is the default 30
+ * seconds, and 25 seconds into script execution a call such as
+ * set_time_limit(20) is made, the script will run for a
+ * total of 45 seconds before timing out.
+ *
+ * @param int $seconds The maximum execution time, in seconds. If set to zero, no time limit
+ * is imposed.
+ * @throws InfoException
+ *
+ */
+function set_time_limit(int $seconds): void
+{
+ error_clear_last();
+ $result = \set_time_limit($seconds);
+ if ($result === false) {
+ throw InfoException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\IngresiiException;
+
+/**
+ * ingres_autocommit is called before opening a
+ * transaction (before the first call to
+ * ingres_query or just after a call to
+ * ingres_rollback or
+ * ingres_commit) to switch the
+ * autocommit mode of the server on or off (when the script begins
+ * the autocommit mode is off).
+ *
+ * When autocommit mode is on, every query is automatically
+ * committed by the server, as if ingres_commit
+ * was called after every call to ingres_query.
+ * To see if autocommit is enabled use,
+ * ingres_autocommit_state.
+ *
+ * By default Ingres will rollback any uncommitted transactions at the end of
+ * a request. Use this function or ingres_commit to
+ * ensure your data is committed to the database.
+ *
+ * @param resource $link The connection link identifier
+ * @throws IngresiiException
+ *
+ */
+function ingres_autocommit($link): void
+{
+ error_clear_last();
+ $result = \ingres_autocommit($link);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_close closes the connection to
+ * the Ingres server that is associated with the specified link.
+ *
+ * ingres_close is usually unnecessary, as it
+ * will not close persistent connections and all non-persistent connections
+ * are automatically closed at the end of the script.
+ *
+ * @param resource $link The connection link identifier
+ * @throws IngresiiException
+ *
+ */
+function ingres_close($link): void
+{
+ error_clear_last();
+ $result = \ingres_close($link);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_commit commits the currently open
+ * transaction, making all changes made to the database permanent.
+ *
+ * This closes the transaction. A new transaction can be opened by sending a
+ * query with ingres_query.
+ *
+ * You can also have the server commit automatically after every
+ * query by calling ingres_autocommit before
+ * opening the transaction.
+ *
+ * By default Ingres will roll back any uncommitted transactions at the end of
+ * a request. Use this function or ingres_autocommit to
+ * ensure your that data is committed to the database.
+ *
+ * @param resource $link The connection link identifier
+ * @throws IngresiiException
+ *
+ */
+function ingres_commit($link): void
+{
+ error_clear_last();
+ $result = \ingres_commit($link);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_connect opens a connection with the
+ * given Ingres database.
+ *
+ * The connection is closed when the script ends or when
+ * ingres_close is called on this link.
+ *
+ * @param string $database The database name. Must follow the syntax:
+ *
+ * [vnode::]dbname[/svr_class]
+ * @param string $username The Ingres user name
+ * @param string $password The password associated with username
+ * @param array $options ingres_connect options
+ *
+ *
+ *
+ *
+ * Option name
+ * Option type
+ * Description
+ * Example
+ *
+ *
+ *
+ *
+ * date_century_boundary
+ * integer
+ * The threshold by which a 2-digit year is determined to be in
+ * the current century or in the next century. Equivalent to II_DATE_CENTURY_BOUNDARY.
+ * 50
+ *
+ *
+ * group
+ * string
+ * Specifies the group ID of the user, equivalent to the "-G"
+ * flag
+ * payroll
+ *
+ *
+ * role
+ * string
+ * The role ID of the application. If a role password is
+ * required, the parameter value should be specified as "role/password"
+ *
+ *
+ * effective_user
+ * string
+ * The ingres user account being impersonated, equivalent to the "-u" flag
+ * another_user
+ *
+ *
+ * dbms_password
+ * string
+ * The internal database password for the user connecting to Ingres
+ * s3cr3t
+ *
+ *
+ * table_structure
+ * string
+ *
+ * The default structure for new tables.
+ * Valid values for table_structure are:
+ *
+ * INGRES_STRUCTURE_BTREE
+ * INGRES_STRUCTURE_HASH
+ * INGRES_STRUCTURE_HEAP
+ * INGRES_STRUCTURE_ISAM
+ * INGRES_STRUCTURE_CBTREE
+ * INGRES_STRUCTURE_CISAM
+ * INGRES_STRUCTURE_CHASH
+ * INGRES_STRUCTURE_CHEAP
+ *
+ *
+ *
+ * INGRES_STRUCTURE_BTREE
+ *
+ *
+ * index_structure
+ * string
+ *
+ * The default structure for new secondary indexes. Valid values
+ * for index_structure are:
+ *
+ * INGRES_STRUCTURE_CBTREE
+ * INGRES_STRUCTURE_CISAM
+ * INGRES_STRUCTURE_CHASH
+ * INGRES_STRUCTURE_BTREE
+ * INGRES_STRUCTURE_HASH
+ * INGRES_STRUCTURE_ISAM
+ *
+ *
+ *
+ * INGRES_STRUCTURE_HASH
+ *
+ *
+ * login_local
+ * boolean
+ * Determines how the connection user ID and password are
+ * used when a VNODE is included in the target database string.
+ * If set to TRUE, the user ID and password are used to locally access
+ * the VNODE, and the VNODE login information is used to establish the DBMS
+ * connection. If set to FALSE, the process user ID is used to access
+ * the VNODE, and the connection user ID and password are used in place
+ * of the VNODE login information to establish the DBMS connection.
+ * This parameter is ignored if no VNODE is included in the target
+ * database string. The default is FALSE.
+ * TRUE
+ *
+ *
+ * timezone
+ * string
+ * Controls the timezone of the session. If not set it will
+ * default to the value defined by II_TIMEZONE_NAME. If
+ * II_TIMEZONE_NAME is not defined, NA-PACIFIC (GMT-8 with Daylight
+ * Savings) is used.
+ *
+ *
+ * date_format
+ * integer
+ * Sets the allowable input and output format for Ingres dates.
+ * Defaults to the value defined by II_DATE_FORMAT. If II_DATE_FORMAT is
+ * not set the default date format is US, e.g. mm/dd/yy. Valid values
+ * for date_format are:
+ *
+ * INGRES_DATE_DMY
+ * INGRES_DATE_FINISH
+ * INGRES_DATE_GERMAN
+ * INGRES_DATE_ISO
+ * INGRES_DATE_ISO4
+ * INGRES_DATE_MDY
+ * INGRES_DATE_MULTINATIONAL
+ * INGRES_DATE_MULTINATIONAL4
+ * INGRES_DATE_YMD
+ * INGRES_DATE_US
+ *
+ *
+ *
+ * INGRES_DATE_MULTINATIONAL4
+ *
+ *
+ * decimal_separator
+ * string
+ * The character identifier for decimal data
+ * ","
+ *
+ *
+ * money_lort
+ * integer
+ * Leading or trailing currency sign. Valid values for money_lort
+ * are:
+ *
+ * INGRES_MONEY_LEADING
+ * INGRES_MONEY_TRAILING
+ *
+ *
+ *
+ * INGRES_MONEY_TRAILING
+ *
+ *
+ * money_sign
+ * string
+ * The currency symbol to be used with the MONEY datatype
+ * €
+ *
+ *
+ * money_precision
+ * integer
+ * The precision of the MONEY datatype
+ * 3
+ *
+ *
+ * float4_precision
+ * integer
+ * Precision of the FLOAT4 datatype
+ * 10
+ *
+ *
+ * float8_precision
+ * integer
+ * Precision of the FLOAT8 data
+ * 10
+ *
+ *
+ * blob_segment_length
+ * integer
+ * The amount of data in bytes to fetch at a time when retrieving
+ * BLOB or CLOB data, defaults to 4096 bytes when not explicitly set
+ * 8192
+ *
+ *
+ *
+ *
+ *
+ * The default structure for new tables.
+ * Valid values for table_structure are:
+ *
+ * INGRES_STRUCTURE_BTREE
+ * INGRES_STRUCTURE_HASH
+ * INGRES_STRUCTURE_HEAP
+ * INGRES_STRUCTURE_ISAM
+ * INGRES_STRUCTURE_CBTREE
+ * INGRES_STRUCTURE_CISAM
+ * INGRES_STRUCTURE_CHASH
+ * INGRES_STRUCTURE_CHEAP
+ *
+ *
+ * The default structure for new secondary indexes. Valid values
+ * for index_structure are:
+ *
+ * INGRES_STRUCTURE_CBTREE
+ * INGRES_STRUCTURE_CISAM
+ * INGRES_STRUCTURE_CHASH
+ * INGRES_STRUCTURE_BTREE
+ * INGRES_STRUCTURE_HASH
+ * INGRES_STRUCTURE_ISAM
+ *
+ *
+ * Sets the allowable input and output format for Ingres dates.
+ * Defaults to the value defined by II_DATE_FORMAT. If II_DATE_FORMAT is
+ * not set the default date format is US, e.g. mm/dd/yy. Valid values
+ * for date_format are:
+ *
+ * INGRES_DATE_DMY
+ * INGRES_DATE_FINISH
+ * INGRES_DATE_GERMAN
+ * INGRES_DATE_ISO
+ * INGRES_DATE_ISO4
+ * INGRES_DATE_MDY
+ * INGRES_DATE_MULTINATIONAL
+ * INGRES_DATE_MULTINATIONAL4
+ * INGRES_DATE_YMD
+ * INGRES_DATE_US
+ *
+ *
+ * Leading or trailing currency sign. Valid values for money_lort
+ * are:
+ *
+ * INGRES_MONEY_LEADING
+ * INGRES_MONEY_TRAILING
+ *
+ * @return resource Returns a Ingres link resource on success
+ * @throws IngresiiException
+ *
+ */
+function ingres_connect(string $database = null, string $username = null, string $password = null, array $options = null)
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \ingres_connect($database, $username, $password, $options);
+ } elseif ($password !== null) {
+ $result = \ingres_connect($database, $username, $password);
+ } elseif ($username !== null) {
+ $result = \ingres_connect($database, $username);
+ } elseif ($database !== null) {
+ $result = \ingres_connect($database);
+ } else {
+ $result = \ingres_connect();
+ }
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Execute a query prepared using ingres_prepare.
+ *
+ * @param resource $result The result query identifier
+ * @param array $params An array of parameter values to be used with the query
+ * @param string $types A string containing a sequence of types for the parameter values
+ * passed. See the types parameter in
+ * ingres_query for the list of type codes.
+ * @throws IngresiiException
+ *
+ */
+function ingres_execute($result, array $params = null, string $types = null): void
+{
+ error_clear_last();
+ if ($types !== null) {
+ $result = \ingres_execute($result, $params, $types);
+ } elseif ($params !== null) {
+ $result = \ingres_execute($result, $params);
+ } else {
+ $result = \ingres_execute($result);
+ }
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_field_name returns the name of a field
+ * in a query result.
+ *
+ * @param resource $result The query result identifier
+ * @param int $index index is the field whose name will be
+ * retrieved.
+ *
+ * The possible values of index depend upon
+ * the value
+ * of ingres.array_index_start.
+ * If ingres.array_index_start
+ * is 1 (the default)
+ * then index must be
+ * between 1 and the value returned
+ * by ingres_num_fields. If ingres.array_index_start
+ * is 0 then index must
+ * be between 0
+ * and ingres_num_fields -
+ * 1.
+ * @return string Returns the name of a field
+ * in a query result
+ * @throws IngresiiException
+ *
+ */
+function ingres_field_name($result, int $index): string
+{
+ error_clear_last();
+ $result = \ingres_field_name($result, $index);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Get the type of a field in a query result.
+ *
+ * @param resource $result The query result identifier
+ * @param int $index index is the field whose type will be
+ * retrieved.
+ *
+ * The possible values of index depend upon
+ * the value
+ * of ingres.array_index_start.
+ * If ingres.array_index_start
+ * is 1 (the default)
+ * then index must be
+ * between 1 and the value returned
+ * by ingres_num_fields. If ingres.array_index_start
+ * is 0 then index must
+ * be between 0
+ * and ingres_num_fields -
+ * 1.
+ * @return string ingres_field_type returns the type of a
+ * field in a query result. Examples of
+ * types returned are IIAPI_BYTE_TYPE,
+ * IIAPI_CHA_TYPE, IIAPI_DTE_TYPE,
+ * IIAPI_FLT_TYPE, IIAPI_INT_TYPE,
+ * IIAPI_VCH_TYPE. Some of these types can map to more
+ * than one SQL type depending on the length of the field (see
+ * ingres_field_length). For example
+ * IIAPI_FLT_TYPE can be a float4 or a float8. For detailed
+ * information, see the Ingres OpenAPI User Guide, Appendix
+ * "Data Types" in the Ingres documentation.
+ * @throws IngresiiException
+ *
+ */
+function ingres_field_type($result, int $index): string
+{
+ error_clear_last();
+ $result = \ingres_field_type($result, $index);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $result The query result identifier
+ * @throws IngresiiException
+ *
+ */
+function ingres_free_result($result): void
+{
+ error_clear_last();
+ $result = \ingres_free_result($result);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Open a persistent connection to an Ingres database.
+ *
+ * There are only two differences between this function and
+ * ingres_connect: First, when connecting, the
+ * function will initially try to find a (persistent) link that is
+ * already opened with the same parameters. If one is found, an
+ * identifier for it will be returned instead of opening a new
+ * connection. Second, the connection to the Ingres server will not
+ * be closed when the execution of the script ends. Instead, the
+ * link will remain open for future use
+ * (ingres_close will not close links
+ * established by ingres_pconnect). This type
+ * of link is therefore called "persistent".
+ *
+ * @param string $database The database name. Must follow the syntax:
+ *
+ * [vnode::]dbname[/svr_class]
+ * @param string $username The Ingres user name
+ * @param string $password The password associated with username
+ * @param array $options See ingres_connect for the list of options that
+ * can be passed
+ * @return resource Returns an Ingres link resource on success
+ * @throws IngresiiException
+ *
+ */
+function ingres_pconnect(string $database = null, string $username = null, string $password = null, array $options = null)
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \ingres_pconnect($database, $username, $password, $options);
+ } elseif ($password !== null) {
+ $result = \ingres_pconnect($database, $username, $password);
+ } elseif ($username !== null) {
+ $result = \ingres_pconnect($database, $username);
+ } elseif ($database !== null) {
+ $result = \ingres_pconnect($database);
+ } else {
+ $result = \ingres_pconnect();
+ }
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is used to position the cursor associated with the result
+ * resource before issuing a fetch. If ingres.array_index_start
+ * is set to 0 then the first row is 0 else it is 1.
+ * ingres_result_seek can be used only with queries that
+ * make use of scrollable
+ * cursors. It cannot be used with
+ * ingres_unbuffered_query.
+ *
+ * @param resource $result The result identifier for a query
+ * @param int $position The row to position the cursor on. If ingres.array_index_start
+ * is set to 0, then the first row is 0, else it is 1
+ * @throws IngresiiException
+ *
+ */
+function ingres_result_seek($result, int $position): void
+{
+ error_clear_last();
+ $result = \ingres_result_seek($result, $position);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_rollback rolls back the currently
+ * open transaction, actually cancelling all changes made to the
+ * database during the transaction.
+ *
+ * This closes the transaction. A new transaction can be opened by sending a
+ * query with ingres_query.
+ *
+ * @param resource $link The connection link identifier
+ * @throws IngresiiException
+ *
+ */
+function ingres_rollback($link): void
+{
+ error_clear_last();
+ $result = \ingres_rollback($link);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
+
+
+/**
+ * ingres_set_environment is called to set environmental
+ * options that affect the output of certain values from Ingres, such as the
+ * timezone, date format, decimal character separator, and float precision.
+ *
+ * @param resource $link The connection link identifier
+ * @param array $options An enumerated array of option name/value pairs. The following table
+ * lists the option name and the expected type
+ *
+ *
+ *
+ *
+ *
+ * Option name
+ * Option type
+ * Description
+ * Example
+ *
+ *
+ *
+ *
+ * date_century_boundary
+ * integer
+ * The threshold by which a 2-digit year is determined to be in
+ * the current century or in the next century. Equivalent to II_DATE_CENTURY_BOUNDARY
+ * 50
+ *
+ *
+ * timezone
+ * string
+ * Controls the timezone of the session. If not set, it will
+ * default the value defined by II_TIMEZONE_NAME. If
+ * II_TIMEZONE_NAME is not defined, NA-PACIFIC (GMT-8 with Daylight
+ * Savings) is used.
+ * UNITED-KINGDOM
+ *
+ *
+ * date_format
+ * integer
+ * Sets the allowable input and output format for Ingres dates.
+ * Defaults to the value defined by II_DATE_FORMAT. If II_DATE_FORMAT is
+ * not set, the default date format is US, for example mm/dd/yy. Valid values
+ * for date_format are:
+ *
+ * INGRES_DATE_DMY
+ * INGRES_DATE_FINISH
+ * INGRES_DATE_GERMAN
+ * INGRES_DATE_ISO
+ * INGRES_DATE_ISO4
+ * INGRES_DATE_MDY
+ * INGRES_DATE_MULTINATIONAL
+ * INGRES_DATE_MULTINATIONAL4
+ * INGRES_DATE_YMD
+ * INGRES_DATE_US
+ *
+ *
+ *
+ * INGRES_DATE_ISO4
+ *
+ *
+ * decimal_separator
+ * string
+ * The character identifier for decimal data
+ * ","
+ *
+ *
+ * money_lort
+ * integer
+ * Leading or trailing currency sign. Valid values for money_lort
+ * are:
+ *
+ * INGRES_MONEY_LEADING
+ * INGRES_MONEY_TRAILING
+ *
+ *
+ *
+ * INGRES_MONEY_LEADING
+ *
+ *
+ * money_sign
+ * string
+ * The currency symbol to be used with the MONEY datatype
+ * €
+ *
+ *
+ * money_precision
+ * integer
+ * The precision of the MONEY datatype
+ * 2
+ *
+ *
+ * float4_precision
+ * integer
+ * Precision of the FLOAT4 datatype
+ * 10
+ *
+ *
+ * float8_precision
+ * integer
+ * Precision of the FLOAT8 data
+ * 10
+ *
+ *
+ * blob_segment_length
+ * integer
+ * The amount of data in bytes to fetch at a time when retrieving
+ * BLOB or CLOB data. Defaults to 4096 bytes when not set explicitly
+ * 8192
+ *
+ *
+ *
+ *
+ *
+ * Sets the allowable input and output format for Ingres dates.
+ * Defaults to the value defined by II_DATE_FORMAT. If II_DATE_FORMAT is
+ * not set, the default date format is US, for example mm/dd/yy. Valid values
+ * for date_format are:
+ *
+ * INGRES_DATE_DMY
+ * INGRES_DATE_FINISH
+ * INGRES_DATE_GERMAN
+ * INGRES_DATE_ISO
+ * INGRES_DATE_ISO4
+ * INGRES_DATE_MDY
+ * INGRES_DATE_MULTINATIONAL
+ * INGRES_DATE_MULTINATIONAL4
+ * INGRES_DATE_YMD
+ * INGRES_DATE_US
+ *
+ *
+ * Leading or trailing currency sign. Valid values for money_lort
+ * are:
+ *
+ * INGRES_MONEY_LEADING
+ * INGRES_MONEY_TRAILING
+ *
+ * @throws IngresiiException
+ *
+ */
+function ingres_set_environment($link, array $options): void
+{
+ error_clear_last();
+ $result = \ingres_set_environment($link, $options);
+ if ($result === false) {
+ throw IngresiiException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\InotifyException;
+
+/**
+ * Initialize an inotify instance for use with
+ * inotify_add_watch
+ *
+ * @return resource A stream resource.
+ * @throws InotifyException
+ *
+ */
+function inotify_init()
+{
+ error_clear_last();
+ $result = \inotify_init();
+ if ($result === false) {
+ throw InotifyException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * inotify_rm_watch removes the watch
+ * watch_descriptor from the inotify instance
+ * inotify_instance.
+ *
+ * @param resource $inotify_instance Resource returned by
+ * inotify_init
+ * @param int $watch_descriptor Watch to remove from the instance
+ * @throws InotifyException
+ *
+ */
+function inotify_rm_watch($inotify_instance, int $watch_descriptor): void
+{
+ error_clear_last();
+ $result = \inotify_rm_watch($inotify_instance, $watch_descriptor);
+ if ($result === false) {
+ throw InotifyException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\JsonException;
+
+/**
+ * Returns a string containing the JSON representation of the supplied
+ * value.
+ *
+ * The encoding is affected by the supplied options
+ * and additionally the encoding of float values depends on the value of
+ * serialize_precision.
+ *
+ * @param mixed $value The value being encoded. Can be any type except
+ * a resource.
+ *
+ * All string data must be UTF-8 encoded.
+ *
+ * PHP implements a superset of JSON as specified in the original
+ * RFC 7159.
+ * @param int $options Bitmask consisting of
+ * JSON_FORCE_OBJECT,
+ * JSON_HEX_QUOT,
+ * JSON_HEX_TAG,
+ * JSON_HEX_AMP,
+ * JSON_HEX_APOS,
+ * JSON_INVALID_UTF8_IGNORE,
+ * JSON_INVALID_UTF8_SUBSTITUTE,
+ * JSON_NUMERIC_CHECK,
+ * JSON_PARTIAL_OUTPUT_ON_ERROR,
+ * JSON_PRESERVE_ZERO_FRACTION,
+ * JSON_PRETTY_PRINT,
+ * JSON_UNESCAPED_LINE_TERMINATORS,
+ * JSON_UNESCAPED_SLASHES,
+ * JSON_UNESCAPED_UNICODE,
+ * JSON_THROW_ON_ERROR.
+ * The behaviour of these constants is described on the
+ * JSON constants page.
+ * @param int $depth Set the maximum depth. Must be greater than zero.
+ * @return string Returns a JSON encoded string on success.
+ * @throws JsonException
+ *
+ */
+function json_encode($value, int $options = 0, int $depth = 512): string
+{
+ error_clear_last();
+ $result = \json_encode($value, $options, $depth);
+ if ($result === false) {
+ throw JsonException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the error string of the last json_encode or json_decode
+ * call, which did not specify JSON_THROW_ON_ERROR.
+ *
+ * @return string Returns the error message on success, "No error" if no
+ * error has occurred.
+ * @throws JsonException
+ *
+ */
+function json_last_error_msg(): string
+{
+ error_clear_last();
+ $result = \json_last_error_msg();
+ if ($result === false) {
+ throw JsonException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\LdapException;
+
+/**
+ * Does the same thing as ldap_add but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param array $entry
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_add_ext($link_identifier, string $dn, array $entry, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_add_ext($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Add entries in the LDAP directory.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $entry An array that specifies the information about the entry. The values in
+ * the entries are indexed by individual attributes.
+ * In case of multiple values for an attribute, they are indexed using
+ * integers starting with 0.
+ *
+ *
+ *
+ * ]]>
+ *
+ *
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_add($link_identifier, string $dn, array $entry, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_add($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Does the same thing as ldap_bind but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string|null $bind_rdn
+ * @param string|null $bind_password
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_bind_ext($link_identifier, ?string $bind_rdn = null, ?string $bind_password = null, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_bind_ext($link_identifier, $bind_rdn, $bind_password, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Binds to the LDAP directory with specified RDN and password.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string|null $bind_rdn
+ * @param string|null $bind_password
+ * @throws LdapException
+ *
+ */
+function ldap_bind($link_identifier, ?string $bind_rdn = null, ?string $bind_password = null): void
+{
+ error_clear_last();
+ $result = \ldap_bind($link_identifier, $bind_rdn, $bind_password);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieve the pagination information send by the server.
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result
+ * @param string|null $cookie An opaque structure sent by the server.
+ * @param int|null $estimated The estimated number of entries to retrieve.
+ * @throws LdapException
+ *
+ */
+function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void
+{
+ error_clear_last();
+ $result = \ldap_control_paged_result_response($link, $result, $cookie, $estimated);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Enable LDAP pagination by sending the pagination control (page size, cookie...).
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param int $pagesize The number of entries by page.
+ * @param bool $iscritical Indicates whether the pagination is critical or not.
+ * If true and if the server doesn't support pagination, the search
+ * will return no result.
+ * @param string $cookie An opaque structure sent by the server
+ * (ldap_control_paged_result_response).
+ * @throws LdapException
+ *
+ */
+function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void
+{
+ error_clear_last();
+ $result = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the number of entries stored in the result of previous search
+ * operations.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_identifier The internal LDAP result.
+ * @return int Returns number of entries in the result.
+ * @throws LdapException
+ *
+ */
+function ldap_count_entries($link_identifier, $result_identifier): int
+{
+ error_clear_last();
+ $result = \ldap_count_entries($link_identifier, $result_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Does the same thing as ldap_delete but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_delete_ext($link_identifier, string $dn, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_delete_ext($link_identifier, $dn, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Deletes a particular entry in LDAP directory.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_delete($link_identifier, string $dn, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_delete($link_identifier, $dn, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Performs a PASSWD extended operation.
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param string $user dn of the user to change the password of.
+ * @param string $oldpw The old password of this user. May be ommited depending of server configuration.
+ * @param string $newpw The new password for this user. May be omitted or empty to have a generated password.
+ * @param array $serverctrls If provided, a password policy request control is send with the request and this is
+ * filled with an array of LDAP Controls
+ * returned with the request.
+ * @return mixed Returns the generated password if newpw is empty or omitted.
+ * Otherwise returns TRUE on success.
+ * @throws LdapException
+ *
+ */
+function ldap_exop_passwd($link, string $user = "", string $oldpw = "", string $newpw = "", array &$serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_exop_passwd($link, $user, $oldpw, $newpw, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Performs a WHOAMI extended operation and returns the data.
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @return string The data returned by the server.
+ * @throws LdapException
+ *
+ */
+function ldap_exop_whoami($link): string
+{
+ error_clear_last();
+ $result = \ldap_exop_whoami($link);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Performs an extended operation on the specified link with
+ * reqoid the OID of the operation and
+ * reqdata the data.
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param string $reqoid The extended operation request OID. You may use one of LDAP_EXOP_START_TLS, LDAP_EXOP_MODIFY_PASSWD, LDAP_EXOP_REFRESH, LDAP_EXOP_WHO_AM_I, LDAP_EXOP_TURN, or a string with the OID of the operation you want to send.
+ * @param string $reqdata The extended operation request data. May be NULL for some operations like LDAP_EXOP_WHO_AM_I, may also need to be BER encoded.
+ * @param array|null $serverctrls Array of LDAP Controls to send with the request.
+ * @param string|null $retdata Will be filled with the extended operation response data if provided.
+ * If not provided you may use ldap_parse_exop on the result object
+ * later to get this data.
+ * @param string|null $retoid Will be filled with the response OID if provided, usually equal to the request OID.
+ * @return mixed When used with retdata, returns TRUE on success.
+ * When used without retdata, returns a result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_exop($link, string $reqoid, string $reqdata = null, ?array $serverctrls = null, ?string &$retdata = null, ?string &$retoid = null)
+{
+ error_clear_last();
+ $result = \ldap_exop($link, $reqoid, $reqdata, $serverctrls, $retdata, $retoid);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Splits the DN returned by ldap_get_dn and breaks it
+ * up into its component parts. Each part is known as Relative Distinguished
+ * Name, or RDN.
+ *
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param int $with_attrib Used to request if the RDNs are returned with only values or their
+ * attributes as well. To get RDNs with the attributes (i.e. in
+ * attribute=value format) set with_attrib to 0
+ * and to get only values set it to 1.
+ * @return array Returns an array of all DN components.
+ * The first element in the array has count key and
+ * represents the number of returned values, next elements are numerically
+ * indexed DN components.
+ * @throws LdapException
+ *
+ */
+function ldap_explode_dn(string $dn, int $with_attrib): array
+{
+ error_clear_last();
+ $result = \ldap_explode_dn($dn, $with_attrib);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the first attribute in the given entry. Remaining attributes are
+ * retrieved by calling ldap_next_attribute successively.
+ *
+ * Similar to reading entries, attributes are also read one by one from a
+ * particular entry.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @return string Returns the first attribute in the entry on success and FALSE on
+ * error.
+ * @throws LdapException
+ *
+ */
+function ldap_first_attribute($link_identifier, $result_entry_identifier): string
+{
+ error_clear_last();
+ $result = \ldap_first_attribute($link_identifier, $result_entry_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the entry identifier for first entry in the result. This entry
+ * identifier is then supplied to ldap_next_entry
+ * routine to get successive entries from the result.
+ *
+ * Entries in the LDAP result are read sequentially using the
+ * ldap_first_entry and
+ * ldap_next_entry functions.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_identifier
+ * @return resource Returns the result entry identifier for the first entry on success and
+ * FALSE on error.
+ * @throws LdapException
+ *
+ */
+function ldap_first_entry($link_identifier, $result_identifier)
+{
+ error_clear_last();
+ $result = \ldap_first_entry($link_identifier, $result_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Frees up the memory allocated internally to store the result. All result
+ * memory will be automatically freed when the script terminates.
+ *
+ * Typically all the memory allocated for the LDAP result gets freed at the
+ * end of the script. In case the script is making successive searches which
+ * return large result sets, ldap_free_result could be
+ * called to keep the runtime memory usage by the script low.
+ *
+ * @param resource $result_identifier
+ * @throws LdapException
+ *
+ */
+function ldap_free_result($result_identifier): void
+{
+ error_clear_last();
+ $result = \ldap_free_result($result_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Reads attributes and values from an entry in the search result.
+ *
+ * Having located a specific entry in the directory, you can find out what
+ * information is held for that entry by using this call. You would use this
+ * call for an application which "browses" directory entries and/or where you
+ * do not know the structure of the directory entries. In many applications
+ * you will be searching for a specific attribute such as an email address or
+ * a surname, and won't care what other data is held.
+ *
+ *
+ *
+ *
+ *
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @return array Returns a complete entry information in a multi-dimensional array
+ * on success and FALSE on error.
+ * @throws LdapException
+ *
+ */
+function ldap_get_attributes($link_identifier, $result_entry_identifier): array
+{
+ error_clear_last();
+ $result = \ldap_get_attributes($link_identifier, $result_entry_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Finds out the DN of an entry in the result.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @return string Returns the DN of the result entry and FALSE on error.
+ * @throws LdapException
+ *
+ */
+function ldap_get_dn($link_identifier, $result_entry_identifier): string
+{
+ error_clear_last();
+ $result = \ldap_get_dn($link_identifier, $result_entry_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads multiple entries from the given result, and then reading the
+ * attributes and multiple values.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_identifier
+ * @return array Returns a complete result information in a multi-dimensional array on
+ * success and FALSE on error.
+ *
+ * The structure of the array is as follows.
+ * The attribute index is converted to lowercase. (Attributes are
+ * case-insensitive for directory servers, but not when used as
+ * array indices.)
+ *
+ *
+ *
+ *
+ *
+ * @throws LdapException
+ *
+ */
+function ldap_get_entries($link_identifier, $result_identifier): array
+{
+ error_clear_last();
+ $result = \ldap_get_entries($link_identifier, $result_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets retval to the value of the specified option.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param int $option The parameter option can be one of:
+ *
+ *
+ *
+ *
+ * Option
+ * Type
+ * since
+ *
+ *
+ *
+ *
+ * LDAP_OPT_DEREF
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_SIZELIMIT
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_TIMELIMIT
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_NETWORK_TIMEOUT
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_PROTOCOL_VERSION
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_ERROR_NUMBER
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_DIAGNOSTIC_MESSAGE
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_REFERRALS
+ * bool
+ *
+ *
+ *
+ * LDAP_OPT_RESTART
+ * bool
+ *
+ *
+ *
+ * LDAP_OPT_HOST_NAME
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_ERROR_STRING
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_MATCHED_DN
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_SERVER_CONTROLS
+ * array
+ *
+ *
+ *
+ * LDAP_OPT_CLIENT_CONTROLS
+ * array
+ *
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_IDLE
+ * int
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_PROBES
+ * int
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_INTERVAL
+ * int
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CACERTDIR
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CACERTFILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CERTFILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CIPHER_SUITE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CRLCHECK
+ * integer
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CRL_NONE
+ * integer
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CRL_PEER
+ * integer
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CRL_ALL
+ * integer
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_CRLFILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_DHFILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_KEYILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_PACKAGE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_PROTOCOL_MIN
+ * integer
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_RANDOM_FILE
+ * string
+ * 7.1
+ *
+ *
+ * LDAP_OPT_X_TLS_REQUIRE_CERT
+ * integer
+ *
+ *
+ *
+ *
+ *
+ * @param mixed $retval This will be set to the option value.
+ * @throws LdapException
+ *
+ */
+function ldap_get_option($link_identifier, int $option, &$retval): void
+{
+ error_clear_last();
+ $result = \ldap_get_option($link_identifier, $option, $retval);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Reads all the values of the attribute in the entry in the result.
+ *
+ * This function is used exactly like ldap_get_values
+ * except that it handles binary data and not string data.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @param string $attribute
+ * @return array Returns an array of values for the attribute on success and FALSE on
+ * error. Individual values are accessed by integer index in the array. The
+ * first index is 0. The number of values can be found by indexing "count"
+ * in the resultant array.
+ * @throws LdapException
+ *
+ */
+function ldap_get_values_len($link_identifier, $result_entry_identifier, string $attribute): array
+{
+ error_clear_last();
+ $result = \ldap_get_values_len($link_identifier, $result_entry_identifier, $attribute);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads all the values of the attribute in the entry in the result.
+ *
+ * This call needs a result_entry_identifier,
+ * so needs to be preceded by one of the ldap search calls and one
+ * of the calls to get an individual entry.
+ *
+ * You application will either be hard coded to look for certain
+ * attributes (such as "surname" or "mail") or you will have to use
+ * the ldap_get_attributes call to work out
+ * what attributes exist for a given entry.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @param string $attribute
+ * @return array Returns an array of values for the attribute on success and FALSE on
+ * error. The number of values can be found by indexing "count" in the
+ * resultant array. Individual values are accessed by integer index in the
+ * array. The first index is 0.
+ *
+ * LDAP allows more than one entry for an attribute, so it can, for example,
+ * store a number of email addresses for one person's directory entry all
+ * labeled with the attribute "mail"
+ *
+ *
+ * return_value["count"] = number of values for attribute
+ * return_value[0] = first value of attribute
+ * return_value[i] = ith value of attribute
+ *
+ *
+ * @throws LdapException
+ *
+ */
+function ldap_get_values($link_identifier, $result_entry_identifier, string $attribute): array
+{
+ error_clear_last();
+ $result = \ldap_get_values($link_identifier, $result_entry_identifier, $attribute);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Performs the search for a specified filter on the
+ * directory with the scope LDAP_SCOPE_ONELEVEL.
+ *
+ * LDAP_SCOPE_ONELEVEL means that the search should only
+ * return information that is at the level immediately below the
+ * base_dn given in the call.
+ * (Equivalent to typing "ls" and getting a list of files and folders in the
+ * current working directory.)
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $base_dn The base DN for the directory.
+ * @param string $filter
+ * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn").
+ * Note that the "dn" is always returned irrespective of which attributes
+ * types are requested.
+ *
+ * Using this parameter is much more efficient than the default action
+ * (which is to return all attributes and their associated values).
+ * The use of this parameter should therefore be considered good
+ * practice.
+ * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0
+ * both attributes types and attribute values are fetched which is the
+ * default behaviour.
+ * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0
+ * means no limit.
+ *
+ * This parameter can NOT override server-side preset sizelimit. You can
+ * set it lower though.
+ *
+ * Some directory server hosts will be configured to return no more than
+ * a preset number of entries. If this occurs, the server will indicate
+ * that it has only returned a partial results set. This also occurs if
+ * you use this parameter to limit the count of fetched entries.
+ * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting
+ * this to 0 means no limit.
+ *
+ * This parameter can NOT override server-side preset timelimit. You can
+ * set it lower though.
+ * @param int $deref Specifies how aliases should be handled during the search. It can be
+ * one of the following:
+ *
+ *
+ *
+ * LDAP_DEREF_NEVER - (default) aliases are never
+ * dereferenced.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_SEARCHING - aliases should be
+ * dereferenced during the search but not when locating the base object
+ * of the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_FINDING - aliases should be
+ * dereferenced when locating the base object but not during the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_ALWAYS - aliases should be dereferenced
+ * always.
+ *
+ *
+ *
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @return resource Returns a search result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_list($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null)
+{
+ error_clear_last();
+ if ($serverctrls !== null) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls);
+ } elseif ($deref !== LDAP_DEREF_NEVER) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
+ } elseif ($timelimit !== -1) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit);
+ } elseif ($sizelimit !== -1) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit);
+ } elseif ($attrsonly !== 0) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly);
+ } elseif ($attributes !== null) {
+ $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes);
+ } else {
+ $result = \ldap_list($link_identifier, $base_dn, $filter);
+ }
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Does the same thing as ldap_mod_add but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param array $entry
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_add_ext($link_identifier, string $dn, array $entry, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_mod_add_ext($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Adds one or more attribute values to the specified dn.
+ * To add a whole new object see ldap_add function.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $entry An associative array listing the attirbute values to add. If an attribute was not existing yet it will be added. If an attribute is existing you can only add values to it if it supports multiple values.
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_add($link_identifier, string $dn, array $entry, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_mod_add($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Does the same thing as ldap_mod_del but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param array $entry
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_del_ext($link_identifier, string $dn, array $entry, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_mod_del_ext($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Removes one or more attribute values from the specified dn.
+ * Object deletions are done by the
+ * ldap_delete function.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $entry
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_del($link_identifier, string $dn, array $entry, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_mod_del($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Does the same thing as ldap_mod_replace but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param array $entry
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_replace_ext($link_identifier, string $dn, array $entry, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_mod_replace_ext($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Replaces one or more attributes from the specified dn.
+ * It may also add or remove attributes.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $entry An associative array listing the attributes to replace. Sending an empty array as value will remove the attribute, while sending an attribute not existing yet on this entry will add it.
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_mod_replace($link_identifier, string $dn, array $entry, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_mod_replace($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Modifies an existing entry in the LDAP directory. Allows detailed
+ * specification of the modifications to perform.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param array $entry An array that specifies the modifications to make. Each entry in this
+ * array is an associative array with two or three keys:
+ * attrib maps to the name of the attribute to modify,
+ * modtype maps to the type of modification to perform,
+ * and (depending on the type of modification) values
+ * maps to an array of attribute values relevant to the modification.
+ *
+ * Possible values for modtype include:
+ *
+ *
+ * LDAP_MODIFY_BATCH_ADD
+ *
+ *
+ * Each value specified through values is added (as
+ * an additional value) to the attribute named by
+ * attrib.
+ *
+ *
+ *
+ *
+ * LDAP_MODIFY_BATCH_REMOVE
+ *
+ *
+ * Each value specified through values is removed
+ * from the attribute named by attrib. Any value of
+ * the attribute not contained in the values array
+ * will remain untouched.
+ *
+ *
+ *
+ *
+ * LDAP_MODIFY_BATCH_REMOVE_ALL
+ *
+ *
+ * All values are removed from the attribute named by
+ * attrib. A values entry must
+ * not be provided.
+ *
+ *
+ *
+ *
+ * LDAP_MODIFY_BATCH_REPLACE
+ *
+ *
+ * All current values of the attribute named by
+ * attrib are replaced with the values specified
+ * through values.
+ *
+ *
+ *
+ *
+ *
+ * Each value specified through values is added (as
+ * an additional value) to the attribute named by
+ * attrib.
+ *
+ * Each value specified through values is removed
+ * from the attribute named by attrib. Any value of
+ * the attribute not contained in the values array
+ * will remain untouched.
+ *
+ * All values are removed from the attribute named by
+ * attrib. A values entry must
+ * not be provided.
+ *
+ * All current values of the attribute named by
+ * attrib are replaced with the values specified
+ * through values.
+ *
+ * Note that any value for attrib must be a string, any
+ * value for values must be an array of strings, and
+ * any value for modtype must be one of the
+ * LDAP_MODIFY_BATCH_* constants listed above.
+ * @param array $serverctrls Each value specified through values is added (as
+ * an additional value) to the attribute named by
+ * attrib.
+ * @throws LdapException
+ *
+ */
+function ldap_modify_batch($link_identifier, string $dn, array $entry, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_modify_batch($link_identifier, $dn, $entry, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieves the attributes in an entry. The first call to
+ * ldap_next_attribute is made with the
+ * result_entry_identifier returned from
+ * ldap_first_attribute.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result_entry_identifier
+ * @return string Returns the next attribute in an entry on success and FALSE on
+ * error.
+ * @throws LdapException
+ *
+ */
+function ldap_next_attribute($link_identifier, $result_entry_identifier): string
+{
+ error_clear_last();
+ $result = \ldap_next_attribute($link_identifier, $result_entry_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Parse LDAP extended operation data from result object result
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result An LDAP result resource, returned by ldap_exop.
+ * @param string|null $retdata Will be filled by the response data.
+ * @param string|null $retoid Will be filled by the response OID.
+ * @throws LdapException
+ *
+ */
+function ldap_parse_exop($link, $result, ?string &$retdata = null, ?string &$retoid = null): void
+{
+ error_clear_last();
+ $result = \ldap_parse_exop($link, $result, $retdata, $retoid);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Parses an LDAP search result.
+ *
+ * @param resource $link An LDAP link identifier, returned by ldap_connect.
+ * @param resource $result An LDAP result resource, returned by ldap_list or
+ * ldap_search.
+ * @param int|null $errcode A reference to a variable that will be set to the LDAP error code in
+ * the result, or 0 if no error occurred.
+ * @param string|null $matcheddn A reference to a variable that will be set to a matched DN if one was
+ * recognised within the request, otherwise it will be set to NULL.
+ * @param string|null $errmsg A reference to a variable that will be set to the LDAP error message in
+ * the result, or an empty string if no error occurred.
+ * @param array|null $referrals A reference to a variable that will be set to an array set
+ * to all of the referral strings in the result, or an empty array if no
+ * referrals were returned.
+ * @param array|null $serverctrls An array of LDAP Controls which have been sent with the response.
+ * @throws LdapException
+ *
+ */
+function ldap_parse_result($link, $result, ?int &$errcode, ?string &$matcheddn = null, ?string &$errmsg = null, ?array &$referrals = null, ?array &$serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_parse_result($link, $result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Performs the search for a specified filter on the
+ * directory with the scope LDAP_SCOPE_BASE. So it is
+ * equivalent to reading an entry from the directory.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $base_dn The base DN for the directory.
+ * @param string $filter An empty filter is not allowed. If you want to retrieve absolutely all
+ * information for this entry, use a filter of
+ * objectClass=*. If you know which entry types are
+ * used on the directory server, you might use an appropriate filter such
+ * as objectClass=inetOrgPerson.
+ * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn").
+ * Note that the "dn" is always returned irrespective of which attributes
+ * types are requested.
+ *
+ * Using this parameter is much more efficient than the default action
+ * (which is to return all attributes and their associated values).
+ * The use of this parameter should therefore be considered good
+ * practice.
+ * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0
+ * both attributes types and attribute values are fetched which is the
+ * default behaviour.
+ * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0
+ * means no limit.
+ *
+ * This parameter can NOT override server-side preset sizelimit. You can
+ * set it lower though.
+ *
+ * Some directory server hosts will be configured to return no more than
+ * a preset number of entries. If this occurs, the server will indicate
+ * that it has only returned a partial results set. This also occurs if
+ * you use this parameter to limit the count of fetched entries.
+ * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting
+ * this to 0 means no limit.
+ *
+ * This parameter can NOT override server-side preset timelimit. You can
+ * set it lower though.
+ * @param int $deref Specifies how aliases should be handled during the search. It can be
+ * one of the following:
+ *
+ *
+ *
+ * LDAP_DEREF_NEVER - (default) aliases are never
+ * dereferenced.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_SEARCHING - aliases should be
+ * dereferenced during the search but not when locating the base object
+ * of the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_FINDING - aliases should be
+ * dereferenced when locating the base object but not during the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_ALWAYS - aliases should be dereferenced
+ * always.
+ *
+ *
+ *
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @return resource Returns a search result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_read($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null)
+{
+ error_clear_last();
+ if ($serverctrls !== null) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls);
+ } elseif ($deref !== LDAP_DEREF_NEVER) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
+ } elseif ($timelimit !== -1) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit);
+ } elseif ($sizelimit !== -1) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit);
+ } elseif ($attrsonly !== 0) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly);
+ } elseif ($attributes !== null) {
+ $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes);
+ } else {
+ $result = \ldap_read($link_identifier, $base_dn, $filter);
+ }
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Does the same thing as ldap_rename but returns the LDAP result resource to be parsed with ldap_parse_result.
+ *
+ * @param resource $link_identifier
+ * @param string $dn
+ * @param string $newrdn
+ * @param string $newparent
+ * @param bool $deleteoldrdn
+ * @param array $serverctrls
+ * @return resource Returns an LDAP result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_rename_ext($link_identifier, string $dn, string $newrdn, string $newparent, bool $deleteoldrdn, array $serverctrls = null)
+{
+ error_clear_last();
+ $result = \ldap_rename_ext($link_identifier, $dn, $newrdn, $newparent, $deleteoldrdn, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The entry specified by dn is renamed/moved.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $dn The distinguished name of an LDAP entity.
+ * @param string $newrdn The new RDN.
+ * @param string $newparent The new parent/superior entry.
+ * @param bool $deleteoldrdn If TRUE the old RDN value(s) is removed, else the old RDN value(s)
+ * is retained as non-distinguished values of the entry.
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @throws LdapException
+ *
+ */
+function ldap_rename($link_identifier, string $dn, string $newrdn, string $newparent, bool $deleteoldrdn, array $serverctrls = null): void
+{
+ error_clear_last();
+ $result = \ldap_rename($link_identifier, $dn, $newrdn, $newparent, $deleteoldrdn, $serverctrls);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $link
+ * @param string $binddn
+ * @param string $password
+ * @param string $sasl_mech
+ * @param string $sasl_realm
+ * @param string $sasl_authc_id
+ * @param string $sasl_authz_id
+ * @param string $props
+ * @throws LdapException
+ *
+ */
+function ldap_sasl_bind($link, string $binddn = null, string $password = null, string $sasl_mech = null, string $sasl_realm = null, string $sasl_authc_id = null, string $sasl_authz_id = null, string $props = null): void
+{
+ error_clear_last();
+ $result = \ldap_sasl_bind($link, $binddn, $password, $sasl_mech, $sasl_realm, $sasl_authc_id, $sasl_authz_id, $props);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Performs the search for a specified filter on the directory with the scope
+ * of LDAP_SCOPE_SUBTREE. This is equivalent to searching
+ * the entire directory.
+ *
+ * From 4.0.5 on it's also possible to do parallel searches. To do this
+ * you use an array of link identifiers, rather than a single identifier,
+ * as the first argument. If you don't want the same base DN and the
+ * same filter for all the searches, you can also use an array of base DNs
+ * and/or an array of filters. Those arrays must be of the same size as
+ * the link identifier array since the first entries of the arrays are
+ * used for one search, the second entries are used for another, and so
+ * on. When doing parallel searches an array of search result
+ * identifiers is returned, except in case of error, then the entry
+ * corresponding to the search will be FALSE. This is very much like
+ * the value normally returned, except that a result identifier is always
+ * returned when a search was made. There are some rare cases where the
+ * normal search returns FALSE while the parallel search returns an
+ * identifier.
+ *
+ * @param resource|array $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param string $base_dn The base DN for the directory.
+ * @param string $filter The search filter can be simple or advanced, using boolean operators in
+ * the format described in the LDAP documentation (see the Netscape Directory SDK or
+ * RFC4515 for full
+ * information on filters).
+ * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn").
+ * Note that the "dn" is always returned irrespective of which attributes
+ * types are requested.
+ *
+ * Using this parameter is much more efficient than the default action
+ * (which is to return all attributes and their associated values).
+ * The use of this parameter should therefore be considered good
+ * practice.
+ * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0
+ * both attributes types and attribute values are fetched which is the
+ * default behaviour.
+ * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0
+ * means no limit.
+ *
+ * This parameter can NOT override server-side preset sizelimit. You can
+ * set it lower though.
+ *
+ * Some directory server hosts will be configured to return no more than
+ * a preset number of entries. If this occurs, the server will indicate
+ * that it has only returned a partial results set. This also occurs if
+ * you use this parameter to limit the count of fetched entries.
+ * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting
+ * this to 0 means no limit.
+ *
+ * This parameter can NOT override server-side preset timelimit. You can
+ * set it lower though.
+ * @param int $deref Specifies how aliases should be handled during the search. It can be
+ * one of the following:
+ *
+ *
+ *
+ * LDAP_DEREF_NEVER - (default) aliases are never
+ * dereferenced.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_SEARCHING - aliases should be
+ * dereferenced during the search but not when locating the base object
+ * of the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_FINDING - aliases should be
+ * dereferenced when locating the base object but not during the search.
+ *
+ *
+ *
+ *
+ * LDAP_DEREF_ALWAYS - aliases should be dereferenced
+ * always.
+ *
+ *
+ *
+ * @param array $serverctrls Array of LDAP Controls to send with the request.
+ * @return resource Returns a search result identifier.
+ * @throws LdapException
+ *
+ */
+function ldap_search($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null)
+{
+ error_clear_last();
+ if ($serverctrls !== null) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls);
+ } elseif ($deref !== LDAP_DEREF_NEVER) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
+ } elseif ($timelimit !== -1) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit);
+ } elseif ($sizelimit !== -1) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit);
+ } elseif ($attrsonly !== 0) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly);
+ } elseif ($attributes !== null) {
+ $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes);
+ } else {
+ $result = \ldap_search($link_identifier, $base_dn, $filter);
+ }
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the value of the specified option to be newval.
+ *
+ * @param resource|null $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @param int $option The parameter option can be one of:
+ *
+ *
+ *
+ *
+ * Option
+ * Type
+ * Available since
+ *
+ *
+ *
+ *
+ * LDAP_OPT_DEREF
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_SIZELIMIT
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_TIMELIMIT
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_NETWORK_TIMEOUT
+ * integer
+ * PHP 5.3.0
+ *
+ *
+ * LDAP_OPT_PROTOCOL_VERSION
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_ERROR_NUMBER
+ * integer
+ *
+ *
+ *
+ * LDAP_OPT_REFERRALS
+ * bool
+ *
+ *
+ *
+ * LDAP_OPT_RESTART
+ * bool
+ *
+ *
+ *
+ * LDAP_OPT_HOST_NAME
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_ERROR_STRING
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_DIAGNOSTIC_MESSAGE
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_MATCHED_DN
+ * string
+ *
+ *
+ *
+ * LDAP_OPT_SERVER_CONTROLS
+ * array
+ *
+ *
+ *
+ * LDAP_OPT_CLIENT_CONTROLS
+ * array
+ *
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_IDLE
+ * int
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_PROBES
+ * int
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_KEEPALIVE_INTERVAL
+ * int
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CACERTDIR
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CACERTFILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CERTFILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CIPHER_SUITE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CRLCHECK
+ * integer
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_CRLFILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_DHFILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_KEYFILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_PROTOCOL_MIN
+ * integer
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_RANDOM_FILE
+ * string
+ * PHP 7.1.0
+ *
+ *
+ * LDAP_OPT_X_TLS_REQUIRE_CERT
+ * integer
+ * PHP 7.0.5
+ *
+ *
+ *
+ *
+ *
+ * LDAP_OPT_SERVER_CONTROLS and
+ * LDAP_OPT_CLIENT_CONTROLS require a list of
+ * controls, this means that the value must be an array of controls. A
+ * control consists of an oid identifying the control,
+ * an optional value, and an optional flag for
+ * criticality. In PHP a control is given by an
+ * array containing an element with the key oid
+ * and string value, and two optional elements. The optional
+ * elements are key value with string value
+ * and key iscritical with boolean value.
+ * iscritical defaults to FALSE
+ * if not supplied. See draft-ietf-ldapext-ldap-c-api-xx.txt
+ * for details. See also the second example below.
+ * @param mixed $newval The new value for the specified option.
+ * @throws LdapException
+ *
+ */
+function ldap_set_option($link_identifier, int $option, $newval): void
+{
+ error_clear_last();
+ $result = \ldap_set_option($link_identifier, $option, $newval);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Unbinds from the LDAP directory.
+ *
+ * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect.
+ * @throws LdapException
+ *
+ */
+function ldap_unbind($link_identifier): void
+{
+ error_clear_last();
+ $result = \ldap_unbind($link_identifier);
+ if ($result === false) {
+ throw LdapException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\LibxmlException;
+
+/**
+ * Retrieve last error from libxml.
+ *
+ * @return \LibXMLError Returns a LibXMLError object if there is any error in the
+ * buffer, FALSE otherwise.
+ * @throws LibxmlException
+ *
+ */
+function libxml_get_last_error(): \LibXMLError
+{
+ error_clear_last();
+ $result = \libxml_get_last_error();
+ if ($result === false) {
+ throw LibxmlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Changes the default external entity loader.
+ *
+ * @param callable $resolver_function A callable that takes three arguments. Two strings, a public id
+ * and system id, and a context (an array with four keys) as the third argument.
+ * This callback should return a resource, a string from which a resource can be
+ * opened, or NULL.
+ * @throws LibxmlException
+ *
+ */
+function libxml_set_external_entity_loader(callable $resolver_function): void
+{
+ error_clear_last();
+ $result = \libxml_set_external_entity_loader($resolver_function);
+ if ($result === false) {
+ throw LibxmlException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\LzfException;
+
+/**
+ * lzf_compress compresses the given
+ * data string using LZF encoding.
+ *
+ * @param string $data The string to compress.
+ * @return string Returns the compressed data.
+ * @throws LzfException
+ *
+ */
+function lzf_compress(string $data): string
+{
+ error_clear_last();
+ $result = \lzf_compress($data);
+ if ($result === false) {
+ throw LzfException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * lzf_compress decompresses the given
+ * data string containing lzf encoded data.
+ *
+ * @param string $data The compressed string.
+ * @return string Returns the decompressed data.
+ * @throws LzfException
+ *
+ */
+function lzf_decompress(string $data): string
+{
+ error_clear_last();
+ $result = \lzf_decompress($data);
+ if ($result === false) {
+ throw LzfException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MailparseException;
+
+/**
+ * Extracts/decodes a message section from the supplied filename.
+ *
+ * The contents of the section will be decoded according to their transfer
+ * encoding - base64, quoted-printable and uuencoded text are supported.
+ *
+ * @param resource $mimemail A valid MIME resource, created with
+ * mailparse_msg_create.
+ * @param mixed $filename Can be a file name or a valid stream resource.
+ * @param callable $callbackfunc If set, this must be either a valid callback that will be passed the
+ * extracted section, or NULL to make this function return the
+ * extracted section.
+ *
+ * If not specified, the contents will be sent to "stdout".
+ * @return string If callbackfunc is not NULL returns TRUE on
+ * success.
+ *
+ * If callbackfunc is set to NULL, returns the
+ * extracted section as a string.
+ * @throws MailparseException
+ *
+ */
+function mailparse_msg_extract_part_file($mimemail, $filename, callable $callbackfunc = null): string
+{
+ error_clear_last();
+ if ($callbackfunc !== null) {
+ $result = \mailparse_msg_extract_part_file($mimemail, $filename, $callbackfunc);
+ } else {
+ $result = \mailparse_msg_extract_part_file($mimemail, $filename);
+ }
+ if ($result === false) {
+ throw MailparseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Frees a MIME resource.
+ *
+ * @param resource $mimemail A valid MIME resource allocated by
+ * mailparse_msg_create or
+ * mailparse_msg_parse_file.
+ * @throws MailparseException
+ *
+ */
+function mailparse_msg_free($mimemail): void
+{
+ error_clear_last();
+ $result = \mailparse_msg_free($mimemail);
+ if ($result === false) {
+ throw MailparseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Parses a file.
+ * This is the optimal way of parsing a mail file that you have on disk.
+ *
+ * @param string $filename Path to the file holding the message.
+ * The file is opened and streamed through the parser.
+ *
+ * The message contained in filename is supposed to end with a newline
+ * (CRLF); otherwise the last line of the message will not be parsed.
+ * @return resource Returns a MIME resource representing the structure.
+ * @throws MailparseException
+ *
+ */
+function mailparse_msg_parse_file(string $filename)
+{
+ error_clear_last();
+ $result = \mailparse_msg_parse_file($filename);
+ if ($result === false) {
+ throw MailparseException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Incrementally parse data into the supplied mime mail resource.
+ *
+ * This function allow you to stream portions of a file at a time, rather
+ * than read and parse the whole thing.
+ *
+ * @param resource $mimemail A valid MIME resource.
+ * @param string $data The final chunk of data is supposed to end with a newline
+ * (CRLF); otherwise the last line of the message will not be parsed.
+ * @throws MailparseException
+ *
+ */
+function mailparse_msg_parse($mimemail, string $data): void
+{
+ error_clear_last();
+ $result = \mailparse_msg_parse($mimemail, $data);
+ if ($result === false) {
+ throw MailparseException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Streams data from the source file pointer, apply
+ * encoding and write to the destination file pointer.
+ *
+ * @param resource $sourcefp A valid file handle. The file is streamed through the parser.
+ * @param resource $destfp The destination file handle in which the encoded data will be written.
+ * @param string $encoding One of the character encodings supported by the
+ * mbstring module.
+ * @throws MailparseException
+ *
+ */
+function mailparse_stream_encode($sourcefp, $destfp, string $encoding): void
+{
+ error_clear_last();
+ $result = \mailparse_stream_encode($sourcefp, $destfp, $encoding);
+ if ($result === false) {
+ throw MailparseException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MbstringException;
+
+/**
+ *
+ *
+ * @param int $cp
+ * @param string $encoding
+ * @return string Returns a specific character.
+ * @throws MbstringException
+ *
+ */
+function mb_chr(int $cp, string $encoding = null): string
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_chr($cp, $encoding);
+ } else {
+ $result = \mb_chr($cp);
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the automatic character
+ * encoding detection order to encoding_list.
+ *
+ * @param mixed $encoding_list encoding_list is an array or
+ * comma separated list of character encoding. See supported encodings.
+ *
+ * If encoding_list is omitted, it returns
+ * the current character encoding detection order as array.
+ *
+ * This setting affects mb_detect_encoding and
+ * mb_send_mail.
+ *
+ * mbstring currently implements the following
+ * encoding detection filters. If there is an invalid byte sequence
+ * for the following encodings, encoding detection will fail.
+ *
+ * For ISO-8859-*, mbstring
+ * always detects as ISO-8859-*.
+ *
+ * For UTF-16, UTF-32,
+ * UCS2 and UCS4, encoding
+ * detection will fail always.
+ * @return bool|array When setting the encoding detection order, TRUE is returned on success.
+ *
+ * When getting the encoding detection order, an ordered array of the encodings is returned.
+ * @throws MbstringException
+ *
+ */
+function mb_detect_order($encoding_list = null)
+{
+ error_clear_last();
+ if ($encoding_list !== null) {
+ $result = \mb_detect_order($encoding_list);
+ } else {
+ $result = \mb_detect_order();
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns an array of aliases for a known encoding type.
+ *
+ * @param string $encoding The encoding type being checked, for aliases.
+ * @return array Returns a numerically indexed array of encoding aliases on success
+ * @throws MbstringException
+ *
+ */
+function mb_encoding_aliases(string $encoding): array
+{
+ error_clear_last();
+ $result = \mb_encoding_aliases($encoding);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Scans string for matches to
+ * pattern, then replaces the matched text
+ * with the output of callback function.
+ *
+ * The behavior of this function is almost identical to mb_ereg_replace,
+ * except for the fact that instead of
+ * replacement parameter, one should specify a
+ * callback.
+ *
+ * @param string $pattern The regular expression pattern.
+ *
+ * Multibyte characters may be used in pattern.
+ * @param callable $callback A callback that will be called and passed an array of matched elements
+ * in the subject string. The callback should
+ * return the replacement string.
+ *
+ * You'll often need the callback function
+ * for a mb_ereg_replace_callback in just one place.
+ * In this case you can use an
+ * anonymous function to
+ * declare the callback within the call to
+ * mb_ereg_replace_callback. By doing it this way
+ * you have all information for the call in one place and do not
+ * clutter the function namespace with a callback function's name
+ * not used anywhere else.
+ * @param string $string The string being checked.
+ * @param string $option The search option. See mb_regex_set_options for explanation.
+ * @return string The resultant string on success.
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_replace_callback(string $pattern, callable $callback, string $string, string $option = "msr"): string
+{
+ error_clear_last();
+ $result = \mb_ereg_replace_callback($pattern, $callback, $string, $option);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $pattern The regular expression pattern.
+ *
+ * Multibyte characters may be used in pattern.
+ * @param string $replacement The replacement text.
+ * @param string $string The string being checked.
+ * @param string $option
+ * @return string The resultant string on success.
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_replace(string $pattern, string $replacement, string $string, string $option = "msr"): string
+{
+ error_clear_last();
+ $result = \mb_ereg_replace($pattern, $replacement, $string, $option);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @return array
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_search_getregs(): array
+{
+ error_clear_last();
+ $result = \mb_ereg_search_getregs();
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mb_ereg_search_init sets
+ * string and pattern
+ * for a multibyte regular expression. These values are used for
+ * mb_ereg_search,
+ * mb_ereg_search_pos, and
+ * mb_ereg_search_regs.
+ *
+ * @param string $string The search string.
+ * @param string $pattern The search pattern.
+ * @param string $option The search option. See mb_regex_set_options for explanation.
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_search_init(string $string, string $pattern = null, string $option = "msr"): void
+{
+ error_clear_last();
+ if ($option !== "msr") {
+ $result = \mb_ereg_search_init($string, $pattern, $option);
+ } elseif ($pattern !== null) {
+ $result = \mb_ereg_search_init($string, $pattern);
+ } else {
+ $result = \mb_ereg_search_init($string);
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the matched part of a multibyte regular expression.
+ *
+ * @param string $pattern The search pattern.
+ * @param string $option The search option. See mb_regex_set_options for explanation.
+ * @return array
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_search_regs(string $pattern = null, string $option = "ms"): array
+{
+ error_clear_last();
+ if ($option !== "ms") {
+ $result = \mb_ereg_search_regs($pattern, $option);
+ } elseif ($pattern !== null) {
+ $result = \mb_ereg_search_regs($pattern);
+ } else {
+ $result = \mb_ereg_search_regs();
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param int $position The position to set. If it is negative, it counts from the end of the string.
+ * @throws MbstringException
+ *
+ */
+function mb_ereg_search_setpos(int $position): void
+{
+ error_clear_last();
+ $result = \mb_ereg_search_setpos($position);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $pattern The regular expression pattern. Multibyte characters may be used. The case will be ignored.
+ * @param string $replace The replacement text.
+ * @param string $string The searched string.
+ * @param string $option
+ * @return string The resultant string.
+ * @throws MbstringException
+ *
+ */
+function mb_eregi_replace(string $pattern, string $replace, string $string, string $option = "msri"): string
+{
+ error_clear_last();
+ $result = \mb_eregi_replace($pattern, $replace, $string, $option);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Set/Get the HTTP output character encoding.
+ * Output after this function is called will be converted from the set internal encoding to encoding.
+ *
+ * @param string $encoding If encoding is set,
+ * mb_http_output sets the HTTP output character
+ * encoding to encoding.
+ *
+ * If encoding is omitted,
+ * mb_http_output returns the current HTTP output
+ * character encoding.
+ * @return string|bool If encoding is omitted,
+ * mb_http_output returns the current HTTP output
+ * character encoding. Otherwise,
+ * Returns TRUE on success.
+ * @throws MbstringException
+ *
+ */
+function mb_http_output(string $encoding = null)
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_http_output($encoding);
+ } else {
+ $result = \mb_http_output();
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Set/Get the internal character encoding
+ *
+ * @param string $encoding encoding is the character encoding name
+ * used for the HTTP input character encoding conversion, HTTP output
+ * character encoding conversion, and the default character encoding
+ * for string functions defined by the mbstring module.
+ * You should notice that the internal encoding is totally different from the one for multibyte regex.
+ * @return string|bool If encoding is set, then
+ * Returns TRUE on success.
+ * In this case, the character encoding for multibyte regex is NOT changed.
+ * If encoding is omitted, then
+ * the current character encoding name is returned.
+ * @throws MbstringException
+ *
+ */
+function mb_internal_encoding(string $encoding = null)
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_internal_encoding($encoding);
+ } else {
+ $result = \mb_internal_encoding();
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $str
+ * @param string $encoding
+ * @return int Returns a code point of character.
+ * @throws MbstringException
+ *
+ */
+function mb_ord(string $str, string $encoding = null): int
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_ord($str, $encoding);
+ } else {
+ $result = \mb_ord($str);
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Parses GET/POST/COOKIE data and
+ * sets global variables. Since PHP does not provide raw POST/COOKIE
+ * data, it can only be used for GET data for now. It parses URL
+ * encoded data, detects encoding, converts coding to internal
+ * encoding and set values to the result array or
+ * global variables.
+ *
+ * @param string $encoded_string The URL encoded data.
+ * @param array|null $result An array containing decoded and character encoded converted values.
+ * @throws MbstringException
+ *
+ */
+function mb_parse_str(string $encoded_string, ?array &$result): void
+{
+ error_clear_last();
+ $result = \mb_parse_str($encoded_string, $result);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set/Get character encoding for a multibyte regex.
+ *
+ * @param string $encoding The encoding
+ * parameter is the character encoding. If it is omitted, the internal character
+ * encoding value will be used.
+ * @return string|bool
+ * @throws MbstringException
+ *
+ */
+function mb_regex_encoding(string $encoding = null)
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_regex_encoding($encoding);
+ } else {
+ $result = \mb_regex_encoding();
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sends email. Headers and messages are converted and encoded according
+ * to the mb_language setting. It's a wrapper function
+ * for mail, so see also mail for details.
+ *
+ * @param string $to The mail addresses being sent to. Multiple
+ * recipients may be specified by putting a comma between each
+ * address in to.
+ * This parameter is not automatically encoded.
+ * @param string $subject The subject of the mail.
+ * @param string $message The message of the mail.
+ * @param string|array|null $additional_headers String or array to be inserted at the end of the email header.
+ *
+ * This is typically used to add extra headers (From, Cc, and Bcc).
+ * Multiple extra headers should be separated with a CRLF (\r\n).
+ * Validate parameter not to be injected unwanted headers by attackers.
+ *
+ * If an array is passed, its keys are the header names and its
+ * values are the respective header values.
+ *
+ * When sending mail, the mail must contain
+ * a From header. This can be set with the
+ * additional_headers parameter, or a default
+ * can be set in php.ini.
+ *
+ * Failing to do this will result in an error
+ * message similar to Warning: mail(): "sendmail_from" not
+ * set in php.ini or custom "From:" header missing.
+ * The From header sets also
+ * Return-Path under Windows.
+ *
+ * If messages are not received, try using a LF (\n) only.
+ * Some Unix mail transfer agents (most notably
+ * qmail) replace LF by CRLF
+ * automatically (which leads to doubling CR if CRLF is used).
+ * This should be a last resort, as it does not comply with
+ * RFC 2822.
+ * @param string $additional_parameter additional_parameter is a MTA command line
+ * parameter. It is useful when setting the correct Return-Path
+ * header when using sendmail.
+ *
+ * This parameter is escaped by escapeshellcmd internally
+ * to prevent command execution. escapeshellcmd prevents
+ * command execution, but allows to add additional parameters. For security reason,
+ * this parameter should be validated.
+ *
+ * Since escapeshellcmd is applied automatically, some characters
+ * that are allowed as email addresses by internet RFCs cannot be used. Programs
+ * that are required to use these characters mail cannot be used.
+ *
+ * The user that the webserver runs as should be added as a trusted user to the
+ * sendmail configuration to prevent a 'X-Warning' header from being added
+ * to the message when the envelope sender (-f) is set using this method.
+ * For sendmail users, this file is /etc/mail/trusted-users.
+ * @throws MbstringException
+ *
+ */
+function mb_send_mail(string $to, string $subject, string $message, $additional_headers = null, string $additional_parameter = null): void
+{
+ error_clear_last();
+ $result = \mb_send_mail($to, $subject, $message, $additional_headers, $additional_parameter);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $pattern The regular expression pattern.
+ * @param string $string The string being split.
+ * @param int $limit
+ * @return array The result as an array.
+ * @throws MbstringException
+ *
+ */
+function mb_split(string $pattern, string $string, int $limit = -1): array
+{
+ error_clear_last();
+ $result = \mb_split($pattern, $string, $limit);
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function will return an array of strings, it is a version of str_split with support for encodings of variable character size as well as fixed-size encodings of 1,2 or 4 byte characters.
+ * If the split_length parameter is specified, the string is broken down into chunks of the specified length in characters (not bytes).
+ * The encoding parameter can be optionally specified and it is good practice to do so.
+ *
+ * @param string $string The string to split into characters or chunks.
+ * @param int $split_length If specified, each element of the returned array will be composed of multiple characters instead of a single character.
+ * @param string $encoding The encoding
+ * parameter is the character encoding. If it is omitted, the internal character
+ * encoding value will be used.
+ *
+ * A string specifying one of the supported encodings.
+ * @return array mb_str_split returns an array of strings.
+ * @throws MbstringException
+ *
+ */
+function mb_str_split(string $string, int $split_length = 1, string $encoding = null): array
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \mb_str_split($string, $split_length, $encoding);
+ } else {
+ $result = \mb_str_split($string, $split_length);
+ }
+ if ($result === false) {
+ throw MbstringException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MiscException;
+
+/**
+ * Defines a named constant at runtime.
+ *
+ * @param string $name The name of the constant.
+ *
+ * It is possible to define constants with reserved or
+ * even invalid names, whose value can (only) be retrieved with
+ * constant. However, doing so is not recommended.
+ * @param mixed $value The value of the constant. In PHP 5, value must
+ * be a scalar value (integer,
+ * float, string, boolean, or
+ * NULL). In PHP 7, array values are also accepted.
+ *
+ * While it is possible to define resource constants, it is
+ * not recommended and may cause unpredictable behavior.
+ * @param bool $case_insensitive If set to TRUE, the constant will be defined case-insensitive.
+ * The default behavior is case-sensitive; i.e.
+ * CONSTANT and Constant represent
+ * different values.
+ *
+ * Case-insensitive constants are stored as lower-case.
+ * @throws MiscException
+ *
+ */
+function define(string $name, $value, bool $case_insensitive = false): void
+{
+ error_clear_last();
+ $result = \define($name, $value, $case_insensitive);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prints out or returns a syntax highlighted version of the code contained
+ * in filename using the colors defined in the
+ * built-in syntax highlighter for PHP.
+ *
+ * Many servers are configured to automatically highlight files
+ * with a phps extension. For example,
+ * example.phps when viewed will show the
+ * syntax highlighted source of the file. To enable this, add this
+ * line to the httpd.conf:
+ *
+ * @param string $filename Path to the PHP file to be highlighted.
+ * @param bool $return Set this parameter to TRUE to make this function return the
+ * highlighted code.
+ * @return string|bool If return is set to TRUE, returns the highlighted
+ * code as a string instead of printing it out. Otherwise, it will return
+ * TRUE on success, FALSE on failure.
+ * @throws MiscException
+ *
+ */
+function highlight_file(string $filename, bool $return = false)
+{
+ error_clear_last();
+ $result = \highlight_file($filename, $return);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $str The PHP code to be highlighted. This should include the opening tag.
+ * @param bool $return Set this parameter to TRUE to make this function return the
+ * highlighted code.
+ * @return string|bool If return is set to TRUE, returns the highlighted
+ * code as a string instead of printing it out. Otherwise, it will return
+ * TRUE on success, FALSE on failure.
+ * @throws MiscException
+ *
+ */
+function highlight_string(string $str, bool $return = false)
+{
+ error_clear_last();
+ $result = \highlight_string($str, $return);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Pack given arguments into a binary string according to
+ * format.
+ *
+ * The idea for this function was taken from Perl and all formatting codes
+ * work the same as in Perl. However, there are some formatting codes that are
+ * missing such as Perl's "u" format code.
+ *
+ * Note that the distinction between signed and unsigned values only
+ * affects the function unpack, where as
+ * function pack gives the same result for
+ * signed and unsigned format codes.
+ *
+ * @param string $format The format string consists of format codes
+ * followed by an optional repeater argument. The repeater argument can
+ * be either an integer value or * for repeating to
+ * the end of the input data. For a, A, h, H the repeat count specifies
+ * how many characters of one data argument are taken, for @ it is the
+ * absolute position where to put the next data, for everything else the
+ * repeat count specifies how many data arguments are consumed and packed
+ * into the resulting binary string.
+ *
+ * Currently implemented formats are:
+ *
+ * pack format characters
+ *
+ *
+ *
+ * Code
+ * Description
+ *
+ *
+ *
+ *
+ * a
+ * NUL-padded string
+ *
+ *
+ * A
+ * SPACE-padded string
+ *
+ * h
+ * Hex string, low nibble first
+ *
+ * H
+ * Hex string, high nibble first
+ * csigned char
+ *
+ * C
+ * unsigned char
+ *
+ * s
+ * signed short (always 16 bit, machine byte order)
+ *
+ *
+ * S
+ * unsigned short (always 16 bit, machine byte order)
+ *
+ *
+ * n
+ * unsigned short (always 16 bit, big endian byte order)
+ *
+ *
+ * v
+ * unsigned short (always 16 bit, little endian byte order)
+ *
+ *
+ * i
+ * signed integer (machine dependent size and byte order)
+ *
+ *
+ * I
+ * unsigned integer (machine dependent size and byte order)
+ *
+ *
+ * l
+ * signed long (always 32 bit, machine byte order)
+ *
+ *
+ * L
+ * unsigned long (always 32 bit, machine byte order)
+ *
+ *
+ * N
+ * unsigned long (always 32 bit, big endian byte order)
+ *
+ *
+ * V
+ * unsigned long (always 32 bit, little endian byte order)
+ *
+ *
+ * q
+ * signed long long (always 64 bit, machine byte order)
+ *
+ *
+ * Q
+ * unsigned long long (always 64 bit, machine byte order)
+ *
+ *
+ * J
+ * unsigned long long (always 64 bit, big endian byte order)
+ *
+ *
+ * P
+ * unsigned long long (always 64 bit, little endian byte order)
+ *
+ *
+ * f
+ * float (machine dependent size and representation)
+ *
+ *
+ * g
+ * float (machine dependent size, little endian byte order)
+ *
+ *
+ * G
+ * float (machine dependent size, big endian byte order)
+ *
+ *
+ * d
+ * double (machine dependent size and representation)
+ *
+ *
+ * e
+ * double (machine dependent size, little endian byte order)
+ *
+ *
+ * E
+ * double (machine dependent size, big endian byte order)
+ *
+ *
+ * x
+ * NUL byte
+ *
+ *
+ * X
+ * Back up one byte
+ *
+ *
+ * Z
+ * NUL-padded string (new in PHP 5.5)
+ *
+ *
+ * @
+ * NUL-fill to absolute position
+ *
+ *
+ *
+ *
+ * @param mixed $params
+ * @return string Returns a binary string containing data.
+ * @throws MiscException
+ *
+ */
+function pack(string $format, ...$params): string
+{
+ error_clear_last();
+ if ($params !== []) {
+ $result = \pack($format, ...$params);
+ } else {
+ $result = \pack($format);
+ }
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Convert string from one codepage to another.
+ *
+ * @param int|string $in_codepage The codepage of the subject string.
+ * Either the codepage name or identifier.
+ * @param int|string $out_codepage The codepage to convert the subject string to.
+ * Either the codepage name or identifier.
+ * @param string $subject The string to convert.
+ * @return string The subject string converted to
+ * out_codepage.
+ * @throws MiscException
+ *
+ */
+function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject): string
+{
+ error_clear_last();
+ $result = \sapi_windows_cp_conv($in_codepage, $out_codepage, $subject);
+ if ($result === null) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Set the codepage of the current process.
+ *
+ * @param int $cp A codepage identifier.
+ * @throws MiscException
+ *
+ */
+function sapi_windows_cp_set(int $cp): void
+{
+ error_clear_last();
+ $result = \sapi_windows_cp_set($cp);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sends a CTRL event to another process in the same process group.
+ *
+ * @param int $event The CTRL even to send;
+ * either PHP_WINDOWS_EVENT_CTRL_C
+ * or PHP_WINDOWS_EVENT_CTRL_BREAK.
+ * @param int $pid The ID of the process to which to send the event to. If 0
+ * is given, the event is sent to all processes of the process group.
+ * @throws MiscException
+ *
+ */
+function sapi_windows_generate_ctrl_event(int $event, int $pid = 0): void
+{
+ error_clear_last();
+ $result = \sapi_windows_generate_ctrl_event($event, $pid);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+}
+
+
+/**
+ * If enable is omitted, the function returns TRUE if the stream stream has VT100 control codes enabled, FALSE otherwise.
+ *
+ * If enable is specified, the function will try to enable or disable the VT100 features of the stream stream.
+ * If the feature has been successfully enabled (or disabled).
+ *
+ * At startup, PHP tries to enable the VT100 feature of the STDOUT/STDERR streams. By the way, if those streams are redirected to a file, the VT100 features may not be enabled.
+ *
+ * If VT100 support is enabled, it is possible to use control sequences as they are known from the VT100 terminal.
+ * They allow the modification of the terminal's output. On Windows these sequences are called Console Virtual Terminal Sequences.
+ *
+ * @param resource $stream The stream on which the function will operate.
+ * @param bool $enable If specified, the VT100 feature will be enabled (if TRUE) or disabled (if FALSE).
+ * @throws MiscException
+ *
+ */
+function sapi_windows_vt100_support($stream, bool $enable = null): void
+{
+ error_clear_last();
+ if ($enable !== null) {
+ $result = \sapi_windows_vt100_support($stream, $enable);
+ } else {
+ $result = \sapi_windows_vt100_support($stream);
+ }
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $seconds Halt time in seconds.
+ * @return int Returns zero on success.
+ *
+ * If the call was interrupted by a signal, sleep returns
+ * a non-zero value. On Windows, this value will always be
+ * 192 (the value of the
+ * WAIT_IO_COMPLETION constant within the Windows API).
+ * On other platforms, the return value will be the number of seconds left to
+ * sleep.
+ * @throws MiscException
+ *
+ */
+function sleep(int $seconds): int
+{
+ error_clear_last();
+ $result = \sleep($seconds);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Delays program execution for the given number of
+ * seconds and nanoseconds.
+ *
+ * @param int $seconds Must be a non-negative integer.
+ * @param int $nanoseconds Must be a non-negative integer less than 1 billion.
+ * @return array{0:int,1:int}|bool Returns TRUE on success.
+ *
+ * If the delay was interrupted by a signal, an associative array will be
+ * returned with the components:
+ *
+ *
+ *
+ * seconds - number of seconds remaining in
+ * the delay
+ *
+ *
+ *
+ *
+ * nanoseconds - number of nanoseconds
+ * remaining in the delay
+ *
+ *
+ *
+ * @throws MiscException
+ *
+ */
+function time_nanosleep(int $seconds, int $nanoseconds)
+{
+ error_clear_last();
+ $result = \time_nanosleep($seconds, $nanoseconds);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Makes the script sleep until the specified
+ * timestamp.
+ *
+ * @param float $timestamp The timestamp when the script should wake.
+ * @throws MiscException
+ *
+ */
+function time_sleep_until(float $timestamp): void
+{
+ error_clear_last();
+ $result = \time_sleep_until($timestamp);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Unpacks from a binary string into an array according to the given
+ * format.
+ *
+ * The unpacked data is stored in an associative array. To
+ * accomplish this you have to name the different format codes and
+ * separate them by a slash /. If a repeater argument is present,
+ * then each of the array keys will have a sequence number behind
+ * the given name.
+ *
+ * @param string $format See pack for an explanation of the format codes.
+ * @param string $data The packed data.
+ * @param int $offset The offset to begin unpacking from.
+ * @return array Returns an associative array containing unpacked elements of binary
+ * string.
+ * @throws MiscException
+ *
+ */
+function unpack(string $format, string $data, int $offset = 0): array
+{
+ error_clear_last();
+ $result = \unpack($format, $data, $offset);
+ if ($result === false) {
+ throw MiscException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MsqlException;
+
+/**
+ * Returns number of affected rows by the last SELECT, UPDATE or DELETE
+ * query associated with result.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @return int Returns the number of affected rows on success.
+ * @throws MsqlException
+ *
+ */
+function msql_affected_rows($result): int
+{
+ error_clear_last();
+ $result = \msql_affected_rows($result);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_close closes the non-persistent connection to
+ * the mSQL server that's associated with the specified link identifier.
+ *
+ * Using msql_close isn't usually necessary, as
+ * non-persistent open links are automatically closed at the end of the
+ * script's execution. See also freeing resources.
+ *
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @throws MsqlException
+ *
+ */
+function msql_close($link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_close($link_identifier);
+ } else {
+ $result = \msql_close();
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msql_connect establishes a connection to a mSQL
+ * server.
+ *
+ * If a second call is made to msql_connect with
+ * the same arguments, no new link will be established, but instead, the
+ * link identifier of the already opened link will be returned.
+ *
+ * The link to the server will be closed as soon as the execution of the
+ * script ends, unless it's closed earlier by explicitly calling
+ * msql_close.
+ *
+ * @param string $hostname The hostname can also include a port number. e.g.
+ * hostname,port.
+ *
+ * If not specified, the connection is established by the means of a Unix
+ * domain socket, being then more efficient then a localhost TCP socket
+ * connection.
+ *
+ * While this function will accept a colon (:) as a
+ * host/port separator, a comma (,) is the preferred
+ * method.
+ * @return resource Returns a positive mSQL link identifier on success.
+ * @throws MsqlException
+ *
+ */
+function msql_connect(string $hostname = null)
+{
+ error_clear_last();
+ if ($hostname !== null) {
+ $result = \msql_connect($hostname);
+ } else {
+ $result = \msql_connect();
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_create_db attempts to create a new database on
+ * the mSQL server.
+ *
+ * @param string $database_name The name of the mSQL database.
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @throws MsqlException
+ *
+ */
+function msql_create_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_create_db($database_name, $link_identifier);
+ } else {
+ $result = \msql_create_db($database_name);
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msql_data_seek moves the internal row
+ * pointer of the mSQL result associated with the specified query
+ * identifier to point to the specified row number. The next call
+ * to msql_fetch_row would return that
+ * row.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $row_number The seeked row number.
+ * @throws MsqlException
+ *
+ */
+function msql_data_seek($result, int $row_number): void
+{
+ error_clear_last();
+ $result = \msql_data_seek($result, $row_number);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msql_db_query selects a database and executes a query
+ * on it.
+ *
+ * @param string $database The name of the mSQL database.
+ * @param string $query The SQL query.
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @return resource Returns a positive mSQL query identifier to the query result.
+ * @throws MsqlException
+ *
+ */
+function msql_db_query(string $database, string $query, $link_identifier = null)
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_db_query($database, $query, $link_identifier);
+ } else {
+ $result = \msql_db_query($database, $query);
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_drop_db attempts to drop (remove) a database
+ * from the mSQL server.
+ *
+ * @param string $database_name The name of the database.
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @throws MsqlException
+ *
+ */
+function msql_drop_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_drop_db($database_name, $link_identifier);
+ } else {
+ $result = \msql_drop_db($database_name);
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msql_field_len returns the length of the specified
+ * field.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 1.
+ * @return int Returns the length of the specified field.
+ * @throws MsqlException
+ *
+ */
+function msql_field_len($result, int $field_offset): int
+{
+ error_clear_last();
+ $result = \msql_field_len($result, $field_offset);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_field_name gets the name of the specified field
+ * index.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 1.
+ * @return string The name of the field.
+ * @throws MsqlException
+ *
+ */
+function msql_field_name($result, int $field_offset): string
+{
+ error_clear_last();
+ $result = \msql_field_name($result, $field_offset);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Seeks to the specified field offset. If the next call to
+ * msql_fetch_field won't include a field offset, this
+ * field would be returned.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 1.
+ * @throws MsqlException
+ *
+ */
+function msql_field_seek($result, int $field_offset): void
+{
+ error_clear_last();
+ $result = \msql_field_seek($result, $field_offset);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns the name of the table that the specified field is in.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 1.
+ * @return int The name of the table on success.
+ * @throws MsqlException
+ *
+ */
+function msql_field_table($result, int $field_offset): int
+{
+ error_clear_last();
+ $result = \msql_field_table($result, $field_offset);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_field_type gets the type of the specified field
+ * index.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 1.
+ * @return string The type of the field. One of int,
+ * char, real, ident,
+ * null or unknown. This functions will
+ * return FALSE on failure.
+ * @throws MsqlException
+ *
+ */
+function msql_field_type($result, int $field_offset): string
+{
+ error_clear_last();
+ $result = \msql_field_type($result, $field_offset);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_free_result frees the memory associated
+ * with query_identifier. When PHP completes a
+ * request, this memory is freed automatically, so you only need to
+ * call this function when you want to make sure you don't use too
+ * much memory while the script is running.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * msql_query.
+ * @throws MsqlException
+ *
+ */
+function msql_free_result($result): void
+{
+ error_clear_last();
+ $result = \msql_free_result($result);
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msql_pconnect acts very much like
+ * msql_connect with two major differences.
+ *
+ * First, when connecting, the function would first try to find a
+ * (persistent) link that's already open with the same host.
+ * If one is found, an identifier for it will be returned instead of opening
+ * a new connection.
+ *
+ * Second, the connection to the SQL server will not be closed when the
+ * execution of the script ends. Instead, the link will remain open for
+ * future use (msql_close will not close links
+ * established by this function).
+ *
+ * @param string $hostname The hostname can also include a port number. e.g.
+ * hostname,port.
+ *
+ * If not specified, the connection is established by the means of a Unix
+ * domain socket, being more efficient than a localhost TCP socket
+ * connection.
+ * @return resource Returns a positive mSQL link identifier on success.
+ * @throws MsqlException
+ *
+ */
+function msql_pconnect(string $hostname = null)
+{
+ error_clear_last();
+ if ($hostname !== null) {
+ $result = \msql_pconnect($hostname);
+ } else {
+ $result = \msql_pconnect();
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_query sends a query to the currently active
+ * database on the server that's associated with the specified link
+ * identifier.
+ *
+ * @param string $query The SQL query.
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @return resource Returns a positive mSQL query identifier on success.
+ * @throws MsqlException
+ *
+ */
+function msql_query(string $query, $link_identifier = null)
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_query($query, $link_identifier);
+ } else {
+ $result = \msql_query($query);
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * msql_select_db sets the current active database on
+ * the server that's associated with the specified
+ * link_identifier.
+ *
+ * Subsequent calls to msql_query will be made on the
+ * active database.
+ *
+ * @param string $database_name The database name.
+ * @param resource|null $link_identifier The mSQL connection.
+ * If not specified, the last link opened by msql_connect
+ * is assumed. If no such link is found, the function will try to establish a
+ * link as if msql_connect was called, and use it.
+ * @throws MsqlException
+ *
+ */
+function msql_select_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ if ($link_identifier !== null) {
+ $result = \msql_select_db($database_name, $link_identifier);
+ } else {
+ $result = \msql_select_db($database_name);
+ }
+ if ($result === false) {
+ throw MsqlException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MysqlException;
+
+/**
+ * mysql_close closes the non-persistent connection to
+ * the MySQL server that's associated with the specified link identifier. If
+ * link_identifier isn't specified, the last opened
+ * link is used.
+ *
+ *
+ * Open non-persistent MySQL connections and result sets are automatically destroyed when a
+ * PHP script finishes its execution. So, while explicitly closing open
+ * connections and freeing result sets is optional, doing so is recommended.
+ * This will immediately return resources to PHP and MySQL, which can
+ * improve performance. For related information, see
+ * freeing resources
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no connection is found or
+ * established, an E_WARNING level error is
+ * generated.
+ * @throws MysqlException
+ *
+ */
+function mysql_close($link_identifier = null): void
+{
+ error_clear_last();
+ $result = \mysql_close($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Opens or reuses a connection to a MySQL server.
+ *
+ * @param string $server The MySQL server. It can also include a port number. e.g.
+ * "hostname:port" or a path to a local socket e.g. ":/path/to/socket" for
+ * the localhost.
+ *
+ * If the PHP directive
+ * mysql.default_host is undefined (default), then the default
+ * value is 'localhost:3306'. In SQL safe mode, this parameter is ignored
+ * and value 'localhost:3306' is always used.
+ * @param string $username The username. Default value is defined by mysql.default_user. In
+ * SQL safe mode, this parameter is ignored and the name of the user that
+ * owns the server process is used.
+ * @param string $password The password. Default value is defined by mysql.default_password. In
+ * SQL safe mode, this parameter is ignored and empty password is used.
+ * @param bool $new_link If a second call is made to mysql_connect
+ * with the same arguments, no new link will be established, but
+ * instead, the link identifier of the already opened link will be
+ * returned. The new_link parameter modifies this
+ * behavior and makes mysql_connect always open
+ * a new link, even if mysql_connect was called
+ * before with the same parameters.
+ * In SQL safe mode, this parameter is ignored.
+ * @param int $client_flags The client_flags parameter can be a combination
+ * of the following constants:
+ * 128 (enable LOAD DATA LOCAL handling),
+ * MYSQL_CLIENT_SSL,
+ * MYSQL_CLIENT_COMPRESS,
+ * MYSQL_CLIENT_IGNORE_SPACE or
+ * MYSQL_CLIENT_INTERACTIVE.
+ * Read the section about for further information.
+ * In SQL safe mode, this parameter is ignored.
+ * @return resource Returns a MySQL link identifier on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_connect(string $server = null, string $username = null, string $password = null, bool $new_link = false, int $client_flags = 0)
+{
+ error_clear_last();
+ if ($client_flags !== 0) {
+ $result = \mysql_connect($server, $username, $password, $new_link, $client_flags);
+ } elseif ($new_link !== false) {
+ $result = \mysql_connect($server, $username, $password, $new_link);
+ } elseif ($password !== null) {
+ $result = \mysql_connect($server, $username, $password);
+ } elseif ($username !== null) {
+ $result = \mysql_connect($server, $username);
+ } elseif ($server !== null) {
+ $result = \mysql_connect($server);
+ } else {
+ $result = \mysql_connect();
+ }
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_create_db attempts to create a new
+ * database on the server associated with the specified link
+ * identifier.
+ *
+ * @param string $database_name The name of the database being created.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @throws MysqlException
+ *
+ */
+function mysql_create_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ $result = \mysql_create_db($database_name, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * mysql_data_seek moves the internal row
+ * pointer of the MySQL result associated with the specified result
+ * identifier to point to the specified row number. The next call
+ * to a MySQL fetch function, such as mysql_fetch_assoc,
+ * would return that row.
+ *
+ * row_number starts at 0. The
+ * row_number should be a value in the range from 0 to
+ * mysql_num_rows - 1. However if the result set
+ * is empty (mysql_num_rows == 0), a seek to 0 will
+ * fail with an E_WARNING and
+ * mysql_data_seek will return FALSE.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $row_number The desired row number of the new result pointer.
+ * @throws MysqlException
+ *
+ */
+function mysql_data_seek($result, int $row_number): void
+{
+ error_clear_last();
+ $result = \mysql_data_seek($result, $row_number);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieve the database name from a call to
+ * mysql_list_dbs.
+ *
+ * @param resource $result The result pointer from a call to mysql_list_dbs.
+ * @param int $row The index into the result set.
+ * @param mixed $field The field name.
+ * @return string Returns the database name on success. If FALSE
+ * is returned, use mysql_error to determine the nature
+ * of the error.
+ * @throws MysqlException
+ *
+ */
+function mysql_db_name($result, int $row, $field = null): string
+{
+ error_clear_last();
+ $result = \mysql_db_name($result, $row, $field);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_db_query selects a database, and executes a
+ * query on it.
+ *
+ * @param string $database The name of the database that will be selected.
+ * @param string $query The MySQL query.
+ *
+ * Data inside the query should be properly escaped.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource|bool Returns a positive MySQL result resource to the query result. The function also returns TRUE/FALSE for
+ * INSERT/UPDATE/DELETE
+ * queries to indicate success/failure.
+ * @throws MysqlException
+ *
+ */
+function mysql_db_query(string $database, string $query, $link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_db_query($database, $query, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_drop_db attempts to drop (remove) an
+ * entire database from the server associated with the specified
+ * link identifier. This function is deprecated, it is preferable to use
+ * mysql_query to issue an sql
+ * DROP DATABASE statement instead.
+ *
+ * @param string $database_name The name of the database that will be deleted.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @throws MysqlException
+ *
+ */
+function mysql_drop_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ $result = \mysql_drop_db($database_name, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns an array that corresponds to the lengths of each field
+ * in the last row fetched by MySQL.
+ *
+ * mysql_fetch_lengths stores the lengths of
+ * each result column in the last row returned by
+ * mysql_fetch_row,
+ * mysql_fetch_assoc,
+ * mysql_fetch_array, and
+ * mysql_fetch_object in an array, starting at
+ * offset 0.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @return array An array of lengths on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_fetch_lengths($result): array
+{
+ error_clear_last();
+ $result = \mysql_fetch_lengths($result);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_field_flags returns the field flags of
+ * the specified field. The flags are reported as a single word
+ * per flag separated by a single space, so that you can split the
+ * returned value using explode.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 0. If
+ * field_offset does not exist, an error of level
+ * E_WARNING is also issued.
+ * @return string Returns a string of flags associated with the result.
+ *
+ * The following flags are reported, if your version of MySQL
+ * is current enough to support them: "not_null",
+ * "primary_key", "unique_key",
+ * "multiple_key", "blob",
+ * "unsigned", "zerofill",
+ * "binary", "enum",
+ * "auto_increment" and "timestamp".
+ * @throws MysqlException
+ *
+ */
+function mysql_field_flags($result, int $field_offset): string
+{
+ error_clear_last();
+ $result = \mysql_field_flags($result, $field_offset);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_field_len returns the length of the
+ * specified field.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 0. If
+ * field_offset does not exist, an error of level
+ * E_WARNING is also issued.
+ * @return int The length of the specified field index on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_field_len($result, int $field_offset): int
+{
+ error_clear_last();
+ $result = \mysql_field_len($result, $field_offset);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_field_name returns the name of the
+ * specified field index.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 0. If
+ * field_offset does not exist, an error of level
+ * E_WARNING is also issued.
+ * @return string The name of the specified field index on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_field_name($result, int $field_offset): string
+{
+ error_clear_last();
+ $result = \mysql_field_name($result, $field_offset);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Seeks to the specified field offset. If the next call to
+ * mysql_fetch_field doesn't include a field
+ * offset, the field offset specified in
+ * mysql_field_seek will be returned.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $field_offset The numerical field offset. The
+ * field_offset starts at 0. If
+ * field_offset does not exist, an error of level
+ * E_WARNING is also issued.
+ * @throws MysqlException
+ *
+ */
+function mysql_field_seek($result, int $field_offset): void
+{
+ error_clear_last();
+ $result = \mysql_field_seek($result, $field_offset);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * mysql_free_result will free all memory
+ * associated with the result identifier result.
+ *
+ * mysql_free_result only needs to be called if
+ * you are concerned about how much memory is being used for queries
+ * that return large result sets. All associated result memory is
+ * automatically freed at the end of the script's execution.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @throws MysqlException
+ *
+ */
+function mysql_free_result($result): void
+{
+ error_clear_last();
+ $result = \mysql_free_result($result);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Describes the type of connection in use for the connection, including the
+ * server host name.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return string Returns a string describing the type of MySQL connection in use for the
+ * connection.
+ * @throws MysqlException
+ *
+ */
+function mysql_get_host_info($link_identifier = null): string
+{
+ error_clear_last();
+ $result = \mysql_get_host_info($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the MySQL protocol.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return int Returns the MySQL protocol on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_get_proto_info($link_identifier = null): int
+{
+ error_clear_last();
+ $result = \mysql_get_proto_info($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the MySQL server version.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return string Returns the MySQL server version on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_get_server_info($link_identifier = null): string
+{
+ error_clear_last();
+ $result = \mysql_get_server_info($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns detailed information about the last query.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return string Returns information about the statement on success. See the example below for which statements provide information,
+ * and what the returned value may look like. Statements that are not listed
+ * will return FALSE.
+ * @throws MysqlException
+ *
+ */
+function mysql_info($link_identifier = null): string
+{
+ error_clear_last();
+ $result = \mysql_info($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns a result pointer containing the databases available from the
+ * current mysql daemon.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource Returns a result pointer resource on success. Use the mysql_tablename function to traverse
+ * this result pointer, or any function for result tables, such as
+ * mysql_fetch_array.
+ * @throws MysqlException
+ *
+ */
+function mysql_list_dbs($link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_list_dbs($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves information about the given table name.
+ *
+ * This function is deprecated. It is preferable to use
+ * mysql_query to issue an SQL SHOW COLUMNS FROM
+ * table [LIKE 'name'] statement instead.
+ *
+ * @param string $database_name The name of the database that's being queried.
+ * @param string $table_name The name of the table that's being queried.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource A result pointer resource on success.
+ *
+ * The returned result can be used with mysql_field_flags,
+ * mysql_field_len,
+ * mysql_field_name and
+ * mysql_field_type.
+ * @throws MysqlException
+ *
+ */
+function mysql_list_fields(string $database_name, string $table_name, $link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_list_fields($database_name, $table_name, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the current MySQL server threads.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource A result pointer resource on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_list_processes($link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_list_processes($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves a list of table names from a MySQL database.
+ *
+ * This function is deprecated. It is preferable to use
+ * mysql_query to issue an SQL SHOW TABLES
+ * [FROM db_name] [LIKE 'pattern'] statement instead.
+ *
+ * @param string $database The name of the database
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource A result pointer resource on success.
+ *
+ * Use the mysql_tablename function to
+ * traverse this result pointer, or any function for result tables,
+ * such as mysql_fetch_array.
+ * @throws MysqlException
+ *
+ */
+function mysql_list_tables(string $database, $link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_list_tables($database, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the number of fields from a query.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @return int Returns the number of fields in the result set resource on
+ * success.
+ * @throws MysqlException
+ *
+ */
+function mysql_num_fields($result): int
+{
+ error_clear_last();
+ $result = \mysql_num_fields($result);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the number of rows from a result set. This command is only valid
+ * for statements like SELECT or SHOW that return an actual result set.
+ * To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or
+ * DELETE query, use mysql_affected_rows.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @return int The number of rows in a result set on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_num_rows($result): int
+{
+ error_clear_last();
+ $result = \mysql_num_rows($result);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_query sends a unique query (multiple queries
+ * are not supported) to the currently
+ * active database on the server that's associated with the
+ * specified link_identifier.
+ *
+ * @param string $query An SQL query
+ *
+ * The query string should not end with a semicolon.
+ * Data inside the query should be properly escaped.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource|bool For SELECT, SHOW, DESCRIBE, EXPLAIN and other statements returning resultset,
+ * mysql_query
+ * returns a resource on success.
+ *
+ * For other type of SQL statements, INSERT, UPDATE, DELETE, DROP, etc,
+ * mysql_query returns TRUE on success.
+ *
+ * The returned result resource should be passed to
+ * mysql_fetch_array, and other
+ * functions for dealing with result tables, to access the returned data.
+ *
+ * Use mysql_num_rows to find out how many rows
+ * were returned for a SELECT statement or
+ * mysql_affected_rows to find out how many
+ * rows were affected by a DELETE, INSERT, REPLACE, or UPDATE
+ * statement.
+ *
+ * mysql_query will also fail and return FALSE
+ * if the user does not have permission to access the table(s) referenced by
+ * the query.
+ * @throws MysqlException
+ *
+ */
+function mysql_query(string $query, $link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_query($query, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Escapes special characters in the unescaped_string,
+ * taking into account the current character set of the connection so that it
+ * is safe to place it in a mysql_query. If binary data
+ * is to be inserted, this function must be used.
+ *
+ * mysql_real_escape_string calls MySQL's library function
+ * mysql_real_escape_string, which prepends backslashes to the following characters:
+ * \x00, \n,
+ * \r, \, ',
+ * " and \x1a.
+ *
+ * This function must always (with few exceptions) be used to make data
+ * safe before sending a query to MySQL.
+ *
+ * @param string $unescaped_string The string that is to be escaped.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return string Returns the escaped string.
+ * @throws MysqlException
+ *
+ */
+function mysql_real_escape_string(string $unescaped_string, $link_identifier = null): string
+{
+ error_clear_last();
+ $result = \mysql_real_escape_string($unescaped_string, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the contents of one cell from a MySQL result set.
+ *
+ * When working on large result sets, you should consider using one
+ * of the functions that fetch an entire row (specified below). As
+ * these functions return the contents of multiple cells in one
+ * function call, they're MUCH quicker than
+ * mysql_result. Also, note that specifying a
+ * numeric offset for the field argument is much quicker than
+ * specifying a fieldname or tablename.fieldname argument.
+ *
+ * @param resource $result The result resource that
+ * is being evaluated. This result comes from a call to
+ * mysql_query.
+ * @param int $row The row number from the result that's being retrieved. Row numbers
+ * start at 0.
+ * @param mixed $field The name or offset of the field being retrieved.
+ *
+ * It can be the field's offset, the field's name, or the field's table
+ * dot field name (tablename.fieldname). If the column name has been
+ * aliased ('select foo as bar from...'), use the alias instead of the
+ * column name. If undefined, the first field is retrieved.
+ * @return string The contents of one cell from a MySQL result set on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_result($result, int $row, $field = 0): string
+{
+ error_clear_last();
+ $result = \mysql_result($result, $row, $field);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the current active database on the server that's associated with the
+ * specified link identifier. Every subsequent call to
+ * mysql_query will be made on the active database.
+ *
+ * @param string $database_name The name of the database that is to be selected.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @throws MysqlException
+ *
+ */
+function mysql_select_db(string $database_name, $link_identifier = null): void
+{
+ error_clear_last();
+ $result = \mysql_select_db($database_name, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the default character set for the current connection.
+ *
+ * @param string $charset A valid character set name.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @throws MysqlException
+ *
+ */
+function mysql_set_charset(string $charset, $link_identifier = null): void
+{
+ error_clear_last();
+ $result = \mysql_set_charset($charset, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieves the table name from a result.
+ *
+ * This function is deprecated. It is preferable to use
+ * mysql_query to issue an SQL SHOW TABLES
+ * [FROM db_name] [LIKE 'pattern'] statement instead.
+ *
+ * @param resource $result A result pointer resource that's returned from
+ * mysql_list_tables.
+ * @param int $i The integer index (row/table number)
+ * @return string The name of the table on success.
+ *
+ * Use the mysql_tablename function to
+ * traverse this result pointer, or any function for result tables,
+ * such as mysql_fetch_array.
+ * @throws MysqlException
+ *
+ */
+function mysql_tablename($result, int $i): string
+{
+ error_clear_last();
+ $result = \mysql_tablename($result, $i);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the current thread ID. If the connection is lost, and a reconnect
+ * with mysql_ping is executed, the thread ID will
+ * change. This means only retrieve the thread ID when needed.
+ *
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return int The thread ID on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_thread_id($link_identifier = null): int
+{
+ error_clear_last();
+ $result = \mysql_thread_id($link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * mysql_unbuffered_query sends the SQL query
+ * query to MySQL without automatically
+ * fetching and buffering the result rows as
+ * mysql_query does. This saves a considerable
+ * amount of memory with SQL queries that produce large result sets,
+ * and you can start working on the result set immediately after the
+ * first row has been retrieved as you don't have to wait until the
+ * complete SQL query has been performed. To use
+ * mysql_unbuffered_query while multiple database
+ * connections are open, you must specify the optional parameter
+ * link_identifier to identify which connection
+ * you want to use.
+ *
+ * @param string $query The SQL query to execute.
+ *
+ * Data inside the query should be properly escaped.
+ * @param resource $link_identifier The MySQL connection. If the
+ * link identifier is not specified, the last link opened by
+ * mysql_connect is assumed. If no such link is found, it
+ * will try to create one as if mysql_connect had been called
+ * with no arguments. If no connection is found or established, an
+ * E_WARNING level error is generated.
+ * @return resource|bool For SELECT, SHOW, DESCRIBE or EXPLAIN statements,
+ * mysql_unbuffered_query
+ * returns a resource on success.
+ *
+ * For other type of SQL statements, UPDATE, DELETE, DROP, etc,
+ * mysql_unbuffered_query returns TRUE on success.
+ * @throws MysqlException
+ *
+ */
+function mysql_unbuffered_query(string $query, $link_identifier = null)
+{
+ error_clear_last();
+ $result = \mysql_unbuffered_query($query, $link_identifier);
+ if ($result === false) {
+ throw MysqlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MysqliException;
+
+/**
+ * Returns an empty array.
+ * Available only with mysqlnd.
+ *
+ * @return array Returns an empty array on success, FALSE otherwise.
+ * @throws MysqliException
+ *
+ */
+function mysqli_get_cache_stats(): array
+{
+ error_clear_last();
+ $result = \mysqli_get_cache_stats();
+ if ($result === false) {
+ throw MysqliException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns client per-process statistics.
+ * Available only with mysqlnd.
+ *
+ * @return array Returns an array with client stats if success, FALSE otherwise.
+ * @throws MysqliException
+ *
+ */
+function mysqli_get_client_stats(): array
+{
+ error_clear_last();
+ $result = \mysqli_get_client_stats();
+ if ($result === false) {
+ throw MysqliException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MysqlndMsException;
+
+/**
+ * Returns a list of currently configured servers.
+ *
+ * @param mixed $connection A MySQL connection handle obtained from any of the
+ * connect functions of the mysqli,
+ * mysql or
+ * PDO_MYSQL extensions.
+ * @return array FALSE on error. Otherwise, returns an array with two entries
+ * masters and slaves each of which contains
+ * an array listing all corresponding servers.
+ *
+ * The function can be used to check and debug the list of servers currently
+ * used by the plugin. It is mostly useful when the list of servers changes at
+ * runtime, for example, when using MySQL Fabric.
+ *
+ * masters and slaves server entries
+ * @throws MysqlndMsException
+ *
+ */
+function mysqlnd_ms_dump_servers($connection): array
+{
+ error_clear_last();
+ $result = \mysqlnd_ms_dump_servers($connection);
+ if ($result === false) {
+ throw MysqlndMsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * MySQL Fabric related.
+ *
+ * Switch the connection to the nodes handling global sharding queries
+ * for the given table name.
+ *
+ * @param mixed $connection A MySQL connection handle obtained from any of the
+ * connect functions of the mysqli,
+ * mysql or
+ * PDO_MYSQL extensions.
+ * @param mixed $table_name The table name to ask Fabric about.
+ * @return array FALSE on error. Otherwise, TRUE
+ * @throws MysqlndMsException
+ *
+ */
+function mysqlnd_ms_fabric_select_global($connection, $table_name): array
+{
+ error_clear_last();
+ $result = \mysqlnd_ms_fabric_select_global($connection, $table_name);
+ if ($result === false) {
+ throw MysqlndMsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * MySQL Fabric related.
+ *
+ * Switch the connection to the shards responsible for the
+ * given table name and shard key.
+ *
+ * @param mixed $connection A MySQL connection handle obtained from any of the
+ * connect functions of the mysqli,
+ * mysql or
+ * PDO_MYSQL extensions.
+ * @param mixed $table_name The table name to ask Fabric about.
+ * @param mixed $shard_key The shard key to ask Fabric about.
+ * @return array FALSE on error. Otherwise, TRUE
+ * @throws MysqlndMsException
+ *
+ */
+function mysqlnd_ms_fabric_select_shard($connection, $table_name, $shard_key): array
+{
+ error_clear_last();
+ $result = \mysqlnd_ms_fabric_select_shard($connection, $table_name, $shard_key);
+ if ($result === false) {
+ throw MysqlndMsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns an array which describes the last used connection from the plugins
+ * connection pool currently pointed to by the user connection handle. If using the
+ * plugin, a user connection handle represents a pool of database connections.
+ * It is not possible to tell from the user connection handles properties to which
+ * database server from the pool the user connection handle points.
+ *
+ * The function can be used to debug or monitor PECL mysqlnd_ms.
+ *
+ * @param mixed $connection A MySQL connection handle obtained from any of the
+ * connect functions of the mysqli,
+ * mysql or
+ * PDO_MYSQL extensions.
+ * @return array FALSE on error. Otherwise, an
+ * array which describes the connection used to
+ * execute the last statement on.
+ *
+ * Array which describes the connection.
+ * @throws MysqlndMsException
+ *
+ */
+function mysqlnd_ms_get_last_used_connection($connection): array
+{
+ error_clear_last();
+ $result = \mysqlnd_ms_get_last_used_connection($connection);
+ if ($result === false) {
+ throw MysqlndMsException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\MysqlndQcException;
+
+/**
+ * Flush all cache contents.
+ *
+ * Flushing the cache is a storage handler responsibility.
+ * All built-in storage handler but the
+ * memcache storage
+ * handler support flushing the cache. The
+ * memcache
+ * storage handler cannot flush its cache contents.
+ *
+ * User-defined storage handler may or may not support the operation.
+ *
+ * @throws MysqlndQcException
+ *
+ */
+function mysqlnd_qc_clear_cache(): void
+{
+ error_clear_last();
+ $result = \mysqlnd_qc_clear_cache();
+ if ($result === false) {
+ throw MysqlndQcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Installs a callback which decides whether a statement is cached.
+ *
+ * There are several ways of hinting PELC/mysqlnd_qc to cache a query.
+ * By default, PECL/mysqlnd_qc attempts to cache a if caching of all statements
+ * is enabled or the query string begins with a certain SQL hint.
+ * The plugin internally calls a function named is_select()
+ * to find out. This internal function can be replaced with a user-defined callback.
+ * Then, the user-defined callback is responsible to decide whether the plugin
+ * attempts to cache a statement. Because the internal function is replaced
+ * with the callback, the callback gains full control. The callback is free
+ * to ignore the configuration setting mysqlnd_qc.cache_by_default
+ * and SQL hints.
+ *
+ * The callback is invoked for every statement inspected by the plugin.
+ * It is given the statements string as a parameter. The callback returns
+ * FALSE if the statement shall not be cached. It returns TRUE to
+ * make the plugin attempt to cache the statements result set, if any.
+ * A so-created cache entry is given the default TTL set with the
+ * PHP configuration directive mysqlnd_qc.ttl.
+ * If a different TTL shall be used, the callback returns a numeric
+ * value to be used as the TTL.
+ *
+ * The internal is_select function is part of the internal
+ * cache storage handler interface. Thus, a user-defined storage handler
+ * offers the same capabilities.
+ *
+ * @param string $callback
+ * @return mixed Returns TRUE on success.
+ * @throws MysqlndQcException
+ *
+ */
+function mysqlnd_qc_set_is_select(string $callback)
+{
+ error_clear_last();
+ $result = \mysqlnd_qc_set_is_select($callback);
+ if ($result === false) {
+ throw MysqlndQcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the storage handler used by the query cache. A list of available
+ * storage handler can be obtained from
+ * mysqlnd_qc_get_available_handlers.
+ * Which storage are available depends on the compile time
+ * configuration of the query cache plugin. The
+ * default storage handler is always available.
+ * All other storage handler must be enabled explicitly when building the
+ * extension.
+ *
+ * @param string $handler Handler can be of type string representing the name of a
+ * built-in storage handler or an object of type
+ * mysqlnd_qc_handler_default.
+ * The names of the built-in storage handler are
+ * default,
+ * APC,
+ * MEMCACHE,
+ * sqlite.
+ * @throws MysqlndQcException
+ *
+ */
+function mysqlnd_qc_set_storage_handler(string $handler): void
+{
+ error_clear_last();
+ $result = \mysqlnd_qc_set_storage_handler($handler);
+ if ($result === false) {
+ throw MysqlndQcException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\NetworkException;
+
+/**
+ * closelog closes the descriptor being used to write to
+ * the system logger. The use of closelog is optional.
+ *
+ * @throws NetworkException
+ *
+ */
+function closelog(): void
+{
+ error_clear_last();
+ $result = \closelog();
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetch DNS Resource Records associated with the given
+ * hostname.
+ *
+ * @param string $hostname hostname should be a valid DNS hostname such
+ * as "www.example.com". Reverse lookups can be generated
+ * using in-addr.arpa notation, but
+ * gethostbyaddr is more suitable for
+ * the majority of reverse lookups.
+ *
+ * Per DNS standards, email addresses are given in user.host format (for
+ * example: hostmaster.example.com as opposed to hostmaster@example.com),
+ * be sure to check this value and modify if necessary before using it
+ * with a functions such as mail.
+ * @param int $type By default, dns_get_record will search for any
+ * resource records associated with hostname.
+ * To limit the query, specify the optional type
+ * parameter. May be any one of the following:
+ * DNS_A, DNS_CNAME,
+ * DNS_HINFO, DNS_CAA,
+ * DNS_MX, DNS_NS,
+ * DNS_PTR, DNS_SOA,
+ * DNS_TXT, DNS_AAAA,
+ * DNS_SRV, DNS_NAPTR,
+ * DNS_A6, DNS_ALL
+ * or DNS_ANY.
+ *
+ * Because of eccentricities in the performance of libresolv
+ * between platforms, DNS_ANY will not
+ * always return every record, the slower DNS_ALL
+ * will collect all records more reliably.
+ *
+ * DNS_CAA is not supported on Windows.
+ * @param array|null $authns Passed by reference and, if given, will be populated with Resource
+ * Records for the Authoritative Name Servers.
+ * @param array|null $addtl Passed by reference and, if given, will be populated with any
+ * Additional Records.
+ * @param bool $raw The type will be interpreted as a raw DNS type ID
+ * (the DNS_* constants cannot be used).
+ * The return value will contain a data key, which needs
+ * to be manually parsed.
+ * @return array This function returns an array of associative arrays. Each associative array contains
+ * at minimum the following keys:
+ *
+ * Basic DNS attributes
+ *
+ *
+ *
+ * Attribute
+ * Meaning
+ *
+ *
+ *
+ *
+ * host
+ *
+ * The record in the DNS namespace to which the rest of the associated data refers.
+ *
+ *
+ *
+ * class
+ *
+ * dns_get_record only returns Internet class records and as
+ * such this parameter will always return IN.
+ *
+ *
+ *
+ * type
+ *
+ * String containing the record type. Additional attributes will also be contained
+ * in the resulting array dependant on the value of type. See table below.
+ *
+ *
+ *
+ * ttl
+ *
+ * "Time To Live" remaining for this record. This will not equal
+ * the record's original ttl, but will rather equal the original ttl minus whatever
+ * length of time has passed since the authoritative name server was queried.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Other keys in associative arrays dependant on 'type'
+ *
+ *
+ *
+ * Type
+ * Extra Columns
+ *
+ *
+ *
+ *
+ * A
+ *
+ * ip: An IPv4 addresses in dotted decimal notation.
+ *
+ *
+ *
+ * MX
+ *
+ * pri: Priority of mail exchanger.
+ * Lower numbers indicate greater priority.
+ * target: FQDN of the mail exchanger.
+ * See also dns_get_mx.
+ *
+ *
+ *
+ * CNAME
+ *
+ * target: FQDN of location in DNS namespace to which
+ * the record is aliased.
+ *
+ *
+ *
+ * NS
+ *
+ * target: FQDN of the name server which is authoritative
+ * for this hostname.
+ *
+ *
+ *
+ * PTR
+ *
+ * target: Location within the DNS namespace to which
+ * this record points.
+ *
+ *
+ *
+ * TXT
+ *
+ * txt: Arbitrary string data associated with this record.
+ *
+ *
+ *
+ * HINFO
+ *
+ * cpu: IANA number designating the CPU of the machine
+ * referenced by this record.
+ * os: IANA number designating the Operating System on
+ * the machine referenced by this record.
+ * See IANA's Operating System
+ * Names for the meaning of these values.
+ *
+ *
+ *
+ * CAA
+ *
+ * flags: A one-byte bitfield; currently only bit 0 is defined,
+ * meaning 'critical'; other bits are reserved and should be ignored.
+ * tag: The CAA tag name (alphanumeric ASCII string).
+ * value: The CAA tag value (binary string, may use subformats).
+ * For additional information see: RFC 6844
+ *
+ *
+ *
+ * SOA
+ *
+ * mname: FQDN of the machine from which the resource
+ * records originated.
+ * rname: Email address of the administrative contact
+ * for this domain.
+ * serial: Serial # of this revision of the requested
+ * domain.
+ * refresh: Refresh interval (seconds) secondary name
+ * servers should use when updating remote copies of this domain.
+ * retry: Length of time (seconds) to wait after a
+ * failed refresh before making a second attempt.
+ * expire: Maximum length of time (seconds) a secondary
+ * DNS server should retain remote copies of the zone data without a
+ * successful refresh before discarding.
+ * minimum-ttl: Minimum length of time (seconds) a
+ * client can continue to use a DNS resolution before it should request
+ * a new resolution from the server. Can be overridden by individual
+ * resource records.
+ *
+ *
+ *
+ * AAAA
+ *
+ * ipv6: IPv6 address
+ *
+ *
+ *
+ * A6(PHP >= 5.1.0)
+ *
+ * masklen: Length (in bits) to inherit from the target
+ * specified by chain.
+ * ipv6: Address for this specific record to merge with
+ * chain.
+ * chain: Parent record to merge with
+ * ipv6 data.
+ *
+ *
+ *
+ * SRV
+ *
+ * pri: (Priority) lowest priorities should be used first.
+ * weight: Ranking to weight which of commonly prioritized
+ * targets should be chosen at random.
+ * target and port: hostname and port
+ * where the requested service can be found.
+ * For additional information see: RFC 2782
+ *
+ *
+ *
+ * NAPTR
+ *
+ * order and pref: Equivalent to
+ * pri and weight above.
+ * flags, services, regex,
+ * and replacement: Parameters as defined by
+ * RFC 2915.
+ *
+ *
+ *
+ *
+ *
+ * @throws NetworkException
+ *
+ */
+function dns_get_record(string $hostname, int $type = DNS_ANY, ?array &$authns = null, ?array &$addtl = null, bool $raw = false): array
+{
+ error_clear_last();
+ $result = \dns_get_record($hostname, $type, $authns, $addtl, $raw);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Initiates a socket connection to the resource specified by
+ * hostname.
+ *
+ * PHP supports targets in the Internet and Unix domains as described in
+ * . A list of supported transports can also be
+ * retrieved using stream_get_transports.
+ *
+ * The socket will by default be opened in blocking mode. You can
+ * switch it to non-blocking mode by using
+ * stream_set_blocking.
+ *
+ * The function stream_socket_client is similar but
+ * provides a richer set of options, including non-blocking connection and the
+ * ability to provide a stream context.
+ *
+ * @param string $hostname If OpenSSL support is
+ * installed, you may prefix the hostname
+ * with either ssl:// or tls:// to
+ * use an SSL or TLS client connection over TCP/IP to connect to the
+ * remote host.
+ * @param int $port The port number. This can be omitted and skipped with
+ * -1 for transports that do not use ports, such as
+ * unix://.
+ * @param int|null $errno If provided, holds the system level error number that occurred in the
+ * system-level connect() call.
+ *
+ * If the value returned in errno is
+ * 0 and the function returned FALSE, it is an
+ * indication that the error occurred before the
+ * connect() call. This is most likely due to a
+ * problem initializing the socket.
+ * @param string|null $errstr The error message as a string.
+ * @param float $timeout The connection timeout, in seconds.
+ *
+ * If you need to set a timeout for reading/writing data over the
+ * socket, use stream_set_timeout, as the
+ * timeout parameter to
+ * fsockopen only applies while connecting the
+ * socket.
+ * @return resource fsockopen returns a file pointer which may be used
+ * together with the other file functions (such as
+ * fgets, fgetss,
+ * fwrite, fclose, and
+ * feof). If the call fails, it will return FALSE
+ * @throws NetworkException
+ *
+ */
+function fsockopen(string $hostname, int $port = -1, ?int &$errno = null, ?string &$errstr = null, float $timeout = null)
+{
+ error_clear_last();
+ if ($timeout !== null) {
+ $result = \fsockopen($hostname, $port, $errno, $errstr, $timeout);
+ } else {
+ $result = \fsockopen($hostname, $port, $errno, $errstr);
+ }
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * gethostname gets the standard host name for
+ * the local machine.
+ *
+ * @return string Returns a string with the hostname on success, otherwise FALSE is
+ * returned.
+ * @throws NetworkException
+ *
+ */
+function gethostname(): string
+{
+ error_clear_last();
+ $result = \gethostname();
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * getprotobyname returns the protocol number
+ * associated with the protocol name as per
+ * /etc/protocols.
+ *
+ * @param string $name The protocol name.
+ * @return int Returns the protocol number.
+ * @throws NetworkException
+ *
+ */
+function getprotobyname(string $name): int
+{
+ error_clear_last();
+ $result = \getprotobyname($name);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * getprotobynumber returns the protocol name
+ * associated with protocol number as per
+ * /etc/protocols.
+ *
+ * @param int $number The protocol number.
+ * @return string Returns the protocol name as a string.
+ * @throws NetworkException
+ *
+ */
+function getprotobynumber(int $number): string
+{
+ error_clear_last();
+ $result = \getprotobynumber($number);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Registers a function that will be called when PHP starts sending output.
+ *
+ * The callback is executed just after PHP prepares all
+ * headers to be sent, and before any other output is sent, creating a window
+ * to manipulate the outgoing headers before being sent.
+ *
+ * @param callable $callback Function called just before the headers are sent. It gets no parameters
+ * and the return value is ignored.
+ * @throws NetworkException
+ *
+ */
+function header_register_callback(callable $callback): void
+{
+ error_clear_last();
+ $result = \header_register_callback($callback);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $in_addr A 32bit IPv4, or 128bit IPv6 address.
+ * @return string Returns a string representation of the address.
+ * @throws NetworkException
+ *
+ */
+function inet_ntop(string $in_addr): string
+{
+ error_clear_last();
+ $result = \inet_ntop($in_addr);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openlog opens a connection to the system
+ * logger for a program.
+ *
+ * The use of openlog is optional. It
+ * will automatically be called by syslog if
+ * necessary, in which case ident will default
+ * to FALSE.
+ *
+ * @param string $ident The string ident is added to each message.
+ * @param int $option The option argument is used to indicate
+ * what logging options will be used when generating a log message.
+ *
+ * openlog Options
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * LOG_CONS
+ *
+ * if there is an error while sending data to the system logger,
+ * write directly to the system console
+ *
+ *
+ *
+ * LOG_NDELAY
+ *
+ * open the connection to the logger immediately
+ *
+ *
+ *
+ * LOG_ODELAY
+ *
+ * (default) delay opening the connection until the first
+ * message is logged
+ *
+ *
+ *
+ * LOG_PERROR
+ * print log message also to standard error
+ *
+ *
+ * LOG_PID
+ * include PID with each message
+ *
+ *
+ *
+ *
+ * You can use one or more of these options. When using multiple options
+ * you need to OR them, i.e. to open the connection
+ * immediately, write to the console and include the PID in each message,
+ * you will use: LOG_CONS | LOG_NDELAY | LOG_PID
+ * @param int $facility The facility argument is used to specify what
+ * type of program is logging the message. This allows you to specify
+ * (in your machine's syslog configuration) how messages coming from
+ * different facilities will be handled.
+ *
+ * openlog Facilities
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * LOG_AUTH
+ *
+ * security/authorization messages (use
+ * LOG_AUTHPRIV instead
+ * in systems where that constant is defined)
+ *
+ *
+ *
+ * LOG_AUTHPRIV
+ * security/authorization messages (private)
+ *
+ *
+ * LOG_CRON
+ * clock daemon (cron and at)
+ *
+ *
+ * LOG_DAEMON
+ * other system daemons
+ *
+ *
+ * LOG_KERN
+ * kernel messages
+ *
+ *
+ * LOG_LOCAL0 ... LOG_LOCAL7
+ * reserved for local use, these are not available in Windows
+ *
+ *
+ * LOG_LPR
+ * line printer subsystem
+ *
+ *
+ * LOG_MAIL
+ * mail subsystem
+ *
+ *
+ * LOG_NEWS
+ * USENET news subsystem
+ *
+ *
+ * LOG_SYSLOG
+ * messages generated internally by syslogd
+ *
+ *
+ * LOG_USER
+ * generic user-level messages
+ *
+ *
+ * LOG_UUCP
+ * UUCP subsystem
+ *
+ *
+ *
+ *
+ *
+ * LOG_USER is the only valid log type under Windows
+ * operating systems
+ * @throws NetworkException
+ *
+ */
+function openlog(string $ident, int $option, int $facility): void
+{
+ error_clear_last();
+ $result = \openlog($ident, $option, $facility);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+}
+
+
+/**
+ * syslog generates a log message that will be
+ * distributed by the system logger.
+ *
+ * For information on setting up a user defined log handler, see the
+ * syslog.conf
+ * 5 Unix manual page. More
+ * information on the syslog facilities and option can be found in the man
+ * pages for syslog
+ * 3 on Unix machines.
+ *
+ * @param int $priority priority is a combination of the facility and
+ * the level. Possible values are:
+ *
+ * syslog Priorities (in descending order)
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * LOG_EMERG
+ * system is unusable
+ *
+ *
+ * LOG_ALERT
+ * action must be taken immediately
+ *
+ *
+ * LOG_CRIT
+ * critical conditions
+ *
+ *
+ * LOG_ERR
+ * error conditions
+ *
+ *
+ * LOG_WARNING
+ * warning conditions
+ *
+ *
+ * LOG_NOTICE
+ * normal, but significant, condition
+ *
+ *
+ * LOG_INFO
+ * informational message
+ *
+ *
+ * LOG_DEBUG
+ * debug-level message
+ *
+ *
+ *
+ *
+ * @param string $message The message to send, except that the two characters
+ * %m will be replaced by the error message string
+ * (strerror) corresponding to the present value of
+ * errno.
+ * @throws NetworkException
+ *
+ */
+function syslog(int $priority, string $message): void
+{
+ error_clear_last();
+ $result = \syslog($priority, $message);
+ if ($result === false) {
+ throw NetworkException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\Oci8Exception;
+
+/**
+ * Binds the PHP array var_array to the Oracle
+ * placeholder name, which points to an Oracle PL/SQL
+ * array. Whether it will be used for input or output will be determined at
+ * run-time.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param string $name The Oracle placeholder.
+ * @param array $var_array An array.
+ * @param int $max_table_length Sets the maximum length both for incoming and result arrays.
+ * @param int $max_item_length Sets maximum length for array items. If not specified or equals to -1,
+ * oci_bind_array_by_name will find the longest
+ * element in the incoming array and will use it as the maximum length.
+ * @param int $type Should be used to set the type of PL/SQL array items. See list of
+ * available types below:
+ *
+ *
+ *
+ *
+ * SQLT_NUM - for arrays of NUMBER.
+ *
+ *
+ *
+ *
+ * SQLT_INT - for arrays of INTEGER (Note: INTEGER
+ * it is actually a synonym for NUMBER(38), but
+ * SQLT_NUM type won't work in this case even
+ * though they are synonyms).
+ *
+ *
+ *
+ *
+ * SQLT_FLT - for arrays of FLOAT.
+ *
+ *
+ *
+ *
+ * SQLT_AFC - for arrays of CHAR.
+ *
+ *
+ *
+ *
+ * SQLT_CHR - for arrays of VARCHAR2.
+ *
+ *
+ *
+ *
+ * SQLT_VCS - for arrays of VARCHAR.
+ *
+ *
+ *
+ *
+ * SQLT_AVC - for arrays of CHARZ.
+ *
+ *
+ *
+ *
+ * SQLT_STR - for arrays of STRING.
+ *
+ *
+ *
+ *
+ * SQLT_LVC - for arrays of LONG VARCHAR.
+ *
+ *
+ *
+ *
+ * SQLT_ODT - for arrays of DATE.
+ *
+ *
+ *
+ *
+ * SQLT_NUM - for arrays of NUMBER.
+ *
+ * SQLT_INT - for arrays of INTEGER (Note: INTEGER
+ * it is actually a synonym for NUMBER(38), but
+ * SQLT_NUM type won't work in this case even
+ * though they are synonyms).
+ *
+ * SQLT_FLT - for arrays of FLOAT.
+ *
+ * SQLT_AFC - for arrays of CHAR.
+ *
+ * SQLT_CHR - for arrays of VARCHAR2.
+ *
+ * SQLT_VCS - for arrays of VARCHAR.
+ *
+ * SQLT_AVC - for arrays of CHARZ.
+ *
+ * SQLT_STR - for arrays of STRING.
+ *
+ * SQLT_LVC - for arrays of LONG VARCHAR.
+ *
+ * SQLT_ODT - for arrays of DATE.
+ * @throws Oci8Exception
+ *
+ */
+function oci_bind_array_by_name($statement, string $name, array &$var_array, int $max_table_length, int $max_item_length = -1, int $type = SQLT_AFC): void
+{
+ error_clear_last();
+ $result = \oci_bind_array_by_name($statement, $name, $var_array, $max_table_length, $max_item_length, $type);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Binds a PHP variable variable to the Oracle
+ * bind variable placeholder bv_name. Binding
+ * is important for Oracle database performance and also as a way to
+ * avoid SQL Injection security issues.
+ *
+ * Binding allows the database to reuse the statement context and
+ * caches from previous executions of the statement, even if another
+ * user or process originally executed it. Binding reduces SQL
+ * Injection concerns because the data associated with a bind
+ * variable is never treated as part of the SQL statement. It does
+ * not need quoting or escaping.
+ *
+ * PHP variables that have been bound can be changed and the
+ * statement re-executed without needing to re-parse the statement or
+ * re-bind.
+ *
+ * In Oracle, bind variables are commonly divided
+ * into IN binds for values that are passed into
+ * the database, and OUT binds for values that are
+ * returned to PHP. A bind variable may be
+ * both IN and OUT. Whether a
+ * bind variable will be used for input or output is determined at
+ * run-time.
+ *
+ * You must specify maxlength when using
+ * an OUT bind so that PHP allocates enough memory
+ * to hold the returned value.
+ *
+ * For IN binds it is recommended to set
+ * the maxlength length if the statement is
+ * re-executed multiple times with different values for the PHP
+ * variable. Otherwise Oracle may truncate data to the length of the
+ * initial PHP variable value. If you don't know what the maximum
+ * length will be, then re-call oci_bind_by_name
+ * with the current data size prior to
+ * each oci_execute call. Binding an
+ * unnecessarily large length will have an impact on process memory
+ * in the database.
+ *
+ * A bind call tells Oracle which memory address to read data from.
+ * For IN binds that address needs to contain
+ * valid data when oci_execute is called. This
+ * means that the variable bound must remain in scope until
+ * execution. If it doesn't, unexpected results or errors such as
+ * "ORA-01460: unimplemented or unreasonable conversion requested"
+ * may occur. For OUT binds one symptom is no
+ * value being set in the PHP variable.
+ *
+ * For a statement that is repeatedly executed, binding values that
+ * never change may reduce the ability of the Oracle optimizer to
+ * choose the best statement execution plan. Long running statements
+ * that are rarely re-executed may not benefit from binding. However
+ * in both cases, binding might be safer than joining strings into a
+ * SQL statement, as this can be a security risk if unfiltered user
+ * text is concatenated.
+ *
+ * @param resource $statement A valid OCI8 statement identifer.
+ * @param string $bv_name The colon-prefixed bind variable placeholder used in the
+ * statement. The colon is optional
+ * in bv_name. Oracle does not use question
+ * marks for placeholders.
+ * @param mixed $variable The PHP variable to be associated with bv_name
+ * @param int $maxlength Sets the maximum length for the data. If you set it to -1, this
+ * function will use the current length
+ * of variable to set the maximum
+ * length. In this case the variable must
+ * exist and contain data
+ * when oci_bind_by_name is called.
+ * @param int $type The datatype that Oracle will treat the data as. The
+ * default type used
+ * is SQLT_CHR. Oracle will convert the data
+ * between this type and the database column (or PL/SQL variable
+ * type), when possible.
+ *
+ * If you need to bind an abstract datatype (LOB/ROWID/BFILE) you
+ * need to allocate it first using the
+ * oci_new_descriptor function. The
+ * length is not used for abstract datatypes
+ * and should be set to -1.
+ *
+ * Possible values for type are:
+ *
+ *
+ *
+ * SQLT_BFILEE or OCI_B_BFILE
+ * - for BFILEs;
+ *
+ *
+ *
+ *
+ * SQLT_CFILEE or OCI_B_CFILEE
+ * - for CFILEs;
+ *
+ *
+ *
+ *
+ * SQLT_CLOB or OCI_B_CLOB
+ * - for CLOBs;
+ *
+ *
+ *
+ *
+ * SQLT_BLOB or OCI_B_BLOB
+ * - for BLOBs;
+ *
+ *
+ *
+ *
+ * SQLT_RDD or OCI_B_ROWID
+ * - for ROWIDs;
+ *
+ *
+ *
+ *
+ * SQLT_NTY or OCI_B_NTY
+ * - for named datatypes;
+ *
+ *
+ *
+ *
+ * SQLT_INT or OCI_B_INT - for integers;
+ *
+ *
+ *
+ *
+ * SQLT_CHR - for VARCHARs;
+ *
+ *
+ *
+ *
+ * SQLT_BIN or OCI_B_BIN
+ * - for RAW columns;
+ *
+ *
+ *
+ *
+ * SQLT_LNG - for LONG columns;
+ *
+ *
+ *
+ *
+ * SQLT_LBI - for LONG RAW columns;
+ *
+ *
+ *
+ *
+ * SQLT_RSET - for cursors created
+ * with oci_new_cursor;
+ *
+ *
+ *
+ *
+ * SQLT_BOL or OCI_B_BOL
+ * - for PL/SQL BOOLEANs (Requires OCI8 2.0.7 and Oracle Database 12c)
+ *
+ *
+ *
+ *
+ * SQLT_BFILEE or OCI_B_BFILE
+ * - for BFILEs;
+ *
+ * SQLT_CFILEE or OCI_B_CFILEE
+ * - for CFILEs;
+ *
+ * SQLT_CLOB or OCI_B_CLOB
+ * - for CLOBs;
+ *
+ * SQLT_BLOB or OCI_B_BLOB
+ * - for BLOBs;
+ *
+ * SQLT_RDD or OCI_B_ROWID
+ * - for ROWIDs;
+ *
+ * SQLT_NTY or OCI_B_NTY
+ * - for named datatypes;
+ *
+ * SQLT_INT or OCI_B_INT - for integers;
+ *
+ * SQLT_CHR - for VARCHARs;
+ *
+ * SQLT_BIN or OCI_B_BIN
+ * - for RAW columns;
+ *
+ * SQLT_LNG - for LONG columns;
+ *
+ * SQLT_LBI - for LONG RAW columns;
+ *
+ * SQLT_RSET - for cursors created
+ * with oci_new_cursor;
+ *
+ * SQLT_BOL or OCI_B_BOL
+ * - for PL/SQL BOOLEANs (Requires OCI8 2.0.7 and Oracle Database 12c)
+ * @throws Oci8Exception
+ *
+ */
+function oci_bind_by_name($statement, string $bv_name, &$variable, int $maxlength = -1, int $type = SQLT_CHR): void
+{
+ error_clear_last();
+ $result = \oci_bind_by_name($statement, $bv_name, $variable, $maxlength, $type);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Invalidates a cursor, freeing all associated resources and cancels the
+ * ability to read from it.
+ *
+ * @param resource $statement An OCI statement.
+ * @throws Oci8Exception
+ *
+ */
+function oci_cancel($statement): void
+{
+ error_clear_last();
+ $result = \oci_cancel($statement);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Unsets connection. The underlying database
+ * connection is closed if no other resources are using it and if it
+ * was created with oci_connect
+ * or oci_new_connect.
+ *
+ * It is recommended to close connections that are no longer needed
+ * because this makes database resources available for other users.
+ *
+ * @param resource $connection An Oracle connection identifier returned by
+ * oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @throws Oci8Exception
+ *
+ */
+function oci_close($connection): void
+{
+ error_clear_last();
+ $result = \oci_close($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Commits the outstanding transaction for the
+ * Oracle connection. A commit ends the
+ * current transaction and makes permanent all changes. It releases
+ * all locks held.
+ *
+ * A transaction begins when the first SQL statement that changes data
+ * is executed with oci_execute using
+ * the OCI_NO_AUTO_COMMIT flag. Further data
+ * changes made by other statements become part of the same
+ * transaction. Data changes made in a transaction are temporary
+ * until the transaction is committed or rolled back. Other users of
+ * the database will not see the changes until they are committed.
+ *
+ * When inserting or updating data, using transactions is recommended
+ * for relational data consistency and for performance reasons.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect, oci_pconnect, or oci_new_connect.
+ * @throws Oci8Exception
+ *
+ */
+function oci_commit($connection): void
+{
+ error_clear_last();
+ $result = \oci_commit($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns a connection identifier needed for most other OCI8 operations.
+ *
+ * See Connection Handling for
+ * general information on connection management and connection
+ * pooling.
+ *
+ * From PHP 5.1.2 (PECL OCI8 1.1) oci_close can
+ * be used to close the connection.
+ *
+ * The second and subsequent calls to oci_connect
+ * with the same parameters will return the connection handle returned
+ * from the first call. This means that transactions in one handle are
+ * also in the other handles, because they use the
+ * same underlying database connection. If two
+ * handles need to be transactionally isolated from each other, use
+ * oci_new_connect instead.
+ *
+ * @param string $username The Oracle user name.
+ * @param string $password The password for username.
+ * @param string $connection_string Contains
+ * the Oracle instance to connect to. It can be
+ * an Easy Connect
+ * string, or a Connect Name from
+ * the tnsnames.ora file, or the name of a local
+ * Oracle instance.
+ *
+ * If not specified, PHP uses
+ * environment variables such as TWO_TASK (on Linux)
+ * or LOCAL (on Windows)
+ * and ORACLE_SID to determine the
+ * Oracle instance to connect to.
+ *
+ *
+ * To use the Easy Connect naming method, PHP must be linked with Oracle
+ * 10g or greater Client libraries. The Easy Connect string for Oracle
+ * 10g is of the form:
+ * [//]host_name[:port][/service_name]. From Oracle
+ * 11g, the syntax is:
+ * [//]host_name[:port][/service_name][:server_type][/instance_name].
+ * Service names can be found by running the Oracle
+ * utility lsnrctl status on the database server
+ * machine.
+ *
+ *
+ * The tnsnames.ora file can be in the Oracle Net
+ * search path, which
+ * includes $ORACLE_HOME/network/admin
+ * and /etc. Alternatively
+ * set TNS_ADMIN so
+ * that $TNS_ADMIN/tnsnames.ora is read. Make sure
+ * the web daemon has read access to the file.
+ * @param string $character_set Determines
+ * the character set used by the Oracle Client libraries. The character
+ * set does not need to match the character set used by the database. If
+ * it doesn't match, Oracle will do its best to convert data to and from
+ * the database character set. Depending on the character sets this may
+ * not give usable results. Conversion also adds some time overhead.
+ *
+ * If not specified, the
+ * Oracle Client libraries determine a character set from
+ * the NLS_LANG environment variable.
+ *
+ * Passing this parameter can
+ * reduce the time taken to connect.
+ * @param int $session_mode This
+ * parameter is available since version PHP 5 (PECL OCI8 1.1) and accepts the
+ * following values: OCI_DEFAULT,
+ * OCI_SYSOPER and OCI_SYSDBA.
+ * If either OCI_SYSOPER or
+ * OCI_SYSDBA were specified, this function will try
+ * to establish privileged connection using external credentials.
+ * Privileged connections are disabled by default. To enable them you
+ * need to set oci8.privileged_connect
+ * to On.
+ *
+ *
+ * PHP 5.3 (PECL OCI8 1.3.4) introduced the
+ * OCI_CRED_EXT mode value. This tells Oracle to use
+ * External or OS authentication, which must be configured in the
+ * database. The OCI_CRED_EXT flag can only be used
+ * with username of "/" and a empty password.
+ * oci8.privileged_connect
+ * may be On or Off.
+ *
+ *
+ * OCI_CRED_EXT may be combined with the
+ * OCI_SYSOPER or
+ * OCI_SYSDBA modes.
+ *
+ *
+ * OCI_CRED_EXT is not supported on Windows for
+ * security reasons.
+ * @return resource Returns a connection identifier.
+ * @throws Oci8Exception
+ *
+ */
+function oci_connect(string $username, string $password, string $connection_string = null, string $character_set = null, int $session_mode = null)
+{
+ error_clear_last();
+ if ($session_mode !== null) {
+ $result = \oci_connect($username, $password, $connection_string, $character_set, $session_mode);
+ } elseif ($character_set !== null) {
+ $result = \oci_connect($username, $password, $connection_string, $character_set);
+ } elseif ($connection_string !== null) {
+ $result = \oci_connect($username, $password, $connection_string);
+ } else {
+ $result = \oci_connect($username, $password);
+ }
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Associates a PHP variable with a column for query fetches using oci_fetch.
+ *
+ * The oci_define_by_name call must occur before
+ * executing oci_execute.
+ *
+ * @param resource $statement A valid OCI8 statement
+ * identifier created by oci_parse and executed
+ * by oci_execute, or a REF
+ * CURSOR statement identifier.
+ * @param string $column_name The column name used in the query.
+ *
+ * Use uppercase for Oracle's default, non-case sensitive column
+ * names. Use the exact column name case for case-sensitive
+ * column names.
+ * @param mixed $variable The PHP variable that will contain the returned column value.
+ * @param int $type The data type to be returned. Generally not needed. Note that
+ * Oracle-style data conversions are not performed. For example,
+ * SQLT_INT will be ignored and the returned
+ * data type will still be SQLT_CHR.
+ *
+ * You can optionally use oci_new_descriptor
+ * to allocate LOB/ROWID/BFILE descriptors.
+ * @throws Oci8Exception
+ *
+ */
+function oci_define_by_name($statement, string $column_name, &$variable, int $type = SQLT_CHR): void
+{
+ error_clear_last();
+ $result = \oci_define_by_name($statement, $column_name, $variable, $type);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Executes a statement previously returned
+ * from oci_parse.
+ *
+ * After execution, statements like INSERT will
+ * have data committed to the database by default. For statements
+ * like SELECT, execution performs the logic of the
+ * query. Query results can subsequently be fetched in PHP with
+ * functions like oci_fetch_array.
+ *
+ * Each parsed statement may be executed multiple times, saving the
+ * cost of re-parsing. This is commonly used
+ * for INSERT statements when data is bound
+ * with oci_bind_by_name.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param int $mode An optional second parameter can be one of the following constants:
+ *
+ * Execution Modes
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * OCI_COMMIT_ON_SUCCESS
+ * Automatically commit all outstanding changes for
+ * this connection when the statement has succeeded. This
+ * is the default.
+ *
+ *
+ * OCI_DESCRIBE_ONLY
+ * Make query meta data available to functions
+ * like oci_field_name but do not
+ * create a result set. Any subsequent fetch call such
+ * as oci_fetch_array will
+ * fail.
+ *
+ *
+ * OCI_NO_AUTO_COMMIT
+ * Do not automatically commit changes. Prior to PHP
+ * 5.3.2 (PECL OCI8 1.4)
+ * use OCI_DEFAULT which is equivalent
+ * to OCI_NO_AUTO_COMMIT.
+ *
+ *
+ *
+ *
+ *
+ * Using OCI_NO_AUTO_COMMIT mode starts or continues a
+ * transaction. Transactions are automatically rolled back when
+ * the connection is closed, or when the script ends. Explicitly
+ * call oci_commit to commit a transaction,
+ * or oci_rollback to abort it.
+ *
+ * When inserting or updating data, using transactions is
+ * recommended for relational data consistency and for performance
+ * reasons.
+ *
+ * If OCI_NO_AUTO_COMMIT mode is used for any
+ * statement including queries, and
+ * oci_commit
+ * or oci_rollback is not subsequently
+ * called, then OCI8 will perform a rollback at the end of the
+ * script even if no data was changed. To avoid an unnecessary
+ * rollback, many scripts do not
+ * use OCI_NO_AUTO_COMMIT mode for queries or
+ * PL/SQL. Be careful to ensure the appropriate transactional
+ * consistency for the application when
+ * using oci_execute with different modes in
+ * the same script.
+ * @throws Oci8Exception
+ *
+ */
+function oci_execute($statement, int $mode = OCI_COMMIT_ON_SUCCESS): void
+{
+ error_clear_last();
+ $result = \oci_execute($statement, $mode);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetches multiple rows from a query into a two-dimensional array.
+ * By default, all rows are returned.
+ *
+ * This function can be called only once for each query executed
+ * with oci_execute.
+ *
+ * @param resource $statement A valid OCI8 statement
+ * identifier created by oci_parse and executed
+ * by oci_execute, or a REF
+ * CURSOR statement identifier.
+ * @param array|null $output The variable to contain the returned rows.
+ *
+ * LOB columns are returned as strings, where Oracle supports
+ * conversion.
+ *
+ * See oci_fetch_array for more information
+ * on how data and types are fetched.
+ * @param int $skip The number of initial rows to discard when fetching the
+ * result. The default value is 0, so the first row onwards is
+ * returned.
+ * @param int $maxrows The number of rows to return. The default is -1 meaning return
+ * all the rows from skip + 1 onwards.
+ * @param int $flags Parameter flags indicates the array
+ * structure and whether associative arrays should be used.
+ *
+ * oci_fetch_all Array Structure Modes
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * OCI_FETCHSTATEMENT_BY_ROW
+ * The outer array will contain one sub-array per query
+ * row.
+ *
+ *
+ * OCI_FETCHSTATEMENT_BY_COLUMN
+ * The outer array will contain one sub-array per query
+ * column. This is the default.
+ *
+ *
+ *
+ *
+ *
+ * Arrays can be indexed either by column heading or numerically.
+ * Only one index mode will be returned.
+ *
+ * oci_fetch_all Array Index Modes
+ *
+ *
+ *
+ * Constant
+ * Description
+ *
+ *
+ *
+ *
+ * OCI_NUM
+ * Numeric indexes are used for each column's array.
+ *
+ *
+ * OCI_ASSOC
+ * Associative indexes are used for each column's
+ * array. This is the default.
+ *
+ *
+ *
+ *
+ *
+ * Use the addition operator "+" to choose a combination
+ * of array structure and index modes.
+ *
+ * Oracle's default, non-case sensitive column names will have
+ * uppercase array keys. Case-sensitive column names will have
+ * array keys using the exact column case.
+ * Use var_dump
+ * on output to verify the appropriate case
+ * to use for each query.
+ *
+ * Queries that have more than one column with the same name
+ * should use column aliases. Otherwise only one of the columns
+ * will appear in an associative array.
+ * @return int Returns the number of rows in output, which
+ * may be 0 or more.
+ * @throws Oci8Exception
+ *
+ */
+function oci_fetch_all($statement, ?array &$output, int $skip = 0, int $maxrows = -1, int $flags = OCI_FETCHSTATEMENT_BY_COLUMN + OCI_ASSOC): int
+{
+ error_clear_last();
+ $result = \oci_fetch_all($statement, $output, $skip, $maxrows, $flags);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the name of the field.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return string Returns the name as a strings.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_name($statement, $field): string
+{
+ error_clear_last();
+ $result = \oci_field_name($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns precision of the field.
+ *
+ * For FLOAT columns, precision is nonzero and scale is -127.
+ * If precision is 0, then column is NUMBER. Else it's
+ * NUMBER(precision, scale).
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return int Returns the precision as an integers.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_precision($statement, $field): int
+{
+ error_clear_last();
+ $result = \oci_field_precision($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the scale of the column with field index.
+ *
+ * For FLOAT columns, precision is nonzero and scale is -127.
+ * If precision is 0, then column is NUMBER. Else it's
+ * NUMBER(precision, scale).
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return int Returns the scale as an integers.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_scale($statement, $field): int
+{
+ error_clear_last();
+ $result = \oci_field_scale($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the size of a field.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return int Returns the size of a field in bytess.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_size($statement, $field): int
+{
+ error_clear_last();
+ $result = \oci_field_size($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns Oracle's raw "SQLT" data type of the field.
+ *
+ * If you want a field's type name, then use oci_field_type instead.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return int Returns Oracle's raw data type as a numbers.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_type_raw($statement, $field): int
+{
+ error_clear_last();
+ $result = \oci_field_type_raw($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns a field's data type name.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @param mixed $field Can be the field's index (1-based) or name.
+ * @return mixed Returns the field data type as a strings.
+ * @throws Oci8Exception
+ *
+ */
+function oci_field_type($statement, $field)
+{
+ error_clear_last();
+ $result = \oci_field_type($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Frees a descriptor allocated by oci_new_descriptor.
+ *
+ * @param resource $descriptor
+ * @throws Oci8Exception
+ *
+ */
+function oci_free_descriptor($descriptor): void
+{
+ error_clear_last();
+ $result = \oci_free_descriptor($descriptor);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees resources associated with Oracle's cursor or statement, which was
+ * received from as a result of oci_parse or obtained
+ * from Oracle.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @throws Oci8Exception
+ *
+ */
+function oci_free_statement($statement): void
+{
+ error_clear_last();
+ $result = \oci_free_statement($statement);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Allocates a new collection object.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect or oci_pconnect.
+ * @param string $tdo Should be a valid named type (uppercase).
+ * @param string $schema Should point to the scheme, where the named type was created. The name
+ * of the current user is the default value.
+ * @return \OCI-Collection Returns a new OCICollection object.
+ * @throws Oci8Exception
+ *
+ */
+function oci_new_collection($connection, string $tdo, string $schema = null)
+{
+ error_clear_last();
+ $result = \oci_new_collection($connection, $tdo, $schema);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Establishes a new connection to an Oracle server and logs on.
+ *
+ * Unlike oci_connect and
+ * oci_pconnect, oci_new_connect
+ * does not cache connections and will always return a brand-new freshly
+ * opened connection handle. This is useful if your application needs
+ * transactional isolation between two sets of queries.
+ *
+ * @param string $username The Oracle user name.
+ * @param string $password The password for username.
+ * @param string $connection_string Contains
+ * the Oracle instance to connect to. It can be
+ * an Easy Connect
+ * string, or a Connect Name from
+ * the tnsnames.ora file, or the name of a local
+ * Oracle instance.
+ *
+ * If not specified, PHP uses
+ * environment variables such as TWO_TASK (on Linux)
+ * or LOCAL (on Windows)
+ * and ORACLE_SID to determine the
+ * Oracle instance to connect to.
+ *
+ *
+ * To use the Easy Connect naming method, PHP must be linked with Oracle
+ * 10g or greater Client libraries. The Easy Connect string for Oracle
+ * 10g is of the form:
+ * [//]host_name[:port][/service_name]. From Oracle
+ * 11g, the syntax is:
+ * [//]host_name[:port][/service_name][:server_type][/instance_name].
+ * Service names can be found by running the Oracle
+ * utility lsnrctl status on the database server
+ * machine.
+ *
+ *
+ * The tnsnames.ora file can be in the Oracle Net
+ * search path, which
+ * includes $ORACLE_HOME/network/admin
+ * and /etc. Alternatively
+ * set TNS_ADMIN so
+ * that $TNS_ADMIN/tnsnames.ora is read. Make sure
+ * the web daemon has read access to the file.
+ * @param string $character_set Determines
+ * the character set used by the Oracle Client libraries. The character
+ * set does not need to match the character set used by the database. If
+ * it doesn't match, Oracle will do its best to convert data to and from
+ * the database character set. Depending on the character sets this may
+ * not give usable results. Conversion also adds some time overhead.
+ *
+ * If not specified, the
+ * Oracle Client libraries determine a character set from
+ * the NLS_LANG environment variable.
+ *
+ * Passing this parameter can
+ * reduce the time taken to connect.
+ * @param int $session_mode This
+ * parameter is available since version PHP 5 (PECL OCI8 1.1) and accepts the
+ * following values: OCI_DEFAULT,
+ * OCI_SYSOPER and OCI_SYSDBA.
+ * If either OCI_SYSOPER or
+ * OCI_SYSDBA were specified, this function will try
+ * to establish privileged connection using external credentials.
+ * Privileged connections are disabled by default. To enable them you
+ * need to set oci8.privileged_connect
+ * to On.
+ *
+ *
+ * PHP 5.3 (PECL OCI8 1.3.4) introduced the
+ * OCI_CRED_EXT mode value. This tells Oracle to use
+ * External or OS authentication, which must be configured in the
+ * database. The OCI_CRED_EXT flag can only be used
+ * with username of "/" and a empty password.
+ * oci8.privileged_connect
+ * may be On or Off.
+ *
+ *
+ * OCI_CRED_EXT may be combined with the
+ * OCI_SYSOPER or
+ * OCI_SYSDBA modes.
+ *
+ *
+ * OCI_CRED_EXT is not supported on Windows for
+ * security reasons.
+ * @return resource Returns a connection identifier.
+ * @throws Oci8Exception
+ *
+ */
+function oci_new_connect(string $username, string $password, string $connection_string = null, string $character_set = null, int $session_mode = null)
+{
+ error_clear_last();
+ if ($session_mode !== null) {
+ $result = \oci_new_connect($username, $password, $connection_string, $character_set, $session_mode);
+ } elseif ($character_set !== null) {
+ $result = \oci_new_connect($username, $password, $connection_string, $character_set);
+ } elseif ($connection_string !== null) {
+ $result = \oci_new_connect($username, $password, $connection_string);
+ } else {
+ $result = \oci_new_connect($username, $password);
+ }
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Allocates a new statement handle on the specified connection.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect or oci_pconnect.
+ * @return resource Returns a new statement handle.
+ * @throws Oci8Exception
+ *
+ */
+function oci_new_cursor($connection)
+{
+ error_clear_last();
+ $result = \oci_new_cursor($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Allocates resources to hold descriptor or LOB locator.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect or oci_pconnect.
+ * @param int $type Valid values for type are:
+ * OCI_DTYPE_FILE, OCI_DTYPE_LOB and
+ * OCI_DTYPE_ROWID.
+ * @return \OCI-Lob Returns a new LOB or FILE descriptor on success, FALSE on error.
+ * @throws Oci8Exception
+ *
+ */
+function oci_new_descriptor($connection, int $type = OCI_DTYPE_LOB)
+{
+ error_clear_last();
+ $result = \oci_new_descriptor($connection, $type);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the number of columns in the given statement.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @return int Returns the number of columns as an integers.
+ * @throws Oci8Exception
+ *
+ */
+function oci_num_fields($statement): int
+{
+ error_clear_last();
+ $result = \oci_num_fields($statement);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the number of rows affected during statement execution.
+ *
+ * @param resource $statement A valid OCI statement identifier.
+ * @return int Returns the number of rows affected as an integers.
+ * @throws Oci8Exception
+ *
+ */
+function oci_num_rows($statement): int
+{
+ error_clear_last();
+ $result = \oci_num_rows($statement);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Prepares sql_text using
+ * connection and returns the statement identifier,
+ * which can be used with oci_bind_by_name,
+ * oci_execute and other functions.
+ *
+ * Statement identifiers can be freed
+ * with oci_free_statement or by setting the
+ * variable to NULL.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect, oci_pconnect, or oci_new_connect.
+ * @param string $sql_text The SQL or PL/SQL statement.
+ *
+ * SQL statements should not end with a
+ * semi-colon (";"). PL/SQL
+ * statements should end with a semi-colon
+ * (";").
+ * @return resource Returns a statement handle on success.
+ * @throws Oci8Exception
+ *
+ */
+function oci_parse($connection, string $sql_text)
+{
+ error_clear_last();
+ $result = \oci_parse($connection, $sql_text);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Creates a persistent connection to an Oracle server and logs on.
+ *
+ * Persistent connections are cached and re-used between requests, resulting
+ * in reduced overhead on each page load; a typical PHP application will have
+ * a single persistent connection open against an Oracle server per Apache
+ * child process (or PHP FastCGI/CGI process). See the Persistent Database
+ * Connections section for more information.
+ *
+ * @param string $username The Oracle user name.
+ * @param string $password The password for username.
+ * @param string $connection_string Contains
+ * the Oracle instance to connect to. It can be
+ * an Easy Connect
+ * string, or a Connect Name from
+ * the tnsnames.ora file, or the name of a local
+ * Oracle instance.
+ *
+ * If not specified, PHP uses
+ * environment variables such as TWO_TASK (on Linux)
+ * or LOCAL (on Windows)
+ * and ORACLE_SID to determine the
+ * Oracle instance to connect to.
+ *
+ *
+ * To use the Easy Connect naming method, PHP must be linked with Oracle
+ * 10g or greater Client libraries. The Easy Connect string for Oracle
+ * 10g is of the form:
+ * [//]host_name[:port][/service_name]. From Oracle
+ * 11g, the syntax is:
+ * [//]host_name[:port][/service_name][:server_type][/instance_name].
+ * Service names can be found by running the Oracle
+ * utility lsnrctl status on the database server
+ * machine.
+ *
+ *
+ * The tnsnames.ora file can be in the Oracle Net
+ * search path, which
+ * includes $ORACLE_HOME/network/admin
+ * and /etc. Alternatively
+ * set TNS_ADMIN so
+ * that $TNS_ADMIN/tnsnames.ora is read. Make sure
+ * the web daemon has read access to the file.
+ * @param string $character_set Determines
+ * the character set used by the Oracle Client libraries. The character
+ * set does not need to match the character set used by the database. If
+ * it doesn't match, Oracle will do its best to convert data to and from
+ * the database character set. Depending on the character sets this may
+ * not give usable results. Conversion also adds some time overhead.
+ *
+ * If not specified, the
+ * Oracle Client libraries determine a character set from
+ * the NLS_LANG environment variable.
+ *
+ * Passing this parameter can
+ * reduce the time taken to connect.
+ * @param int $session_mode This
+ * parameter is available since version PHP 5 (PECL OCI8 1.1) and accepts the
+ * following values: OCI_DEFAULT,
+ * OCI_SYSOPER and OCI_SYSDBA.
+ * If either OCI_SYSOPER or
+ * OCI_SYSDBA were specified, this function will try
+ * to establish privileged connection using external credentials.
+ * Privileged connections are disabled by default. To enable them you
+ * need to set oci8.privileged_connect
+ * to On.
+ *
+ *
+ * PHP 5.3 (PECL OCI8 1.3.4) introduced the
+ * OCI_CRED_EXT mode value. This tells Oracle to use
+ * External or OS authentication, which must be configured in the
+ * database. The OCI_CRED_EXT flag can only be used
+ * with username of "/" and a empty password.
+ * oci8.privileged_connect
+ * may be On or Off.
+ *
+ *
+ * OCI_CRED_EXT may be combined with the
+ * OCI_SYSOPER or
+ * OCI_SYSDBA modes.
+ *
+ *
+ * OCI_CRED_EXT is not supported on Windows for
+ * security reasons.
+ * @return resource Returns a connection identifier.
+ * @throws Oci8Exception
+ *
+ */
+function oci_pconnect(string $username, string $password, string $connection_string = null, string $character_set = null, int $session_mode = null)
+{
+ error_clear_last();
+ if ($session_mode !== null) {
+ $result = \oci_pconnect($username, $password, $connection_string, $character_set, $session_mode);
+ } elseif ($character_set !== null) {
+ $result = \oci_pconnect($username, $password, $connection_string, $character_set);
+ } elseif ($connection_string !== null) {
+ $result = \oci_pconnect($username, $password, $connection_string);
+ } else {
+ $result = \oci_pconnect($username, $password);
+ }
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the data from field in the current row,
+ * fetched by oci_fetch.
+ *
+ * For details on the data type mapping performed by
+ * the OCI8 extension, see the datatypes
+ * supported by the driver
+ *
+ * @param resource $statement
+ * @param mixed $field Can be either use the column number (1-based) or the column name.
+ * The case of the column name must be the case that Oracle meta data
+ * describes the column as, which is uppercase for columns created
+ * case insensitively.
+ * @return string Returns everything as strings except for abstract types (ROWIDs, LOBs and
+ * FILEs).
+ * @throws Oci8Exception
+ *
+ */
+function oci_result($statement, $field): string
+{
+ error_clear_last();
+ $result = \oci_result($statement, $field);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reverts all uncommitted changes for the Oracle
+ * connection and ends the transaction. It
+ * releases all locks held. All Oracle SAVEPOINTS
+ * are erased.
+ *
+ * A transaction begins when the first SQL statement that changes data
+ * is executed with oci_execute using
+ * the OCI_NO_AUTO_COMMIT flag. Further data
+ * changes made by other statements become part of the same
+ * transaction. Data changes made in a transaction are temporary
+ * until the transaction is committed or rolled back. Other users of
+ * the database will not see the changes until they are committed.
+ *
+ * When inserting or updating data, using transactions is recommended
+ * for relational data consistency and for performance reasons.
+ *
+ * @param resource $connection An Oracle connection identifier, returned by
+ * oci_connect, oci_pconnect
+ * or oci_new_connect.
+ * @throws Oci8Exception
+ *
+ */
+function oci_rollback($connection): void
+{
+ error_clear_last();
+ $result = \oci_rollback($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns a string with the Oracle Database version and available options
+ *
+ * @param resource $connection
+ * @return string Returns the version information as a string.
+ * @throws Oci8Exception
+ *
+ */
+function oci_server_version($connection): string
+{
+ error_clear_last();
+ $result = \oci_server_version($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the action name for Oracle tracing.
+ *
+ * The action name is registered with the database when the next
+ * 'round-trip' from PHP to the database occurs, typically when an SQL
+ * statement is executed.
+ *
+ * The action name can subsequently be queried from database administration
+ * views such as V$SESSION. It can be used for
+ * tracing and monitoring such as with V$SQLAREA
+ * and DBMS_MONITOR.SERV_MOD_ACT_STAT_ENABLE.
+ *
+ * The value may be retained across persistent connections.
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param string $action_name User chosen string up to 32 bytes long.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_action($connection, string $action_name): void
+{
+ error_clear_last();
+ $result = \oci_set_action($connection, $action_name);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets a timeout limiting the maxium time a database round-trip using this connection may take.
+ *
+ * Each OCI8 operation may make zero or more calls to Oracle's client
+ * library. These internal calls may then may make zero or more
+ * round-trips to Oracle Database. If any one of those round-trips
+ * takes more than time_out milliseconds, then the
+ * operation is cancelled and an error is returned to the application.
+ *
+ * The time_out value applies to each round-trip
+ * individually, not to the sum of all round-trips. Time spent
+ * processing in PHP OCI8 before or after the completion of each
+ * round-trip is not counted.
+ *
+ * When a call is interrupted, Oracle will attempt to clean up the
+ * connection for reuse. This operation is allowed to run for
+ * another time_out period. Depending on the
+ * outcome of the cleanup, the connection may or may not be reusable.
+ *
+ * When persistent connections are used, the timeout value will be
+ * retained across PHP requests.
+ *
+ * The oci_set_call_timeout function is available
+ * when OCI8 uses Oracle 18 (or later) Client libraries.
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param int $time_out The maximum time in milliseconds that any single round-trip between PHP and Oracle Database may take.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_call_timeout($connection, int $time_out): void
+{
+ error_clear_last();
+ $result = \oci_set_call_timeout($connection, $time_out);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the client identifier used by various database components to
+ * identify lightweight application users who authenticate as the same
+ * database user.
+ *
+ * The client identifier is registered with the database when the next
+ * 'round-trip' from PHP to the database occurs, typically when an SQL
+ * statement is executed.
+ *
+ * The identifier can subsequently be queried, for example
+ * with SELECT SYS_CONTEXT('USERENV','CLIENT_IDENTIFIER')
+ * FROM DUAL. Database administration views such
+ * as V$SESSION will also contain the value. It
+ * can be used with DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE
+ * for tracing and can also be used for auditing.
+ *
+ * The value may be retained across page requests that use the same persistent connection.
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param string $client_identifier User chosen string up to 64 bytes long.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_client_identifier($connection, string $client_identifier): void
+{
+ error_clear_last();
+ $result = \oci_set_client_identifier($connection, $client_identifier);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the client information for Oracle tracing.
+ *
+ * The client information is registered with the database when the next
+ * 'round-trip' from PHP to the database occurs, typically when an SQL
+ * statement is executed.
+ *
+ * The client information can subsequently be queried from database
+ * administration views such as V$SESSION.
+ *
+ * The value may be retained across persistent connections.
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param string $client_info User chosen string up to 64 bytes long.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_client_info($connection, string $client_info): void
+{
+ error_clear_last();
+ $result = \oci_set_client_info($connection, $client_info);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the DBOP for Oracle tracing.
+ *
+ * The database operation name is registered with the database when the next
+ * 'round-trip' from PHP to the database occurs, typically when a SQL
+ * statement is executed.
+ *
+ * The database operation can subsequently be queried from database administration
+ * views such as V$SQL_MONITOR.
+ *
+ * The oci_set_db_operation function is available
+ * when OCI8 uses Oracle 12 (or later) Client libraries and Oracle Database 12 (or later).
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param string $dbop User chosen string.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_db_operation($connection, string $dbop): void
+{
+ error_clear_last();
+ $result = \oci_set_db_operation($connection, $dbop);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the database "edition" of objects to be used by a subsequent
+ * connections.
+ *
+ * Oracle Editions allow concurrent versions of applications to run
+ * using the same schema and object names. This is useful for
+ * upgrading live systems.
+ *
+ * Call oci_set_edition before calling
+ * oci_connect, oci_pconnect
+ * or oci_new_connect.
+ *
+ * If an edition is set that is not valid in the database, connection
+ * will fail even if oci_set_edition returns success.
+ *
+ * When using persistent connections, if a connection with the
+ * requested edition setting already exists, it is reused. Otherwise,
+ * a different persistent connection is created
+ *
+ * @param string $edition Oracle Database edition name previously created with the SQL
+ * "CREATE EDITION" command.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_edition(string $edition): void
+{
+ error_clear_last();
+ $result = \oci_set_edition($edition);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the module name for Oracle tracing.
+ *
+ * The module name is registered with the database when the next
+ * 'round-trip' from PHP to the database occurs, typically when an SQL
+ * statement is executed.
+ *
+ * The name can subsequently be queried from database administration
+ * views such as V$SESSION. It can be used for
+ * tracing and monitoring such as with V$SQLAREA
+ * and DBMS_MONITOR.SERV_MOD_ACT_STAT_ENABLE.
+ *
+ * The value may be retained across persistent connections.
+ *
+ * @param resource $connection An Oracle connection identifier,
+ * returned by oci_connect, oci_pconnect,
+ * or oci_new_connect.
+ * @param string $module_name User chosen string up to 48 bytes long.
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_module_name($connection, string $module_name): void
+{
+ error_clear_last();
+ $result = \oci_set_module_name($connection, $module_name);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the number of rows to be buffered by the Oracle Client
+ * libraries after a successful query call
+ * to oci_execute and for each subsequent
+ * internal fetch request to the database. For queries returning a
+ * large number of rows, performance can be significantly improved by
+ * increasing the prefetch count above the
+ * default oci8.default_prefetch
+ * value.
+ *
+ * Prefetching is Oracle's efficient way of returning more than one
+ * data row from the database in each network request. This can
+ * result in better network and CPU utilization. The buffering of
+ * rows is internal to OCI8 and the behavior of OCI8 fetching
+ * functions is unchanged regardless of the prefetch count. For
+ * example, oci_fetch_row will always return one
+ * row. The prefetch buffer is per-statement and is not used by
+ * re-executed statements or by other connections.
+ *
+ * Call oci_set_prefetch before
+ * calling oci_execute.
+ *
+ * A tuning goal is to set the prefetch value to a reasonable size for
+ * the network and database to handle. For queries returning a very
+ * large number of rows, overall system efficiency might be better if
+ * rows are retrieved from the database in several chunks (i.e set the
+ * prefetch value smaller than the number of rows). This allows the
+ * database to handle other users' statements while the PHP script is
+ * processing the current set of rows.
+ *
+ * Query prefetching was introduced in Oracle 8i. REF CURSOR
+ * prefetching was introduced in Oracle 11gR2 and occurs when PHP is
+ * linked with Oracle 11gR2 (or later) Client libraries.
+ * Nested cursor prefetching was
+ * introduced in Oracle 11gR2 and requires both the Oracle Client
+ * libraries and the database to be version 11gR2 or greater.
+ *
+ * Prefetching is not supported when queries contain LONG or LOB
+ * columns. The prefetch value is ignored and single-row fetches will
+ * be used in all the situations when prefetching is not supported.
+ *
+ * When using Oracle Database 12c, the prefetch
+ * value set by PHP can be overridden by Oracle's
+ * client oraaccess.xml configuration file. Refer
+ * to Oracle documentation for more detail.
+ *
+ * @param resource $statement A valid OCI8 statement
+ * identifier created by oci_parse and executed
+ * by oci_execute, or a REF
+ * CURSOR statement identifier.
+ * @param int $rows The number of rows to be prefetched, >= 0
+ * @throws Oci8Exception
+ *
+ */
+function oci_set_prefetch($statement, int $rows): void
+{
+ error_clear_last();
+ $result = \oci_set_prefetch($statement, $rows);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns a keyword identifying the type of the
+ * OCI8 statement.
+ *
+ * @param resource $statement A valid OCI8 statement identifier from oci_parse.
+ * @return string Returns the type of statement as one of the
+ * following strings.
+ *
+ * Statement type
+ *
+ *
+ *
+ * Return String
+ * Notes
+ *
+ *
+ *
+ *
+ * ALTER
+ *
+ *
+ *
+ * BEGIN
+ *
+ *
+ *
+ * CALL
+ * Introduced in PHP 5.2.1 (PECL OCI8 1.2.3)
+ *
+ *
+ * CREATE
+ *
+ *
+ *
+ * DECLARE
+ *
+ *
+ *
+ * DELETE
+ *
+ *
+ *
+ * DROP
+ *
+ *
+ *
+ * INSERT
+ *
+ *
+ *
+ * SELECT
+ *
+ *
+ *
+ * UPDATE
+ *
+ *
+ *
+ * UNKNOW.
+ * @throws Oci8Exception
+ *
+ */
+function oci_statement_type($statement): string
+{
+ error_clear_last();
+ $result = \oci_statement_type($statement);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Unregister the user-defined callback function registered to connection
+ * by oci_register_taf_callback. See
+ * OCI8 Transparent Application Failover (TAF) Support
+ * for information.
+ *
+ * @param resource $connection An Oracle connection identifier.
+ * @throws Oci8Exception
+ *
+ */
+function oci_unregister_taf_callback($connection): void
+{
+ error_clear_last();
+ $result = \oci_unregister_taf_callback($connection);
+ if ($result === false) {
+ throw Oci8Exception::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\OpcacheException;
+
+/**
+ * This function compiles a PHP script and adds it to the opcode cache without
+ * executing it. This can be used to prime the cache after a Web server
+ * restart by pre-caching files that will be included in later requests.
+ *
+ * @param string $file The path to the PHP script to be compiled.
+ * @throws OpcacheException
+ *
+ */
+function opcache_compile_file(string $file): void
+{
+ error_clear_last();
+ $result = \opcache_compile_file($file);
+ if ($result === false) {
+ throw OpcacheException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function returns state information about the cache instance
+ *
+ * @param bool $get_scripts Include script specific state information
+ * @return array Returns an array of information, optionally containing script specific state information.
+ * @throws OpcacheException
+ *
+ */
+function opcache_get_status(bool $get_scripts = true): array
+{
+ error_clear_last();
+ $result = \opcache_get_status($get_scripts);
+ if ($result === false) {
+ throw OpcacheException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\OpensslException;
+
+/**
+ * Gets the cipher initialization vector (iv) length.
+ *
+ * @param string $method The cipher method, see openssl_get_cipher_methods for a list of potential values.
+ * @return int Returns the cipher length on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_cipher_iv_length(string $method): int
+{
+ error_clear_last();
+ $result = \openssl_cipher_iv_length($method);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_csr_export_to_file takes the Certificate
+ * Signing Request represented by csr and saves it
+ * in PEM format into the file named by outfilename.
+ *
+ * @param string|resource $csr See CSR parameters for a list of valid values.
+ * @param string $outfilename Path to the output file.
+ * @param bool $notext
+ * The optional parameter notext affects
+ * the verbosity of the output; if it is FALSE, then additional human-readable
+ * information is included in the output. The default value of
+ * notext is TRUE.
+ * @throws OpensslException
+ *
+ */
+function openssl_csr_export_to_file($csr, string $outfilename, bool $notext = true): void
+{
+ error_clear_last();
+ $result = \openssl_csr_export_to_file($csr, $outfilename, $notext);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_csr_export takes the Certificate Signing
+ * Request represented by csr and stores it in
+ * PEM format in out, which is passed by
+ * reference.
+ *
+ * @param string|resource $csr See CSR parameters for a list of valid values.
+ * @param string|null $out on success, this string will contain the PEM encoded CSR
+ * @param bool $notext
+ * The optional parameter notext affects
+ * the verbosity of the output; if it is FALSE, then additional human-readable
+ * information is included in the output. The default value of
+ * notext is TRUE.
+ * @throws OpensslException
+ *
+ */
+function openssl_csr_export($csr, ?string &$out, bool $notext = true): void
+{
+ error_clear_last();
+ $result = \openssl_csr_export($csr, $out, $notext);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_csr_get_subject returns subject
+ * distinguished name information encoded in the csr
+ * including fields commonName (CN), organizationName (O), countryName (C) etc.
+ *
+ * @param string|resource $csr See CSR parameters for a list of valid values.
+ * @param bool $use_shortnames shortnames controls how the data is indexed in the
+ * array - if shortnames is TRUE (the default) then
+ * fields will be indexed with the short name form, otherwise, the long name
+ * form will be used - e.g.: CN is the shortname form of commonName.
+ * @return array Returns an associative array with subject description.
+ * @throws OpensslException
+ *
+ */
+function openssl_csr_get_subject($csr, bool $use_shortnames = true): array
+{
+ error_clear_last();
+ $result = \openssl_csr_get_subject($csr, $use_shortnames);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_csr_new generates a new CSR (Certificate Signing Request)
+ * based on the information provided by dn.
+ *
+ * @param array $dn The Distinguished Name or subject fields to be used in the certificate.
+ * @param resource $privkey privkey should be set to a private key that was
+ * previously generated by openssl_pkey_new (or
+ * otherwise obtained from the other openssl_pkey family of functions).
+ * The corresponding public portion of the key will be used to sign the
+ * CSR.
+ * @param array $configargs By default, the information in your system openssl.conf
+ * is used to initialize the request; you can specify a configuration file
+ * section by setting the config_section_section key of
+ * configargs. You can also specify an alternative
+ * openssl configuration file by setting the value of the
+ * config key to the path of the file you want to use.
+ * The following keys, if present in configargs
+ * behave as their equivalents in the openssl.conf, as
+ * listed in the table below.
+ *
+ * Configuration overrides
+ *
+ *
+ *
+ * configargs key
+ * type
+ * openssl.conf equivalent
+ * description
+ *
+ *
+ *
+ *
+ * digest_alg
+ * string
+ * default_md
+ * Digest method or signature hash, usually one of openssl_get_md_methods
+ *
+ *
+ * x509_extensions
+ * string
+ * x509_extensions
+ * Selects which extensions should be used when creating an x509
+ * certificate
+ *
+ *
+ * req_extensions
+ * string
+ * req_extensions
+ * Selects which extensions should be used when creating a CSR
+ *
+ *
+ * private_key_bits
+ * integer
+ * default_bits
+ * Specifies how many bits should be used to generate a private
+ * key
+ *
+ *
+ * private_key_type
+ * integer
+ * none
+ * Specifies the type of private key to create. This can be one
+ * of OPENSSL_KEYTYPE_DSA,
+ * OPENSSL_KEYTYPE_DH,
+ * OPENSSL_KEYTYPE_RSA or
+ * OPENSSL_KEYTYPE_EC.
+ * The default value is OPENSSL_KEYTYPE_RSA.
+ *
+ *
+ *
+ * encrypt_key
+ * boolean
+ * encrypt_key
+ * Should an exported key (with passphrase) be encrypted?
+ *
+ *
+ * encrypt_key_cipher
+ * integer
+ * none
+ *
+ * One of cipher constants.
+ *
+ *
+ *
+ * curve_name
+ * string
+ * none
+ *
+ * One of openssl_get_curve_names.
+ *
+ *
+ *
+ * config
+ * string
+ * N/A
+ *
+ * Path to your own alternative openssl.conf file.
+ *
+ *
+ *
+ *
+ *
+ * @param array $extraattribs extraattribs is used to specify additional
+ * configuration options for the CSR. Both dn and
+ * extraattribs are associative arrays whose keys are
+ * converted to OIDs and applied to the relevant part of the request.
+ * @return resource Returns the CSR.
+ * @throws OpensslException
+ *
+ */
+function openssl_csr_new(array $dn, &$privkey, array $configargs = null, array $extraattribs = null)
+{
+ error_clear_last();
+ if ($extraattribs !== null) {
+ $result = \openssl_csr_new($dn, $privkey, $configargs, $extraattribs);
+ } elseif ($configargs !== null) {
+ $result = \openssl_csr_new($dn, $privkey, $configargs);
+ } else {
+ $result = \openssl_csr_new($dn, $privkey);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_csr_sign generates an x509 certificate
+ * resource from the given CSR.
+ *
+ * @param string|resource $csr A CSR previously generated by openssl_csr_new.
+ * It can also be the path to a PEM encoded CSR when specified as
+ * file://path/to/csr or an exported string generated
+ * by openssl_csr_export.
+ * @param mixed $cacert The generated certificate will be signed by cacert.
+ * If cacert is NULL, the generated certificate
+ * will be a self-signed certificate.
+ * @param string|resource|array $priv_key priv_key is the private key that corresponds to
+ * cacert.
+ * @param int $days days specifies the length of time for which the
+ * generated certificate will be valid, in days.
+ * @param array $configargs You can finetune the CSR signing by configargs.
+ * See openssl_csr_new for more information about
+ * configargs.
+ * @param int $serial An optional the serial number of issued certificate. If not specified
+ * it will default to 0.
+ * @return resource Returns an x509 certificate resource on success, FALSE on failure.
+ * @throws OpensslException
+ *
+ */
+function openssl_csr_sign($csr, $cacert, $priv_key, int $days, array $configargs = null, int $serial = 0)
+{
+ error_clear_last();
+ if ($serial !== 0) {
+ $result = \openssl_csr_sign($csr, $cacert, $priv_key, $days, $configargs, $serial);
+ } elseif ($configargs !== null) {
+ $result = \openssl_csr_sign($csr, $cacert, $priv_key, $days, $configargs);
+ } else {
+ $result = \openssl_csr_sign($csr, $cacert, $priv_key, $days);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Takes a raw or base64 encoded string and decrypts it using a given method and key.
+ *
+ * @param string $data The encrypted message to be decrypted.
+ * @param string $method The cipher method. For a list of available cipher methods, use
+ * openssl_get_cipher_methods.
+ * @param string $key The key.
+ * @param int $options options can be one of
+ * OPENSSL_RAW_DATA,
+ * OPENSSL_ZERO_PADDING.
+ * @param string $iv A non-NULL Initialization Vector.
+ * @param string $tag The authentication tag in AEAD cipher mode. If it is incorrect, the authentication fails and the function returns FALSE.
+ * @param string $aad Additional authentication data.
+ * @return string The decrypted string on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_decrypt(string $data, string $method, string $key, int $options = 0, string $iv = "", string $tag = "", string $aad = ""): string
+{
+ error_clear_last();
+ $result = \openssl_decrypt($data, $method, $key, $options, $iv, $tag, $aad);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The shared secret returned by openssl_dh_compute_key is
+ * often used as an encryption key to secretly communicate with a remote party.
+ * This is known as the Diffie-Hellman key exchange.
+ *
+ * @param string $pub_key DH Public key of the remote party.
+ * @param resource $dh_key A local DH private key, corresponding to the public key to be shared with the remote party.
+ * @return string Returns shared secret on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_dh_compute_key(string $pub_key, $dh_key): string
+{
+ error_clear_last();
+ $result = \openssl_dh_compute_key($pub_key, $dh_key);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Computes a digest hash value for the given data using a given method,
+ * and returns a raw or binhex encoded string.
+ *
+ * @param string $data The data.
+ * @param string $method The digest method to use, e.g. "sha256", see openssl_get_md_methods for a list of available digest methods.
+ * @param bool $raw_output Setting to TRUE will return as raw output data, otherwise the return
+ * value is binhex encoded.
+ * @return string Returns the digested hash value on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_digest(string $data, string $method, bool $raw_output = false): string
+{
+ error_clear_last();
+ $result = \openssl_digest($data, $method, $raw_output);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_open opens (decrypts)
+ * sealed_data using the private key associated with
+ * the key identifier priv_key_id and the envelope key
+ * env_key, and fills
+ * open_data with the decrypted data.
+ * The envelope key is generated when the
+ * data are sealed and can only be used by one specific private key. See
+ * openssl_seal for more information.
+ *
+ * @param string $sealed_data
+ * @param string|null $open_data If the call is successful the opened data is returned in this
+ * parameter.
+ * @param string $env_key
+ * @param string|array|resource $priv_key_id
+ * @param string $method The cipher method.
+ * @param string $iv The initialization vector.
+ * @throws OpensslException
+ *
+ */
+function openssl_open(string $sealed_data, ?string &$open_data, string $env_key, $priv_key_id, string $method = "RC4", string $iv = null): void
+{
+ error_clear_last();
+ if ($iv !== null) {
+ $result = \openssl_open($sealed_data, $open_data, $env_key, $priv_key_id, $method, $iv);
+ } else {
+ $result = \openssl_open($sealed_data, $open_data, $env_key, $priv_key_id, $method);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pbkdf2 computes PBKDF2 (Password-Based Key Derivation Function 2),
+ * a key derivation function defined in PKCS5 v2.
+ *
+ * @param string $password Password from which the derived key is generated.
+ * @param string $salt PBKDF2 recommends a crytographic salt of at least 64 bits (8 bytes).
+ * @param int $key_length Length of desired output key.
+ * @param int $iterations The number of iterations desired. NIST
+ * recommends at least 10,000.
+ * @param string $digest_algorithm Optional hash or digest algorithm from openssl_get_md_methods. Defaults to SHA-1.
+ * @return string Returns raw binary string.
+ * @throws OpensslException
+ *
+ */
+function openssl_pbkdf2(string $password, string $salt, int $key_length, int $iterations, string $digest_algorithm = "sha1"): string
+{
+ error_clear_last();
+ $result = \openssl_pbkdf2($password, $salt, $key_length, $iterations, $digest_algorithm);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_pkcs12_export_to_file stores
+ * x509 into a file named by
+ * filename in a PKCS#12 file format.
+ *
+ * @param string|resource $x509 See Key/Certificate parameters for a list of valid values.
+ * @param string $filename Path to the output file.
+ * @param string|array|resource $priv_key Private key component of PKCS#12 file.
+ * See Public/Private Key parameters for a list of valid values.
+ * @param string $pass Encryption password for unlocking the PKCS#12 file.
+ * @param array $args Optional array, other keys will be ignored.
+ *
+ *
+ *
+ *
+ * Key
+ * Description
+ *
+ *
+ *
+ *
+ * "extracerts"
+ * array of extra certificates or a single certificate to be included in the PKCS#12 file.
+ *
+ *
+ * "friendlyname"
+ * string to be used for the supplied certificate and key
+ *
+ *
+ *
+ *
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs12_export_to_file($x509, string $filename, $priv_key, string $pass, array $args = null): void
+{
+ error_clear_last();
+ if ($args !== null) {
+ $result = \openssl_pkcs12_export_to_file($x509, $filename, $priv_key, $pass, $args);
+ } else {
+ $result = \openssl_pkcs12_export_to_file($x509, $filename, $priv_key, $pass);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkcs12_export stores
+ * x509 into a string named by
+ * out in a PKCS#12 file format.
+ *
+ * @param string|resource $x509 See Key/Certificate parameters for a list of valid values.
+ * @param string|null $out On success, this will hold the PKCS#12.
+ * @param string|array|resource $priv_key Private key component of PKCS#12 file.
+ * See Public/Private Key parameters for a list of valid values.
+ * @param string $pass Encryption password for unlocking the PKCS#12 file.
+ * @param array $args Optional array, other keys will be ignored.
+ *
+ *
+ *
+ *
+ * Key
+ * Description
+ *
+ *
+ *
+ *
+ * "extracerts"
+ * array of extra certificates or a single certificate to be included in the PKCS#12 file.
+ *
+ *
+ * "friendlyname"
+ * string to be used for the supplied certificate and key
+ *
+ *
+ *
+ *
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs12_export($x509, ?string &$out, $priv_key, string $pass, array $args = null): void
+{
+ error_clear_last();
+ if ($args !== null) {
+ $result = \openssl_pkcs12_export($x509, $out, $priv_key, $pass, $args);
+ } else {
+ $result = \openssl_pkcs12_export($x509, $out, $priv_key, $pass);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkcs12_read parses the PKCS#12 certificate store supplied by
+ * pkcs12 into a array named
+ * certs.
+ *
+ * @param string $pkcs12 The certificate store contents, not its file name.
+ * @param array|null $certs On success, this will hold the Certificate Store Data.
+ * @param string $pass Encryption password for unlocking the PKCS#12 file.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs12_read(string $pkcs12, ?array &$certs, string $pass): void
+{
+ error_clear_last();
+ $result = \openssl_pkcs12_read($pkcs12, $certs, $pass);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Decrypts the S/MIME encrypted message contained in the file specified by
+ * infilename using the certificate and its
+ * associated private key specified by recipcert and
+ * recipkey.
+ *
+ * @param string $infilename
+ * @param string $outfilename The decrypted message is written to the file specified by
+ * outfilename.
+ * @param string|resource $recipcert
+ * @param string|resource|array $recipkey
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs7_decrypt(string $infilename, string $outfilename, $recipcert, $recipkey = null): void
+{
+ error_clear_last();
+ if ($recipkey !== null) {
+ $result = \openssl_pkcs7_decrypt($infilename, $outfilename, $recipcert, $recipkey);
+ } else {
+ $result = \openssl_pkcs7_decrypt($infilename, $outfilename, $recipcert);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkcs7_encrypt takes the contents of the
+ * file named infile and encrypts them using an RC2
+ * 40-bit cipher so that they can only be read by the intended recipients
+ * specified by recipcerts.
+ *
+ * @param string $infile
+ * @param string $outfile
+ * @param string|resource|array $recipcerts Either a lone X.509 certificate, or an array of X.509 certificates.
+ * @param array $headers headers is an array of headers that
+ * will be prepended to the data after it has been encrypted.
+ *
+ * headers can be either an associative array
+ * keyed by header name, or an indexed array, where each element contains
+ * a single header line.
+ * @param int $flags flags can be used to specify options that affect
+ * the encoding process - see PKCS7
+ * constants.
+ * @param int $cipherid One of cipher constants.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs7_encrypt(string $infile, string $outfile, $recipcerts, array $headers, int $flags = 0, int $cipherid = OPENSSL_CIPHER_RC2_40): void
+{
+ error_clear_last();
+ $result = \openssl_pkcs7_encrypt($infile, $outfile, $recipcerts, $headers, $flags, $cipherid);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $infilename
+ * @param array|null $certs
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs7_read(string $infilename, ?array &$certs): void
+{
+ error_clear_last();
+ $result = \openssl_pkcs7_read($infilename, $certs);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkcs7_sign takes the contents of the file
+ * named infilename and signs them using the
+ * certificate and its matching private key specified by
+ * signcert and privkey
+ * parameters.
+ *
+ * @param string $infilename The input file you are intending to digitally sign.
+ * @param string $outfilename The file which the digital signature will be written to.
+ * @param string|resource $signcert The X.509 certificate used to digitally sign infilename.
+ * See Key/Certificate parameters for a list of valid values.
+ * @param string|resource|array $privkey privkey is the private key corresponding to signcert.
+ * See Public/Private Key parameters for a list of valid values.
+ * @param array $headers headers is an array of headers that
+ * will be prepended to the data after it has been signed (see
+ * openssl_pkcs7_encrypt for more information about
+ * the format of this parameter).
+ * @param int $flags flags can be used to alter the output - see PKCS7 constants.
+ * @param string $extracerts extracerts specifies the name of a file containing
+ * a bunch of extra certificates to include in the signature which can for
+ * example be used to help the recipient to verify the certificate that you used.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkcs7_sign(string $infilename, string $outfilename, $signcert, $privkey, array $headers, int $flags = PKCS7_DETACHED, string $extracerts = null): void
+{
+ error_clear_last();
+ if ($extracerts !== null) {
+ $result = \openssl_pkcs7_sign($infilename, $outfilename, $signcert, $privkey, $headers, $flags, $extracerts);
+ } else {
+ $result = \openssl_pkcs7_sign($infilename, $outfilename, $signcert, $privkey, $headers, $flags);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkey_export_to_file saves an ascii-armoured
+ * (PEM encoded) rendition of key into the file named
+ * by outfilename.
+ *
+ * @param resource|string|array $key
+ * @param string $outfilename Path to the output file.
+ * @param string $passphrase The key can be optionally protected by a
+ * passphrase.
+ * @param array $configargs configargs can be used to fine-tune the export
+ * process by specifying and/or overriding options for the openssl
+ * configuration file. See openssl_csr_new for more
+ * information about configargs.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkey_export_to_file($key, string $outfilename, string $passphrase = null, array $configargs = null): void
+{
+ error_clear_last();
+ if ($configargs !== null) {
+ $result = \openssl_pkey_export_to_file($key, $outfilename, $passphrase, $configargs);
+ } elseif ($passphrase !== null) {
+ $result = \openssl_pkey_export_to_file($key, $outfilename, $passphrase);
+ } else {
+ $result = \openssl_pkey_export_to_file($key, $outfilename);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkey_export exports
+ * key as a PEM encoded string and stores it into
+ * out (which is passed by reference).
+ *
+ * @param resource $key
+ * @param string|null $out
+ * @param string $passphrase The key is optionally protected by passphrase.
+ * @param array $configargs configargs can be used to fine-tune the export
+ * process by specifying and/or overriding options for the openssl
+ * configuration file. See openssl_csr_new for more
+ * information about configargs.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkey_export($key, ?string &$out, string $passphrase = null, array $configargs = null): void
+{
+ error_clear_last();
+ if ($configargs !== null) {
+ $result = \openssl_pkey_export($key, $out, $passphrase, $configargs);
+ } elseif ($passphrase !== null) {
+ $result = \openssl_pkey_export($key, $out, $passphrase);
+ } else {
+ $result = \openssl_pkey_export($key, $out);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_pkey_get_private parses
+ * key and prepares it for use by other functions.
+ *
+ * @param string $key key can be one of the following:
+ *
+ * a string having the format
+ * file://path/to/file.pem. The named file must
+ * contain a PEM encoded certificate/private key (it may contain both).
+ *
+ *
+ * A PEM formatted private key.
+ *
+ * @param string $passphrase The optional parameter passphrase must be used
+ * if the specified key is encrypted (protected by a passphrase).
+ * @return resource Returns a positive key resource identifier on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkey_get_private(string $key, string $passphrase = "")
+{
+ error_clear_last();
+ $result = \openssl_pkey_get_private($key, $passphrase);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_pkey_get_public extracts the public key from
+ * certificate and prepares it for use by other
+ * functions.
+ *
+ * @param resource|string $certificate certificate can be one of the following:
+ *
+ * an X.509 certificate resource
+ * a string having the format
+ * file://path/to/file.pem. The named file must
+ * contain a PEM encoded certificate/public key (it may contain both).
+ *
+ *
+ * A PEM formatted public key.
+ *
+ * @return resource Returns a positive key resource identifier on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkey_get_public($certificate)
+{
+ error_clear_last();
+ $result = \openssl_pkey_get_public($certificate);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_pkey_new generates a new private and public
+ * key pair. The public component of the key can be obtained using
+ * openssl_pkey_get_public.
+ *
+ * @param array $configargs You can finetune the key generation (such as specifying the number of
+ * bits) using configargs. See
+ * openssl_csr_new for more information about
+ * configargs.
+ * @return resource Returns a resource identifier for the pkey on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_pkey_new(array $configargs = null)
+{
+ error_clear_last();
+ if ($configargs !== null) {
+ $result = \openssl_pkey_new($configargs);
+ } else {
+ $result = \openssl_pkey_new();
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_private_decrypt decrypts
+ * data that was previously encrypted via
+ * openssl_public_encrypt and stores the result into
+ * decrypted.
+ *
+ * You can use this function e.g. to decrypt data which is supposed to only be available to you.
+ *
+ * @param string $data
+ * @param string|null $decrypted
+ * @param string|resource|array $key key must be the private key corresponding that
+ * was used to encrypt the data.
+ * @param int $padding padding can be one of
+ * OPENSSL_PKCS1_PADDING,
+ * OPENSSL_SSLV23_PADDING,
+ * OPENSSL_PKCS1_OAEP_PADDING,
+ * OPENSSL_NO_PADDING.
+ * @throws OpensslException
+ *
+ */
+function openssl_private_decrypt(string $data, ?string &$decrypted, $key, int $padding = OPENSSL_PKCS1_PADDING): void
+{
+ error_clear_last();
+ $result = \openssl_private_decrypt($data, $decrypted, $key, $padding);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_private_encrypt encrypts data
+ * with private key and stores the result into
+ * crypted. Encrypted data can be decrypted via
+ * openssl_public_decrypt.
+ *
+ * This function can be used e.g. to sign data (or its hash) to prove that it
+ * is not written by someone else.
+ *
+ * @param string $data
+ * @param string|null $crypted
+ * @param string|resource|array $key
+ * @param int $padding padding can be one of
+ * OPENSSL_PKCS1_PADDING,
+ * OPENSSL_NO_PADDING.
+ * @throws OpensslException
+ *
+ */
+function openssl_private_encrypt(string $data, ?string &$crypted, $key, int $padding = OPENSSL_PKCS1_PADDING): void
+{
+ error_clear_last();
+ $result = \openssl_private_encrypt($data, $crypted, $key, $padding);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_public_decrypt decrypts
+ * data that was previous encrypted via
+ * openssl_private_encrypt and stores the result into
+ * decrypted.
+ *
+ * You can use this function e.g. to check if the message was written by the
+ * owner of the private key.
+ *
+ * @param string $data
+ * @param string|null $decrypted
+ * @param string|resource $key key must be the public key corresponding that
+ * was used to encrypt the data.
+ * @param int $padding padding can be one of
+ * OPENSSL_PKCS1_PADDING,
+ * OPENSSL_NO_PADDING.
+ * @throws OpensslException
+ *
+ */
+function openssl_public_decrypt(string $data, ?string &$decrypted, $key, int $padding = OPENSSL_PKCS1_PADDING): void
+{
+ error_clear_last();
+ $result = \openssl_public_decrypt($data, $decrypted, $key, $padding);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_public_encrypt encrypts data
+ * with public key and stores the result into
+ * crypted. Encrypted data can be decrypted via
+ * openssl_private_decrypt.
+ *
+ * This function can be used e.g. to encrypt message which can be then read
+ * only by owner of the private key. It can be also used to store secure data
+ * in database.
+ *
+ * @param string $data
+ * @param string|null $crypted This will hold the result of the encryption.
+ * @param string|resource $key The public key.
+ * @param int $padding padding can be one of
+ * OPENSSL_PKCS1_PADDING,
+ * OPENSSL_SSLV23_PADDING,
+ * OPENSSL_PKCS1_OAEP_PADDING,
+ * OPENSSL_NO_PADDING.
+ * @throws OpensslException
+ *
+ */
+function openssl_public_encrypt(string $data, ?string &$crypted, $key, int $padding = OPENSSL_PKCS1_PADDING): void
+{
+ error_clear_last();
+ $result = \openssl_public_encrypt($data, $crypted, $key, $padding);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Generates a string of pseudo-random bytes, with the number of bytes
+ * determined by the length parameter.
+ *
+ * It also indicates if a cryptographically strong algorithm was used to produce the
+ * pseudo-random bytes, and does this via the optional crypto_strong
+ * parameter. It's rare for this to be FALSE, but some systems may be broken or old.
+ *
+ * @param int $length The length of the desired string of bytes. Must be a positive integer. PHP will
+ * try to cast this parameter to a non-null integer to use it.
+ * @param bool|null $crypto_strong If passed into the function, this will hold a boolean value that determines
+ * if the algorithm used was "cryptographically strong", e.g., safe for usage with GPG,
+ * passwords, etc. TRUE if it did, otherwise FALSE
+ * @return string Returns the generated string of bytes on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_random_pseudo_bytes(int $length, ?bool &$crypto_strong = null): string
+{
+ error_clear_last();
+ $result = \openssl_random_pseudo_bytes($length, $crypto_strong);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_seal seals (encrypts)
+ * data by using the given method with a randomly generated
+ * secret key. The key is encrypted with each of the public keys
+ * associated with the identifiers in pub_key_ids
+ * and each encrypted key is returned
+ * in env_keys. This means that one can send
+ * sealed data to multiple recipients (provided one has obtained their
+ * public keys). Each recipient must receive both the sealed data and
+ * the envelope key that was encrypted with the recipient's public key.
+ *
+ * @param string $data The data to seal.
+ * @param string|null $sealed_data The sealed data.
+ * @param array $env_keys Array of encrypted keys.
+ * @param array $pub_key_ids Array of public key resource identifiers.
+ * @param string $method The cipher method.
+ * @param string $iv The initialization vector.
+ * @return int Returns the length of the sealed data on success.
+ * If successful the sealed data is returned in
+ * sealed_data, and the envelope keys in
+ * env_keys.
+ * @throws OpensslException
+ *
+ */
+function openssl_seal(string $data, ?string &$sealed_data, array &$env_keys, array $pub_key_ids, string $method = "RC4", string &$iv = null): int
+{
+ error_clear_last();
+ $result = \openssl_seal($data, $sealed_data, $env_keys, $pub_key_ids, $method, $iv);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_sign computes a signature for the
+ * specified data by generating a cryptographic
+ * digital signature using the private key associated with
+ * priv_key_id. Note that the data itself is
+ * not encrypted.
+ *
+ * @param string $data The string of data you wish to sign
+ * @param string|null $signature If the call was successful the signature is returned in
+ * signature.
+ * @param resource|string $priv_key_id resource - a key, returned by openssl_get_privatekey
+ *
+ * string - a PEM formatted key
+ * @param int|string $signature_alg int - one of these Signature Algorithms.
+ *
+ * string - a valid string returned by openssl_get_md_methods example, "sha256WithRSAEncryption" or "sha384".
+ * @throws OpensslException
+ *
+ */
+function openssl_sign(string $data, ?string &$signature, $priv_key_id, $signature_alg = OPENSSL_ALGO_SHA1): void
+{
+ error_clear_last();
+ $result = \openssl_sign($data, $signature, $priv_key_id, $signature_alg);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_x509_export_to_file stores
+ * x509 into a file named by
+ * outfilename in a PEM encoded format.
+ *
+ * @param string|resource $x509 See Key/Certificate parameters for a list of valid values.
+ * @param string $outfilename Path to the output file.
+ * @param bool $notext
+ * The optional parameter notext affects
+ * the verbosity of the output; if it is FALSE, then additional human-readable
+ * information is included in the output. The default value of
+ * notext is TRUE.
+ * @throws OpensslException
+ *
+ */
+function openssl_x509_export_to_file($x509, string $outfilename, bool $notext = true): void
+{
+ error_clear_last();
+ $result = \openssl_x509_export_to_file($x509, $outfilename, $notext);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_x509_export stores
+ * x509 into a string named by
+ * output in a PEM encoded format.
+ *
+ * @param string|resource $x509 See Key/Certificate parameters for a list of valid values.
+ * @param string|null $output On success, this will hold the PEM.
+ * @param bool $notext
+ * The optional parameter notext affects
+ * the verbosity of the output; if it is FALSE, then additional human-readable
+ * information is included in the output. The default value of
+ * notext is TRUE.
+ * @throws OpensslException
+ *
+ */
+function openssl_x509_export($x509, ?string &$output, bool $notext = true): void
+{
+ error_clear_last();
+ $result = \openssl_x509_export($x509, $output, $notext);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+}
+
+
+/**
+ * openssl_x509_fingerprint returns the digest of
+ * x509 as a string.
+ *
+ * @param string|resource $x509 See Key/Certificate parameters for a list of valid values.
+ * @param string $hash_algorithm The digest method or hash algorithm to use, e.g. "sha256", one of openssl_get_md_methods.
+ * @param bool $raw_output When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
+ * @return string Returns a string containing the calculated certificate fingerprint as lowercase hexits unless raw_output is set to TRUE in which case the raw binary representation of the message digest is returned.
+ *
+ * Returns FALSE on failure.
+ * @throws OpensslException
+ *
+ */
+function openssl_x509_fingerprint($x509, string $hash_algorithm = "sha1", bool $raw_output = false): string
+{
+ error_clear_last();
+ $result = \openssl_x509_fingerprint($x509, $hash_algorithm, $raw_output);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * openssl_x509_read parses the certificate supplied by
+ * x509certdata and returns a resource identifier for
+ * it.
+ *
+ * @param string|resource $x509certdata X509 certificate. See Key/Certificate parameters for a list of valid values.
+ * @return resource Returns a resource identifier on success.
+ * @throws OpensslException
+ *
+ */
+function openssl_x509_read($x509certdata)
+{
+ error_clear_last();
+ $result = \openssl_x509_read($x509certdata);
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\OutcontrolException;
+
+/**
+ * This function discards the contents of the topmost output buffer and turns
+ * off this output buffering. If you want to further process the buffer's
+ * contents you have to call ob_get_contents before
+ * ob_end_clean as the buffer contents are discarded
+ * when ob_end_clean is called.
+ *
+ * The output buffer must be started by
+ * ob_start with PHP_OUTPUT_HANDLER_CLEANABLE
+ * and PHP_OUTPUT_HANDLER_REMOVABLE
+ * flags. Otherwise ob_end_clean will not work.
+ *
+ * @throws OutcontrolException
+ *
+ */
+function ob_end_clean(): void
+{
+ error_clear_last();
+ $result = \ob_end_clean();
+ if ($result === false) {
+ throw OutcontrolException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function will send the contents of the topmost output buffer (if
+ * any) and turn this output buffer off. If you want to further
+ * process the buffer's contents you have to call
+ * ob_get_contents before
+ * ob_end_flush as the buffer contents are
+ * discarded after ob_end_flush is called.
+ *
+ * The output buffer must be started by
+ * ob_start with PHP_OUTPUT_HANDLER_FLUSHABLE
+ * and PHP_OUTPUT_HANDLER_REMOVABLE
+ * flags. Otherwise ob_end_flush will not work.
+ *
+ * @throws OutcontrolException
+ *
+ */
+function ob_end_flush(): void
+{
+ error_clear_last();
+ $result = \ob_end_flush();
+ if ($result === false) {
+ throw OutcontrolException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function adds another name/value pair to the URL rewrite mechanism.
+ * The name and value will be added to URLs (as GET parameter) and forms
+ * (as hidden input fields) the same way as the session ID when transparent
+ * URL rewriting is enabled with session.use_trans_sid.
+ *
+ * This function's behaviour is controlled by the url_rewriter.tags and
+ * url_rewriter.hosts php.ini
+ * parameters.
+ *
+ * Note that this function can be successfully called at most once per request.
+ *
+ * @param string $name The variable name.
+ * @param string $value The variable value.
+ * @throws OutcontrolException
+ *
+ */
+function output_add_rewrite_var(string $name, string $value): void
+{
+ error_clear_last();
+ $result = \output_add_rewrite_var($name, $value);
+ if ($result === false) {
+ throw OutcontrolException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function resets the URL rewriter and removes all rewrite
+ * variables previously set by the output_add_rewrite_var
+ * function.
+ *
+ * @throws OutcontrolException
+ *
+ */
+function output_reset_rewrite_vars(): void
+{
+ error_clear_last();
+ $result = \output_reset_rewrite_vars();
+ if ($result === false) {
+ throw OutcontrolException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PasswordException;
+
+/**
+ * password_hash creates a new password hash using a strong one-way hashing
+ * algorithm. password_hash is compatible with crypt.
+ * Therefore, password hashes created by crypt can be used with
+ * password_hash.
+ *
+ *
+ *
+ *
+ * PASSWORD_DEFAULT - Use the bcrypt algorithm (default as of PHP 5.5.0).
+ * Note that this constant is designed to change over time as new and stronger algorithms are added
+ * to PHP. For that reason, the length of the result from using this identifier can change over
+ * time. Therefore, it is recommended to store the result in a database column that can expand
+ * beyond 60 characters (255 characters would be a good choice).
+ *
+ *
+ *
+ *
+ * PASSWORD_BCRYPT - Use the CRYPT_BLOWFISH algorithm to
+ * create the hash. This will produce a standard crypt compatible hash using
+ * the "$2y$" identifier. The result will always be a 60 character string.
+ *
+ *
+ *
+ *
+ * PASSWORD_ARGON2I - Use the Argon2i hashing algorithm to create the hash.
+ * This algorithm is only available if PHP has been compiled with Argon2 support.
+ *
+ *
+ *
+ *
+ * PASSWORD_ARGON2ID - Use the Argon2id hashing algorithm to create the hash.
+ * This algorithm is only available if PHP has been compiled with Argon2 support.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * salt (string) - to manually provide a salt to use when hashing the password.
+ * Note that this will override and prevent a salt from being automatically generated.
+ *
+ *
+ * If omitted, a random salt will be generated by password_hash for
+ * each password hashed. This is the intended mode of operation.
+ *
+ *
+ *
+ * The salt option has been deprecated as of PHP 7.0.0. It is now
+ * preferred to simply use the salt that is generated by default.
+ *
+ *
+ *
+ *
+ *
+ * cost (integer) - which denotes the algorithmic cost that should be used.
+ * Examples of these values can be found on the crypt page.
+ *
+ *
+ * If omitted, a default value of 10 will be used. This is a good
+ * baseline cost, but you may want to consider increasing it depending on your hardware.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * memory_cost (integer) - Maximum memory (in kibibytes) that may
+ * be used to compute the Argon2 hash. Defaults to PASSWORD_ARGON2_DEFAULT_MEMORY_COST.
+ *
+ *
+ *
+ *
+ * time_cost (integer) - Maximum amount of time it may
+ * take to compute the Argon2 hash. Defaults to PASSWORD_ARGON2_DEFAULT_TIME_COST.
+ *
+ *
+ *
+ *
+ * threads (integer) - Number of threads to use for computing
+ * the Argon2 hash. Defaults to PASSWORD_ARGON2_DEFAULT_THREADS.
+ *
+ *
+ *
+ *
+ * @param string $password The user's password.
+ *
+ * Using the PASSWORD_BCRYPT as the
+ * algorithm, will result
+ * in the password parameter being truncated to a
+ * maximum length of 72 characters.
+ * @param int|string|null $algo A password algorithm constant denoting the algorithm to use when hashing the password.
+ * @param array $options An associative array containing options. See the password algorithm constants for documentation on the supported options for each algorithm.
+ *
+ * If omitted, a random salt will be created and the default cost will be
+ * used.
+ * @return string Returns the hashed password.
+ *
+ * The used algorithm, cost and salt are returned as part of the hash. Therefore,
+ * all information that's needed to verify the hash is included in it. This allows
+ * the password_verify function to verify the hash without
+ * needing separate storage for the salt or algorithm information.
+ * @throws PasswordException
+ *
+ */
+function password_hash(string $password, $algo, array $options = null): string
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \password_hash($password, $algo, $options);
+ } else {
+ $result = \password_hash($password, $algo);
+ }
+ if ($result === false) {
+ throw PasswordException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PcntlException;
+
+/**
+ * Executes the program with the given arguments.
+ *
+ * @param string $path path must be the path to a binary executable or a
+ * script with a valid path pointing to an executable in the shebang (
+ * #!/usr/local/bin/perl for example) as the first line. See your system's
+ * man execve(2) page for additional information.
+ * @param array $args args is an array of argument strings passed to the
+ * program.
+ * @param array $envs envs is an array of strings which are passed as
+ * environment to the program. The array is in the format of name => value,
+ * the key being the name of the environmental variable and the value being
+ * the value of that variable.
+ * @throws PcntlException
+ *
+ */
+function pcntl_exec(string $path, array $args = null, array $envs = null): void
+{
+ error_clear_last();
+ if ($envs !== null) {
+ $result = \pcntl_exec($path, $args, $envs);
+ } elseif ($args !== null) {
+ $result = \pcntl_exec($path, $args);
+ } else {
+ $result = \pcntl_exec($path);
+ }
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pcntl_getpriority gets the priority of
+ * pid. Because priority levels can differ between
+ * system types and kernel versions, please see your system's getpriority(2)
+ * man page for specific details.
+ *
+ * @param int $pid If not specified, the pid of the current process is used.
+ * @param int $process_identifier One of PRIO_PGRP, PRIO_USER
+ * or PRIO_PROCESS.
+ * @return int pcntl_getpriority returns the priority of the process. A lower numerical value causes more favorable
+ * scheduling.
+ * @throws PcntlException
+ *
+ */
+function pcntl_getpriority(int $pid = null, int $process_identifier = PRIO_PROCESS): int
+{
+ error_clear_last();
+ if ($process_identifier !== PRIO_PROCESS) {
+ $result = \pcntl_getpriority($pid, $process_identifier);
+ } elseif ($pid !== null) {
+ $result = \pcntl_getpriority($pid);
+ } else {
+ $result = \pcntl_getpriority();
+ }
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pcntl_setpriority sets the priority of
+ * pid.
+ *
+ * @param int $priority priority is generally a value in the range
+ * -20 to 20. The default priority
+ * is 0 while a lower numerical value causes more
+ * favorable scheduling. Because priority levels can differ between
+ * system types and kernel versions, please see your system's setpriority(2)
+ * man page for specific details.
+ * @param int $pid If not specified, the pid of the current process is used.
+ * @param int $process_identifier One of PRIO_PGRP, PRIO_USER
+ * or PRIO_PROCESS.
+ * @throws PcntlException
+ *
+ */
+function pcntl_setpriority(int $priority, int $pid = null, int $process_identifier = PRIO_PROCESS): void
+{
+ error_clear_last();
+ if ($process_identifier !== PRIO_PROCESS) {
+ $result = \pcntl_setpriority($priority, $pid, $process_identifier);
+ } elseif ($pid !== null) {
+ $result = \pcntl_setpriority($priority, $pid);
+ } else {
+ $result = \pcntl_setpriority($priority);
+ }
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The pcntl_signal_dispatch function calls the signal
+ * handlers installed by pcntl_signal for each pending
+ * signal.
+ *
+ * @throws PcntlException
+ *
+ */
+function pcntl_signal_dispatch(): void
+{
+ error_clear_last();
+ $result = \pcntl_signal_dispatch();
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The pcntl_sigprocmask function adds, removes or sets blocked
+ * signals, depending on the how parameter.
+ *
+ * @param int $how Sets the behavior of pcntl_sigprocmask. Possible
+ * values:
+ *
+ * SIG_BLOCK: Add the signals to the
+ * currently blocked signals.
+ * SIG_UNBLOCK: Remove the signals from the
+ * currently blocked signals.
+ * SIG_SETMASK: Replace the currently
+ * blocked signals by the given list of signals.
+ *
+ * @param array $set List of signals.
+ * @param array|null $oldset The oldset parameter is set to an array
+ * containing the list of the previously blocked signals.
+ * @throws PcntlException
+ *
+ */
+function pcntl_sigprocmask(int $how, array $set, ?array &$oldset = null): void
+{
+ error_clear_last();
+ $result = \pcntl_sigprocmask($how, $set, $oldset);
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $errno
+ * @return string Returns error description on success.
+ * @throws PcntlException
+ *
+ */
+function pcntl_strerror(int $errno): string
+{
+ error_clear_last();
+ $result = \pcntl_strerror($errno);
+ if ($result === false) {
+ throw PcntlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PcreException;
+
+/**
+ * Searches subject for all matches to the regular
+ * expression given in pattern and puts them in
+ * matches in the order specified by
+ * flags.
+ *
+ * After the first match is found, the subsequent searches are continued
+ * on from end of the last match.
+ *
+ * @param string $pattern The pattern to search for, as a string.
+ * @param string $subject The input string.
+ * @param array $matches Array of all matches in multi-dimensional array ordered according to
+ * flags.
+ * @param int $flags Can be a combination of the following flags (note that it doesn't make
+ * sense to use PREG_PATTERN_ORDER together with
+ * PREG_SET_ORDER):
+ *
+ *
+ * PREG_PATTERN_ORDER
+ *
+ *
+ * Orders results so that $matches[0] is an array of full
+ * pattern matches, $matches[1] is an array of strings matched by
+ * the first parenthesized subpattern, and so on.
+ *
+ *
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * example: , this is a test
+ * example: , this is a test
+ * ]]>
+ *
+ *
+ * So, $out[0] contains array of strings that matched full pattern,
+ * and $out[1] contains array of strings enclosed by tags.
+ *
+ *
+ *
+ *
+ * If the pattern contains named subpatterns, $matches
+ * additionally contains entries for keys with the subpattern name.
+ *
+ *
+ * If the pattern contains duplicate named subpatterns, only the rightmost
+ * subpattern is stored in $matches[NAME].
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ *
+ * [1] => bar
+ * )
+ * ]]>
+ *
+ *
+ *
+ *
+ *
+ *
+ * PREG_SET_ORDER
+ *
+ *
+ * Orders results so that $matches[0] is an array of first set
+ * of matches, $matches[1] is an array of second set of matches,
+ * and so on.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * example: , example:
+ * this is a test, this is a test
+ * ]]>
+ *
+ *
+ *
+ *
+ *
+ *
+ * PREG_OFFSET_CAPTURE
+ *
+ *
+ * If this flag is passed, for every occurring match the appendant string
+ * offset (in bytes) will also be returned. Note that this changes the value of
+ * matches into an array of arrays where every element is an
+ * array consisting of the matched string at offset 0
+ * and its string offset into subject at offset
+ * 1.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => foobarbaz
+ * [1] => 0
+ * )
+ *
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => foo
+ * [1] => 0
+ * )
+ *
+ * )
+ *
+ * [2] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => bar
+ * [1] => 3
+ * )
+ *
+ * )
+ *
+ * [3] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => baz
+ * [1] => 6
+ * )
+ *
+ * )
+ *
+ * )
+ * ]]>
+ *
+ *
+ *
+ *
+ *
+ *
+ * PREG_UNMATCHED_AS_NULL
+ *
+ *
+ * If this flag is passed, unmatched subpatterns are reported as NULL;
+ * otherwise they are reported as an empty string.
+ *
+ *
+ *
+ *
+ *
+ * Orders results so that $matches[0] is an array of full
+ * pattern matches, $matches[1] is an array of strings matched by
+ * the first parenthesized subpattern, and so on.
+ *
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * example: , this is a test
+ * example: , this is a test
+ * ]]>
+ *
+ *
+ * So, $out[0] contains array of strings that matched full pattern,
+ * and $out[1] contains array of strings enclosed by tags.
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * So, $out[0] contains array of strings that matched full pattern,
+ * and $out[1] contains array of strings enclosed by tags.
+ *
+ * If the pattern contains named subpatterns, $matches
+ * additionally contains entries for keys with the subpattern name.
+ *
+ * If the pattern contains duplicate named subpatterns, only the rightmost
+ * subpattern is stored in $matches[NAME].
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ *
+ * [1] => bar
+ * )
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * Orders results so that $matches[0] is an array of first set
+ * of matches, $matches[1] is an array of second set of matches,
+ * and so on.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * example: , example:
+ * this is a test, this is a test
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * If this flag is passed, for every occurring match the appendant string
+ * offset (in bytes) will also be returned. Note that this changes the value of
+ * matches into an array of arrays where every element is an
+ * array consisting of the matched string at offset 0
+ * and its string offset into subject at offset
+ * 1.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => foobarbaz
+ * [1] => 0
+ * )
+ *
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => foo
+ * [1] => 0
+ * )
+ *
+ * )
+ *
+ * [2] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => bar
+ * [1] => 3
+ * )
+ *
+ * )
+ *
+ * [3] => Array
+ * (
+ * [0] => Array
+ * (
+ * [0] => baz
+ * [1] => 6
+ * )
+ *
+ * )
+ *
+ * )
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * If this flag is passed, unmatched subpatterns are reported as NULL;
+ * otherwise they are reported as an empty string.
+ *
+ * If no order flag is given, PREG_PATTERN_ORDER is
+ * assumed.
+ * @param int $offset Orders results so that $matches[0] is an array of full
+ * pattern matches, $matches[1] is an array of strings matched by
+ * the first parenthesized subpattern, and so on.
+ *
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * example: , this is a test
+ * example: , this is a test
+ * ]]>
+ *
+ *
+ * So, $out[0] contains array of strings that matched full pattern,
+ * and $out[1] contains array of strings enclosed by tags.
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * So, $out[0] contains array of strings that matched full pattern,
+ * and $out[1] contains array of strings enclosed by tags.
+ *
+ * If the pattern contains named subpatterns, $matches
+ * additionally contains entries for keys with the subpattern name.
+ *
+ * If the pattern contains duplicate named subpatterns, only the rightmost
+ * subpattern is stored in $matches[NAME].
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ *
+ * [1] => bar
+ * )
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ * @return int Returns the number of full pattern matches (which might be zero).
+ * @throws PcreException
+ *
+ */
+function preg_match_all(string $pattern, string $subject, array &$matches = null, int $flags = PREG_PATTERN_ORDER, int $offset = 0): int
+{
+ error_clear_last();
+ $result = \preg_match_all($pattern, $subject, $matches, $flags, $offset);
+ if ($result === false) {
+ throw PcreException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Searches subject for a match to the regular
+ * expression given in pattern.
+ *
+ * @param string $pattern The pattern to search for, as a string.
+ * @param string $subject The input string.
+ * @param array $matches If matches is provided, then it is filled with
+ * the results of search. $matches[0] will contain the
+ * text that matched the full pattern, $matches[1]
+ * will have the text that matched the first captured parenthesized
+ * subpattern, and so on.
+ * @param int $flags flags can be a combination of the following flags:
+ *
+ *
+ * PREG_OFFSET_CAPTURE
+ *
+ *
+ * If this flag is passed, for every occurring match the appendant string
+ * offset (in bytes) will also be returned. Note that this changes the value of
+ * matches into an array where every element is an
+ * array consisting of the matched string at offset 0
+ * and its string offset into subject at offset
+ * 1.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * Array
+ * (
+ * [0] => foobarbaz
+ * [1] => 0
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => foo
+ * [1] => 0
+ * )
+ *
+ * [2] => Array
+ * (
+ * [0] => bar
+ * [1] => 3
+ * )
+ *
+ * [3] => Array
+ * (
+ * [0] => baz
+ * [1] => 6
+ * )
+ *
+ * )
+ * ]]>
+ *
+ *
+ *
+ *
+ *
+ *
+ * PREG_UNMATCHED_AS_NULL
+ *
+ *
+ * If this flag is passed, unmatched subpatterns are reported as NULL;
+ * otherwise they are reported as an empty string.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ *
+ * string(2) "ac"
+ * [1]=>
+ * string(1) "a"
+ * [2]=>
+ * string(0) ""
+ * [3]=>
+ * string(1) "c"
+ * }
+ * array(4) {
+ * [0]=>
+ * string(2) "ac"
+ * [1]=>
+ * string(1) "a"
+ * [2]=>
+ * NULL
+ * [3]=>
+ * string(1) "c"
+ * }
+ * ]]>
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * If this flag is passed, for every occurring match the appendant string
+ * offset (in bytes) will also be returned. Note that this changes the value of
+ * matches into an array where every element is an
+ * array consisting of the matched string at offset 0
+ * and its string offset into subject at offset
+ * 1.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * Array
+ * (
+ * [0] => foobarbaz
+ * [1] => 0
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => foo
+ * [1] => 0
+ * )
+ *
+ * [2] => Array
+ * (
+ * [0] => bar
+ * [1] => 3
+ * )
+ *
+ * [3] => Array
+ * (
+ * [0] => baz
+ * [1] => 6
+ * )
+ *
+ * )
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ *
+ * If this flag is passed, unmatched subpatterns are reported as NULL;
+ * otherwise they are reported as an empty string.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ *
+ * string(2) "ac"
+ * [1]=>
+ * string(1) "a"
+ * [2]=>
+ * string(0) ""
+ * [3]=>
+ * string(1) "c"
+ * }
+ * array(4) {
+ * [0]=>
+ * string(2) "ac"
+ * [1]=>
+ * string(1) "a"
+ * [2]=>
+ * NULL
+ * [3]=>
+ * string(1) "c"
+ * }
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ * @param int $offset If this flag is passed, for every occurring match the appendant string
+ * offset (in bytes) will also be returned. Note that this changes the value of
+ * matches into an array where every element is an
+ * array consisting of the matched string at offset 0
+ * and its string offset into subject at offset
+ * 1.
+ *
+ *
+ *
+ * ]]>
+ *
+ * The above example will output:
+ *
+ * Array
+ * (
+ * [0] => foobarbaz
+ * [1] => 0
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => foo
+ * [1] => 0
+ * )
+ *
+ * [2] => Array
+ * (
+ * [0] => bar
+ * [1] => 3
+ * )
+ *
+ * [3] => Array
+ * (
+ * [0] => baz
+ * [1] => 6
+ * )
+ *
+ * )
+ * ]]>
+ *
+ *
+ *
+ * The above example will output:
+ * @return int preg_match returns 1 if the pattern
+ * matches given subject, 0 if it does not.
+ * @throws PcreException
+ *
+ */
+function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int
+{
+ error_clear_last();
+ $result = \preg_match($pattern, $subject, $matches, $flags, $offset);
+ if ($result === false) {
+ throw PcreException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Split the given string by a regular expression.
+ *
+ * @param string $pattern The pattern to search for, as a string.
+ * @param string $subject The input string.
+ * @param int|null $limit If specified, then only substrings up to limit
+ * are returned with the rest of the string being placed in the last
+ * substring. A limit of -1 or 0 means "no limit".
+ * @param int $flags flags can be any combination of the following
+ * flags (combined with the | bitwise operator):
+ *
+ *
+ * PREG_SPLIT_NO_EMPTY
+ *
+ *
+ * If this flag is set, only non-empty pieces will be returned by
+ * preg_split.
+ *
+ *
+ *
+ *
+ * PREG_SPLIT_DELIM_CAPTURE
+ *
+ *
+ * If this flag is set, parenthesized expression in the delimiter pattern
+ * will be captured and returned as well.
+ *
+ *
+ *
+ *
+ * PREG_SPLIT_OFFSET_CAPTURE
+ *
+ *
+ * If this flag is set, for every occurring match the appendant string
+ * offset will also be returned. Note that this changes the return
+ * value in an array where every element is an array consisting of the
+ * matched string at offset 0 and its string offset
+ * into subject at offset 1.
+ *
+ *
+ *
+ *
+ *
+ * If this flag is set, for every occurring match the appendant string
+ * offset will also be returned. Note that this changes the return
+ * value in an array where every element is an array consisting of the
+ * matched string at offset 0 and its string offset
+ * into subject at offset 1.
+ * @return array Returns an array containing substrings of subject
+ * split along boundaries matched by pattern.
+ * @throws PcreException
+ *
+ */
+function preg_split(string $pattern, string $subject, ?int $limit = -1, int $flags = 0): array
+{
+ error_clear_last();
+ $result = \preg_split($pattern, $subject, $limit, $flags);
+ if ($result === false) {
+ throw PcreException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PdfException;
+
+/**
+ * Activates a previously created structure element or other content item.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param int $id
+ * @throws PdfException
+ *
+ */
+function PDF_activate_item($pdfdoc, int $id): void
+{
+ error_clear_last();
+ $result = \PDF_activate_item($pdfdoc, $id);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Add a link annotation to a target within the current PDF file.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_create_action with type=GoTo
+ * and PDF_create_annotation with
+ * type=Link instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $lowerleftx
+ * @param float $lowerlefty
+ * @param float $upperrightx
+ * @param float $upperrighty
+ * @param int $page
+ * @param string $dest
+ * @throws PdfException
+ *
+ */
+function PDF_add_locallink($pdfdoc, float $lowerleftx, float $lowerlefty, float $upperrightx, float $upperrighty, int $page, string $dest): void
+{
+ error_clear_last();
+ $result = \PDF_add_locallink($pdfdoc, $lowerleftx, $lowerlefty, $upperrightx, $upperrighty, $page, $dest);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a named destination on an arbitrary page in the current document.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param string $name
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_add_nameddest($pdfdoc, string $name, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_add_nameddest($pdfdoc, $name, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets an annotation for the current page. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_create_annotation with
+ * type=Text instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $llx
+ * @param float $lly
+ * @param float $urx
+ * @param float $ury
+ * @param string $contents
+ * @param string $title
+ * @param string $icon
+ * @param int $open
+ * @throws PdfException
+ *
+ */
+function PDF_add_note($pdfdoc, float $llx, float $lly, float $urx, float $ury, string $contents, string $title, string $icon, int $open): void
+{
+ error_clear_last();
+ $result = \PDF_add_note($pdfdoc, $llx, $lly, $urx, $ury, $contents, $title, $icon, $open);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Add a file link annotation to a PDF target.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_create_action with
+ * type=GoToR and
+ * PDF_create_annotation with
+ * type=Link instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $bottom_left_x
+ * @param float $bottom_left_y
+ * @param float $up_right_x
+ * @param float $up_right_y
+ * @param string $filename
+ * @param int $page
+ * @param string $dest
+ * @throws PdfException
+ *
+ */
+function PDF_add_pdflink($pdfdoc, float $bottom_left_x, float $bottom_left_y, float $up_right_x, float $up_right_y, string $filename, int $page, string $dest): void
+{
+ error_clear_last();
+ $result = \PDF_add_pdflink($pdfdoc, $bottom_left_x, $bottom_left_y, $up_right_x, $up_right_y, $filename, $page, $dest);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds an existing image as thumbnail for the current page.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param int $image
+ * @throws PdfException
+ *
+ */
+function PDF_add_thumbnail($pdfdoc, int $image): void
+{
+ error_clear_last();
+ $result = \PDF_add_thumbnail($pdfdoc, $image);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a weblink annotation to a target url on the Web.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_create_action with type=URI
+ * and PDF_create_annotation with
+ * type=Link instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $lowerleftx
+ * @param float $lowerlefty
+ * @param float $upperrightx
+ * @param float $upperrighty
+ * @param string $url
+ * @throws PdfException
+ *
+ */
+function PDF_add_weblink($pdfdoc, float $lowerleftx, float $lowerlefty, float $upperrightx, float $upperrighty, string $url): void
+{
+ error_clear_last();
+ $result = \PDF_add_weblink($pdfdoc, $lowerleftx, $lowerlefty, $upperrightx, $upperrighty, $url);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a file attachment annotation. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_create_annotation with
+ * type=FileAttachment instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $llx
+ * @param float $lly
+ * @param float $urx
+ * @param float $ury
+ * @param string $filename
+ * @param string $description
+ * @param string $author
+ * @param string $mimetype
+ * @param string $icon
+ * @throws PdfException
+ *
+ */
+function PDF_attach_file($pdfdoc, float $llx, float $lly, float $urx, float $ury, string $filename, string $description, string $author, string $mimetype, string $icon): void
+{
+ error_clear_last();
+ $result = \PDF_attach_file($pdfdoc, $llx, $lly, $urx, $ury, $filename, $description, $author, $mimetype, $icon);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Starts a layer for subsequent output on the page. Returns TRUE on success.
+ *
+ * This function requires PDF 1.5.
+ *
+ * @param resource $pdfdoc
+ * @param int $layer
+ * @throws PdfException
+ *
+ */
+function PDF_begin_layer($pdfdoc, int $layer): void
+{
+ error_clear_last();
+ $result = \PDF_begin_layer($pdfdoc, $layer);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a new page to the document, and specifies various options.
+ * The parameters width and height
+ * are the dimensions of the new page in points. Returns TRUE on success.
+ *
+ *
+ * Common Page Sizes in Points
+ *
+ *
+ *
+ * name
+ * size
+ *
+ *
+ *
+ *
+ * A0
+ * 2380 x 3368
+ *
+ *
+ * A1
+ * 1684 x 2380
+ *
+ *
+ * A2
+ * 1190 x 1684
+ *
+ *
+ * A3
+ * 842 x 1190
+ *
+ *
+ * A4
+ * 595 x 842
+ *
+ *
+ * A5
+ * 421 x 595
+ *
+ *
+ * A6
+ * 297 x 421
+ *
+ *
+ * B5
+ * 501 x 709
+ *
+ *
+ * letter (8.5" x 11")
+ * 612 x 792
+ *
+ *
+ * legal (8.5" x 14")
+ * 612 x 1008
+ *
+ *
+ * ledger (17" x 11")
+ * 1224 x 792
+ *
+ *
+ * 11" x 17"
+ * 792 x 1224
+ *
+ *
+ *
+ *
+ *
+ * @param resource $pdfdoc
+ * @param float $width
+ * @param float $height
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_begin_page_ext($pdfdoc, float $width, float $height, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_begin_page_ext($pdfdoc, $width, $height, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a new page to the document. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_begin_page_ext instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $width
+ * @param float $height
+ * @throws PdfException
+ *
+ */
+function PDF_begin_page($pdfdoc, float $width, float $height): void
+{
+ error_clear_last();
+ $result = \PDF_begin_page($pdfdoc, $width, $height);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a circle. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param float $x
+ * @param float $y
+ * @param float $r
+ * @throws PdfException
+ *
+ */
+function PDF_circle($pdfdoc, float $x, float $y, float $r): void
+{
+ error_clear_last();
+ $result = \PDF_circle($pdfdoc, $x, $y, $r);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Uses the current path as clipping path, and terminate the path. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_clip($p): void
+{
+ error_clear_last();
+ $result = \PDF_clip($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the page handle, and frees all page-related resources. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param int $page
+ * @throws PdfException
+ *
+ */
+function PDF_close_pdi_page($p, int $page): void
+{
+ error_clear_last();
+ $result = \PDF_close_pdi_page($p, $page);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes all open page handles, and closes the input PDF document. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 7,
+ * use PDF_close_pdi_document instead.
+ *
+ * @param resource $p
+ * @param int $doc
+ * @throws PdfException
+ *
+ */
+function PDF_close_pdi($p, int $doc): void
+{
+ error_clear_last();
+ $result = \PDF_close_pdi($p, $doc);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the generated PDF file, and frees all document-related resources.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_end_document instead.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_close($p): void
+{
+ error_clear_last();
+ $result = \PDF_close($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the path, fills, and strokes it. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_closepath_fill_stroke($p): void
+{
+ error_clear_last();
+ $result = \PDF_closepath_fill_stroke($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the path, and strokes it. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_closepath_stroke($p): void
+{
+ error_clear_last();
+ $result = \PDF_closepath_stroke($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the current path. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_closepath($p): void
+{
+ error_clear_last();
+ $result = \PDF_closepath($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Concatenates a matrix to the current transformation matrix (CTM). Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $a
+ * @param float $b
+ * @param float $c
+ * @param float $d
+ * @param float $e
+ * @param float $f
+ * @throws PdfException
+ *
+ */
+function PDF_concat($p, float $a, float $b, float $c, float $d, float $e, float $f): void
+{
+ error_clear_last();
+ $result = \PDF_concat($p, $a, $b, $c, $d, $e, $f);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prints text at the next line. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $text
+ * @throws PdfException
+ *
+ */
+function PDF_continue_text($p, string $text): void
+{
+ error_clear_last();
+ $result = \PDF_continue_text($p, $text);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a Bezier curve from the current point, using 3 more control points.
+ * Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param float $x3
+ * @param float $y3
+ * @throws PdfException
+ *
+ */
+function PDF_curveto($p, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3): void
+{
+ error_clear_last();
+ $result = \PDF_curveto($p, $x1, $y1, $x2, $y2, $x3, $y3);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deletes a PDFlib object, and frees all internal resources. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @throws PdfException
+ *
+ */
+function PDF_delete($pdfdoc): void
+{
+ error_clear_last();
+ $result = \PDF_delete($pdfdoc);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deactivates all active layers. Returns TRUE on success.
+ *
+ * This function requires PDF 1.5.
+ *
+ * @param resource $pdfdoc
+ * @throws PdfException
+ *
+ */
+function PDF_end_layer($pdfdoc): void
+{
+ error_clear_last();
+ $result = \PDF_end_layer($pdfdoc);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Finishes a page, and applies various options. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_end_page_ext($pdfdoc, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_end_page_ext($pdfdoc, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Finishes the page. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_end_page($p): void
+{
+ error_clear_last();
+ $result = \PDF_end_page($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Finishes the pattern definition. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_end_pattern($p): void
+{
+ error_clear_last();
+ $result = \PDF_end_pattern($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Finishes a template definition. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_end_template($p): void
+{
+ error_clear_last();
+ $result = \PDF_end_template($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fills and strokes the current path with the current fill and stroke color.
+ * Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_fill_stroke($p): void
+{
+ error_clear_last();
+ $result = \PDF_fill_stroke($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fills the interior of the current path with the current fill color.
+ * Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_fill($p): void
+{
+ error_clear_last();
+ $result = \PDF_fill($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places an image or template on the page, subject to various options.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param int $image
+ * @param float $x
+ * @param float $y
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_fit_image($pdfdoc, int $image, float $x, float $y, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_fit_image($pdfdoc, $image, $x, $y, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places an imported PDF page on the page, subject to various options.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param int $page
+ * @param float $x
+ * @param float $y
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_fit_pdi_page($pdfdoc, int $page, float $x, float $y, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_fit_pdi_page($pdfdoc, $page, $x, $y, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a single line of text on the page, subject to various options. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param string $text
+ * @param float $x
+ * @param float $y
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_fit_textline($pdfdoc, string $text, float $x, float $y, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_fit_textline($pdfdoc, $text, $x, $y, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Reset all color and graphics state parameters to their defaults.
+ * Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_initgraphics($p): void
+{
+ error_clear_last();
+ $result = \PDF_initgraphics($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a line from the current point to another point. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $x
+ * @param float $y
+ * @throws PdfException
+ *
+ */
+function PDF_lineto($p, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \PDF_lineto($p, $x, $y);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Finds a built-in spot color name, or makes a named spot color from the
+ * current fill color. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $spotname
+ * @return int
+ * @throws PdfException
+ *
+ */
+function PDF_makespotcolor($p, string $spotname): int
+{
+ error_clear_last();
+ $result = \PDF_makespotcolor($p, $spotname);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the current point for graphics output. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $x
+ * @param float $y
+ * @throws PdfException
+ *
+ */
+function PDF_moveto($p, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \PDF_moveto($p, $x, $y);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a new PDF file using the supplied file name.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * PDF_begin_document instead.
+ *
+ * @param resource $p
+ * @param string $filename
+ * @throws PdfException
+ *
+ */
+function PDF_open_file($p, string $filename): void
+{
+ error_clear_last();
+ $result = \PDF_open_file($p, $filename);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places an image and scales it. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 5, use
+ * PDF_fit_image instead.
+ *
+ * @param resource $pdfdoc
+ * @param int $image
+ * @param float $x
+ * @param float $y
+ * @param float $scale
+ * @throws PdfException
+ *
+ */
+function PDF_place_image($pdfdoc, int $image, float $x, float $y, float $scale): void
+{
+ error_clear_last();
+ $result = \PDF_place_image($pdfdoc, $image, $x, $y, $scale);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a PDF page and scales it. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 5, use
+ * PDF_fit_pdi_page instead.
+ *
+ * @param resource $pdfdoc
+ * @param int $page
+ * @param float $x
+ * @param float $y
+ * @param float $sx
+ * @param float $sy
+ * @throws PdfException
+ *
+ */
+function PDF_place_pdi_page($pdfdoc, int $page, float $x, float $y, float $sx, float $sy): void
+{
+ error_clear_last();
+ $result = \PDF_place_pdi_page($pdfdoc, $page, $x, $y, $sx, $sy);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a rectangle. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $x
+ * @param float $y
+ * @param float $width
+ * @param float $height
+ * @throws PdfException
+ *
+ */
+function PDF_rect($p, float $x, float $y, float $width, float $height): void
+{
+ error_clear_last();
+ $result = \PDF_rect($p, $x, $y, $width, $height);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Restores the most recently saved graphics state. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_restore($p): void
+{
+ error_clear_last();
+ $result = \PDF_restore($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Rotates the coordinate system. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $phi
+ * @throws PdfException
+ *
+ */
+function PDF_rotate($p, float $phi): void
+{
+ error_clear_last();
+ $result = \PDF_rotate($p, $phi);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Saves the current graphics state. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_save($p): void
+{
+ error_clear_last();
+ $result = \PDF_save($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Scales the coordinate system. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $sx
+ * @param float $sy
+ * @throws PdfException
+ *
+ */
+function PDF_scale($p, float $sx, float $sy): void
+{
+ error_clear_last();
+ $result = \PDF_scale($p, $sx, $sy);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the border color for all kinds of annotations. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * the option annotcolor in
+ * PDF_create_annotation instead.
+ *
+ * @param resource $p
+ * @param float $red
+ * @param float $green
+ * @param float $blue
+ * @throws PdfException
+ *
+ */
+function PDF_set_border_color($p, float $red, float $green, float $blue): void
+{
+ error_clear_last();
+ $result = \PDF_set_border_color($p, $red, $green, $blue);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the border dash style for all kinds of annotations. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * the option dasharray in
+ * PDF_create_annotation instead.
+ *
+ * @param resource $pdfdoc
+ * @param float $black
+ * @param float $white
+ * @throws PdfException
+ *
+ */
+function PDF_set_border_dash($pdfdoc, float $black, float $white): void
+{
+ error_clear_last();
+ $result = \PDF_set_border_dash($pdfdoc, $black, $white);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the border style for all kinds of annotations. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 6, use
+ * the options borderstyle and
+ * linewidth in
+ * PDF_create_annotation instead.
+ *
+ * @param resource $pdfdoc
+ * @param string $style
+ * @param float $width
+ * @throws PdfException
+ *
+ */
+function PDF_set_border_style($pdfdoc, string $style, float $width): void
+{
+ error_clear_last();
+ $result = \PDF_set_border_style($pdfdoc, $style, $width);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fill document information field key with
+ * value. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $key
+ * @param string $value
+ * @throws PdfException
+ *
+ */
+function PDF_set_info($p, string $key, string $value): void
+{
+ error_clear_last();
+ $result = \PDF_set_info($p, $key, $value);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Defines hierarchical and group relationships among layers. Returns TRUE on success.
+ *
+ * This function requires PDF 1.5.
+ *
+ * @param resource $pdfdoc
+ * @param string $type
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_set_layer_dependency($pdfdoc, string $type, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_set_layer_dependency($pdfdoc, $type, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets some PDFlib parameter with string type. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $key
+ * @param string $value
+ * @throws PdfException
+ *
+ */
+function PDF_set_parameter($p, string $key, string $value): void
+{
+ error_clear_last();
+ $result = \PDF_set_parameter($p, $key, $value);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the position for text output on the page. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $x
+ * @param float $y
+ * @throws PdfException
+ *
+ */
+function PDF_set_text_pos($p, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \PDF_set_text_pos($p, $x, $y);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the value of some PDFlib parameter with numerical type. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $key
+ * @param float $value
+ * @throws PdfException
+ *
+ */
+function PDF_set_value($p, string $key, float $value): void
+{
+ error_clear_last();
+ $result = \PDF_set_value($p, $key, $value);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current color space and color. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $fstype
+ * @param string $colorspace
+ * @param float $c1
+ * @param float $c2
+ * @param float $c3
+ * @param float $c4
+ * @throws PdfException
+ *
+ */
+function PDF_setcolor($p, string $fstype, string $colorspace, float $c1, float $c2, float $c3, float $c4): void
+{
+ error_clear_last();
+ $result = \PDF_setcolor($p, $fstype, $colorspace, $c1, $c2, $c3, $c4);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current dash pattern to b black
+ * and w white units. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param float $b
+ * @param float $w
+ * @throws PdfException
+ *
+ */
+function PDF_setdash($pdfdoc, float $b, float $w): void
+{
+ error_clear_last();
+ $result = \PDF_setdash($pdfdoc, $b, $w);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets a dash pattern defined by an option list. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param string $optlist
+ * @throws PdfException
+ *
+ */
+function PDF_setdashpattern($pdfdoc, string $optlist): void
+{
+ error_clear_last();
+ $result = \PDF_setdashpattern($pdfdoc, $optlist);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the flatness parameter. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param float $flatness
+ * @throws PdfException
+ *
+ */
+function PDF_setflat($pdfdoc, float $flatness): void
+{
+ error_clear_last();
+ $result = \PDF_setflat($pdfdoc, $flatness);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current font in the specified fontsize, using a
+ * font handle returned by PDF_load_font.
+ * Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param int $font
+ * @param float $fontsize
+ * @throws PdfException
+ *
+ */
+function PDF_setfont($pdfdoc, int $font, float $fontsize): void
+{
+ error_clear_last();
+ $result = \PDF_setfont($pdfdoc, $font, $fontsize);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current fill color to a gray value between 0 and 1 inclusive.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $g
+ * @throws PdfException
+ *
+ */
+function PDF_setgray_fill($p, float $g): void
+{
+ error_clear_last();
+ $result = \PDF_setgray_fill($p, $g);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current stroke color to a gray value between 0 and 1 inclusive.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $g
+ * @throws PdfException
+ *
+ */
+function PDF_setgray_stroke($p, float $g): void
+{
+ error_clear_last();
+ $result = \PDF_setgray_stroke($p, $g);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current fill and stroke color to a gray value between 0 and 1 inclusive. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $g
+ * @throws PdfException
+ *
+ */
+function PDF_setgray($p, float $g): void
+{
+ error_clear_last();
+ $result = \PDF_setgray($p, $g);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the linejoin parameter to specify the shape
+ * at the corners of paths that are stroked. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param int $value
+ * @throws PdfException
+ *
+ */
+function PDF_setlinejoin($p, int $value): void
+{
+ error_clear_last();
+ $result = \PDF_setlinejoin($p, $value);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current line width. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $width
+ * @throws PdfException
+ *
+ */
+function PDF_setlinewidth($p, float $width): void
+{
+ error_clear_last();
+ $result = \PDF_setlinewidth($p, $width);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Explicitly sets the current transformation matrix. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $a
+ * @param float $b
+ * @param float $c
+ * @param float $d
+ * @param float $e
+ * @param float $f
+ * @throws PdfException
+ *
+ */
+function PDF_setmatrix($p, float $a, float $b, float $c, float $d, float $e, float $f): void
+{
+ error_clear_last();
+ $result = \PDF_setmatrix($p, $a, $b, $c, $d, $e, $f);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the miter limit.Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param float $miter
+ * @throws PdfException
+ *
+ */
+function PDF_setmiterlimit($pdfdoc, float $miter): void
+{
+ error_clear_last();
+ $result = \PDF_setmiterlimit($pdfdoc, $miter);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current fill color to the supplied RGB values. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $red
+ * @param float $green
+ * @param float $blue
+ * @throws PdfException
+ *
+ */
+function PDF_setrgbcolor_fill($p, float $red, float $green, float $blue): void
+{
+ error_clear_last();
+ $result = \PDF_setrgbcolor_fill($p, $red, $green, $blue);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current stroke color to the supplied RGB values. Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $red
+ * @param float $green
+ * @param float $blue
+ * @throws PdfException
+ *
+ */
+function PDF_setrgbcolor_stroke($p, float $red, float $green, float $blue): void
+{
+ error_clear_last();
+ $result = \PDF_setrgbcolor_stroke($p, $red, $green, $blue);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current fill and stroke color to the supplied RGB values.
+ * Returns TRUE on success.
+ *
+ * This function is deprecated since PDFlib version 4, use
+ * PDF_setcolor instead.
+ *
+ * @param resource $p
+ * @param float $red
+ * @param float $green
+ * @param float $blue
+ * @throws PdfException
+ *
+ */
+function PDF_setrgbcolor($p, float $red, float $green, float $blue): void
+{
+ error_clear_last();
+ $result = \PDF_setrgbcolor($p, $red, $green, $blue);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prints text in the current font. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param string $text
+ * @param float $x
+ * @param float $y
+ * @throws PdfException
+ *
+ */
+function PDF_show_xy($p, string $text, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \PDF_show_xy($p, $text, $x, $y);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prints text in the current font and size at
+ * the current position. Returns TRUE on success.
+ *
+ * @param resource $pdfdoc
+ * @param string $text
+ * @throws PdfException
+ *
+ */
+function PDF_show($pdfdoc, string $text): void
+{
+ error_clear_last();
+ $result = \PDF_show($pdfdoc, $text);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Skews the coordinate system in x and y direction by alpha
+ * and beta degrees, respectively. Returns TRUE on success.
+ *
+ * @param resource $p
+ * @param float $alpha
+ * @param float $beta
+ * @throws PdfException
+ *
+ */
+function PDF_skew($p, float $alpha, float $beta): void
+{
+ error_clear_last();
+ $result = \PDF_skew($p, $alpha, $beta);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Strokes the path with the current color and line width, and clear it.
+ * Returns TRUE on success.
+ *
+ * @param resource $p
+ * @throws PdfException
+ *
+ */
+function PDF_stroke($p): void
+{
+ error_clear_last();
+ $result = \PDF_stroke($p);
+ if ($result === false) {
+ throw PdfException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PgsqlException;
+
+/**
+ * pg_cancel_query cancels an asynchronous query sent with
+ * pg_send_query, pg_send_query_params
+ * or pg_send_execute. You cannot cancel a query executed using
+ * pg_query.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @throws PgsqlException
+ *
+ */
+function pg_cancel_query($connection): void
+{
+ error_clear_last();
+ $result = \pg_cancel_query($connection);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * PostgreSQL supports automatic character set conversion between
+ * server and client for certain character sets.
+ * pg_client_encoding returns the client
+ * encoding as a string. The returned string will be one of the
+ * standard PostgreSQL encoding identifiers.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string The client encoding.
+ * @throws PgsqlException
+ *
+ */
+function pg_client_encoding($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_client_encoding($connection);
+ } else {
+ $result = \pg_client_encoding();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_close closes the non-persistent
+ * connection to a PostgreSQL database associated with the given
+ * connection resource.
+ *
+ * If there is open large object resource on the connection, do not
+ * close the connection before closing all large object resources.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @throws PgsqlException
+ *
+ */
+function pg_close($connection = null): void
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_close($connection);
+ } else {
+ $result = \pg_close();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_connect opens a connection to a
+ * PostgreSQL database specified by the
+ * connection_string.
+ *
+ * If a second call is made to pg_connect with
+ * the same connection_string as an existing connection, the
+ * existing connection will be returned unless you pass
+ * PGSQL_CONNECT_FORCE_NEW as
+ * connect_type.
+ *
+ * The old syntax with multiple parameters
+ * $conn = pg_connect("host", "port", "options", "tty", "dbname")
+ * has been deprecated.
+ *
+ * @param string $connection_string The connection_string can be empty to use all default parameters, or it
+ * can contain one or more parameter settings separated by whitespace.
+ * Each parameter setting is in the form keyword = value. Spaces around
+ * the equal sign are optional. To write an empty value or a value
+ * containing spaces, surround it with single quotes, e.g., keyword =
+ * 'a value'. Single quotes and backslashes within the value must be
+ * escaped with a backslash, i.e., \' and \\.
+ *
+ * The currently recognized parameter keywords are:
+ * host, hostaddr, port,
+ * dbname (defaults to value of user),
+ * user,
+ * password, connect_timeout,
+ * options, tty (ignored), sslmode,
+ * requiressl (deprecated in favor of sslmode), and
+ * service. Which of these arguments exist depends
+ * on your PostgreSQL version.
+ *
+ * The options parameter can be used to set command line parameters
+ * to be invoked by the server.
+ * @param int $connect_type If PGSQL_CONNECT_FORCE_NEW is passed, then a new connection
+ * is created, even if the connection_string is identical to
+ * an existing connection.
+ *
+ * If PGSQL_CONNECT_ASYNC is given, then the
+ * connection is established asynchronously. The state of the connection
+ * can then be checked via pg_connect_poll or
+ * pg_connection_status.
+ * @return resource PostgreSQL connection resource on success, FALSE on failure.
+ * @throws PgsqlException
+ *
+ */
+function pg_connect(string $connection_string, int $connect_type = null)
+{
+ error_clear_last();
+ if ($connect_type !== null) {
+ $result = \pg_connect($connection_string, $connect_type);
+ } else {
+ $result = \pg_connect($connection_string);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_connection_reset resets the connection.
+ * It is useful for error recovery.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @throws PgsqlException
+ *
+ */
+function pg_connection_reset($connection): void
+{
+ error_clear_last();
+ $result = \pg_connection_reset($connection);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_convert checks and converts the values in
+ * assoc_array into suitable values for use in an SQL
+ * statement. Precondition for pg_convert is the
+ * existence of a table table_name which has at least
+ * as many columns as assoc_array has elements. The
+ * fieldnames in table_name must match the indices in
+ * assoc_array and the corresponding datatypes must be
+ * compatible. Returns an array with the converted values on success, FALSE
+ * otherwise.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table against which to convert types.
+ * @param array $assoc_array Data to be converted.
+ * @param int $options Any number of PGSQL_CONV_IGNORE_DEFAULT,
+ * PGSQL_CONV_FORCE_NULL or
+ * PGSQL_CONV_IGNORE_NOT_NULL, combined.
+ * @return array An array of converted values.
+ * @throws PgsqlException
+ *
+ */
+function pg_convert($connection, string $table_name, array $assoc_array, int $options = 0): array
+{
+ error_clear_last();
+ $result = \pg_convert($connection, $table_name, $assoc_array, $options);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_copy_from inserts records into a table from
+ * rows. It issues a COPY FROM SQL command
+ * internally to insert records.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table into which to copy the rows.
+ * @param array $rows An array of data to be copied into table_name.
+ * Each value in rows becomes a row in table_name.
+ * Each value in rows should be a delimited string of the values
+ * to insert into each field. Values should be linefeed terminated.
+ * @param string $delimiter The token that separates values for each field in each element of
+ * rows. Default is TAB.
+ * @param string $null_as How SQL NULL values are represented in the
+ * rows. Default is \N ("\\N").
+ * @throws PgsqlException
+ *
+ */
+function pg_copy_from($connection, string $table_name, array $rows, string $delimiter = null, string $null_as = null): void
+{
+ error_clear_last();
+ if ($null_as !== null) {
+ $result = \pg_copy_from($connection, $table_name, $rows, $delimiter, $null_as);
+ } elseif ($delimiter !== null) {
+ $result = \pg_copy_from($connection, $table_name, $rows, $delimiter);
+ } else {
+ $result = \pg_copy_from($connection, $table_name, $rows);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_copy_to copies a table to an array. It
+ * issues COPY TO SQL command internally to
+ * retrieve records.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table from which to copy the data into rows.
+ * @param string $delimiter The token that separates values for each field in each element of
+ * rows. Default is TAB.
+ * @param string $null_as How SQL NULL values are represented in the
+ * rows. Default is \N ("\\N").
+ * @return array An array with one element for each line of COPY data.
+ * It returns FALSE on failure.
+ * @throws PgsqlException
+ *
+ */
+function pg_copy_to($connection, string $table_name, string $delimiter = null, string $null_as = null): array
+{
+ error_clear_last();
+ if ($null_as !== null) {
+ $result = \pg_copy_to($connection, $table_name, $delimiter, $null_as);
+ } elseif ($delimiter !== null) {
+ $result = \pg_copy_to($connection, $table_name, $delimiter);
+ } else {
+ $result = \pg_copy_to($connection, $table_name);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_dbname returns the name of the database
+ * that the given PostgreSQL connection
+ * resource.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string A string containing the name of the database the
+ * connection is to.
+ * @throws PgsqlException
+ *
+ */
+function pg_dbname($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_dbname($connection);
+ } else {
+ $result = \pg_dbname();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_delete deletes records from a table
+ * specified by the keys and values
+ * in assoc_array. If options
+ * is specified, pg_convert is applied
+ * to assoc_array with the specified options.
+ *
+ * If options is specified,
+ * pg_convert is applied to
+ * assoc_array with the specified flags.
+ *
+ * By default pg_delete passes raw values. Values
+ * must be escaped or PGSQL_DML_ESCAPE option must be
+ * specified. PGSQL_DML_ESCAPE quotes and escapes
+ * parameters/identifiers. Therefore, table/column names became case
+ * sensitive.
+ *
+ * Note that neither escape nor prepared query can protect LIKE query,
+ * JSON, Array, Regex, etc. These parameters should be handled
+ * according to their contexts. i.e. Escape/validate values.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table from which to delete rows.
+ * @param array $assoc_array An array whose keys are field names in the table table_name,
+ * and whose values are the values of those fields that are to be deleted.
+ * @param int $options Any number of PGSQL_CONV_FORCE_NULL,
+ * PGSQL_DML_NO_CONV,
+ * PGSQL_DML_ESCAPE,
+ * PGSQL_DML_EXEC,
+ * PGSQL_DML_ASYNC or
+ * PGSQL_DML_STRING combined. If PGSQL_DML_STRING is part of the
+ * options then query string is returned. When PGSQL_DML_NO_CONV
+ * or PGSQL_DML_ESCAPE is set, it does not call pg_convert internally.
+ * @return mixed Returns TRUE on success. Returns string if PGSQL_DML_STRING is passed
+ * via options.
+ * @throws PgsqlException
+ *
+ */
+function pg_delete($connection, string $table_name, array $assoc_array, int $options = PGSQL_DML_EXEC)
+{
+ error_clear_last();
+ $result = \pg_delete($connection, $table_name, $assoc_array, $options);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_end_copy syncs the PostgreSQL frontend
+ * (usually a web server process) with the PostgreSQL server after
+ * doing a copy operation performed by
+ * pg_put_line. pg_end_copy
+ * must be issued, otherwise the PostgreSQL server may get out of
+ * sync with the frontend and will report an error.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @throws PgsqlException
+ *
+ */
+function pg_end_copy($connection = null): void
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_end_copy($connection);
+ } else {
+ $result = \pg_end_copy();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sends a request to execute a prepared statement with given parameters, and
+ * waits for the result.
+ *
+ * pg_execute is like pg_query_params,
+ * but the command to be executed is
+ * specified by naming a previously-prepared statement, instead of giving a
+ * query string. This feature allows commands that will be used repeatedly to
+ * be parsed and planned just once, rather than each time they are executed.
+ * The statement must have been prepared previously in the current session.
+ * pg_execute is supported only against PostgreSQL 7.4 or
+ * higher connections; it will fail when using earlier versions.
+ *
+ * The parameters are identical to pg_query_params, except that the name of a
+ * prepared statement is given instead of a query string.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $stmtname The name of the prepared statement to execute. if
+ * "" is specified, then the unnamed statement is executed. The name must have
+ * been previously prepared using pg_prepare,
+ * pg_send_prepare or a PREPARE SQL
+ * command.
+ * @param array $params An array of parameter values to substitute for the $1, $2, etc. placeholders
+ * in the original prepared query string. The number of elements in the array
+ * must match the number of placeholders.
+ *
+ * Elements are converted to strings by calling this function.
+ * @return resource A query result resource on success.
+ * @throws PgsqlException
+ *
+ */
+function pg_execute($connection = null, string $stmtname = null, array $params = null)
+{
+ error_clear_last();
+ if ($params !== null) {
+ $result = \pg_execute($connection, $stmtname, $params);
+ } elseif ($stmtname !== null) {
+ $result = \pg_execute($connection, $stmtname);
+ } elseif ($connection !== null) {
+ $result = \pg_execute($connection);
+ } else {
+ $result = \pg_execute();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_field_name returns the name of the field
+ * occupying the given field_number in the
+ * given PostgreSQL result resource. Field
+ * numbering starts from 0.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @param int $field_number Field number, starting from 0.
+ * @return string The field name.
+ * @throws PgsqlException
+ *
+ */
+function pg_field_name($result, int $field_number): string
+{
+ error_clear_last();
+ $result = \pg_field_name($result, $field_number);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_field_table returns the name of the table that field
+ * belongs to, or the table's oid if oid_only is TRUE.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @param int $field_number Field number, starting from 0.
+ * @param bool $oid_only By default the tables name that field belongs to is returned but
+ * if oid_only is set to TRUE, then the
+ * oid will instead be returned.
+ * @return mixed On success either the fields table name or oid. Or, FALSE on failure.
+ * @throws PgsqlException
+ *
+ */
+function pg_field_table($result, int $field_number, bool $oid_only = false)
+{
+ error_clear_last();
+ $result = \pg_field_table($result, $field_number, $oid_only);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_field_type returns a string containing the
+ * base type name of the given field_number in the
+ * given PostgreSQL result resource.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @param int $field_number Field number, starting from 0.
+ * @return string A string containing the base name of the field's type.
+ * @throws PgsqlException
+ *
+ */
+function pg_field_type($result, int $field_number): string
+{
+ error_clear_last();
+ $result = \pg_field_type($result, $field_number);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_flush flushes any outbound query data waiting to be
+ * sent on the connection.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @return mixed Returns TRUE if the flush was successful or no data was waiting to be
+ * flushed, 0 if part of the pending data was flushed but
+ * more remains.
+ * @throws PgsqlException
+ *
+ */
+function pg_flush($connection)
+{
+ error_clear_last();
+ $result = \pg_flush($connection);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_free_result frees the memory and data associated with the
+ * specified PostgreSQL query result resource.
+ *
+ * This function need only be called if memory
+ * consumption during script execution is a problem. Otherwise, all result memory will
+ * be automatically freed when the script ends.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @throws PgsqlException
+ *
+ */
+function pg_free_result($result): void
+{
+ error_clear_last();
+ $result = \pg_free_result($result);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_host returns the host name of the given
+ * PostgreSQL connection resource is
+ * connected to.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string A string containing the name of the host the
+ * connection is to.
+ * @throws PgsqlException
+ *
+ */
+function pg_host($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_host($connection);
+ } else {
+ $result = \pg_host();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_insert inserts the values
+ * of assoc_array into the table specified
+ * by table_name. If options
+ * is specified, pg_convert is applied
+ * to assoc_array with the specified options.
+ *
+ * If options is specified,
+ * pg_convert is applied to
+ * assoc_array with the specified flags.
+ *
+ * By default pg_insert passes raw values. Values
+ * must be escaped or PGSQL_DML_ESCAPE option must be
+ * specified. PGSQL_DML_ESCAPE quotes and escapes
+ * parameters/identifiers. Therefore, table/column names became case
+ * sensitive.
+ *
+ * Note that neither escape nor prepared query can protect LIKE query,
+ * JSON, Array, Regex, etc. These parameters should be handled
+ * according to their contexts. i.e. Escape/validate values.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table into which to insert rows. The table table_name must at least
+ * have as many columns as assoc_array has elements.
+ * @param array $assoc_array An array whose keys are field names in the table table_name,
+ * and whose values are the values of those fields that are to be inserted.
+ * @param int $options Any number of PGSQL_CONV_OPTS,
+ * PGSQL_DML_NO_CONV,
+ * PGSQL_DML_ESCAPE,
+ * PGSQL_DML_EXEC,
+ * PGSQL_DML_ASYNC or
+ * PGSQL_DML_STRING combined. If PGSQL_DML_STRING is part of the
+ * options then query string is returned. When PGSQL_DML_NO_CONV
+ * or PGSQL_DML_ESCAPE is set, it does not call pg_convert internally.
+ * @return mixed Returns the connection resource on success. Returns string if PGSQL_DML_STRING is passed
+ * via options.
+ * @throws PgsqlException
+ *
+ */
+function pg_insert($connection, string $table_name, array $assoc_array, int $options = PGSQL_DML_EXEC)
+{
+ error_clear_last();
+ $result = \pg_insert($connection, $table_name, $assoc_array, $options);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_last_error returns the last error message
+ * for a given connection.
+ *
+ * Error messages may be overwritten by internal PostgreSQL (libpq)
+ * function calls. It may not return an appropriate error message if
+ * multiple errors occur inside a PostgreSQL module function.
+ *
+ * Use pg_result_error, pg_result_error_field,
+ * pg_result_status and
+ * pg_connection_status for better error handling.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string A string containing the last error message on the
+ * given connection.
+ * @throws PgsqlException
+ *
+ */
+function pg_last_error($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_last_error($connection);
+ } else {
+ $result = \pg_last_error();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_last_notice returns the last notice
+ * message from the PostgreSQL server on the specified
+ * connection. The PostgreSQL server sends notice
+ * messages in several cases, for instance when creating a SERIAL
+ * column in a table.
+ *
+ * With pg_last_notice, you can avoid issuing useless
+ * queries by checking whether or not the notice is related to your transaction.
+ *
+ * Notice message tracking can be set to optional by setting 1 for
+ * pgsql.ignore_notice in php.ini.
+ *
+ * Notice message logging can be set to optional by setting 0 for
+ * pgsql.log_notice in php.ini.
+ * Unless pgsql.ignore_notice is set
+ * to 0, notice message cannot be logged.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param int $option One of PGSQL_NOTICE_LAST (to return last notice),
+ * PGSQL_NOTICE_ALL (to return all notices),
+ * or PGSQL_NOTICE_CLEAR (to clear notices).
+ * @return string A string containing the last notice on the
+ * given connection with
+ * PGSQL_NOTICE_LAST,
+ * an array with PGSQL_NOTICE_ALL,
+ * a boolean with PGSQL_NOTICE_CLEAR.
+ * @throws PgsqlException
+ *
+ */
+function pg_last_notice($connection, int $option = PGSQL_NOTICE_LAST): string
+{
+ error_clear_last();
+ $result = \pg_last_notice($connection, $option);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_last_oid is used to retrieve the
+ * OID assigned to an inserted row.
+ *
+ * OID field became an optional field from PostgreSQL 7.2 and will
+ * not be present by default in PostgreSQL 8.1. When the
+ * OID field is not present in a table, the programmer must use
+ * pg_result_status to check for successful
+ * insertion.
+ *
+ * To get the value of a SERIAL field in an inserted
+ * row, it is necessary to use the PostgreSQL CURRVAL
+ * function, naming the sequence whose last value is required. If the
+ * name of the sequence is unknown, the pg_get_serial_sequence
+ * PostgreSQL 8.0 function is necessary.
+ *
+ * PostgreSQL 8.1 has a function LASTVAL that returns
+ * the value of the most recently used sequence in the session. This avoids
+ * the need for naming the sequence, table or column altogether.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @return string A string containing the OID assigned to the most recently inserted
+ * row in the specified connection or
+ * no available OID.
+ * @throws PgsqlException
+ *
+ */
+function pg_last_oid($result): string
+{
+ error_clear_last();
+ $result = \pg_last_oid($result);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_lo_close closes a large
+ * object. large_object is a resource for the
+ * large object from pg_lo_open.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_close($large_object): void
+{
+ error_clear_last();
+ $result = \pg_lo_close($large_object);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_lo_export takes a large object in a
+ * PostgreSQL database and saves its contents to a file on the local
+ * filesystem.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param int $oid The OID of the large object in the database.
+ * @param string $pathname The full path and file name of the file in which to write the
+ * large object on the client filesystem.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_export($connection = null, int $oid = null, string $pathname = null): void
+{
+ error_clear_last();
+ if ($pathname !== null) {
+ $result = \pg_lo_export($connection, $oid, $pathname);
+ } elseif ($oid !== null) {
+ $result = \pg_lo_export($connection, $oid);
+ } elseif ($connection !== null) {
+ $result = \pg_lo_export($connection);
+ } else {
+ $result = \pg_lo_export();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_lo_import creates a new large object
+ * in the database using a file on the filesystem as its data
+ * source.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $pathname The full path and file name of the file on the client
+ * filesystem from which to read the large object data.
+ * @param mixed $object_id If an object_id is given the function
+ * will try to create a large object with this id, else a free
+ * object id is assigned by the server. The parameter
+ * was added in PHP 5.3 and relies on functionality that first
+ * appeared in PostgreSQL 8.1.
+ * @return int The OID of the newly created large object.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_import($connection = null, string $pathname = null, $object_id = null): int
+{
+ error_clear_last();
+ if ($object_id !== null) {
+ $result = \pg_lo_import($connection, $pathname, $object_id);
+ } elseif ($pathname !== null) {
+ $result = \pg_lo_import($connection, $pathname);
+ } elseif ($connection !== null) {
+ $result = \pg_lo_import($connection);
+ } else {
+ $result = \pg_lo_import();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_lo_open opens a large object in the database
+ * and returns large object resource so that it can be manipulated.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param int $oid The OID of the large object in the database.
+ * @param string $mode Can be either "r" for read-only, "w" for write only or "rw" for read and
+ * write.
+ * @return resource A large object resource.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_open($connection, int $oid, string $mode)
+{
+ error_clear_last();
+ $result = \pg_lo_open($connection, $oid, $mode);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_lo_read_all reads a large object and passes
+ * it straight through to the browser after sending all pending
+ * headers. Mainly intended for sending binary data like images or
+ * sound.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @return int Number of bytes read.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_read_all($large_object): int
+{
+ error_clear_last();
+ $result = \pg_lo_read_all($large_object);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_lo_read reads at most
+ * len bytes from a large object and
+ * returns it as a string.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @param int $len An optional maximum number of bytes to return.
+ * @return string A string containing len bytes from the
+ * large object.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_read($large_object, int $len = 8192): string
+{
+ error_clear_last();
+ $result = \pg_lo_read($large_object, $len);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_lo_seek seeks a position within a large object
+ * resource.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @param int $offset The number of bytes to seek.
+ * @param int $whence One of the constants PGSQL_SEEK_SET (seek from object start),
+ * PGSQL_SEEK_CUR (seek from current position)
+ * or PGSQL_SEEK_END (seek from object end) .
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_seek($large_object, int $offset, int $whence = PGSQL_SEEK_CUR): void
+{
+ error_clear_last();
+ $result = \pg_lo_seek($large_object, $offset, $whence);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_lo_truncate truncates a large object
+ * resource.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @param int $size The number of bytes to truncate.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_truncate($large_object, int $size): void
+{
+ error_clear_last();
+ $result = \pg_lo_truncate($large_object, $size);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_lo_unlink deletes a large object with the
+ * oid. Returns TRUE on success.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param int $oid The OID of the large object in the database.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_unlink($connection, int $oid): void
+{
+ error_clear_last();
+ $result = \pg_lo_unlink($connection, $oid);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_lo_write writes data into a large object
+ * at the current seek position.
+ *
+ * To use the large object interface, it is necessary to
+ * enclose it within a transaction block.
+ *
+ * @param resource $large_object PostgreSQL large object (LOB) resource, returned by pg_lo_open.
+ * @param string $data The data to be written to the large object. If len is
+ * specified and is less than the length of data, only
+ * len bytes will be written.
+ * @param int $len An optional maximum number of bytes to write. Must be greater than zero
+ * and no greater than the length of data. Defaults to
+ * the length of data.
+ * @return int The number of bytes written to the large object.
+ * @throws PgsqlException
+ *
+ */
+function pg_lo_write($large_object, string $data, int $len = null): int
+{
+ error_clear_last();
+ if ($len !== null) {
+ $result = \pg_lo_write($large_object, $data, $len);
+ } else {
+ $result = \pg_lo_write($large_object, $data);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_meta_data returns table definition for
+ * table_name as an array.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name The name of the table.
+ * @param bool $extended Flag for returning extended meta data. Default to FALSE.
+ * @return array An array of the table definition.
+ * @throws PgsqlException
+ *
+ */
+function pg_meta_data($connection, string $table_name, bool $extended = false): array
+{
+ error_clear_last();
+ $result = \pg_meta_data($connection, $table_name, $extended);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_options will return a string containing
+ * the options specified on the given PostgreSQL
+ * connection resource.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string A string containing the connection
+ * options.
+ * @throws PgsqlException
+ *
+ */
+function pg_options($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_options($connection);
+ } else {
+ $result = \pg_options();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Looks up a current parameter setting of the server.
+ *
+ * Certain parameter values are reported by the server automatically at
+ * connection startup or whenever their values change. pg_parameter_status can be
+ * used to interrogate these settings. It returns the current value of a
+ * parameter if known, or FALSE if the parameter is not known.
+ *
+ * Parameters reported as of PostgreSQL 8.0 include server_version,
+ * server_encoding, client_encoding,
+ * is_superuser, session_authorization,
+ * DateStyle, TimeZone, and integer_datetimes.
+ * (server_encoding, TimeZone, and
+ * integer_datetimes were not reported by releases before 8.0.) Note that
+ * server_version, server_encoding and integer_datetimes
+ * cannot change after PostgreSQL startup.
+ *
+ * PostgreSQL 7.3 or lower servers do not report parameter settings,
+ * pg_parameter_status
+ * includes logic to obtain values for server_version and
+ * client_encoding
+ * anyway. Applications are encouraged to use pg_parameter_status rather than ad
+ * hoc code to determine these values.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $param_name Possible param_name values include server_version,
+ * server_encoding, client_encoding,
+ * is_superuser, session_authorization,
+ * DateStyle, TimeZone, and
+ * integer_datetimes. Note that this value is case-sensitive.
+ * @return string A string containing the value of the parameter, FALSE on failure or invalid
+ * param_name.
+ * @throws PgsqlException
+ *
+ */
+function pg_parameter_status($connection = null, string $param_name = null): string
+{
+ error_clear_last();
+ if ($param_name !== null) {
+ $result = \pg_parameter_status($connection, $param_name);
+ } elseif ($connection !== null) {
+ $result = \pg_parameter_status($connection);
+ } else {
+ $result = \pg_parameter_status();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_pconnect opens a connection to a
+ * PostgreSQL database. It returns a connection resource that is
+ * needed by other PostgreSQL functions.
+ *
+ * If a second call is made to pg_pconnect with
+ * the same connection_string as an existing connection, the
+ * existing connection will be returned unless you pass
+ * PGSQL_CONNECT_FORCE_NEW as
+ * connect_type.
+ *
+ * To enable persistent connection, the pgsql.allow_persistent
+ * php.ini directive must be set to "On" (which is the default).
+ * The maximum number of persistent connection can be defined with the pgsql.max_persistent
+ * php.ini directive (defaults to -1 for no limit). The total number
+ * of connections can be set with the pgsql.max_links
+ * php.ini directive.
+ *
+ * pg_close will not close persistent links
+ * generated by pg_pconnect.
+ *
+ * @param string $connection_string The connection_string can be empty to use all default parameters, or it
+ * can contain one or more parameter settings separated by whitespace.
+ * Each parameter setting is in the form keyword = value. Spaces around
+ * the equal sign are optional. To write an empty value or a value
+ * containing spaces, surround it with single quotes, e.g., keyword =
+ * 'a value'. Single quotes and backslashes within the value must be
+ * escaped with a backslash, i.e., \' and \\.
+ *
+ * The currently recognized parameter keywords are:
+ * host, hostaddr, port,
+ * dbname, user,
+ * password, connect_timeout,
+ * options, tty (ignored), sslmode,
+ * requiressl (deprecated in favor of sslmode), and
+ * service. Which of these arguments exist depends
+ * on your PostgreSQL version.
+ * @param int $connect_type If PGSQL_CONNECT_FORCE_NEW is passed, then a new connection
+ * is created, even if the connection_string is identical to
+ * an existing connection.
+ * @return resource PostgreSQL connection resource on success, FALSE on failure.
+ * @throws PgsqlException
+ *
+ */
+function pg_pconnect(string $connection_string, int $connect_type = null)
+{
+ error_clear_last();
+ if ($connect_type !== null) {
+ $result = \pg_pconnect($connection_string, $connect_type);
+ } else {
+ $result = \pg_pconnect($connection_string);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_ping pings a database connection and tries to
+ * reconnect it if it is broken.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @throws PgsqlException
+ *
+ */
+function pg_ping($connection = null): void
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_ping($connection);
+ } else {
+ $result = \pg_ping();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_port returns the port number that the
+ * given PostgreSQL connection resource is
+ * connected to.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return int An int containing the port number of the database
+ * server the connection is to.
+ * @throws PgsqlException
+ *
+ */
+function pg_port($connection = null): int
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_port($connection);
+ } else {
+ $result = \pg_port();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_prepare creates a prepared statement for later execution with
+ * pg_execute or pg_send_execute.
+ * This feature allows commands that will be used repeatedly to
+ * be parsed and planned just once, rather than each time they are executed.
+ * pg_prepare is supported only against PostgreSQL 7.4 or
+ * higher connections; it will fail when using earlier versions.
+ *
+ * The function creates a prepared statement named stmtname from the query
+ * string, which must contain a single SQL command. stmtname may be "" to
+ * create an unnamed statement, in which case any pre-existing unnamed
+ * statement is automatically replaced; otherwise it is an error if the
+ * statement name is already defined in the current session. If any parameters
+ * are used, they are referred to in the query as $1, $2, etc.
+ *
+ * Prepared statements for use with pg_prepare can also be created by
+ * executing SQL PREPARE statements. (But pg_prepare is more flexible since it
+ * does not require parameter types to be pre-specified.) Also, although there
+ * is no PHP function for deleting a prepared statement, the SQL DEALLOCATE
+ * statement can be used for that purpose.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $stmtname The name to give the prepared statement. Must be unique per-connection. If
+ * "" is specified, then an unnamed statement is created, overwriting any
+ * previously defined unnamed statement.
+ * @param string $query The parameterized SQL statement. Must contain only a single statement.
+ * (multiple statements separated by semi-colons are not allowed.) If any parameters
+ * are used, they are referred to as $1, $2, etc.
+ * @return resource A query result resource on success.
+ * @throws PgsqlException
+ *
+ */
+function pg_prepare($connection = null, string $stmtname = null, string $query = null)
+{
+ error_clear_last();
+ if ($query !== null) {
+ $result = \pg_prepare($connection, $stmtname, $query);
+ } elseif ($stmtname !== null) {
+ $result = \pg_prepare($connection, $stmtname);
+ } elseif ($connection !== null) {
+ $result = \pg_prepare($connection);
+ } else {
+ $result = \pg_prepare();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_put_line sends a NULL-terminated string
+ * to the PostgreSQL backend server. This is needed in conjunction
+ * with PostgreSQL's COPY FROM command.
+ *
+ * COPY is a high-speed data loading interface
+ * supported by PostgreSQL. Data is passed in without being parsed,
+ * and in a single transaction.
+ *
+ * An alternative to using raw pg_put_line commands
+ * is to use pg_copy_from. This is a far simpler
+ * interface.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $data A line of text to be sent directly to the PostgreSQL backend. A NULL
+ * terminator is added automatically.
+ * @throws PgsqlException
+ *
+ */
+function pg_put_line($connection = null, string $data = null): void
+{
+ error_clear_last();
+ if ($data !== null) {
+ $result = \pg_put_line($connection, $data);
+ } elseif ($connection !== null) {
+ $result = \pg_put_line($connection);
+ } else {
+ $result = \pg_put_line();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Submits a command to the server and waits for the result, with the ability
+ * to pass parameters separately from the SQL command text.
+ *
+ * pg_query_params is like pg_query,
+ * but offers additional functionality: parameter
+ * values can be specified separately from the command string proper.
+ * pg_query_params is supported only against PostgreSQL 7.4 or
+ * higher connections; it will fail when using earlier versions.
+ *
+ * If parameters are used, they are referred to in the
+ * query string as $1, $2, etc. The same parameter may
+ * appear more than once in the query; the same value
+ * will be used in that case. params specifies the
+ * actual values of the parameters. A NULL value in this array means the
+ * corresponding parameter is SQL NULL.
+ *
+ * The primary advantage of pg_query_params over pg_query
+ * is that parameter values
+ * may be separated from the query string, thus avoiding the need for tedious
+ * and error-prone quoting and escaping. Unlike pg_query,
+ * pg_query_params allows at
+ * most one SQL command in the given string. (There can be semicolons in it,
+ * but not more than one nonempty command.)
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $query The parameterized SQL statement. Must contain only a single statement.
+ * (multiple statements separated by semi-colons are not allowed.) If any parameters
+ * are used, they are referred to as $1, $2, etc.
+ *
+ * User-supplied values should always be passed as parameters, not
+ * interpolated into the query string, where they form possible
+ * SQL injection
+ * attack vectors and introduce bugs when handling data containing quotes.
+ * If for some reason you cannot use a parameter, ensure that interpolated
+ * values are properly escaped.
+ * @param array $params An array of parameter values to substitute for the $1, $2, etc. placeholders
+ * in the original prepared query string. The number of elements in the array
+ * must match the number of placeholders.
+ *
+ * Values intended for bytea fields are not supported as
+ * parameters. Use pg_escape_bytea instead, or use the
+ * large object functions.
+ * @return resource A query result resource on success.
+ * @throws PgsqlException
+ *
+ */
+function pg_query_params($connection = null, string $query = null, array $params = null)
+{
+ error_clear_last();
+ if ($params !== null) {
+ $result = \pg_query_params($connection, $query, $params);
+ } elseif ($query !== null) {
+ $result = \pg_query_params($connection, $query);
+ } elseif ($connection !== null) {
+ $result = \pg_query_params($connection);
+ } else {
+ $result = \pg_query_params();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_query executes the query
+ * on the specified database connection.
+ * pg_query_params should be preferred
+ * in most cases.
+ *
+ * If an error occurs, and FALSE is returned, details of the error can
+ * be retrieved using the pg_last_error
+ * function if the connection is valid.
+ *
+ *
+ *
+ * Although connection can be omitted, it
+ * is not recommended, since it can be the cause of hard to find
+ * bugs in scripts.
+ *
+ *
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $query The SQL statement or statements to be executed. When multiple statements are passed to the function,
+ * they are automatically executed as one transaction, unless there are explicit BEGIN/COMMIT commands
+ * included in the query string. However, using multiple transactions in one function call is not recommended.
+ *
+ * String interpolation of user-supplied data is extremely dangerous and is
+ * likely to lead to SQL
+ * injection vulnerabilities. In most cases
+ * pg_query_params should be preferred, passing
+ * user-supplied values as parameters rather than substituting them into
+ * the query string.
+ *
+ * Any user-supplied data substituted directly into a query string should
+ * be properly escaped.
+ * @return resource A query result resource on success.
+ * @throws PgsqlException
+ *
+ */
+function pg_query($connection = null, string $query = null)
+{
+ error_clear_last();
+ if ($query !== null) {
+ $result = \pg_query($connection, $query);
+ } elseif ($connection !== null) {
+ $result = \pg_query($connection);
+ } else {
+ $result = \pg_query();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_result_error_field returns one of the detailed error message
+ * fields associated with result resource. It is only available
+ * against a PostgreSQL 7.4 or above server. The error field is specified by
+ * the fieldcode.
+ *
+ * Because pg_query and pg_query_params return FALSE if the query fails,
+ * you must use pg_send_query and
+ * pg_get_result to get the result handle.
+ *
+ * If you need to get additional error information from failed pg_query queries,
+ * use pg_set_error_verbosity and pg_last_error
+ * and then parse the result.
+ *
+ * @param resource $result A PostgreSQL query result resource from a previously executed
+ * statement.
+ * @param int $fieldcode Possible fieldcode values are: PGSQL_DIAG_SEVERITY,
+ * PGSQL_DIAG_SQLSTATE, PGSQL_DIAG_MESSAGE_PRIMARY,
+ * PGSQL_DIAG_MESSAGE_DETAIL,
+ * PGSQL_DIAG_MESSAGE_HINT, PGSQL_DIAG_STATEMENT_POSITION,
+ * PGSQL_DIAG_INTERNAL_POSITION (PostgreSQL 8.0+ only),
+ * PGSQL_DIAG_INTERNAL_QUERY (PostgreSQL 8.0+ only),
+ * PGSQL_DIAG_CONTEXT, PGSQL_DIAG_SOURCE_FILE,
+ * PGSQL_DIAG_SOURCE_LINE or
+ * PGSQL_DIAG_SOURCE_FUNCTION.
+ * @return string|null A string containing the contents of the error field, NULL if the field does not exist.
+ * @throws PgsqlException
+ *
+ */
+function pg_result_error_field($result, int $fieldcode): ?string
+{
+ error_clear_last();
+ $result = \pg_result_error_field($result, $fieldcode);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_result_seek sets the internal row offset in
+ * a result resource.
+ *
+ * @param resource $result PostgreSQL query result resource, returned by pg_query,
+ * pg_query_params or pg_execute
+ * (among others).
+ * @param int $offset Row to move the internal offset to in the result resource.
+ * Rows are numbered starting from zero.
+ * @throws PgsqlException
+ *
+ */
+function pg_result_seek($result, int $offset): void
+{
+ error_clear_last();
+ $result = \pg_result_seek($result, $offset);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_select selects records specified by
+ * assoc_array which has
+ * field=>value. For a successful query, it returns an
+ * array containing all records and fields that match the condition
+ * specified by assoc_array.
+ *
+ * If options is specified,
+ * pg_convert is applied to
+ * assoc_array with the specified flags.
+ *
+ * By default pg_select passes raw values. Values
+ * must be escaped or PGSQL_DML_ESCAPE option must be
+ * specified. PGSQL_DML_ESCAPE quotes and escapes
+ * parameters/identifiers. Therefore, table/column names became case
+ * sensitive.
+ *
+ * Note that neither escape nor prepared query can protect LIKE query,
+ * JSON, Array, Regex, etc. These parameters should be handled
+ * according to their contexts. i.e. Escape/validate values.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table from which to select rows.
+ * @param array $assoc_array An array whose keys are field names in the table table_name,
+ * and whose values are the conditions that a row must meet to be retrieved.
+ * @param int $options Any number of PGSQL_CONV_FORCE_NULL,
+ * PGSQL_DML_NO_CONV,
+ * PGSQL_DML_ESCAPE,
+ * PGSQL_DML_EXEC,
+ * PGSQL_DML_ASYNC or
+ * PGSQL_DML_STRING combined. If PGSQL_DML_STRING is part of the
+ * options then query string is returned. When PGSQL_DML_NO_CONV
+ * or PGSQL_DML_ESCAPE is set, it does not call pg_convert internally.
+ * @param int $result_type
+ * @return mixed Returns TRUE on success. Returns string if PGSQL_DML_STRING is passed
+ * via options.
+ * @throws PgsqlException
+ *
+ */
+function pg_select($connection, string $table_name, array $assoc_array, int $options = PGSQL_DML_EXEC, int $result_type = PGSQL_ASSOC)
+{
+ error_clear_last();
+ $result = \pg_select($connection, $table_name, $assoc_array, $options, $result_type);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sends a request to execute a prepared statement with given parameters,
+ * without waiting for the result(s).
+ *
+ * This is similar to pg_send_query_params, but the command to be executed is specified
+ * by naming a previously-prepared statement, instead of giving a query string. The
+ * function's parameters are handled identically to pg_execute.
+ * Like pg_execute, it will not work on pre-7.4 versions of
+ * PostgreSQL.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $stmtname The name of the prepared statement to execute. if
+ * "" is specified, then the unnamed statement is executed. The name must have
+ * been previously prepared using pg_prepare,
+ * pg_send_prepare or a PREPARE SQL
+ * command.
+ * @param array $params An array of parameter values to substitute for the $1, $2, etc. placeholders
+ * in the original prepared query string. The number of elements in the array
+ * must match the number of placeholders.
+ * @throws PgsqlException
+ *
+ */
+function pg_send_execute($connection, string $stmtname, array $params): void
+{
+ error_clear_last();
+ $result = \pg_send_execute($connection, $stmtname, $params);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sends a request to create a prepared statement with the given parameters,
+ * without waiting for completion.
+ *
+ * This is an asynchronous version of pg_prepare: it returns TRUE if it was able to
+ * dispatch the request, and FALSE if not. After a successful call, call
+ * pg_get_result to determine whether the server successfully created the
+ * prepared statement. The function's parameters are handled identically to
+ * pg_prepare. Like pg_prepare, it will not work
+ * on pre-7.4 versions of PostgreSQL.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @param string $stmtname The name to give the prepared statement. Must be unique per-connection. If
+ * "" is specified, then an unnamed statement is created, overwriting any
+ * previously defined unnamed statement.
+ * @param string $query The parameterized SQL statement. Must contain only a single statement.
+ * (multiple statements separated by semi-colons are not allowed.) If any parameters
+ * are used, they are referred to as $1, $2, etc.
+ * @throws PgsqlException
+ *
+ */
+function pg_send_prepare($connection, string $stmtname, string $query): void
+{
+ error_clear_last();
+ $result = \pg_send_prepare($connection, $stmtname, $query);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Submits a command and separate parameters to the server without
+ * waiting for the result(s).
+ *
+ * This is equivalent to pg_send_query except that query
+ * parameters can be specified separately from the
+ * query string. The function's parameters are
+ * handled identically to pg_query_params. Like
+ * pg_query_params, it will not work on pre-7.4 PostgreSQL
+ * connections, and it allows only one command in the query string.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $query The parameterized SQL statement. Must contain only a single statement.
+ * (multiple statements separated by semi-colons are not allowed.) If any parameters
+ * are used, they are referred to as $1, $2, etc.
+ * @param array $params An array of parameter values to substitute for the $1, $2, etc. placeholders
+ * in the original prepared query string. The number of elements in the array
+ * must match the number of placeholders.
+ * @throws PgsqlException
+ *
+ */
+function pg_send_query_params($connection, string $query, array $params): void
+{
+ error_clear_last();
+ $result = \pg_send_query_params($connection, $query, $params);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_send_query sends a query or queries asynchronously to the
+ * connection. Unlike
+ * pg_query, it can send multiple queries at once to
+ * PostgreSQL and get the results one by one using
+ * pg_get_result.
+ *
+ * Script execution is not blocked while the queries are executing. Use
+ * pg_connection_busy to check if the connection is
+ * busy (i.e. the query is executing). Queries may be cancelled using
+ * pg_cancel_query.
+ *
+ * Although the user can send multiple queries at once, multiple queries
+ * cannot be sent over a busy connection. If a query is sent while
+ * the connection is busy, it waits until the last query is finished and
+ * discards all its results.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $query The SQL statement or statements to be executed.
+ *
+ * Data inside the query should be properly escaped.
+ * @throws PgsqlException
+ *
+ */
+function pg_send_query($connection, string $query): void
+{
+ error_clear_last();
+ $result = \pg_send_query($connection, $query);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_socket returns a read only resource
+ * corresponding to the socket underlying the given PostgreSQL connection.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @return resource A socket resource on success.
+ * @throws PgsqlException
+ *
+ */
+function pg_socket($connection)
+{
+ error_clear_last();
+ $result = \pg_socket($connection);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_trace enables tracing of the PostgreSQL
+ * frontend/backend communication to a file. To fully understand the results,
+ * one needs to be familiar with the internals of PostgreSQL
+ * communication protocol.
+ *
+ * For those who are not, it can still be
+ * useful for tracing errors in queries sent to the server, you
+ * could do for example grep '^To backend'
+ * trace.log and see what queries actually were sent to the
+ * PostgreSQL server. For more information, refer to the
+ * PostgreSQL Documentation.
+ *
+ * @param string $pathname The full path and file name of the file in which to write the
+ * trace log. Same as in fopen.
+ * @param string $mode An optional file access mode, same as for fopen.
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @throws PgsqlException
+ *
+ */
+function pg_trace(string $pathname, string $mode = "w", $connection = null): void
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_trace($pathname, $mode, $connection);
+ } else {
+ $result = \pg_trace($pathname, $mode);
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pg_tty returns the TTY name that server
+ * side debugging output is sent to on the given PostgreSQL
+ * connection resource.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return string A string containing the debug TTY of
+ * the connection.
+ * @throws PgsqlException
+ *
+ */
+function pg_tty($connection = null): string
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_tty($connection);
+ } else {
+ $result = \pg_tty();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_update updates records that matches
+ * condition with data. If
+ * options is specified,
+ * pg_convert is applied to
+ * data with specified options.
+ *
+ * pg_update updates records specified by
+ * assoc_array which has
+ * field=>value.
+ *
+ * If options is specified,
+ * pg_convert is applied to
+ * assoc_array with the specified flags.
+ *
+ * By default pg_update passes raw values. Values
+ * must be escaped or PGSQL_DML_ESCAPE option must be
+ * specified. PGSQL_DML_ESCAPE quotes and escapes
+ * parameters/identifiers. Therefore, table/column names became case
+ * sensitive.
+ *
+ * Note that neither escape nor prepared query can protect LIKE query,
+ * JSON, Array, Regex, etc. These parameters should be handled
+ * according to their contexts. i.e. Escape/validate values.
+ *
+ * @param resource $connection PostgreSQL database connection resource.
+ * @param string $table_name Name of the table into which to update rows.
+ * @param array $data An array whose keys are field names in the table table_name,
+ * and whose values are what matched rows are to be updated to.
+ * @param array $condition An array whose keys are field names in the table table_name,
+ * and whose values are the conditions that a row must meet to be updated.
+ * @param int $options Any number of PGSQL_CONV_FORCE_NULL,
+ * PGSQL_DML_NO_CONV,
+ * PGSQL_DML_ESCAPE,
+ * PGSQL_DML_EXEC,
+ * PGSQL_DML_ASYNC or
+ * PGSQL_DML_STRING combined. If PGSQL_DML_STRING is part of the
+ * options then query string is returned. When PGSQL_DML_NO_CONV
+ * or PGSQL_DML_ESCAPE is set, it does not call pg_convert internally.
+ * @return mixed Returns TRUE on success. Returns string if PGSQL_DML_STRING is passed
+ * via options.
+ * @throws PgsqlException
+ *
+ */
+function pg_update($connection, string $table_name, array $data, array $condition, int $options = PGSQL_DML_EXEC)
+{
+ error_clear_last();
+ $result = \pg_update($connection, $table_name, $data, $condition, $options);
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pg_version returns an array with the client, protocol
+ * and server version. Protocol and server versions are only available if PHP
+ * was compiled with PostgreSQL 7.4 or later.
+ *
+ * For more detailed server information, use pg_parameter_status.
+ *
+ * @param resource $connection PostgreSQL database connection resource. When
+ * connection is not present, the default connection
+ * is used. The default connection is the last connection made by
+ * pg_connect or pg_pconnect.
+ * @return array Returns an array with client, protocol
+ * and server keys and values (if available) or invalid connection.
+ * @throws PgsqlException
+ *
+ */
+function pg_version($connection = null): array
+{
+ error_clear_last();
+ if ($connection !== null) {
+ $result = \pg_version($connection);
+ } else {
+ $result = \pg_version();
+ }
+ if ($result === false) {
+ throw PgsqlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PosixException;
+
+/**
+ * posix_access checks the user's permission of a file.
+ *
+ * @param string $file The name of the file to be tested.
+ * @param int $mode A mask consisting of one or more of POSIX_F_OK,
+ * POSIX_R_OK, POSIX_W_OK and
+ * POSIX_X_OK.
+ *
+ * POSIX_R_OK, POSIX_W_OK and
+ * POSIX_X_OK request checking whether the file
+ * exists and has read, write and execute permissions, respectively.
+ * POSIX_F_OK just requests checking for the
+ * existence of the file.
+ * @throws PosixException
+ *
+ */
+function posix_access(string $file, int $mode = POSIX_F_OK): void
+{
+ error_clear_last();
+ $result = \posix_access($file, $mode);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets information about a group provided its name.
+ *
+ * @param string $name The name of the group
+ * @return array Returns an array on success.
+ * The array elements returned are:
+ *
+ * The group information array
+ *
+ *
+ *
+ * Element
+ * Description
+ *
+ *
+ *
+ *
+ * name
+ *
+ * The name element contains the name of the group. This is
+ * a short, usually less than 16 character "handle" of the
+ * group, not the real, full name. This should be the same as
+ * the name parameter used when
+ * calling the function, and hence redundant.
+ *
+ *
+ *
+ * passwd
+ *
+ * The passwd element contains the group's password in an
+ * encrypted format. Often, for example on a system employing
+ * "shadow" passwords, an asterisk is returned instead.
+ *
+ *
+ *
+ * gid
+ *
+ * Group ID of the group in numeric form.
+ *
+ *
+ *
+ * members
+ *
+ * This consists of an array of
+ * string's for all the members in the group.
+ *
+ *
+ *
+ *
+ *
+ * @throws PosixException
+ *
+ */
+function posix_getgrnam(string $name): array
+{
+ error_clear_last();
+ $result = \posix_getgrnam($name);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the process group identifier of the process
+ * pid.
+ *
+ * @param int $pid The process id.
+ * @return int Returns the identifier, as an integer.
+ * @throws PosixException
+ *
+ */
+function posix_getpgid(int $pid): int
+{
+ error_clear_last();
+ $result = \posix_getpgid($pid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Calculates the group access list for the user specified in name.
+ *
+ * @param string $name The user to calculate the list for.
+ * @param int $base_group_id Typically the group number from the password file.
+ * @throws PosixException
+ *
+ */
+function posix_initgroups(string $name, int $base_group_id): void
+{
+ error_clear_last();
+ $result = \posix_initgroups($name, $base_group_id);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Send the signal sig to the process with
+ * the process identifier pid.
+ *
+ * @param int $pid The process identifier.
+ * @param int $sig One of the PCNTL signals constants.
+ * @throws PosixException
+ *
+ */
+function posix_kill(int $pid, int $sig): void
+{
+ error_clear_last();
+ $result = \posix_kill($pid, $sig);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * posix_mkfifo creates a special
+ * FIFO file which exists in the file system and acts as
+ * a bidirectional communication endpoint for processes.
+ *
+ * @param string $pathname Path to the FIFO file.
+ * @param int $mode The second parameter mode has to be given in
+ * octal notation (e.g. 0644). The permission of the newly created
+ * FIFO also depends on the setting of the current
+ * umask. The permissions of the created file are
+ * (mode & ~umask).
+ * @throws PosixException
+ *
+ */
+function posix_mkfifo(string $pathname, int $mode): void
+{
+ error_clear_last();
+ $result = \posix_mkfifo($pathname, $mode);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a special or ordinary file.
+ *
+ * @param string $pathname The file to create
+ * @param int $mode This parameter is constructed by a bitwise OR between file type (one of
+ * the following constants: POSIX_S_IFREG,
+ * POSIX_S_IFCHR, POSIX_S_IFBLK,
+ * POSIX_S_IFIFO or
+ * POSIX_S_IFSOCK) and permissions.
+ * @param int $major The major device kernel identifier (required to pass when using
+ * S_IFCHR or S_IFBLK).
+ * @param int $minor The minor device kernel identifier.
+ * @throws PosixException
+ *
+ */
+function posix_mknod(string $pathname, int $mode, int $major = 0, int $minor = 0): void
+{
+ error_clear_last();
+ $result = \posix_mknod($pathname, $mode, $major, $minor);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the effective group ID of the current process. This is a
+ * privileged function and needs appropriate privileges (usually
+ * root) on the system to be able to perform this function.
+ *
+ * @param int $gid The group id.
+ * @throws PosixException
+ *
+ */
+function posix_setegid(int $gid): void
+{
+ error_clear_last();
+ $result = \posix_setegid($gid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the effective user ID of the current process. This is a privileged
+ * function and needs appropriate privileges (usually root) on
+ * the system to be able to perform this function.
+ *
+ * @param int $uid The user id.
+ * @throws PosixException
+ *
+ */
+function posix_seteuid(int $uid): void
+{
+ error_clear_last();
+ $result = \posix_seteuid($uid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the real group ID of the current process. This is a
+ * privileged function and needs appropriate privileges (usually
+ * root) on the system to be able to perform this function. The
+ * appropriate order of function calls is
+ * posix_setgid first,
+ * posix_setuid last.
+ *
+ * @param int $gid The group id.
+ * @throws PosixException
+ *
+ */
+function posix_setgid(int $gid): void
+{
+ error_clear_last();
+ $result = \posix_setgid($gid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Let the process pid join the process group
+ * pgid.
+ *
+ * @param int $pid The process id.
+ * @param int $pgid The process group id.
+ * @throws PosixException
+ *
+ */
+function posix_setpgid(int $pid, int $pgid): void
+{
+ error_clear_last();
+ $result = \posix_setpgid($pid, $pgid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * posix_setrlimit sets the soft and hard limits for a
+ * given system resource.
+ *
+ *
+ * Each resource has an associated soft and hard limit. The soft
+ * limit is the value that the kernel enforces for the corresponding
+ * resource. The hard limit acts as a ceiling for the soft limit.
+ * An unprivileged process may only set its soft limit to a value
+ * from 0 to the hard limit, and irreversibly lower its hard limit.
+ *
+ * @param int $resource The
+ * resource limit constant
+ * corresponding to the limit that is being set.
+ * @param int $softlimit The soft limit, in whatever unit the resource limit requires, or
+ * POSIX_RLIMIT_INFINITY.
+ * @param int $hardlimit The hard limit, in whatever unit the resource limit requires, or
+ * POSIX_RLIMIT_INFINITY.
+ * @throws PosixException
+ *
+ */
+function posix_setrlimit(int $resource, int $softlimit, int $hardlimit): void
+{
+ error_clear_last();
+ $result = \posix_setrlimit($resource, $softlimit, $hardlimit);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the real user ID of the current process. This is a privileged
+ * function that needs appropriate privileges (usually root) on
+ * the system to be able to perform this function.
+ *
+ * @param int $uid The user id.
+ * @throws PosixException
+ *
+ */
+function posix_setuid(int $uid): void
+{
+ error_clear_last();
+ $result = \posix_setuid($uid);
+ if ($result === false) {
+ throw PosixException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PsException;
+
+/**
+ * Places a hyperlink at the given position pointing to a file program
+ * which is being started when clicked on. The hyperlink's source position
+ * is a rectangle
+ * with its lower left corner at (llx, lly) and its upper right corner at
+ * (urx, ury). The rectangle has by default a thin blue border.
+ *
+ * The note will not be visible if the document
+ * is printed or viewed but it will show up if the document is converted to
+ * pdf by either Acrobat Distiller™ or Ghostview.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $llx The x-coordinate of the lower left corner.
+ * @param float $lly The y-coordinate of the lower left corner.
+ * @param float $urx The x-coordinate of the upper right corner.
+ * @param float $ury The y-coordinate of the upper right corner.
+ * @param string $filename The path of the program to be started, when the link is clicked on.
+ * @throws PsException
+ *
+ */
+function ps_add_launchlink($psdoc, float $llx, float $lly, float $urx, float $ury, string $filename): void
+{
+ error_clear_last();
+ $result = \ps_add_launchlink($psdoc, $llx, $lly, $urx, $ury, $filename);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a hyperlink at the given position pointing to a page in the same
+ * document. Clicking on the link will jump to the given page. The first page
+ * in a document has number 1.
+ *
+ * The hyperlink's source position is a rectangle with its lower left corner at
+ * (llx, lly) and its upper
+ * right corner at (urx, ury).
+ * The rectangle has by default a thin blue border.
+ *
+ * The note will not be visible if the document
+ * is printed or viewed but it will show up if the document is converted to
+ * pdf by either Acrobat Distiller™ or Ghostview.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $llx The x-coordinate of the lower left corner.
+ * @param float $lly The y-coordinate of the lower left corner.
+ * @param float $urx The x-coordinate of the upper right corner.
+ * @param float $ury The y-coordinate of the upper right corner.
+ * @param int $page The number of the page displayed when clicking on the link.
+ * @param string $dest The parameter dest determines how the document
+ * is being viewed. It can be fitpage,
+ * fitwidth, fitheight, or
+ * fitbbox.
+ * @throws PsException
+ *
+ */
+function ps_add_locallink($psdoc, float $llx, float $lly, float $urx, float $ury, int $page, string $dest): void
+{
+ error_clear_last();
+ $result = \ps_add_locallink($psdoc, $llx, $lly, $urx, $ury, $page, $dest);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a note at a certain position on the page. Notes are like little
+ * rectangular sheets with text on it, which can be placed anywhere on
+ * a page. They
+ * are shown either folded or unfolded. If folded, the specified icon
+ * is used as a placeholder.
+ *
+ * The note will not be visible if the document
+ * is printed or viewed but it will show up if the document is converted to
+ * pdf by either Acrobat Distiller™ or Ghostview.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $llx The x-coordinate of the lower left corner.
+ * @param float $lly The y-coordinate of the lower left corner.
+ * @param float $urx The x-coordinate of the upper right corner.
+ * @param float $ury The y-coordinate of the upper right corner.
+ * @param string $contents The text of the note.
+ * @param string $title The title of the note as displayed in the header of the note.
+ * @param string $icon The icon shown if the note is folded. This parameter can be set
+ * to comment, insert,
+ * note, paragraph,
+ * newparagraph, key, or
+ * help.
+ * @param int $open If open is unequal to zero the note will
+ * be shown unfolded after opening the document with a pdf viewer.
+ * @throws PsException
+ *
+ */
+function ps_add_note($psdoc, float $llx, float $lly, float $urx, float $ury, string $contents, string $title, string $icon, int $open): void
+{
+ error_clear_last();
+ $result = \ps_add_note($psdoc, $llx, $lly, $urx, $ury, $contents, $title, $icon, $open);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a hyperlink at the given position pointing to a second pdf document.
+ * Clicking on the link will branch to the document at the given page. The
+ * first page in a document has number 1.
+ *
+ * The hyperlink's source position is a rectangle with its lower left corner at
+ * (llx, lly) and its upper
+ * right corner at (urx, ury).
+ * The rectangle has by default a thin blue border.
+ *
+ * The note will not be visible if the document
+ * is printed or viewed but it will show up if the document is converted to
+ * pdf by either Acrobat Distiller™ or Ghostview.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $llx The x-coordinate of the lower left corner.
+ * @param float $lly The y-coordinate of the lower left corner.
+ * @param float $urx The x-coordinate of the upper right corner.
+ * @param float $ury The y-coordinate of the upper right corner.
+ * @param string $filename The name of the pdf document to be opened when clicking on
+ * this link.
+ * @param int $page The page number of the destination pdf document
+ * @param string $dest The parameter dest determines how the document
+ * is being viewed. It can be fitpage,
+ * fitwidth, fitheight, or
+ * fitbbox.
+ * @throws PsException
+ *
+ */
+function ps_add_pdflink($psdoc, float $llx, float $lly, float $urx, float $ury, string $filename, int $page, string $dest): void
+{
+ error_clear_last();
+ $result = \ps_add_pdflink($psdoc, $llx, $lly, $urx, $ury, $filename, $page, $dest);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a hyperlink at the given position pointing to a web page. The
+ * hyperlink's source position is a rectangle with its lower left corner at
+ * (llx, lly) and
+ * its upper right corner at (urx,
+ * ury). The rectangle has by default a thin
+ * blue border.
+ *
+ * The note will not be visible if the document
+ * is printed or viewed but it will show up if the document is converted to
+ * pdf by either Acrobat Distiller™ or Ghostview.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $llx The x-coordinate of the lower left corner.
+ * @param float $lly The y-coordinate of the lower left corner.
+ * @param float $urx The x-coordinate of the upper right corner.
+ * @param float $ury The y-coordinate of the upper right corner.
+ * @param string $url The url of the hyperlink to be opened when clicking on
+ * this link, e.g. http://www.php.net.
+ * @throws PsException
+ *
+ */
+function ps_add_weblink($psdoc, float $llx, float $lly, float $urx, float $ury, string $url): void
+{
+ error_clear_last();
+ $result = \ps_add_weblink($psdoc, $llx, $lly, $urx, $ury, $url);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a portion of a circle with at middle point at
+ * (x, y). The arc starts at an
+ * angle of alpha and ends at an angle of
+ * beta. It is drawn counterclockwise (use
+ * ps_arcn to draw clockwise). The subpath added
+ * to the current path starts on the arc at angle alpha
+ * and ends on the arc at angle beta.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x The x-coordinate of the circle's middle point.
+ * @param float $y The y-coordinate of the circle's middle point.
+ * @param float $radius The radius of the circle
+ * @param float $alpha The start angle given in degrees.
+ * @param float $beta The end angle given in degrees.
+ * @throws PsException
+ *
+ */
+function ps_arc($psdoc, float $x, float $y, float $radius, float $alpha, float $beta): void
+{
+ error_clear_last();
+ $result = \ps_arc($psdoc, $x, $y, $radius, $alpha, $beta);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a portion of a circle with at middle point at
+ * (x, y). The arc starts at an
+ * angle of alpha and ends at an angle of
+ * beta. It is drawn clockwise (use
+ * ps_arc to draw counterclockwise). The subpath added to
+ * the current path starts on the arc at angle beta and
+ * ends on the arc at angle alpha.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x The x-coordinate of the circle's middle point.
+ * @param float $y The y-coordinate of the circle's middle point.
+ * @param float $radius The radius of the circle
+ * @param float $alpha The starting angle given in degrees.
+ * @param float $beta The end angle given in degrees.
+ * @throws PsException
+ *
+ */
+function ps_arcn($psdoc, float $x, float $y, float $radius, float $alpha, float $beta): void
+{
+ error_clear_last();
+ $result = \ps_arcn($psdoc, $x, $y, $radius, $alpha, $beta);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Starts a new page. Although the parameters width
+ * and height imply a different page size for each
+ * page, this is not possible in PostScript. The first call of
+ * ps_begin_page will set the page size for the whole
+ * document. Consecutive calls will have no effect, except for creating a new
+ * page. The situation is different if you intent to convert the PostScript
+ * document into PDF. This function places pdfmarks into the document which
+ * can set the size for each page indiviually. The resulting PDF document will
+ * have different page sizes.
+ *
+ * Though PostScript does not know different page sizes, pslib places
+ * a bounding box for each page into the document. This size is evaluated
+ * by some PostScript viewers and will have precedence over the BoundingBox
+ * in the Header of the document. This can lead to unexpected results when
+ * you set a BoundingBox whose lower left corner is not (0, 0), because the
+ * bounding box of the page will always have a lower left corner (0, 0)
+ * and overwrites the global setting.
+ *
+ * Each page is encapsulated into save/restore. This means, that most of the
+ * settings made on one page will not be retained on the next page.
+ *
+ * If there is up to the first call of ps_begin_page no
+ * call of ps_findfont, then the header of the PostScript
+ * document will be output and the bounding box will be set to the size of
+ * the first page. The lower left corner of the bounding box is set to (0, 0).
+ * If ps_findfont was called before, then the
+ * header has been output already, and the document will not have a valid
+ * bounding box. In order to prevent this, one should call
+ * ps_set_info to set the info field
+ * BoundingBox and possibly Orientation
+ * before any ps_findfont or
+ * ps_begin_page calls.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $width The width of the page in pixel, e.g. 596 for A4 format.
+ * @param float $height The height of the page in pixel, e.g. 842 for A4 format.
+ * @throws PsException
+ *
+ */
+function ps_begin_page($psdoc, float $width, float $height): void
+{
+ error_clear_last();
+ $result = \ps_begin_page($psdoc, $width, $height);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Starts a new pattern. A pattern is like a page containing e.g. a drawing
+ * which can be used for filling areas. It is used like a color by calling
+ * ps_setcolor and setting the color space to
+ * pattern.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $width The width of the pattern in pixel.
+ * @param float $height The height of the pattern in pixel.
+ * @param float $xstep The distance in pixel of placements of the pattern in
+ * horizontal direction.
+ * @param float $ystep The distance in pixel of placements of the pattern in
+ * vertical direction.
+ * @param int $painttype Must be 1 or 2.
+ * @return int The identifier of the pattern.
+ * @throws PsException
+ *
+ */
+function ps_begin_pattern($psdoc, float $width, float $height, float $xstep, float $ystep, int $painttype): int
+{
+ error_clear_last();
+ $result = \ps_begin_pattern($psdoc, $width, $height, $xstep, $ystep, $painttype);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Starts a new template. A template is called a form in the postscript
+ * language. It is created similar to a pattern but used like an image.
+ * Templates are often used for drawings which are placed several times
+ * through out the document, e.g. like a company logo. All drawing functions
+ * may be used within a template. The template will not be drawn until
+ * it is placed by ps_place_image.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $width The width of the template in pixel.
+ * @param float $height The height of the template in pixel.
+ * @return int Returns TRUE on success.
+ * @throws PsException
+ *
+ */
+function ps_begin_template($psdoc, float $width, float $height): int
+{
+ error_clear_last();
+ $result = \ps_begin_template($psdoc, $width, $height);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Draws a circle with its middle point at (x,
+ * y). The circle starts and ends at position
+ * (x+radius,
+ * y). If this function is called outside a path it
+ * will start a new path. If it is called within a path it will add the circle
+ * as a subpath. If the last drawing operation does not end in point
+ * (x+radius,
+ * y) then there will be a gap in the path.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x The x-coordinate of the circle's middle point.
+ * @param float $y The y-coordinate of the circle's middle point.
+ * @param float $radius The radius of the circle
+ * @throws PsException
+ *
+ */
+function ps_circle($psdoc, float $x, float $y, float $radius): void
+{
+ error_clear_last();
+ $result = \ps_circle($psdoc, $x, $y, $radius);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Takes the current path and uses it to define the border of a clipping area.
+ * Everything drawn outside of that area will not be visible.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_clip($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_clip($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes an image and frees its resources. Once an image is closed
+ * it cannot be used anymore.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $imageid Resource identifier of the image as returned by
+ * ps_open_image or
+ * ps_open_image_file.
+ * @throws PsException
+ *
+ */
+function ps_close_image($psdoc, int $imageid): void
+{
+ error_clear_last();
+ $result = \ps_close_image($psdoc, $imageid);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the PostScript document.
+ *
+ * This function writes the trailer of the PostScript document.
+ * It also writes the bookmark tree. ps_close does
+ * not free any resources, which is done by ps_delete.
+ *
+ * This function is also called by ps_delete if it
+ * has not been called before.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_close($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_close($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Connects the last point with first point of a path and draws the resulting
+ * closed line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_closepath_stroke($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_closepath_stroke($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Connects the last point with the first point of a path. The resulting
+ * path can be used for stroking, filling, clipping, etc..
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_closepath($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_closepath($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Output a text one line below the last line. The line spacing is
+ * taken from the value "leading" which must be set with
+ * ps_set_value. The actual position of the
+ * text is determined by the values "textx" and "texty" which can be requested
+ * with ps_get_value
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $text The text to output.
+ * @throws PsException
+ *
+ */
+function ps_continue_text($psdoc, string $text): void
+{
+ error_clear_last();
+ $result = \ps_continue_text($psdoc, $text);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Add a section of a cubic Bézier curve described by the three given control
+ * points to the current path.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x1 x-coordinate of first control point.
+ * @param float $y1 y-coordinate of first control point.
+ * @param float $x2 x-coordinate of second control point.
+ * @param float $y2 y-coordinate of second control point.
+ * @param float $x3 x-coordinate of third control point.
+ * @param float $y3 y-coordinate of third control point.
+ * @throws PsException
+ *
+ */
+function ps_curveto($psdoc, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3): void
+{
+ error_clear_last();
+ $result = \ps_curveto($psdoc, $x1, $y1, $x2, $y2, $x3, $y3);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Mainly frees memory used by the document. Also closes a file, if it was not
+ * closed before with ps_close. You should in any case
+ * close the file with ps_close before, because
+ * ps_close not just closes the file but also outputs a
+ * trailor containing PostScript comments like the number of pages in the
+ * document and adding the bookmark hierarchy.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_delete($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_delete($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Ends a page which was started with ps_begin_page.
+ * Ending a page will leave the current drawing context, which e.g. requires
+ * to reload fonts if they were loading within the page, and to set many
+ * other drawing parameters like the line width, or color..
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_end_page($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_end_page($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Ends a pattern which was started with ps_begin_pattern.
+ * Once a pattern has been ended, it can be used like a color to fill
+ * areas.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_end_pattern($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_end_pattern($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Ends a template which was started with ps_begin_template.
+ * Once a template has been ended, it can be used like an image.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_end_template($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_end_template($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fills and draws the path constructed with previously called drawing
+ * functions like ps_lineto.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_fill_stroke($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_fill_stroke($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fills the path constructed with previously called drawing functions like
+ * ps_lineto.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_fill($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_fill($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets several parameters which were directly set by
+ * ps_set_parameter or indirectly by one of the other
+ * functions. Parameters are by definition string
+ * values. This function cannot be used to retrieve resources which were also
+ * set by ps_set_parameter.
+ *
+ * The parameter name can have the following values.
+ *
+ *
+ *
+ * fontname
+ *
+ *
+ * The name of the currently active font or the font whose
+ * identifier is passed in parameter modifier.
+ *
+ *
+ *
+ *
+ * fontencoding
+ *
+ *
+ * The encoding of the currently active font.
+ *
+ *
+ *
+ *
+ * dottedversion
+ *
+ *
+ * The version of the underlying pslib library in the format
+ * <major>.<minor>.<subminor>
+ *
+ *
+ *
+ *
+ * scope
+ *
+ *
+ * The current drawing scope. Can be object, document, null, page,
+ * pattern, path, template, prolog, font, glyph.
+ *
+ *
+ *
+ *
+ * ligaturedisolvechar
+ *
+ *
+ * The character which dissolves a ligature. If your are using a font
+ * which contains the ligature `ff' and `|' is the char to dissolve the
+ * ligature, then `f|f' will result in two `f' instead of the ligature `ff'.
+ *
+ *
+ *
+ *
+ * imageencoding
+ *
+ *
+ * The encoding used for encoding images. Can be either
+ * hex or 85. hex encoding
+ * uses two bytes in the postscript file each byte in the image.
+ * 85 stand for Ascii85 encoding.
+ *
+ *
+ *
+ *
+ * linenumbermode
+ *
+ *
+ * Set to paragraph if lines are numbered
+ * within a paragraph or box if they are
+ * numbered within the surrounding box.
+ *
+ *
+ *
+ *
+ * linebreak
+ *
+ *
+ * Only used if text is output with ps_show_boxed.
+ * If set to TRUE a carriage return will add a line
+ * break.
+ *
+ *
+ *
+ *
+ * parbreak
+ *
+ *
+ * Only used if text is output with ps_show_boxed.
+ * If set to TRUE a carriage return will start
+ * a new paragraph.
+ *
+ *
+ *
+ *
+ * hyphenation
+ *
+ *
+ * Only used if text is output with ps_show_boxed.
+ * If set to TRUE the paragraph will be hyphenated
+ * if a hypen dictionary is set and exists.
+ *
+ *
+ *
+ *
+ * hyphendict
+ *
+ *
+ * Filename of the dictionary used for hyphenation pattern.
+ *
+ *
+ *
+ *
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $name Name of the parameter.
+ * @param float $modifier An identifier needed if a parameter of a resource is requested,
+ * e.g. the size of an image. In such a case the resource id is
+ * passed.
+ * @return string Returns the value of the parameter.
+ * @throws PsException
+ *
+ */
+function ps_get_parameter($psdoc, string $name, float $modifier = null): string
+{
+ error_clear_last();
+ if ($modifier !== null) {
+ $result = \ps_get_parameter($psdoc, $name, $modifier);
+ } else {
+ $result = \ps_get_parameter($psdoc, $name);
+ }
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Hyphenates the passed word. ps_hyphenate evaluates the
+ * value hyphenminchars (set by ps_set_value) and
+ * the parameter hyphendict (set by ps_set_parameter).
+ * hyphendict must be set before calling this function.
+ *
+ * This function requires the locale category LC_CTYPE to be set properly.
+ * This is done when the extension is initialized by using the environment
+ * variables. On Unix systems read the man page of locale for more information.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $text text should not contain any non alpha
+ * characters. Possible positions for breaks are returned in an array of
+ * interger numbers. Each number is the position of the char in
+ * text after which a hyphenation can take place.
+ * @return array An array of integers indicating the position of possible breaks in
+ * the text.
+ * @throws PsException
+ *
+ */
+function ps_hyphenate($psdoc, string $text): array
+{
+ error_clear_last();
+ $result = \ps_hyphenate($psdoc, $text);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $file
+ * @throws PsException
+ *
+ */
+function ps_include_file($psdoc, string $file): void
+{
+ error_clear_last();
+ $result = \ps_include_file($psdoc, $file);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Adds a straight line from the current point to the given coordinates to the
+ * current path. Use ps_moveto to set the starting point
+ * of the line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x x-coordinate of the end point of the line.
+ * @param float $y y-coordinate of the end point of the line.
+ * @throws PsException
+ *
+ */
+function ps_lineto($psdoc, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_lineto($psdoc, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the current point to new coordinates. If this is the first call of
+ * ps_moveto after a previous path has been ended then it
+ * will start a new path. If this function is called in the middle of a path
+ * it will just set the current point and start a subpath.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x x-coordinate of the point to move to.
+ * @param float $y y-coordinate of the point to move to.
+ * @throws PsException
+ *
+ */
+function ps_moveto($psdoc, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_moveto($psdoc, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a new document instance. It does not create the file on disk or in
+ * memory, it just sets up everything. ps_new is usually
+ * followed by a call of ps_open_file to actually create
+ * the postscript document.
+ *
+ * @return resource Resource of PostScript document. The return value
+ * is passed to all other functions as the first argument.
+ * @throws PsException
+ *
+ */
+function ps_new()
+{
+ error_clear_last();
+ $result = \ps_new();
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Creates a new file on disk and writes the PostScript document into it. The
+ * file will be closed when ps_close is called.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $filename The name of the postscript file.
+ * If filename is not passed the document will be
+ * created in memory and all output will go straight to the browser.
+ * @throws PsException
+ *
+ */
+function ps_open_file($psdoc, string $filename = null): void
+{
+ error_clear_last();
+ if ($filename !== null) {
+ $result = \ps_open_file($psdoc, $filename);
+ } else {
+ $result = \ps_open_file($psdoc);
+ }
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Places a formerly loaded image on the page. The image can be scaled.
+ * If the image shall be rotated as well, you will have to rotate the
+ * coordinate system before with ps_rotate.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $imageid The resource identifier of the image as returned by
+ * ps_open_image or
+ * ps_open_image_file.
+ * @param float $x x-coordinate of the lower left corner of the image.
+ * @param float $y y-coordinate of the lower left corner of the image.
+ * @param float $scale The scaling factor for the image. A scale of 1.0 will result
+ * in a resolution of 72 dpi, because each pixel is equivalent to
+ * 1 point.
+ * @throws PsException
+ *
+ */
+function ps_place_image($psdoc, int $imageid, float $x, float $y, float $scale): void
+{
+ error_clear_last();
+ $result = \ps_place_image($psdoc, $imageid, $x, $y, $scale);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws a rectangle with its lower left corner at (x,
+ * y). The rectangle starts and ends in its lower left
+ * corner. If this function is called outside a path it will start a new path.
+ * If it is called within a path it will add the rectangle as a subpath. If
+ * the last drawing operation does not end in the lower left corner then there
+ * will be a gap in the path.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x x-coordinate of the lower left corner of the rectangle.
+ * @param float $y y-coordinate of the lower left corner of the rectangle.
+ * @param float $width The width of the image.
+ * @param float $height The height of the image.
+ * @throws PsException
+ *
+ */
+function ps_rect($psdoc, float $x, float $y, float $width, float $height): void
+{
+ error_clear_last();
+ $result = \ps_rect($psdoc, $x, $y, $width, $height);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Restores a previously saved graphics context. Any call of
+ * ps_save must be accompanied by a call to
+ * ps_restore. All coordinate transformations, line
+ * style settings, color settings, etc. are being restored to the state
+ * before the call of ps_save.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_restore($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_restore($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the rotation of the coordinate system.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $rot Angle of rotation in degree.
+ * @throws PsException
+ *
+ */
+function ps_rotate($psdoc, float $rot): void
+{
+ error_clear_last();
+ $result = \ps_rotate($psdoc, $rot);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Saves the current graphics context, containing colors, translation and
+ * rotation settings and some more. A saved context can be restored with
+ * ps_restore.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_save($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_save($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets horizontal and vertical scaling of the coordinate system.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x Scaling factor in horizontal direction.
+ * @param float $y Scaling factor in vertical direction.
+ * @throws PsException
+ *
+ */
+function ps_scale($psdoc, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_scale($psdoc, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Links added with one of the functions ps_add_weblink,
+ * ps_add_pdflink, etc. will be displayed with a
+ * surounded rectangle when the postscript document is converted to
+ * pdf and viewed in a pdf viewer. This rectangle is not visible in
+ * the postscript document.
+ * This function sets the color of the rectangle's border line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $red The red component of the border color.
+ * @param float $green The green component of the border color.
+ * @param float $blue The blue component of the border color.
+ * @throws PsException
+ *
+ */
+function ps_set_border_color($psdoc, float $red, float $green, float $blue): void
+{
+ error_clear_last();
+ $result = \ps_set_border_color($psdoc, $red, $green, $blue);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Links added with one of the functions ps_add_weblink,
+ * ps_add_pdflink, etc. will be displayed with a
+ * surounded rectangle when the postscript document is converted to
+ * pdf and viewed in a pdf viewer. This rectangle is not visible in
+ * the postscript document.
+ * This function sets the length of the black and white portion of a
+ * dashed border line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $black The length of the dash.
+ * @param float $white The length of the gap between dashes.
+ * @throws PsException
+ *
+ */
+function ps_set_border_dash($psdoc, float $black, float $white): void
+{
+ error_clear_last();
+ $result = \ps_set_border_dash($psdoc, $black, $white);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Links added with one of the functions ps_add_weblink,
+ * ps_add_pdflink, etc. will be displayed with a
+ * surounded rectangle when the postscript document is converted to
+ * pdf and viewed in a pdf viewer. This rectangle is not visible in
+ * the postscript document.
+ * This function sets the appearance and width of the border line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $style style can be solid or
+ * dashed.
+ * @param float $width The line width of the border.
+ * @throws PsException
+ *
+ */
+function ps_set_border_style($psdoc, string $style, float $width): void
+{
+ error_clear_last();
+ $result = \ps_set_border_style($psdoc, $style, $width);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets certain information fields of the document. This fields will be shown
+ * as a comment in the header of the PostScript file. If the document is
+ * converted to pdf this fields will also be used for the document
+ * information.
+ *
+ * The BoundingBox is usually set to the value given to the
+ * first page. This only works if ps_findfont has not
+ * been called before. In such cases the BoundingBox would be left unset
+ * unless you set it explicitly with this function.
+ *
+ * This function will have no effect anymore when the header of the postscript
+ * file has been already written. It must be called before the first page
+ * or the first call of ps_findfont.
+ *
+ * @param resource $p Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $key The name of the information field to set. The values which can be
+ * set are Keywords, Subject,
+ * Title, Creator,
+ * Author, BoundingBox, and
+ * Orientation. Be aware that some of them has a
+ * meaning to PostScript viewers.
+ * @param string $val The value of the information field. The field
+ * Orientation can be set to either
+ * Portrait or Landscape. The
+ * BoundingBox is a string consisting of four numbers.
+ * The first two numbers are the coordinates of the lower left corner of
+ * the page. The last two numbers are the coordinates of the upper
+ * right corner.
+ *
+ * Up to version 0.2.6 of pslib, the BoundingBox and Orientation
+ * will be overwritten by ps_begin_page,
+ * unless ps_findfont has been called before.
+ * @throws PsException
+ *
+ */
+function ps_set_info($p, string $key, string $val): void
+{
+ error_clear_last();
+ $result = \ps_set_info($p, $key, $val);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets several parameters which are used by many functions. Parameters are by
+ * definition string values.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $name For a list of possible names see ps_get_parameter.
+ * @param string $value The value of the parameter.
+ * @throws PsException
+ *
+ */
+function ps_set_parameter($psdoc, string $name, string $value): void
+{
+ error_clear_last();
+ $result = \ps_set_parameter($psdoc, $name, $value);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set the position for the next text output. You may alternatively set the x
+ * and y value separately by calling ps_set_value and
+ * choosing textx respectively texty as
+ * the value name.
+ *
+ * If you want to output text at a certain position it is more convenient
+ * to use ps_show_xy instead of setting the text position
+ * and calling ps_show.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x x-coordinate of the new text position.
+ * @param float $y y-coordinate of the new text position.
+ * @throws PsException
+ *
+ */
+function ps_set_text_pos($psdoc, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_set_text_pos($psdoc, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets several values which are used by many functions. Parameters are by
+ * definition float values.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $name The name can be one of the following:
+ *
+ *
+ * textrendering
+ *
+ *
+ * The way how text is shown.
+ *
+ *
+ *
+ *
+ * textx
+ *
+ *
+ * The x coordinate for text output.
+ *
+ *
+ *
+ *
+ * texty
+ *
+ *
+ * The y coordinate for text output.
+ *
+ *
+ *
+ *
+ * wordspacing
+ *
+ *
+ * The distance between words relative to the width of a space.
+ *
+ *
+ *
+ *
+ * leading
+ *
+ *
+ * The distance between lines in pixels.
+ *
+ *
+ *
+ *
+ *
+ * The way how text is shown.
+ *
+ * The x coordinate for text output.
+ *
+ * The y coordinate for text output.
+ *
+ * The distance between words relative to the width of a space.
+ *
+ * The distance between lines in pixels.
+ * @param float $value The way how text is shown.
+ * @throws PsException
+ *
+ */
+function ps_set_value($psdoc, string $name, float $value): void
+{
+ error_clear_last();
+ $result = \ps_set_value($psdoc, $name, $value);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the color for drawing, filling, or both.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $type The parameter type can be
+ * both, fill, or
+ * fillstroke.
+ * @param string $colorspace The colorspace should be one of gray,
+ * rgb, cmyk,
+ * spot, pattern. Depending on the
+ * colorspace either only the first, the first three or all parameters
+ * will be used.
+ * @param float $c1 Depending on the colorspace this is either the red component (rgb),
+ * the cyan component (cmyk), the gray value (gray), the identifier of
+ * the spot color or the identifier of the pattern.
+ * @param float $c2 Depending on the colorspace this is either the green component (rgb),
+ * the magenta component (cmyk).
+ * @param float $c3 Depending on the colorspace this is either the blue component (rgb),
+ * the yellow component (cmyk).
+ * @param float $c4 This must only be set in cmyk colorspace and specifies the black
+ * component.
+ * @throws PsException
+ *
+ */
+function ps_setcolor($psdoc, string $type, string $colorspace, float $c1, float $c2, float $c3, float $c4): void
+{
+ error_clear_last();
+ $result = \ps_setcolor($psdoc, $type, $colorspace, $c1, $c2, $c3, $c4);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the length of the black and white portions of a dashed line.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $on The length of the dash.
+ * @param float $off The length of the gap between dashes.
+ * @throws PsException
+ *
+ */
+function ps_setdash($psdoc, float $on, float $off): void
+{
+ error_clear_last();
+ $result = \ps_setdash($psdoc, $on, $off);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $value The value must be between 0.2 and 1.
+ * @throws PsException
+ *
+ */
+function ps_setflat($psdoc, float $value): void
+{
+ error_clear_last();
+ $result = \ps_setflat($psdoc, $value);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets a font, which has to be loaded before with
+ * ps_findfont. Outputting text without setting a font
+ * results in an error.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $fontid The font identifier as returned by ps_findfont.
+ * @param float $size The size of the font.
+ * @throws PsException
+ *
+ */
+function ps_setfont($psdoc, int $fontid, float $size): void
+{
+ error_clear_last();
+ $result = \ps_setfont($psdoc, $fontid, $size);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the gray value for all following drawing operations.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $gray The value must be between 0 (white) and 1 (black).
+ * @throws PsException
+ *
+ */
+function ps_setgray($psdoc, float $gray): void
+{
+ error_clear_last();
+ $result = \ps_setgray($psdoc, $gray);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets how line ends look like.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $type The type of line ends. Possible values are
+ * PS_LINECAP_BUTT,
+ * PS_LINECAP_ROUND, or
+ * PS_LINECAP_SQUARED.
+ * @throws PsException
+ *
+ */
+function ps_setlinecap($psdoc, int $type): void
+{
+ error_clear_last();
+ $result = \ps_setlinecap($psdoc, $type);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets how lines are joined.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $type The way lines are joined. Possible values are
+ * PS_LINEJOIN_MITER,
+ * PS_LINEJOIN_ROUND, or
+ * PS_LINEJOIN_BEVEL.
+ * @throws PsException
+ *
+ */
+function ps_setlinejoin($psdoc, int $type): void
+{
+ error_clear_last();
+ $result = \ps_setlinejoin($psdoc, $type);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the line width for all following drawing operations.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $width The width of lines in points.
+ * @throws PsException
+ *
+ */
+function ps_setlinewidth($psdoc, float $width): void
+{
+ error_clear_last();
+ $result = \ps_setlinewidth($psdoc, $width);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * If two lines join in a small angle and the line join is set to
+ * PS_LINEJOIN_MITER, then
+ * the resulting spike will be very long. The miter limit is the maximum
+ * ratio of the miter length (the length of the spike) and the line width.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $value The maximum ratio between the miter length and the line width. Larger
+ * values (> 10) will result in very long spikes when two lines meet
+ * in a small angle. Keep the default unless you know what you are doing.
+ * @throws PsException
+ *
+ */
+function ps_setmiterlimit($psdoc, float $value): void
+{
+ error_clear_last();
+ $result = \ps_setmiterlimit($psdoc, $value);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $mode
+ * @throws PsException
+ *
+ */
+function ps_setoverprintmode($psdoc, int $mode): void
+{
+ error_clear_last();
+ $result = \ps_setoverprintmode($psdoc, $mode);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the length of the black and white portions of a dashed line.
+ * ps_setpolydash is used to set more complicated dash
+ * patterns.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $arr arr is a list of length elements alternately for
+ * the black and white portion.
+ * @throws PsException
+ *
+ */
+function ps_setpolydash($psdoc, float $arr): void
+{
+ error_clear_last();
+ $result = \ps_setpolydash($psdoc, $arr);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a pattern based on a shading, which has to be created before with
+ * ps_shading. Shading patterns can be used like regular
+ * patterns.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $shadingid The identifier of a shading previously created with
+ * ps_shading.
+ * @param string $optlist This argument is not currently used.
+ * @return int The identifier of the pattern.
+ * @throws PsException
+ *
+ */
+function ps_shading_pattern($psdoc, int $shadingid, string $optlist): int
+{
+ error_clear_last();
+ $result = \ps_shading_pattern($psdoc, $shadingid, $optlist);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Creates a shading, which can be used by ps_shfill or
+ * ps_shading_pattern.
+ *
+ * The color of the shading can be in any color space except for
+ * pattern.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $type The type of shading can be either radial or
+ * axial. Each shading starts with the current fill
+ * color and ends with the given color values passed in the parameters
+ * c1 to c4
+ * (see ps_setcolor for their meaning).
+ * @param float $x0 The coordinates x0, y0,
+ * x1, y1 are the start and
+ * end point of the shading. If the type of shading is
+ * radial the two points are the middle points of
+ * a starting and ending circle.
+ * @param float $y0 See ps_setcolor for their meaning.
+ * @param float $x1 If the shading is of type radial the
+ * optlist must also contain the parameters
+ * r0 and r1 with the radius of the
+ * start and end circle.
+ * @param float $y1
+ * @param float $c1
+ * @param float $c2
+ * @param float $c3
+ * @param float $c4
+ * @param string $optlist
+ * @return int Returns the identifier of the pattern.
+ * @throws PsException
+ *
+ */
+function ps_shading($psdoc, string $type, float $x0, float $y0, float $x1, float $y1, float $c1, float $c2, float $c3, float $c4, string $optlist): int
+{
+ error_clear_last();
+ $result = \ps_shading($psdoc, $type, $x0, $y0, $x1, $y1, $c1, $c2, $c3, $c4, $optlist);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Fills an area with a shading, which has to be created before with
+ * ps_shading. This is an alternative way to creating
+ * a pattern from a shading ps_shading_pattern and using
+ * the pattern as the filling color.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $shadingid The identifier of a shading previously created with
+ * ps_shading.
+ * @throws PsException
+ *
+ */
+function ps_shfill($psdoc, int $shadingid): void
+{
+ error_clear_last();
+ $result = \ps_shfill($psdoc, $shadingid);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Output a text at the given text position.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $text The text to be output.
+ * @param float $x x-coordinate of the lower left corner of the box surrounding the text.
+ * @param float $y y-coordinate of the lower left corner of the box surrounding the text.
+ * @throws PsException
+ *
+ */
+function ps_show_xy($psdoc, string $text, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_show_xy($psdoc, $text, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param resource $psdoc
+ * @param string $text
+ * @param int $len
+ * @param float $xcoor
+ * @param float $ycoor
+ * @throws PsException
+ *
+ */
+function ps_show_xy2($psdoc, string $text, int $len, float $xcoor, float $ycoor): void
+{
+ error_clear_last();
+ $result = \ps_show_xy2($psdoc, $text, $len, $xcoor, $ycoor);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Output a text at the current text position. The text position can be set
+ * by storing the x and y coordinates into the values textx
+ * and texty with the function
+ * ps_set_value. The function will issue an
+ * error if a font was not set before with ps_setfont.
+ *
+ * ps_show evaluates the following parameters and values
+ * as set by ps_set_parameter and
+ * ps_set_value.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $text The text to be output.
+ * @throws PsException
+ *
+ */
+function ps_show($psdoc, string $text): void
+{
+ error_clear_last();
+ $result = \ps_show($psdoc, $text);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Output text at the current position. Do not print more than len characters.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param string $text The text to be output.
+ * @param int $len The maximum number of characters to print.
+ * @throws PsException
+ *
+ */
+function ps_show2($psdoc, string $text, int $len): void
+{
+ error_clear_last();
+ $result = \ps_show2($psdoc, $text, $len);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Draws the path constructed with previously called drawing functions like
+ * ps_lineto.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @throws PsException
+ *
+ */
+function ps_stroke($psdoc): void
+{
+ error_clear_last();
+ $result = \ps_stroke($psdoc);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Output the glyph at position ord in the font
+ * encoding vector of the current font. The font encoding for a font can be
+ * set when loading the font with ps_findfont.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param int $ord The position of the glyph in the font encoding vector.
+ * @throws PsException
+ *
+ */
+function ps_symbol($psdoc, int $ord): void
+{
+ error_clear_last();
+ $result = \ps_symbol($psdoc, $ord);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets a new initial point of the coordinate system.
+ *
+ * @param resource $psdoc Resource identifier of the postscript file
+ * as returned by ps_new.
+ * @param float $x x-coordinate of the origin of the translated coordinate system.
+ * @param float $y y-coordinate of the origin of the translated coordinate system.
+ * @throws PsException
+ *
+ */
+function ps_translate($psdoc, float $x, float $y): void
+{
+ error_clear_last();
+ $result = \ps_translate($psdoc, $x, $y);
+ if ($result === false) {
+ throw PsException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\PspellException;
+
+/**
+ *
+ *
+ * @param int $dictionary_link
+ * @param string $word The added word.
+ * @throws PspellException
+ *
+ */
+function pspell_add_to_personal(int $dictionary_link, string $word): void
+{
+ error_clear_last();
+ $result = \pspell_add_to_personal($dictionary_link, $word);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link
+ * @param string $word The added word.
+ * @throws PspellException
+ *
+ */
+function pspell_add_to_session(int $dictionary_link, string $word): void
+{
+ error_clear_last();
+ $result = \pspell_add_to_session($dictionary_link, $word);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link
+ * @throws PspellException
+ *
+ */
+function pspell_clear_session(int $dictionary_link): void
+{
+ error_clear_last();
+ $result = \pspell_clear_session($dictionary_link);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Create a config used to open a dictionary.
+ *
+ * pspell_config_create has a very similar syntax to
+ * pspell_new. In fact, using
+ * pspell_config_create immediately followed by
+ * pspell_new_config will produce the exact same result.
+ * However, after creating a new config, you can also use
+ * pspell_config_* functions before calling
+ * pspell_new_config to take advantage of some
+ * advanced functionality.
+ *
+ * For more information and examples, check out inline manual pspell
+ * website:http://aspell.net/.
+ *
+ * @param string $language The language parameter is the language code which consists of the
+ * two letter ISO 639 language code and an optional two letter ISO
+ * 3166 country code after a dash or underscore.
+ * @param string $spelling The spelling parameter is the requested spelling for languages
+ * with more than one spelling such as English. Known values are
+ * 'american', 'british', and 'canadian'.
+ * @param string $jargon The jargon parameter contains extra information to distinguish
+ * two different words lists that have the same language and
+ * spelling parameters.
+ * @param string $encoding The encoding parameter is the encoding that words are expected to
+ * be in. Valid values are 'utf-8', 'iso8859-*', 'koi8-r',
+ * 'viscii', 'cp1252', 'machine unsigned 16', 'machine unsigned
+ * 32'. This parameter is largely untested, so be careful when
+ * using.
+ * @return int Returns a pspell config identifier.
+ * @throws PspellException
+ *
+ */
+function pspell_config_create(string $language, string $spelling = null, string $jargon = null, string $encoding = null): int
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \pspell_config_create($language, $spelling, $jargon, $encoding);
+ } elseif ($jargon !== null) {
+ $result = \pspell_config_create($language, $spelling, $jargon);
+ } elseif ($spelling !== null) {
+ $result = \pspell_config_create($language, $spelling);
+ } else {
+ $result = \pspell_config_create($language);
+ }
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param int $conf
+ * @param string $directory
+ * @throws PspellException
+ *
+ */
+function pspell_config_data_dir(int $conf, string $directory): void
+{
+ error_clear_last();
+ $result = \pspell_config_data_dir($conf, $directory);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function is
+ * currently not documented; only its argument list is available.
+ *
+ *
+ * @param int $conf
+ * @param string $directory
+ * @throws PspellException
+ *
+ */
+function pspell_config_dict_dir(int $conf, string $directory): void
+{
+ error_clear_last();
+ $result = \pspell_config_dict_dir($conf, $directory);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link
+ * @param int $n Words less than n characters will be skipped.
+ * @throws PspellException
+ *
+ */
+function pspell_config_ignore(int $dictionary_link, int $n): void
+{
+ error_clear_last();
+ $result = \pspell_config_ignore($dictionary_link, $n);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link
+ * @param int $mode The mode parameter is the mode in which spellchecker will work.
+ * There are several modes available:
+ *
+ *
+ *
+ * PSPELL_FAST - Fast mode (least number of
+ * suggestions)
+ *
+ *
+ *
+ *
+ * PSPELL_NORMAL - Normal mode (more suggestions)
+ *
+ *
+ *
+ *
+ * PSPELL_BAD_SPELLERS - Slow mode (a lot of
+ * suggestions)
+ *
+ *
+ *
+ * @throws PspellException
+ *
+ */
+function pspell_config_mode(int $dictionary_link, int $mode): void
+{
+ error_clear_last();
+ $result = \pspell_config_mode($dictionary_link, $mode);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set a file that contains personal wordlist. The personal wordlist will be
+ * loaded and used in addition to the standard one after you call
+ * pspell_new_config. The file is also the file where
+ * pspell_save_wordlist will save personal wordlist to.
+ *
+ * pspell_config_personal should be used on a config
+ * before calling pspell_new_config.
+ *
+ * @param int $dictionary_link
+ * @param string $file The personal wordlist. If the file does not exist, it will be created.
+ * The file should be writable by whoever PHP runs as (e.g. nobody).
+ * @throws PspellException
+ *
+ */
+function pspell_config_personal(int $dictionary_link, string $file): void
+{
+ error_clear_last();
+ $result = \pspell_config_personal($dictionary_link, $file);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Set a file that contains replacement pairs.
+ *
+ * The replacement pairs improve the quality of the spellchecker. When a word
+ * is misspelled, and a proper suggestion was not found in the list,
+ * pspell_store_replacement can be used to store a
+ * replacement pair and then pspell_save_wordlist to
+ * save the wordlist along with the replacement pairs.
+ *
+ * pspell_config_repl should be used on a config
+ * before calling pspell_new_config.
+ *
+ * @param int $dictionary_link
+ * @param string $file The file should be writable by whoever PHP runs as (e.g. nobody).
+ * @throws PspellException
+ *
+ */
+function pspell_config_repl(int $dictionary_link, string $file): void
+{
+ error_clear_last();
+ $result = \pspell_config_repl($dictionary_link, $file);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function determines whether run-together words will be treated as
+ * legal compounds. That is, "thecat" will be a legal compound, although
+ * there should be a space between the two words. Changing this setting only
+ * affects the results returned by pspell_check;
+ * pspell_suggest will still return suggestions.
+ *
+ * pspell_config_runtogether should be used on a config
+ * before calling pspell_new_config.
+ *
+ * @param int $dictionary_link
+ * @param bool $flag TRUE if run-together words should be treated as legal compounds,
+ * FALSE otherwise.
+ * @throws PspellException
+ *
+ */
+function pspell_config_runtogether(int $dictionary_link, bool $flag): void
+{
+ error_clear_last();
+ $result = \pspell_config_runtogether($dictionary_link, $flag);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ * pspell_config_save_repl determines whether
+ * pspell_save_wordlist will save the replacement pairs
+ * along with the wordlist. Usually there is no need to use this function
+ * because if pspell_config_repl is used, the
+ * replacement pairs will be saved by
+ * pspell_save_wordlist anyway, and if it is not,
+ * the replacement pairs will not be saved.
+ *
+ * pspell_config_save_repl should be used on a config
+ * before calling pspell_new_config.
+ *
+ * @param int $dictionary_link
+ * @param bool $flag TRUE if replacement pairs should be saved, FALSE otherwise.
+ * @throws PspellException
+ *
+ */
+function pspell_config_save_repl(int $dictionary_link, bool $flag): void
+{
+ error_clear_last();
+ $result = \pspell_config_save_repl($dictionary_link, $flag);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $config The config parameter is the one returned by
+ * pspell_config_create when the config was created.
+ * @return int Returns a dictionary link identifier on success.
+ * @throws PspellException
+ *
+ */
+function pspell_new_config(int $config): int
+{
+ error_clear_last();
+ $result = \pspell_new_config($config);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * pspell_new opens up a new dictionary and
+ * returns the dictionary link identifier for use in other pspell
+ * functions.
+ *
+ * For more information and examples, check out inline manual pspell
+ * website:http://aspell.net/.
+ *
+ * @param string $language The language parameter is the language code which consists of the
+ * two letter ISO 639 language code and an optional two letter ISO
+ * 3166 country code after a dash or underscore.
+ * @param string $spelling The spelling parameter is the requested spelling for languages
+ * with more than one spelling such as English. Known values are
+ * 'american', 'british', and 'canadian'.
+ * @param string $jargon The jargon parameter contains extra information to distinguish
+ * two different words lists that have the same language and
+ * spelling parameters.
+ * @param string $encoding The encoding parameter is the encoding that words are expected to
+ * be in. Valid values are 'utf-8', 'iso8859-*', 'koi8-r',
+ * 'viscii', 'cp1252', 'machine unsigned 16', 'machine unsigned
+ * 32'. This parameter is largely untested, so be careful when
+ * using.
+ * @param int $mode The mode parameter is the mode in which spellchecker will work.
+ * There are several modes available:
+ *
+ *
+ *
+ * PSPELL_FAST - Fast mode (least number of
+ * suggestions)
+ *
+ *
+ *
+ *
+ * PSPELL_NORMAL - Normal mode (more suggestions)
+ *
+ *
+ *
+ *
+ * PSPELL_BAD_SPELLERS - Slow mode (a lot of
+ * suggestions)
+ *
+ *
+ *
+ *
+ * PSPELL_RUN_TOGETHER - Consider run-together words
+ * as legal compounds. That is, "thecat" will be a legal compound,
+ * although there should be a space between the two words. Changing this
+ * setting only affects the results returned by
+ * pspell_check; pspell_suggest
+ * will still return suggestions.
+ *
+ *
+ *
+ * Mode is a bitmask constructed from different constants listed above.
+ * However, PSPELL_FAST,
+ * PSPELL_NORMAL and
+ * PSPELL_BAD_SPELLERS are mutually exclusive, so you
+ * should select only one of them.
+ * @return int Returns the dictionary link identifier on success.
+ * @throws PspellException
+ *
+ */
+function pspell_new(string $language, string $spelling = null, string $jargon = null, string $encoding = null, int $mode = 0): int
+{
+ error_clear_last();
+ if ($mode !== 0) {
+ $result = \pspell_new($language, $spelling, $jargon, $encoding, $mode);
+ } elseif ($encoding !== null) {
+ $result = \pspell_new($language, $spelling, $jargon, $encoding);
+ } elseif ($jargon !== null) {
+ $result = \pspell_new($language, $spelling, $jargon);
+ } elseif ($spelling !== null) {
+ $result = \pspell_new($language, $spelling);
+ } else {
+ $result = \pspell_new($language);
+ }
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link A dictionary link identifier opened with
+ * pspell_new_personal.
+ * @throws PspellException
+ *
+ */
+function pspell_save_wordlist(int $dictionary_link): void
+{
+ error_clear_last();
+ $result = \pspell_save_wordlist($dictionary_link);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $dictionary_link A dictionary link identifier, opened with
+ * pspell_new_personal
+ * @param string $misspelled The misspelled word.
+ * @param string $correct The fixed spelling for the misspelled word.
+ * @throws PspellException
+ *
+ */
+function pspell_store_replacement(int $dictionary_link, string $misspelled, string $correct): void
+{
+ error_clear_last();
+ $result = \pspell_store_replacement($dictionary_link, $misspelled, $correct);
+ if ($result === false) {
+ throw PspellException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ReadlineException;
+
+/**
+ * This function adds a line to the command line history.
+ *
+ * @param string $line The line to be added in the history.
+ * @throws ReadlineException
+ *
+ */
+function readline_add_history(string $line): void
+{
+ error_clear_last();
+ $result = \readline_add_history($line);
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets up a readline callback interface then prints
+ * prompt and immediately returns.
+ * Calling this function twice without removing the previous
+ * callback interface will automatically and conveniently overwrite the old
+ * interface.
+ *
+ * The callback feature is useful when combined with
+ * stream_select as it allows interleaving of IO and
+ * user input, unlike readline.
+ *
+ *
+ * Readline Callback Interface Example
+ *
+ * 10) {
+ * $prompting = false;
+ * readline_callback_handler_remove();
+ * } else {
+ * readline_callback_handler_install("[$c] Enter something: ", 'rl_callback');
+ * }
+ * }
+ *
+ * $c = 1;
+ * $prompting = true;
+ *
+ * readline_callback_handler_install("[$c] Enter something: ", 'rl_callback');
+ *
+ * while ($prompting) {
+ * $w = NULL;
+ * $e = NULL;
+ * $n = stream_select($r = array(STDIN), $w, $e, null);
+ * if ($n && in_array(STDIN, $r)) {
+ * // read a character, will call the callback when a newline is entered
+ * readline_callback_read_char();
+ * }
+ * }
+ *
+ * echo "Prompting disabled. All done.\n";
+ * ?>
+ * ]]>
+ *
+ *
+ *
+ * @param string $prompt The prompt message.
+ * @param callable $callback The callback function takes one parameter; the
+ * user input returned.
+ * @throws ReadlineException
+ *
+ */
+function readline_callback_handler_install(string $prompt, callable $callback): void
+{
+ error_clear_last();
+ $result = \readline_callback_handler_install($prompt, $callback);
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function clears the entire command line history.
+ *
+ * @throws ReadlineException
+ *
+ */
+function readline_clear_history(): void
+{
+ error_clear_last();
+ $result = \readline_clear_history();
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function registers a completion function. This is the same kind of
+ * functionality you'd get if you hit your tab key while using Bash.
+ *
+ * @param callable $function You must supply the name of an existing function which accepts a
+ * partial command line and returns an array of possible matches.
+ * @throws ReadlineException
+ *
+ */
+function readline_completion_function(callable $function): void
+{
+ error_clear_last();
+ $result = \readline_completion_function($function);
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function reads a command history from a file.
+ *
+ * @param string $filename Path to the filename containing the command history.
+ * @throws ReadlineException
+ *
+ */
+function readline_read_history(string $filename = null): void
+{
+ error_clear_last();
+ if ($filename !== null) {
+ $result = \readline_read_history($filename);
+ } else {
+ $result = \readline_read_history();
+ }
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function writes the command history to a file.
+ *
+ * @param string $filename Path to the saved file.
+ * @throws ReadlineException
+ *
+ */
+function readline_write_history(string $filename = null): void
+{
+ error_clear_last();
+ if ($filename !== null) {
+ $result = \readline_write_history($filename);
+ } else {
+ $result = \readline_write_history();
+ }
+ if ($result === false) {
+ throw ReadlineException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\RpminfoException;
+
+/**
+ * Add an additional retrieved tag in subsequent queries.
+ *
+ * @param int $tag One of RPMTAG_* constant, see the rpminfo constants page.
+ * @throws RpminfoException
+ *
+ */
+function rpmaddtag(int $tag): void
+{
+ error_clear_last();
+ $result = \rpmaddtag($tag);
+ if ($result === false) {
+ throw RpminfoException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\RrdException;
+
+/**
+ * Creates the rdd database file.
+ *
+ * @param string $filename Filename for newly created rrd file.
+ * @param array $options Options for rrd create - list of strings. See man page of rrd create
+ * for whole list of options.
+ * @throws RrdException
+ *
+ */
+function rrd_create(string $filename, array $options): void
+{
+ error_clear_last();
+ $result = \rrd_create($filename, $options);
+ if ($result === false) {
+ throw RrdException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SemException;
+
+/**
+ * Checks whether the message queue key exists.
+ *
+ * @param int $key Queue key.
+ * @throws SemException
+ *
+ */
+function msg_queue_exists(int $key): void
+{
+ error_clear_last();
+ $result = \msg_queue_exists($key);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msg_receive will receive the first message from the
+ * specified queue of the type specified by
+ * desiredmsgtype.
+ *
+ * @param resource $queue Message queue resource handle
+ * @param int $desiredmsgtype If desiredmsgtype is 0, the message from the front
+ * of the queue is returned. If desiredmsgtype is
+ * greater than 0, then the first message of that type is returned.
+ * If desiredmsgtype is less than 0, the first
+ * message on the queue with a type less than or equal to the
+ * absolute value of desiredmsgtype will be read.
+ * If no messages match the criteria, your script will wait until a suitable
+ * message arrives on the queue. You can prevent the script from blocking
+ * by specifying MSG_IPC_NOWAIT in the
+ * flags parameter.
+ * @param int|null $msgtype The type of the message that was received will be stored in this
+ * parameter.
+ * @param int $maxsize The maximum size of message to be accepted is specified by the
+ * maxsize; if the message in the queue is larger
+ * than this size the function will fail (unless you set
+ * flags as described below).
+ * @param mixed $message The received message will be stored in message,
+ * unless there were errors receiving the message.
+ * @param bool $unserialize If set to
+ * TRUE, the message is treated as though it was serialized using the
+ * same mechanism as the session module. The message will be unserialized
+ * and then returned to your script. This allows you to easily receive
+ * arrays or complex object structures from other PHP scripts, or if you
+ * are using the WDDX serializer, from any WDDX compatible source.
+ *
+ * If unserialize is FALSE, the message will be
+ * returned as a binary-safe string.
+ * @param int $flags The optional flags allows you to pass flags to the
+ * low-level msgrcv system call. It defaults to 0, but you may specify one
+ * or more of the following values (by adding or ORing them together).
+ *
+ * Flag values for msg_receive
+ *
+ *
+ *
+ * MSG_IPC_NOWAIT
+ * If there are no messages of the
+ * desiredmsgtype, return immediately and do not
+ * wait. The function will fail and return an integer value
+ * corresponding to MSG_ENOMSG.
+ *
+ *
+ *
+ * MSG_EXCEPT
+ * Using this flag in combination with a
+ * desiredmsgtype greater than 0 will cause the
+ * function to receive the first message that is not equal to
+ * desiredmsgtype.
+ *
+ *
+ * MSG_NOERROR
+ *
+ * If the message is longer than maxsize,
+ * setting this flag will truncate the message to
+ * maxsize and will not signal an error.
+ *
+ *
+ *
+ *
+ *
+ * @param int|null $errorcode If the function fails, the optional errorcode
+ * will be set to the value of the system errno variable.
+ * @throws SemException
+ *
+ */
+function msg_receive($queue, int $desiredmsgtype, ?int &$msgtype, int $maxsize, &$message, bool $unserialize = true, int $flags = 0, ?int &$errorcode = null): void
+{
+ error_clear_last();
+ $result = \msg_receive($queue, $desiredmsgtype, $msgtype, $maxsize, $message, $unserialize, $flags, $errorcode);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msg_remove_queue destroys the message queue specified
+ * by the queue. Only use this function when all
+ * processes have finished working with the message queue and you need to
+ * release the system resources held by it.
+ *
+ * @param resource $queue Message queue resource handle
+ * @throws SemException
+ *
+ */
+function msg_remove_queue($queue): void
+{
+ error_clear_last();
+ $result = \msg_remove_queue($queue);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msg_send sends a message of type
+ * msgtype (which MUST be greater than 0) to
+ * the message queue specified by queue.
+ *
+ * @param resource $queue Message queue resource handle
+ * @param int $msgtype The type of the message (MUST be greater than 0)
+ * @param mixed $message The body of the message.
+ *
+ * If serialize set to FALSE is supplied,
+ * MUST be of type: string, integer, float
+ * or bool. In other case a warning will be issued.
+ * @param bool $serialize The optional serialize controls how the
+ * message is sent. serialize
+ * defaults to TRUE which means that the message is
+ * serialized using the same mechanism as the session module before being
+ * sent to the queue. This allows complex arrays and objects to be sent to
+ * other PHP scripts, or if you are using the WDDX serializer, to any WDDX
+ * compatible client.
+ * @param bool $blocking If the message is too large to fit in the queue, your script will wait
+ * until another process reads messages from the queue and frees enough
+ * space for your message to be sent.
+ * This is called blocking; you can prevent blocking by setting the
+ * optional blocking parameter to FALSE, in which
+ * case msg_send will immediately return FALSE if the
+ * message is too big for the queue, and set the optional
+ * errorcode to MSG_EAGAIN,
+ * indicating that you should try to send your message again a little
+ * later on.
+ * @param int|null $errorcode If the function fails, the optional errorcode will be set to the value of the system errno variable.
+ * @throws SemException
+ *
+ */
+function msg_send($queue, int $msgtype, $message, bool $serialize = true, bool $blocking = true, ?int &$errorcode = null): void
+{
+ error_clear_last();
+ $result = \msg_send($queue, $msgtype, $message, $serialize, $blocking, $errorcode);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * msg_set_queue allows you to change the values of the
+ * msg_perm.uid, msg_perm.gid, msg_perm.mode and msg_qbytes fields of the
+ * underlying message queue data structure.
+ *
+ * Changing the data structure will require that PHP be running as the same
+ * user that created the queue, owns the queue (as determined by the
+ * existing msg_perm.xxx fields), or be running with root privileges.
+ * root privileges are required to raise the msg_qbytes values above the
+ * system defined limit.
+ *
+ * @param resource $queue Message queue resource handle
+ * @param array $data You specify the values you require by setting the value of the keys
+ * that you require in the data array.
+ * @throws SemException
+ *
+ */
+function msg_set_queue($queue, array $data): void
+{
+ error_clear_last();
+ $result = \msg_set_queue($queue, $data);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * sem_acquire by default blocks (if necessary) until the
+ * semaphore can be acquired. A process attempting to acquire a semaphore which
+ * it has already acquired will block forever if acquiring the semaphore would
+ * cause its maximum number of semaphore to be exceeded.
+ *
+ * After processing a request, any semaphores acquired by the process but not
+ * explicitly released will be released automatically and a warning will be
+ * generated.
+ *
+ * @param resource $sem_identifier sem_identifier is a semaphore resource,
+ * obtained from sem_get.
+ * @param bool $nowait Specifies if the process shouldn't wait for the semaphore to be acquired.
+ * If set to true, the call will return
+ * false immediately if a semaphore cannot be immediately
+ * acquired.
+ * @throws SemException
+ *
+ */
+function sem_acquire($sem_identifier, bool $nowait = false): void
+{
+ error_clear_last();
+ $result = \sem_acquire($sem_identifier, $nowait);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * sem_get returns an id that can be used to
+ * access the System V semaphore with the given key.
+ *
+ * A second call to sem_get for the same key
+ * will return a different semaphore identifier, but both
+ * identifiers access the same underlying semaphore.
+ *
+ * If key is 0, a new private semaphore
+ * is created for each call to sem_get.
+ *
+ * @param int $key
+ * @param int $max_acquire The number of processes that can acquire the semaphore simultaneously
+ * is set to max_acquire.
+ * @param int $perm The semaphore permissions. Actually this value is
+ * set only if the process finds it is the only process currently
+ * attached to the semaphore.
+ * @param int $auto_release Specifies if the semaphore should be automatically released on request
+ * shutdown.
+ * @return resource Returns a positive semaphore identifier on success.
+ * @throws SemException
+ *
+ */
+function sem_get(int $key, int $max_acquire = 1, int $perm = 0666, int $auto_release = 1)
+{
+ error_clear_last();
+ $result = \sem_get($key, $max_acquire, $perm, $auto_release);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * sem_release releases the semaphore if it
+ * is currently acquired by the calling process, otherwise
+ * a warning is generated.
+ *
+ * After releasing the semaphore, sem_acquire
+ * may be called to re-acquire it.
+ *
+ * @param resource $sem_identifier A Semaphore resource handle as returned by
+ * sem_get.
+ * @throws SemException
+ *
+ */
+function sem_release($sem_identifier): void
+{
+ error_clear_last();
+ $result = \sem_release($sem_identifier);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * sem_remove removes the given semaphore.
+ *
+ * After removing the semaphore, it is no longer accessible.
+ *
+ * @param resource $sem_identifier A semaphore resource identifier as returned
+ * by sem_get.
+ * @throws SemException
+ *
+ */
+function sem_remove($sem_identifier): void
+{
+ error_clear_last();
+ $result = \sem_remove($sem_identifier);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * shm_put_var inserts or updates the
+ * variable with the given
+ * variable_key.
+ *
+ * Warnings (E_WARNING level) will be issued if
+ * shm_identifier is not a valid SysV shared memory
+ * index or if there was not enough shared memory remaining to complete your
+ * request.
+ *
+ * @param resource $shm_identifier A shared memory resource handle as returned by
+ * shm_attach
+ * @param int $variable_key The variable key.
+ * @param mixed $variable The variable. All variable types
+ * that serialize supports may be used: generally
+ * this means all types except for resources and some internal objects
+ * that cannot be serialized.
+ * @throws SemException
+ *
+ */
+function shm_put_var($shm_identifier, int $variable_key, $variable): void
+{
+ error_clear_last();
+ $result = \shm_put_var($shm_identifier, $variable_key, $variable);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Removes a variable with a given variable_key
+ * and frees the occupied memory.
+ *
+ * @param resource $shm_identifier The shared memory identifier as returned by
+ * shm_attach
+ * @param int $variable_key The variable key.
+ * @throws SemException
+ *
+ */
+function shm_remove_var($shm_identifier, int $variable_key): void
+{
+ error_clear_last();
+ $result = \shm_remove_var($shm_identifier, $variable_key);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
+
+
+/**
+ * shm_remove removes the shared memory
+ * shm_identifier. All data will be destroyed.
+ *
+ * @param resource $shm_identifier The shared memory identifier as returned by
+ * shm_attach
+ * @throws SemException
+ *
+ */
+function shm_remove($shm_identifier): void
+{
+ error_clear_last();
+ $result = \shm_remove($shm_identifier);
+ if ($result === false) {
+ throw SemException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SessionException;
+
+/**
+ * session_abort finishes session without saving
+ * data. Thus the original values in session data are kept.
+ *
+ * @throws SessionException
+ *
+ */
+function session_abort(): void
+{
+ error_clear_last();
+ $result = \session_abort();
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * session_decode decodes the serialized session data provided in
+ * $data, and populates the $_SESSION superglobal
+ * with the result.
+ *
+ * By default, the unserialization method used is internal to PHP, and is not the same as unserialize.
+ * The serialization method can be set using session.serialize_handler.
+ *
+ * @param string $data The encoded data to be stored.
+ * @throws SessionException
+ *
+ */
+function session_decode(string $data): void
+{
+ error_clear_last();
+ $result = \session_decode($data);
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * In order to kill the session altogether, the
+ * session ID must also be unset. If a cookie is used to propagate the
+ * session ID (default behavior), then the session cookie must be deleted.
+ * setcookie may be used for that.
+ *
+ * When session.use_strict_mode
+ * is enabled. You do not have to remove obsolete session ID cookie because
+ * session module will not accept session ID cookie when there is no
+ * data associated to the session ID and set new session ID cookie.
+ * Enabling session.use_strict_mode
+ * is recommended for all sites.
+ *
+ * @throws SessionException
+ *
+ */
+function session_destroy(): void
+{
+ error_clear_last();
+ $result = \session_destroy();
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * session_regenerate_id will replace the current
+ * session id with a new one, and keep the current session information.
+ *
+ * When session.use_trans_sid
+ * is enabled, output must be started after session_regenerate_id
+ * call. Otherwise, old session ID is used.
+ *
+ * @param bool $delete_old_session Whether to delete the old associated session file or not.
+ * You should not delete old session if you need to avoid
+ * races caused by deletion or detect/avoid session hijack
+ * attacks.
+ * @throws SessionException
+ *
+ */
+function session_regenerate_id(bool $delete_old_session = false): void
+{
+ error_clear_last();
+ $result = \session_regenerate_id($delete_old_session);
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * session_reset reinitializes a session with
+ * original values stored in session storage. This function requires an active session and
+ * discards changes in $_SESSION.
+ *
+ * @throws SessionException
+ *
+ */
+function session_reset(): void
+{
+ error_clear_last();
+ $result = \session_reset();
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The session_unset function frees all session variables
+ * currently registered.
+ *
+ * @throws SessionException
+ *
+ */
+function session_unset(): void
+{
+ error_clear_last();
+ $result = \session_unset();
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
+
+
+/**
+ * End the current session and store session data.
+ *
+ * Session data is usually stored after your script terminated without the
+ * need to call session_write_close, but as session data
+ * is locked to prevent concurrent writes only one script may operate on a
+ * session at any time. When using framesets together with sessions you will
+ * experience the frames loading one by one due to this locking. You can
+ * reduce the time needed to load all the frames by ending the session as
+ * soon as all changes to session variables are done.
+ *
+ * @throws SessionException
+ *
+ */
+function session_write_close(): void
+{
+ error_clear_last();
+ $result = \session_write_close();
+ if ($result === false) {
+ throw SessionException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ShmopException;
+
+/**
+ * shmop_delete is used to delete a shared memory block.
+ *
+ * @param resource $shmid The shared memory block resource created by
+ * shmop_open
+ * @throws ShmopException
+ *
+ */
+function shmop_delete($shmid): void
+{
+ error_clear_last();
+ $result = \shmop_delete($shmid);
+ if ($result === false) {
+ throw ShmopException::createFromPhpError();
+ }
+}
+
+
+/**
+ * shmop_read will read a string from shared memory block.
+ *
+ * @param resource $shmid The shared memory block identifier created by
+ * shmop_open
+ * @param int $start Offset from which to start reading
+ * @param int $count The number of bytes to read.
+ * 0 reads shmop_size($shmid) - $start bytes.
+ * @return string Returns the data.
+ * @throws ShmopException
+ *
+ */
+function shmop_read($shmid, int $start, int $count): string
+{
+ error_clear_last();
+ $result = \shmop_read($shmid, $start, $count);
+ if ($result === false) {
+ throw ShmopException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * shmop_write will write a string into shared memory block.
+ *
+ * @param resource $shmid The shared memory block identifier created by
+ * shmop_open
+ * @param string $data A string to write into shared memory block
+ * @param int $offset Specifies where to start writing data inside the shared memory
+ * segment.
+ * @return int The size of the written data.
+ * @throws ShmopException
+ *
+ */
+function shmop_write($shmid, string $data, int $offset): int
+{
+ error_clear_last();
+ $result = \shmop_write($shmid, $data, $offset);
+ if ($result === false) {
+ throw ShmopException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SimplexmlException;
+
+/**
+ * This function takes a node of a DOM
+ * document and makes it into a SimpleXML node. This new object can
+ * then be used as a native SimpleXML element.
+ *
+ * @param \DOMNode $node A DOM Element node
+ * @param string $class_name You may use this optional parameter so that
+ * simplexml_import_dom will return an object of
+ * the specified class. That class should extend the
+ * SimpleXMLElement class.
+ * @return \SimpleXMLElement Returns a SimpleXMLElement.
+ * @throws SimplexmlException
+ *
+ */
+function simplexml_import_dom(\DOMNode $node, string $class_name = "SimpleXMLElement"): \SimpleXMLElement
+{
+ error_clear_last();
+ $result = \simplexml_import_dom($node, $class_name);
+ if ($result === false) {
+ throw SimplexmlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Convert the well-formed XML document in the given file to an object.
+ *
+ * @param string $filename Path to the XML file
+ *
+ * Libxml 2 unescapes the URI, so if you want to pass e.g.
+ * b&c as the URI parameter a,
+ * you have to call
+ * simplexml_load_file(rawurlencode('http://example.com/?a=' .
+ * urlencode('b&c'))). Since PHP 5.1.0 you don't need to do
+ * this because PHP will do it for you.
+ * @param string $class_name You may use this optional parameter so that
+ * simplexml_load_file will return an object of
+ * the specified class. That class should extend the
+ * SimpleXMLElement class.
+ * @param int $options Since PHP 5.1.0 and Libxml 2.6.0, you may also use the
+ * options parameter to specify additional Libxml parameters.
+ * @param string $ns Namespace prefix or URI.
+ * @param bool $is_prefix TRUE if ns is a prefix, FALSE if it's a URI;
+ * defaults to FALSE.
+ * @return \SimpleXMLElement Returns an object of class SimpleXMLElement with
+ * properties containing the data held within the XML document.
+ * @throws SimplexmlException
+ *
+ */
+function simplexml_load_file(string $filename, string $class_name = "SimpleXMLElement", int $options = 0, string $ns = "", bool $is_prefix = false): \SimpleXMLElement
+{
+ error_clear_last();
+ $result = \simplexml_load_file($filename, $class_name, $options, $ns, $is_prefix);
+ if ($result === false) {
+ throw SimplexmlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Takes a well-formed XML string and returns it as an object.
+ *
+ * @param string $data A well-formed XML string
+ * @param string $class_name You may use this optional parameter so that
+ * simplexml_load_string will return an object of
+ * the specified class. That class should extend the
+ * SimpleXMLElement class.
+ * @param int $options Since PHP 5.1.0 and Libxml 2.6.0, you may also use the
+ * options parameter to specify additional Libxml parameters.
+ * @param string $ns Namespace prefix or URI.
+ * @param bool $is_prefix TRUE if ns is a prefix, FALSE if it's a URI;
+ * defaults to FALSE.
+ * @return \SimpleXMLElement Returns an object of class SimpleXMLElement with
+ * properties containing the data held within the xml document.
+ * @throws SimplexmlException
+ *
+ */
+function simplexml_load_string(string $data, string $class_name = "SimpleXMLElement", int $options = 0, string $ns = "", bool $is_prefix = false): \SimpleXMLElement
+{
+ error_clear_last();
+ $result = \simplexml_load_string($data, $class_name, $options, $ns, $is_prefix);
+ if ($result === false) {
+ throw SimplexmlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SocketsException;
+
+/**
+ * After the socket socket has been created
+ * using socket_create, bound to a name with
+ * socket_bind, and told to listen for connections
+ * with socket_listen, this function will accept
+ * incoming connections on that socket. Once a successful connection
+ * is made, a new socket resource is returned, which may be used
+ * for communication. If there are multiple connections queued on
+ * the socket, the first will be used. If there are no pending
+ * connections, socket_accept will block until
+ * a connection becomes present. If socket
+ * has been made non-blocking using
+ * socket_set_blocking or
+ * socket_set_nonblock, FALSE will be returned.
+ *
+ * The socket resource returned by
+ * socket_accept may not be used to accept new
+ * connections. The original listening socket
+ * socket, however, remains open and may be
+ * reused.
+ *
+ * @param resource $socket A valid socket resource created with socket_create.
+ * @return resource Returns a new socket resource on success. The actual
+ * error code can be retrieved by calling
+ * socket_last_error. This error code may be passed to
+ * socket_strerror to get a textual explanation of the
+ * error.
+ * @throws SocketsException
+ *
+ */
+function socket_accept($socket)
+{
+ error_clear_last();
+ $result = \socket_accept($socket);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a Socket resource, and bind it to the provided AddrInfo resource. The return
+ * value of this function may be used with socket_listen.
+ *
+ * @param resource $addr Resource created from socket_addrinfo_lookup.
+ * @return resource Returns a Socket resource on success.
+ * @throws SocketsException
+ *
+ */
+function socket_addrinfo_bind($addr)
+{
+ error_clear_last();
+ $result = \socket_addrinfo_bind($addr);
+ if ($result === null) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Create a Socket resource, and connect it to the provided AddrInfo resource. The return
+ * value of this function may be used with the rest of the socket functions.
+ *
+ * @param resource $addr Resource created from socket_addrinfo_lookup
+ * @return resource Returns a Socket resource on success.
+ * @throws SocketsException
+ *
+ */
+function socket_addrinfo_connect($addr)
+{
+ error_clear_last();
+ $result = \socket_addrinfo_connect($addr);
+ if ($result === null) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Binds the name given in address to the socket
+ * described by socket. This has to be done before
+ * a connection is be established using socket_connect
+ * or socket_listen.
+ *
+ * @param resource $socket A valid socket resource created with socket_create.
+ * @param string $address If the socket is of the AF_INET family, the
+ * address is an IP in dotted-quad notation
+ * (e.g. 127.0.0.1).
+ *
+ * If the socket is of the AF_UNIX family, the
+ * address is the path of a
+ * Unix-domain socket (e.g. /tmp/my.sock).
+ * @param int $port The port parameter is only used when
+ * binding an AF_INET socket, and designates
+ * the port on which to listen for connections.
+ * @throws SocketsException
+ *
+ */
+function socket_bind($socket, string $address, int $port = 0): void
+{
+ error_clear_last();
+ $result = \socket_bind($socket, $address, $port);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Initiate a connection to address using the socket resource
+ * socket, which must be a valid socket
+ * resource created with socket_create.
+ *
+ * @param resource $socket
+ * @param string $address The address parameter is either an IPv4 address
+ * in dotted-quad notation (e.g. 127.0.0.1) if
+ * socket is AF_INET, a valid
+ * IPv6 address (e.g. ::1) if IPv6 support is enabled and
+ * socket is AF_INET6
+ * or the pathname of a Unix domain socket, if the socket family is
+ * AF_UNIX.
+ * @param int $port The port parameter is only used and is mandatory
+ * when connecting to an AF_INET or an
+ * AF_INET6 socket, and designates
+ * the port on the remote host to which a connection should be made.
+ * @throws SocketsException
+ *
+ */
+function socket_connect($socket, string $address, int $port = 0): void
+{
+ error_clear_last();
+ $result = \socket_connect($socket, $address, $port);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * socket_create_listen creates a new socket resource of
+ * type AF_INET listening on all
+ * local interfaces on the given port waiting for new connections.
+ *
+ * This function is meant to ease the task of creating a new socket which
+ * only listens to accept new connections.
+ *
+ * @param int $port The port on which to listen on all interfaces.
+ * @param int $backlog The backlog parameter defines the maximum length
+ * the queue of pending connections may grow to.
+ * SOMAXCONN may be passed as
+ * backlog parameter, see
+ * socket_listen for more information.
+ * @return resource socket_create_listen returns a new socket resource
+ * on success. The error code can be retrieved with
+ * socket_last_error. This code may be passed to
+ * socket_strerror to get a textual explanation of the
+ * error.
+ * @throws SocketsException
+ *
+ */
+function socket_create_listen(int $port, int $backlog = 128)
+{
+ error_clear_last();
+ $result = \socket_create_listen($port, $backlog);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * socket_create_pair creates two connected and
+ * indistinguishable sockets, and stores them in fd.
+ * This function is commonly used in IPC (InterProcess Communication).
+ *
+ * @param int $domain The domain parameter specifies the protocol
+ * family to be used by the socket. See socket_create
+ * for the full list.
+ * @param int $type The type parameter selects the type of communication
+ * to be used by the socket. See socket_create for the
+ * full list.
+ * @param int $protocol The protocol parameter sets the specific
+ * protocol within the specified domain to be used
+ * when communicating on the returned socket. The proper value can be retrieved by
+ * name by using getprotobyname. If
+ * the desired protocol is TCP, or UDP the corresponding constants
+ * SOL_TCP, and SOL_UDP
+ * can also be used.
+ *
+ * See socket_create for the full list of supported
+ * protocols.
+ * @param resource[]|null $fd Reference to an array in which the two socket resources will be inserted.
+ * @throws SocketsException
+ *
+ */
+function socket_create_pair(int $domain, int $type, int $protocol, ?iterable &$fd): void
+{
+ error_clear_last();
+ $result = \socket_create_pair($domain, $type, $protocol, $fd);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates and returns a socket resource, also referred to as an endpoint
+ * of communication. A typical network connection is made up of 2 sockets, one
+ * performing the role of the client, and another performing the role of the server.
+ *
+ * @param int $domain The domain parameter specifies the protocol
+ * family to be used by the socket.
+ * @param int $type The type parameter selects the type of communication
+ * to be used by the socket.
+ * @param int $protocol The protocol parameter sets the specific
+ * protocol within the specified domain to be used
+ * when communicating on the returned socket. The proper value can be
+ * retrieved by name by using getprotobyname. If
+ * the desired protocol is TCP, or UDP the corresponding constants
+ * SOL_TCP, and SOL_UDP
+ * can also be used.
+ * @return resource socket_create returns a socket resource on success. The actual error code can be retrieved by calling
+ * socket_last_error. This error code may be passed to
+ * socket_strerror to get a textual explanation of the
+ * error.
+ * @throws SocketsException
+ *
+ */
+function socket_create(int $domain, int $type, int $protocol)
+{
+ error_clear_last();
+ $result = \socket_create($domain, $type, $protocol);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $socket
+ * @return resource Return resource.
+ * @throws SocketsException
+ *
+ */
+function socket_export_stream($socket)
+{
+ error_clear_last();
+ $result = \socket_export_stream($socket);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The socket_get_option function retrieves the value for
+ * the option specified by the optname parameter for the
+ * specified socket.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param int $level The level parameter specifies the protocol
+ * level at which the option resides. For example, to retrieve options at
+ * the socket level, a level parameter of
+ * SOL_SOCKET would be used. Other levels, such as
+ * TCP, can be used by
+ * specifying the protocol number of that level. Protocol numbers can be
+ * found by using the getprotobyname function.
+ * @param int $optname Reports whether the socket lingers on
+ * socket_close if data is present. By default,
+ * when the socket is closed, it attempts to send all unsent data.
+ * In the case of a connection-oriented socket,
+ * socket_close will wait for its peer to
+ * acknowledge the data.
+ *
+ * If l_onoff is non-zero and
+ * l_linger is zero, all the
+ * unsent data will be discarded and RST (reset) is sent to the
+ * peer in the case of a connection-oriented socket.
+ *
+ * On the other hand, if l_onoff is
+ * non-zero and l_linger is non-zero,
+ * socket_close will block until all the data
+ * is sent or the time specified in l_linger
+ * elapses. If the socket is non-blocking,
+ * socket_close will fail and return an error.
+ * @return mixed Returns the value of the given options.
+ * @throws SocketsException
+ *
+ */
+function socket_get_option($socket, int $level, int $optname)
+{
+ error_clear_last();
+ $result = \socket_get_option($socket, $level, $optname);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Queries the remote side of the given socket which may either result in
+ * host/port or in a Unix filesystem path, dependent on its type.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param string $address If the given socket is of type AF_INET or
+ * AF_INET6, socket_getpeername
+ * will return the peers (remote) IP address in
+ * appropriate notation (e.g. 127.0.0.1 or
+ * fe80::1) in the address
+ * parameter and, if the optional port parameter is
+ * present, also the associated port.
+ *
+ * If the given socket is of type AF_UNIX,
+ * socket_getpeername will return the Unix filesystem
+ * path (e.g. /var/run/daemon.sock) in the
+ * address parameter.
+ * @param int|null $port If given, this will hold the port associated to
+ * address.
+ * @throws SocketsException
+ *
+ */
+function socket_getpeername($socket, string &$address, ?int &$port = null): void
+{
+ error_clear_last();
+ $result = \socket_getpeername($socket, $address, $port);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param string|null $addr If the given socket is of type AF_INET
+ * or AF_INET6, socket_getsockname
+ * will return the local IP address in appropriate notation (e.g.
+ * 127.0.0.1 or fe80::1) in the
+ * address parameter and, if the optional
+ * port parameter is present, also the associated port.
+ *
+ * If the given socket is of type AF_UNIX,
+ * socket_getsockname will return the Unix filesystem
+ * path (e.g. /var/run/daemon.sock) in the
+ * address parameter.
+ * @param int|null $port If provided, this will hold the associated port.
+ * @throws SocketsException
+ *
+ */
+function socket_getsockname($socket, ?string &$addr, ?int &$port = null): void
+{
+ error_clear_last();
+ $result = \socket_getsockname($socket, $addr, $port);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Imports a stream that encapsulates a socket into a socket extension resource.
+ *
+ * @param resource $stream The stream resource to import.
+ * @return resource|false Returns FALSE.
+ * @throws SocketsException
+ *
+ */
+function socket_import_stream($stream)
+{
+ error_clear_last();
+ $result = \socket_import_stream($stream);
+ if ($result === null) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * After the socket socket has been created
+ * using socket_create and bound to a name with
+ * socket_bind, it may be told to listen for incoming
+ * connections on socket.
+ *
+ * socket_listen is applicable only to sockets of
+ * type SOCK_STREAM or
+ * SOCK_SEQPACKET.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_addrinfo_bind
+ * @param int $backlog A maximum of backlog incoming connections will be
+ * queued for processing. If a connection request arrives with the queue
+ * full the client may receive an error with an indication of
+ * ECONNREFUSED, or, if the underlying protocol supports
+ * retransmission, the request may be ignored so that retries may succeed.
+ *
+ * The maximum number passed to the backlog
+ * parameter highly depends on the underlying platform. On Linux, it is
+ * silently truncated to SOMAXCONN. On win32, if
+ * passed SOMAXCONN, the underlying service provider
+ * responsible for the socket will set the backlog to a maximum
+ * reasonable value. There is no standard provision to
+ * find out the actual backlog value on this platform.
+ * @throws SocketsException
+ *
+ */
+function socket_listen($socket, int $backlog = 0): void
+{
+ error_clear_last();
+ $result = \socket_listen($socket, $backlog);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The function socket_read reads from the socket
+ * resource socket created by the
+ * socket_create or
+ * socket_accept functions.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param int $length The maximum number of bytes read is specified by the
+ * length parameter. Otherwise you can use
+ * \r, \n,
+ * or \0 to end reading (depending on the type
+ * parameter, see below).
+ * @param int $type Optional type parameter is a named constant:
+ *
+ *
+ *
+ * PHP_BINARY_READ (Default) - use the system
+ * recv() function. Safe for reading binary data.
+ *
+ *
+ *
+ *
+ * PHP_NORMAL_READ - reading stops at
+ * \n or \r.
+ *
+ *
+ *
+ * @return string socket_read returns the data as a string on success (including if the remote host has closed the
+ * connection). The error code can be retrieved with
+ * socket_last_error. This code may be passed to
+ * socket_strerror to get a textual representation of
+ * the error.
+ * @throws SocketsException
+ *
+ */
+function socket_read($socket, int $length, int $type = PHP_BINARY_READ): string
+{
+ error_clear_last();
+ $result = \socket_read($socket, $length, $type);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The function socket_send sends
+ * len bytes to the socket
+ * socket from buf.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param string $buf A buffer containing the data that will be sent to the remote host.
+ * @param int $len The number of bytes that will be sent to the remote host from
+ * buf.
+ * @param int $flags The value of flags can be any combination of
+ * the following flags, joined with the binary OR (|)
+ * operator.
+ *
+ * Possible values for flags
+ *
+ *
+ *
+ * MSG_OOB
+ *
+ * Send OOB (out-of-band) data.
+ *
+ *
+ *
+ * MSG_EOR
+ *
+ * Indicate a record mark. The sent data completes the record.
+ *
+ *
+ *
+ * MSG_EOF
+ *
+ * Close the sender side of the socket and include an appropriate
+ * notification of this at the end of the sent data. The sent data
+ * completes the transaction.
+ *
+ *
+ *
+ * MSG_DONTROUTE
+ *
+ * Bypass routing, use direct interface.
+ *
+ *
+ *
+ *
+ *
+ * @return int socket_send returns the number of bytes sent.
+ * @throws SocketsException
+ *
+ */
+function socket_send($socket, string $buf, int $len, int $flags): int
+{
+ error_clear_last();
+ $result = \socket_send($socket, $buf, $len, $flags);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $socket
+ * @param array $message
+ * @param int $flags
+ * @return int Returns the number of bytes sent.
+ * @throws SocketsException
+ *
+ */
+function socket_sendmsg($socket, array $message, int $flags = 0): int
+{
+ error_clear_last();
+ $result = \socket_sendmsg($socket, $message, $flags);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The function socket_sendto sends
+ * len bytes from buf
+ * through the socket socket to the
+ * port at the address addr.
+ *
+ * @param resource $socket A valid socket resource created using socket_create.
+ * @param string $buf The sent data will be taken from buffer buf.
+ * @param int $len len bytes from buf will be
+ * sent.
+ * @param int $flags The value of flags can be any combination of
+ * the following flags, joined with the binary OR (|)
+ * operator.
+ *
+ * Possible values for flags
+ *
+ *
+ *
+ * MSG_OOB
+ *
+ * Send OOB (out-of-band) data.
+ *
+ *
+ *
+ * MSG_EOR
+ *
+ * Indicate a record mark. The sent data completes the record.
+ *
+ *
+ *
+ * MSG_EOF
+ *
+ * Close the sender side of the socket and include an appropriate
+ * notification of this at the end of the sent data. The sent data
+ * completes the transaction.
+ *
+ *
+ *
+ * MSG_DONTROUTE
+ *
+ * Bypass routing, use direct interface.
+ *
+ *
+ *
+ *
+ *
+ * @param string $addr IP address of the remote host.
+ * @param int $port port is the remote port number at which the data
+ * will be sent.
+ * @return int socket_sendto returns the number of bytes sent to the
+ * remote host.
+ * @throws SocketsException
+ *
+ */
+function socket_sendto($socket, string $buf, int $len, int $flags, string $addr, int $port = 0): int
+{
+ error_clear_last();
+ $result = \socket_sendto($socket, $buf, $len, $flags, $addr, $port);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * The socket_set_block function removes the
+ * O_NONBLOCK flag on the socket specified by the
+ * socket parameter.
+ *
+ * When an operation (e.g. receive, send, connect, accept, ...) is performed on
+ * a blocking socket, the script will pause its execution until it receives
+ * a signal or it can perform the operation.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @throws SocketsException
+ *
+ */
+function socket_set_block($socket): void
+{
+ error_clear_last();
+ $result = \socket_set_block($socket);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The socket_set_nonblock function sets the
+ * O_NONBLOCK flag on the socket specified by
+ * the socket parameter.
+ *
+ * When an operation (e.g. receive, send, connect, accept, ...) is performed on
+ * a non-blocking socket, the script will not pause its execution until it receives a
+ * signal or it can perform the operation. Rather, if the operation would result
+ * in a block, the called function will fail.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @throws SocketsException
+ *
+ */
+function socket_set_nonblock($socket): void
+{
+ error_clear_last();
+ $result = \socket_set_nonblock($socket);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The socket_set_option function sets the option
+ * specified by the optname parameter, at the
+ * specified protocol level, to the value pointed to
+ * by the optval parameter for the
+ * socket.
+ *
+ * @param resource $socket A valid socket resource created with socket_create
+ * or socket_accept.
+ * @param int $level The level parameter specifies the protocol
+ * level at which the option resides. For example, to retrieve options at
+ * the socket level, a level parameter of
+ * SOL_SOCKET would be used. Other levels, such as
+ * TCP, can be used by specifying the protocol number of that level.
+ * Protocol numbers can be found by using the
+ * getprotobyname function.
+ * @param int $optname The available socket options are the same as those for the
+ * socket_get_option function.
+ * @param int|string|array $optval The option value.
+ * @throws SocketsException
+ *
+ */
+function socket_set_option($socket, int $level, int $optname, $optval): void
+{
+ error_clear_last();
+ $result = \socket_set_option($socket, $level, $optname, $optval);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * The socket_shutdown function allows you to stop
+ * incoming, outgoing or all data (the default) from being sent through the
+ * socket
+ *
+ * @param resource $socket A valid socket resource created with socket_create.
+ * @param int $how The value of how can be one of the following:
+ *
+ * possible values for how
+ *
+ *
+ *
+ * 0
+ *
+ * Shutdown socket reading
+ *
+ *
+ *
+ * 1
+ *
+ * Shutdown socket writing
+ *
+ *
+ *
+ * 2
+ *
+ * Shutdown socket reading and writing
+ *
+ *
+ *
+ *
+ *
+ * @throws SocketsException
+ *
+ */
+function socket_shutdown($socket, int $how = 2): void
+{
+ error_clear_last();
+ $result = \socket_shutdown($socket, $how);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Exports the WSAPROTOCOL_INFO structure into shared memory and returns
+ * an identifier to be used with socket_wsaprotocol_info_import. The
+ * exported ID is only valid for the given target_pid.
+ *
+ * @param resource $socket A valid socket resource.
+ * @param int $target_pid The ID of the process which will import the socket.
+ * @return string Returns an identifier to be used for the import
+ * @throws SocketsException
+ *
+ */
+function socket_wsaprotocol_info_export($socket, int $target_pid): string
+{
+ error_clear_last();
+ $result = \socket_wsaprotocol_info_export($socket, $target_pid);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Imports a socket which has formerly been exported from another process.
+ *
+ * @param string $info_id The ID which has been returned by a former call to
+ * socket_wsaprotocol_info_export.
+ * @return resource Returns the socket resource
+ * @throws SocketsException
+ *
+ */
+function socket_wsaprotocol_info_import(string $info_id)
+{
+ error_clear_last();
+ $result = \socket_wsaprotocol_info_import($info_id);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Releases the shared memory corresponding to the given info_id.
+ *
+ * @param string $info_id The ID which has been returned by a former call to
+ * socket_wsaprotocol_info_export.
+ * @throws SocketsException
+ *
+ */
+function socket_wsaprotocol_info_release(string $info_id): void
+{
+ error_clear_last();
+ $result = \socket_wsaprotocol_info_release($info_id);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SodiumException;
+
+/**
+ * Uses a CPU- and memory-hard hash algorithm along with a randomly-generated salt, and memory and CPU limits to generate an ASCII-encoded hash suitable for password storage.
+ *
+ * @param string $password string; The password to generate a hash for.
+ * @param int $opslimit Represents a maximum amount of computations to perform. Raising this number will make the function require more CPU cycles to compute a key. There are constants available to set the operations limit to appropriate values depending on intended use, in order of strength: SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE and SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE.
+ * @param int $memlimit The maximum amount of RAM that the function will use, in bytes. There are constants to help you choose an appropriate value, in order of size: SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE, and SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE. Typically these should be paired with the matching opslimit values.
+ * @return string Returns the hashed password.
+ *
+ * In order to produce the same password hash from the same password, the same values for opslimit and memlimit must be used. These are embedded within the generated hash, so
+ * everything that's needed to verify the hash is included. This allows
+ * the sodium_crypto_pwhash_str_verify function to verify the hash without
+ * needing separate storage for the other parameters.
+ * @throws SodiumException
+ *
+ */
+function sodium_crypto_pwhash_str(string $password, int $opslimit, int $memlimit): string
+{
+ error_clear_last();
+ $result = \sodium_crypto_pwhash_str($password, $opslimit, $memlimit);
+ if ($result === false) {
+ throw SodiumException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function provides low-level access to libsodium's crypto_pwhash key derivation function. Unless you have specific reason to use this function, you should use sodium_crypto_pwhash_str or password_hash functions instead.
+ *
+ * @param int $length integer; The length of the password hash to generate, in bytes.
+ * @param string $password string; The password to generate a hash for.
+ * @param string $salt string A salt to add to the password before hashing. The salt should be unpredictable, ideally generated from a good random mumber source such as random_bytes, and have a length of at least SODIUM_CRYPTO_PWHASH_SALTBYTES bytes.
+ * @param int $opslimit Represents a maximum amount of computations to perform. Raising this number will make the function require more CPU cycles to compute a key. There are some constants available to set the operations limit to appropriate values depending on intended use, in order of strength: SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE and SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE.
+ * @param int $memlimit The maximum amount of RAM that the function will use, in bytes. There are constants to help you choose an appropriate value, in order of size: SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE, and SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE. Typically these should be paired with the matching opslimit values.
+ * @param int $alg integer A number indicating the hash algorithm to use. By default SODIUM_CRYPTO_PWHASH_ALG_DEFAULT (the currently recommended algorithm, which can change from one version of libsodium to another), or explicitly using SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13, representing the Argon2id algorithm version 1.3.
+ * @return string Returns the derived key. The return value is a binary string of the hash, not an ASCII-encoded representation, and does not contain additional information about the parameters used to create the hash, so you will need to keep that information if you are ever going to verify the password in future. Use sodium_crypto_pwhash_str to avoid needing to do all that.
+ * @throws SodiumException
+ *
+ */
+function sodium_crypto_pwhash(int $length, string $password, string $salt, int $opslimit, int $memlimit, int $alg = null): string
+{
+ error_clear_last();
+ if ($alg !== null) {
+ $result = \sodium_crypto_pwhash($length, $password, $salt, $opslimit, $memlimit, $alg);
+ } else {
+ $result = \sodium_crypto_pwhash($length, $password, $salt, $opslimit, $memlimit);
+ }
+ if ($result === false) {
+ throw SodiumException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SolrException;
+
+/**
+ * This function returns the current version of the extension as a string.
+ *
+ * @return string It returns a string on success.
+ * @throws SolrException
+ *
+ */
+function solr_get_version(): string
+{
+ error_clear_last();
+ $result = \solr_get_version();
+ if ($result === false) {
+ throw SolrException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SplException;
+
+/**
+ * This function returns an array with the names of the interfaces that the
+ * given class and its parents implement.
+ *
+ * @param mixed $class An object (class instance) or a string (class or interface name).
+ * @param bool $autoload Whether to allow this function to load the class automatically through
+ * the __autoload magic method.
+ * @return array An array on success.
+ * @throws SplException
+ *
+ */
+function class_implements($class, bool $autoload = true): array
+{
+ error_clear_last();
+ $result = \class_implements($class, $autoload);
+ if ($result === false) {
+ throw SplException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns an array with the name of the parent classes of
+ * the given class.
+ *
+ * @param mixed $class An object (class instance) or a string (class name).
+ * @param bool $autoload Whether to allow this function to load the class automatically through
+ * the __autoload magic method.
+ * @return array An array on success.
+ * @throws SplException
+ *
+ */
+function class_parents($class, bool $autoload = true): array
+{
+ error_clear_last();
+ $result = \class_parents($class, $autoload);
+ if ($result === false) {
+ throw SplException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns an array with the names of the traits that the
+ * given class uses. This does however not include
+ * any traits used by a parent class.
+ *
+ * @param mixed $class An object (class instance) or a string (class name).
+ * @param bool $autoload Whether to allow this function to load the class automatically through
+ * the __autoload magic method.
+ * @return array An array on success.
+ * @throws SplException
+ *
+ */
+function class_uses($class, bool $autoload = true): array
+{
+ error_clear_last();
+ $result = \class_uses($class, $autoload);
+ if ($result === false) {
+ throw SplException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Register a function with the spl provided __autoload queue. If the queue
+ * is not yet activated it will be activated.
+ *
+ * If your code has an existing __autoload function then
+ * this function must be explicitly registered on the __autoload queue. This
+ * is because spl_autoload_register will effectively
+ * replace the engine cache for the __autoload function
+ * by either spl_autoload or
+ * spl_autoload_call.
+ *
+ * If there must be multiple autoload functions, spl_autoload_register
+ * allows for this. It effectively creates a queue of autoload functions, and
+ * runs through each of them in the order they are defined. By contrast,
+ * __autoload may only be defined once.
+ *
+ * @param callable(string):void $autoload_function The autoload function being registered.
+ * If no parameter is provided, then the default implementation of
+ * spl_autoload will be registered.
+ * @param bool $throw This parameter specifies whether
+ * spl_autoload_register should throw
+ * exceptions when the autoload_function
+ * cannot be registered.
+ * @param bool $prepend If true, spl_autoload_register will prepend
+ * the autoloader on the autoload queue instead of appending it.
+ * @throws SplException
+ *
+ */
+function spl_autoload_register(callable $autoload_function = null, bool $throw = true, bool $prepend = false): void
+{
+ error_clear_last();
+ if ($prepend !== false) {
+ $result = \spl_autoload_register($autoload_function, $throw, $prepend);
+ } elseif ($throw !== true) {
+ $result = \spl_autoload_register($autoload_function, $throw);
+ } elseif ($autoload_function !== null) {
+ $result = \spl_autoload_register($autoload_function);
+ } else {
+ $result = \spl_autoload_register();
+ }
+ if ($result === false) {
+ throw SplException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Removes a function from the autoload queue. If the queue
+ * is activated and empty after removing the given function then it will
+ * be deactivated.
+ *
+ * When this function results in the queue being deactivated, any
+ * __autoload function that previously existed will not be reactivated.
+ *
+ * @param mixed $autoload_function The autoload function being unregistered.
+ * @throws SplException
+ *
+ */
+function spl_autoload_unregister($autoload_function): void
+{
+ error_clear_last();
+ $result = \spl_autoload_unregister($autoload_function);
+ if ($result === false) {
+ throw SplException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SqlsrvException;
+
+/**
+ * The transaction begun by sqlsrv_begin_transaction includes
+ * all statements that were executed after the call to
+ * sqlsrv_begin_transaction and before calls to
+ * sqlsrv_rollback or sqlsrv_commit.
+ * Explicit transactions should be started and committed or rolled back using
+ * these functions instead of executing SQL statements that begin and commit/roll
+ * back transactions. For more information, see
+ * SQLSRV Transactions.
+ *
+ * @param resource $conn The connection resource returned by a call to sqlsrv_connect.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_begin_transaction($conn): void
+{
+ error_clear_last();
+ $result = \sqlsrv_begin_transaction($conn);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Cancels a statement. Any results associated with the statement that have not
+ * been consumed are deleted. After sqlsrv_cancel has been
+ * called, the specified statement can be re-executed if it was created with
+ * sqlsrv_prepare. Calling sqlsrv_cancel
+ * is not necessary if all the results associated with the statement have been
+ * consumed.
+ *
+ * @param resource $stmt The statement resource to be cancelled.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_cancel($stmt): void
+{
+ error_clear_last();
+ $result = \sqlsrv_cancel($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Returns information about the client and specified connection
+ *
+ * @param resource $conn The connection about which information is returned.
+ * @return array Returns an associative array with keys described in the table below.
+ *
+ * Array returned by sqlsrv_client_info
+ *
+ *
+ *
+ * Key
+ * Description
+ *
+ *
+ *
+ *
+ * DriverDllName
+ * SQLNCLI10.DLL
+ *
+ *
+ * DriverODBCVer
+ * ODBC version (xx.yy)
+ *
+ *
+ * DriverVer
+ * SQL Server Native Client DLL version (10.5.xxx)
+ *
+ *
+ * ExtensionVer
+ * php_sqlsrv.dll version (2.0.xxx.x)
+ *
+ *
+ *
+ *
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_client_info($conn): array
+{
+ error_clear_last();
+ $result = \sqlsrv_client_info($conn);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Closes an open connection and releases resourses associated with the connection.
+ *
+ * @param resource $conn The connection to be closed.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_close($conn): void
+{
+ error_clear_last();
+ $result = \sqlsrv_close($conn);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Commits a transaction that was begun with sqlsrv_begin_transaction.
+ * The connection is returned to auto-commit mode after sqlsrv_commit
+ * is called. The transaction that is committed includes all statements that were
+ * executed after the call to sqlsrv_begin_transaction.
+ * Explicit transactions should be started and committed or rolled back using these
+ * functions instead of executing SQL statements that begin and commit/roll back
+ * transactions. For more information, see
+ * SQLSRV Transactions.
+ *
+ * @param resource $conn The connection on which the transaction is to be committed.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_commit($conn): void
+{
+ error_clear_last();
+ $result = \sqlsrv_commit($conn);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Changes the driver error handling and logging configurations.
+ *
+ * @param string $setting The name of the setting to set. The possible values are
+ * "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity".
+ * @param mixed $value The value of the specified setting. The following table shows possible values:
+ *
+ * Error and Logging Setting Options
+ *
+ *
+ *
+ * Setting
+ * Options
+ *
+ *
+ *
+ *
+ * WarningsReturnAsErrors
+ * 1 (TRUE) or 0 (FALSE)
+ *
+ *
+ * LogSubsystems
+ * SQLSRV_LOG_SYSTEM_ALL (-1)
+ * SQLSRV_LOG_SYSTEM_CONN (2)
+ * SQLSRV_LOG_SYSTEM_INIT (1)
+ * SQLSRV_LOG_SYSTEM_OFF (0)
+ * SQLSRV_LOG_SYSTEM_STMT (4)
+ * SQLSRV_LOG_SYSTEM_UTIL (8)
+ *
+ *
+ * LogSeverity
+ * SQLSRV_LOG_SEVERITY_ALL (-1)
+ * SQLSRV_LOG_SEVERITY_ERROR (1)
+ * SQLSRV_LOG_SEVERITY_NOTICE (4)
+ * SQLSRV_LOG_SEVERITY_WARNING (2)
+ *
+ *
+ *
+ *
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_configure(string $setting, $value): void
+{
+ error_clear_last();
+ $result = \sqlsrv_configure($setting, $value);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Executes a statement prepared with sqlsrv_prepare. This
+ * function is ideal for executing a prepared statement multiple times with
+ * different parameter values.
+ *
+ * @param resource $stmt A statement resource returned by sqlsrv_prepare.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_execute($stmt): void
+{
+ error_clear_last();
+ $result = \sqlsrv_execute($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Frees all resources for the specified statement. The statement cannot be used
+ * after sqlsrv_free_stmt has been called on it. If
+ * sqlsrv_free_stmt is called on an in-progress statement
+ * that alters server state, statement execution is terminated and the statement
+ * is rolled back.
+ *
+ * @param resource $stmt The statement for which resources are freed.
+ * Note that NULL is a valid parameter value. This allows the function to be
+ * called multiple times in a script.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_free_stmt($stmt): void
+{
+ error_clear_last();
+ $result = \sqlsrv_free_stmt($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Gets field data from the currently selected row. Fields must be accessed in
+ * order. Field indices start at 0.
+ *
+ * @param resource $stmt A statement resource returned by sqlsrv_query or
+ * sqlsrv_execute.
+ * @param int $fieldIndex The index of the field to be retrieved. Field indices start at 0. Fields
+ * must be accessed in order. i.e. If you access field index 1, then field
+ * index 0 will not be available.
+ * @param int $getAsType The PHP data type for the returned field data. If this parameter is not
+ * set, the field data will be returned as its default PHP data type.
+ * For information about default PHP data types, see
+ * Default PHP Data Types
+ * in the Microsoft SQLSRV documentation.
+ * @return mixed Returns data from the specified field on success.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_get_field($stmt, int $fieldIndex, int $getAsType = null)
+{
+ error_clear_last();
+ if ($getAsType !== null) {
+ $result = \sqlsrv_get_field($stmt, $fieldIndex, $getAsType);
+ } else {
+ $result = \sqlsrv_get_field($stmt, $fieldIndex);
+ }
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Makes the next result of the specified statement active. Results include result
+ * sets, row counts, and output parameters.
+ *
+ * @param resource $stmt The statement on which the next result is being called.
+ * @return bool|null Returns TRUE if the next result was successfully retrieved, FALSE if an error
+ * occurred, and NULL if there are no more results to retrieve.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_next_result($stmt): ?bool
+{
+ error_clear_last();
+ $result = \sqlsrv_next_result($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the number of fields (columns) on a statement.
+ *
+ * @param resource $stmt The statement for which the number of fields is returned.
+ * sqlsrv_num_fields can be called on a statement before
+ * or after statement execution.
+ * @return int Returns the number of fields on success.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_num_fields($stmt): int
+{
+ error_clear_last();
+ $result = \sqlsrv_num_fields($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves the number of rows in a result set. This function requires that the
+ * statement resource be created with a static or keyset cursor. For more information,
+ * see sqlsrv_query, sqlsrv_prepare,
+ * or Specifying a Cursor Type and Selecting Rows
+ * in the Microsoft SQLSRV documentation.
+ *
+ * @param resource $stmt The statement for which the row count is returned. The statement resource
+ * must be created with a static or keyset cursor. For more information, see
+ * sqlsrv_query, sqlsrv_prepare, or
+ * Specifying a Cursor Type and Selecting Rows
+ * in the Microsoft SQLSRV documentation.
+ * @return int Returns the number of rows retrieved on success.
+ * If a forward cursor (the default) or dynamic cursor is used, FALSE is returned.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_num_rows($stmt): int
+{
+ error_clear_last();
+ $result = \sqlsrv_num_rows($stmt);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Prepares a query for execution. This function is ideal for preparing a query
+ * that will be executed multiple times with different parameter values.
+ *
+ * @param resource $conn A connection resource returned by sqlsrv_connect.
+ * @param string $sql The string that defines the query to be prepared and executed.
+ * @param array $params An array specifying parameter information when executing a parameterized
+ * query. Array elements can be any of the following:
+ *
+ * A literal value
+ * A PHP variable
+ * An array with this structure:
+ * array($value [, $direction [, $phpType [, $sqlType]]])
+ *
+ * The following table describes the elements in the array structure above:
+ * @param array $options An array specifying query property options. The supported keys are described
+ * in the following table:
+ * @return resource Returns a statement resource on success.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_prepare($conn, string $sql, array $params = null, array $options = null)
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \sqlsrv_prepare($conn, $sql, $params, $options);
+ } elseif ($params !== null) {
+ $result = \sqlsrv_prepare($conn, $sql, $params);
+ } else {
+ $result = \sqlsrv_prepare($conn, $sql);
+ }
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Prepares and executes a query.
+ *
+ * @param resource $conn A connection resource returned by sqlsrv_connect.
+ * @param string $sql The string that defines the query to be prepared and executed.
+ * @param array $params An array specifying parameter information when executing a parameterized query.
+ * Array elements can be any of the following:
+ *
+ * A literal value
+ * A PHP variable
+ * An array with this structure:
+ * array($value [, $direction [, $phpType [, $sqlType]]])
+ *
+ * The following table describes the elements in the array structure above:
+ * @param array $options An array specifying query property options. The supported keys are described
+ * in the following table:
+ * @return resource Returns a statement resource on success.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_query($conn, string $sql, array $params = null, array $options = null)
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \sqlsrv_query($conn, $sql, $params, $options);
+ } elseif ($params !== null) {
+ $result = \sqlsrv_query($conn, $sql, $params);
+ } else {
+ $result = \sqlsrv_query($conn, $sql);
+ }
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Rolls back a transaction that was begun with sqlsrv_begin_transaction
+ * and returns the connection to auto-commit mode.
+ *
+ * @param resource $conn The connection resource returned by a call to sqlsrv_connect.
+ * @throws SqlsrvException
+ *
+ */
+function sqlsrv_rollback($conn): void
+{
+ error_clear_last();
+ $result = \sqlsrv_rollback($conn);
+ if ($result === false) {
+ throw SqlsrvException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SsdeepException;
+
+/**
+ * Calculates the match score between signature1
+ * and signature2 using
+ * context-triggered piecewise hashing, and returns the match
+ * score.
+ *
+ * @param string $signature1 The first fuzzy hash signature string.
+ * @param string $signature2 The second fuzzy hash signature string.
+ * @return int Returns an integer from 0 to 100 on success, FALSE otherwise.
+ * @throws SsdeepException
+ *
+ */
+function ssdeep_fuzzy_compare(string $signature1, string $signature2): int
+{
+ error_clear_last();
+ $result = \ssdeep_fuzzy_compare($signature1, $signature2);
+ if ($result === false) {
+ throw SsdeepException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ssdeep_fuzzy_hash_filename calculates the hash
+ * of the file specified by file_name using
+ * context-triggered piecewise
+ * hashing, and returns that hash.
+ *
+ * @param string $file_name The filename of the file to hash.
+ * @return string Returns a string on success, FALSE otherwise.
+ * @throws SsdeepException
+ *
+ */
+function ssdeep_fuzzy_hash_filename(string $file_name): string
+{
+ error_clear_last();
+ $result = \ssdeep_fuzzy_hash_filename($file_name);
+ if ($result === false) {
+ throw SsdeepException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * ssdeep_fuzzy_hash calculates the hash of
+ * to_hash using
+ * context-triggered piecewise hashing, and returns that hash.
+ *
+ * @param string $to_hash The input string.
+ * @return string Returns a string on success, FALSE otherwise.
+ * @throws SsdeepException
+ *
+ */
+function ssdeep_fuzzy_hash(string $to_hash): string
+{
+ error_clear_last();
+ $result = \ssdeep_fuzzy_hash($to_hash);
+ if ($result === false) {
+ throw SsdeepException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\Ssh2Exception;
+
+/**
+ * Authenticate over SSH using the ssh agent
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $username Remote user name.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_auth_agent($session, string $username): void
+{
+ error_clear_last();
+ $result = \ssh2_auth_agent($session, $username);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Authenticate using a public hostkey read from a file.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $username
+ * @param string $hostname
+ * @param string $pubkeyfile
+ * @param string $privkeyfile
+ * @param string $passphrase If privkeyfile is encrypted (which it should
+ * be), the passphrase must be provided.
+ * @param string $local_username If local_username is omitted, then the value
+ * for username will be used for it.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_auth_hostbased_file($session, string $username, string $hostname, string $pubkeyfile, string $privkeyfile, string $passphrase = null, string $local_username = null): void
+{
+ error_clear_last();
+ if ($local_username !== null) {
+ $result = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase, $local_username);
+ } elseif ($passphrase !== null) {
+ $result = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase);
+ } else {
+ $result = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile);
+ }
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Authenticate over SSH using a plain password. Since version 0.12 this function
+ * also supports keyboard_interactive method.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $username Remote user name.
+ * @param string $password Password for username
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_auth_password($session, string $username, string $password): void
+{
+ error_clear_last();
+ $result = \ssh2_auth_password($session, $username, $password);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Authenticate using a public key read from a file.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $username
+ * @param string $pubkeyfile The public key file needs to be in OpenSSH's format. It should look something like:
+ *
+ * ssh-rsa AAAAB3NzaC1yc2EAAA....NX6sqSnHA8= rsa-key-20121110
+ * @param string $privkeyfile
+ * @param string $passphrase If privkeyfile is encrypted (which it should
+ * be), the passphrase must be provided.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_auth_pubkey_file($session, string $username, string $pubkeyfile, string $privkeyfile, string $passphrase = null): void
+{
+ error_clear_last();
+ if ($passphrase !== null) {
+ $result = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile, $passphrase);
+ } else {
+ $result = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile);
+ }
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Establish a connection to a remote SSH server.
+ *
+ * Once connected, the client should verify the server's hostkey using
+ * ssh2_fingerprint, then authenticate using either
+ * password or public key.
+ *
+ * @param string $host
+ * @param int $port
+ * @param array $methods methods may be an associative array with up to four parameters
+ * as described below.
+ *
+ *
+ * methods may be an associative array
+ * with any or all of the following parameters.
+ *
+ *
+ *
+ * Index
+ * Meaning
+ * Supported Values*
+ *
+ *
+ *
+ *
+ * kex
+ *
+ * List of key exchange methods to advertise, comma separated
+ * in order of preference.
+ *
+ *
+ * diffie-hellman-group1-sha1,
+ * diffie-hellman-group14-sha1, and
+ * diffie-hellman-group-exchange-sha1
+ *
+ *
+ *
+ * hostkey
+ *
+ * List of hostkey methods to advertise, comma separated
+ * in order of preference.
+ *
+ *
+ * ssh-rsa and
+ * ssh-dss
+ *
+ *
+ *
+ * client_to_server
+ *
+ * Associative array containing crypt, compression, and
+ * message authentication code (MAC) method preferences
+ * for messages sent from client to server.
+ *
+ *
+ *
+ *
+ * server_to_client
+ *
+ * Associative array containing crypt, compression, and
+ * message authentication code (MAC) method preferences
+ * for messages sent from server to client.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * * - Supported Values are dependent on methods supported by underlying library.
+ * See libssh2 documentation for additional
+ * information.
+ *
+ *
+ *
+ * client_to_server and
+ * server_to_client may be an associative array
+ * with any or all of the following parameters.
+ *
+ *
+ *
+ *
+ * Index
+ * Meaning
+ * Supported Values*
+ *
+ *
+ *
+ *
+ * crypt
+ * List of crypto methods to advertise, comma separated
+ * in order of preference.
+ *
+ * rijndael-cbc@lysator.liu.se,
+ * aes256-cbc,
+ * aes192-cbc,
+ * aes128-cbc,
+ * 3des-cbc,
+ * blowfish-cbc,
+ * cast128-cbc,
+ * arcfour, and
+ * none**
+ *
+ *
+ *
+ * comp
+ * List of compression methods to advertise, comma separated
+ * in order of preference.
+ *
+ * zlib and
+ * none
+ *
+ *
+ *
+ * mac
+ * List of MAC methods to advertise, comma separated
+ * in order of preference.
+ *
+ * hmac-sha1,
+ * hmac-sha1-96,
+ * hmac-ripemd160,
+ * hmac-ripemd160@openssh.com, and
+ * none**
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Crypt and MAC method "none"
+ *
+ * For security reasons, none is disabled by the underlying
+ * libssh2 library unless explicitly enabled
+ * during build time by using the appropriate ./configure options. See documentation
+ * for the underlying library for more information.
+ *
+ *
+ *
+ * For security reasons, none is disabled by the underlying
+ * libssh2 library unless explicitly enabled
+ * during build time by using the appropriate ./configure options. See documentation
+ * for the underlying library for more information.
+ * @param array $callbacks callbacks may be an associative array with any
+ * or all of the following parameters.
+ *
+ *
+ * Callbacks parameters
+ *
+ *
+ *
+ *
+ * Index
+ * Meaning
+ * Prototype
+ *
+ *
+ *
+ *
+ * ignore
+ *
+ * Name of function to call when an
+ * SSH2_MSG_IGNORE packet is received
+ *
+ * void ignore_cb($message)
+ *
+ *
+ * debug
+ *
+ * Name of function to call when an
+ * SSH2_MSG_DEBUG packet is received
+ *
+ * void debug_cb($message, $language, $always_display)
+ *
+ *
+ * macerror
+ *
+ * Name of function to call when a packet is received but the
+ * message authentication code failed. If the callback returns
+ * TRUE, the mismatch will be ignored, otherwise the connection
+ * will be terminated.
+ *
+ * bool macerror_cb($packet)
+ *
+ *
+ * disconnect
+ *
+ * Name of function to call when an
+ * SSH2_MSG_DISCONNECT packet is received
+ *
+ * void disconnect_cb($reason, $message, $language)
+ *
+ *
+ *
+ *
+ * @return resource Returns a resource on success.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_connect(string $host, int $port = 22, array $methods = null, array $callbacks = null)
+{
+ error_clear_last();
+ if ($callbacks !== null) {
+ $result = \ssh2_connect($host, $port, $methods, $callbacks);
+ } elseif ($methods !== null) {
+ $result = \ssh2_connect($host, $port, $methods);
+ } else {
+ $result = \ssh2_connect($host, $port);
+ }
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Close a connection to a remote SSH server.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_disconnect($session): void
+{
+ error_clear_last();
+ $result = \ssh2_disconnect($session);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Execute a command at the remote end and allocate a channel for it.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $command
+ * @param string $pty
+ * @param array $env env may be passed as an associative array of
+ * name/value pairs to set in the target environment.
+ * @param int $width Width of the virtual terminal.
+ * @param int $height Height of the virtual terminal.
+ * @param int $width_height_type width_height_type should be one of
+ * SSH2_TERM_UNIT_CHARS or
+ * SSH2_TERM_UNIT_PIXELS.
+ * @return resource Returns a stream on success.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_exec($session, string $command, string $pty = null, array $env = null, int $width = 80, int $height = 25, int $width_height_type = SSH2_TERM_UNIT_CHARS)
+{
+ error_clear_last();
+ if ($width_height_type !== SSH2_TERM_UNIT_CHARS) {
+ $result = \ssh2_exec($session, $command, $pty, $env, $width, $height, $width_height_type);
+ } elseif ($height !== 25) {
+ $result = \ssh2_exec($session, $command, $pty, $env, $width, $height);
+ } elseif ($width !== 80) {
+ $result = \ssh2_exec($session, $command, $pty, $env, $width);
+ } elseif ($env !== null) {
+ $result = \ssh2_exec($session, $command, $pty, $env);
+ } elseif ($pty !== null) {
+ $result = \ssh2_exec($session, $command, $pty);
+ } else {
+ $result = \ssh2_exec($session, $command);
+ }
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $pkey Publickey Subsystem resource created by ssh2_publickey_init.
+ * @param string $algoname Publickey algorithm (e.g.): ssh-dss, ssh-rsa
+ * @param string $blob Publickey blob as raw binary data
+ * @param bool $overwrite If the specified key already exists, should it be overwritten?
+ * @param array $attributes Associative array of attributes to assign to this public key.
+ * Refer to ietf-secsh-publickey-subsystem for a list of supported attributes.
+ * To mark an attribute as mandatory, precede its name with an asterisk.
+ * If the server is unable to support an attribute marked mandatory,
+ * it will abort the add process.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_publickey_add($pkey, string $algoname, string $blob, bool $overwrite = false, array $attributes = null): void
+{
+ error_clear_last();
+ if ($attributes !== null) {
+ $result = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite, $attributes);
+ } else {
+ $result = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite);
+ }
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Request the Publickey subsystem from an already connected SSH2 server.
+ *
+ * The publickey subsystem allows an already connected and authenticated
+ * client to manage the list of authorized public keys stored on the
+ * target server in an implementation agnostic manner.
+ * If the remote server does not support the publickey subsystem,
+ * the ssh2_publickey_init function will return FALSE.
+ *
+ * @param resource $session
+ * @return resource Returns an SSH2 Publickey Subsystem resource for use
+ * with all other ssh2_publickey_*() methods.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_publickey_init($session)
+{
+ error_clear_last();
+ $result = \ssh2_publickey_init($session);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Removes an authorized publickey.
+ *
+ * @param resource $pkey Publickey Subsystem Resource
+ * @param string $algoname Publickey algorithm (e.g.): ssh-dss, ssh-rsa
+ * @param string $blob Publickey blob as raw binary data
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_publickey_remove($pkey, string $algoname, string $blob): void
+{
+ error_clear_last();
+ $result = \ssh2_publickey_remove($pkey, $algoname, $blob);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Copy a file from the remote server to the local filesystem using the SCP protocol.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $remote_file Path to the remote file.
+ * @param string $local_file Path to the local file.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_scp_recv($session, string $remote_file, string $local_file): void
+{
+ error_clear_last();
+ $result = \ssh2_scp_recv($session, $remote_file, $local_file);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Copy a file from the local filesystem to the remote server using the SCP protocol.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @param string $local_file Path to the local file.
+ * @param string $remote_file Path to the remote file.
+ * @param int $create_mode The file will be created with the mode specified by
+ * create_mode.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_scp_send($session, string $local_file, string $remote_file, int $create_mode = 0644): void
+{
+ error_clear_last();
+ $result = \ssh2_scp_send($session, $local_file, $remote_file, $create_mode);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Attempts to change the mode of the specified file to that given in
+ * mode.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $filename Path to the file.
+ * @param int $mode Permissions on the file. See the chmod for more details on this parameter.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_chmod($sftp, string $filename, int $mode): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_chmod($sftp, $filename, $mode);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a directory on the remote file server with permissions set to
+ * mode.
+ *
+ * This function is similar to using mkdir with the
+ * ssh2.sftp:// wrapper.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $dirname Path of the new directory.
+ * @param int $mode Permissions on the new directory.
+ * @param bool $recursive If recursive is TRUE any parent directories
+ * required for dirname will be automatically created as well.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_mkdir($sftp, string $dirname, int $mode = 0777, bool $recursive = false): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_mkdir($sftp, $dirname, $mode, $recursive);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Renames a file on the remote filesystem.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $from The current file that is being renamed.
+ * @param string $to The new file name that replaces from.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_rename($sftp, string $from, string $to): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_rename($sftp, $from, $to);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Removes a directory from the remote file server.
+ *
+ * This function is similar to using rmdir with the
+ * ssh2.sftp:// wrapper.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $dirname
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_rmdir($sftp, string $dirname): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_rmdir($sftp, $dirname);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Creates a symbolic link named link on the remote
+ * filesystem pointing to target.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $target Target of the symbolic link.
+ * @param string $link
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_symlink($sftp, string $target, string $link): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_symlink($sftp, $target, $link);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Deletes a file on the remote filesystem.
+ *
+ * @param resource $sftp An SSH2 SFTP resource opened by ssh2_sftp.
+ * @param string $filename
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp_unlink($sftp, string $filename): void
+{
+ error_clear_last();
+ $result = \ssh2_sftp_unlink($sftp, $filename);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+}
+
+
+/**
+ * Request the SFTP subsystem from an already connected SSH2 server.
+ *
+ * @param resource $session An SSH connection link identifier, obtained from a call to
+ * ssh2_connect.
+ * @return resource This method returns an SSH2 SFTP resource for use with
+ * all other ssh2_sftp_*() methods and the
+ * ssh2.sftp:// fopen wrapper.
+ * @throws Ssh2Exception
+ *
+ */
+function ssh2_sftp($session)
+{
+ error_clear_last();
+ $result = \ssh2_sftp($session);
+ if ($result === false) {
+ throw Ssh2Exception::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\StreamException;
+
+/**
+ * Sets parameters on the specified context.
+ *
+ * @param resource $stream_or_context The stream or context to apply the parameters too.
+ * @param array $params An array of parameters to set.
+ *
+ * params should be an associative array of the structure:
+ * $params['paramname'] = "paramvalue";.
+ * @throws StreamException
+ *
+ */
+function stream_context_set_params($stream_or_context, array $params): void
+{
+ error_clear_last();
+ $result = \stream_context_set_params($stream_or_context, $params);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes a copy of up to maxlength bytes
+ * of data from the current position (or from the
+ * offset position, if specified) in
+ * source to dest. If
+ * maxlength is not specified, all remaining content in
+ * source will be copied.
+ *
+ * @param resource $source The source stream
+ * @param resource $dest The destination stream
+ * @param int $maxlength Maximum bytes to copy
+ * @param int $offset The offset where to start to copy data
+ * @return int Returns the total count of bytes copied.
+ * @throws StreamException
+ *
+ */
+function stream_copy_to_stream($source, $dest, int $maxlength = -1, int $offset = 0): int
+{
+ error_clear_last();
+ $result = \stream_copy_to_stream($source, $dest, $maxlength, $offset);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Adds filtername to the list of filters
+ * attached to stream.
+ *
+ * @param resource $stream The target stream.
+ * @param string $filtername The filter name.
+ * @param int $read_write By default, stream_filter_append will
+ * attach the filter to the read filter chain
+ * if the file was opened for reading (i.e. File Mode:
+ * r, and/or +). The filter
+ * will also be attached to the write filter chain
+ * if the file was opened for writing (i.e. File Mode:
+ * w, a, and/or +).
+ * STREAM_FILTER_READ,
+ * STREAM_FILTER_WRITE, and/or
+ * STREAM_FILTER_ALL can also be passed to the
+ * read_write parameter to override this behavior.
+ * @param mixed $params This filter will be added with the specified
+ * params to the end of
+ * the list and will therefore be called last during stream operations.
+ * To add a filter to the beginning of the list, use
+ * stream_filter_prepend.
+ * @return resource Returns a resource on success. The resource can be
+ * used to refer to this filter instance during a call to
+ * stream_filter_remove.
+ *
+ * FALSE is returned if stream is not a resource or
+ * if filtername cannot be located.
+ * @throws StreamException
+ *
+ */
+function stream_filter_append($stream, string $filtername, int $read_write = null, $params = null)
+{
+ error_clear_last();
+ if ($params !== null) {
+ $result = \stream_filter_append($stream, $filtername, $read_write, $params);
+ } elseif ($read_write !== null) {
+ $result = \stream_filter_append($stream, $filtername, $read_write);
+ } else {
+ $result = \stream_filter_append($stream, $filtername);
+ }
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Adds filtername to the list of filters
+ * attached to stream.
+ *
+ * @param resource $stream The target stream.
+ * @param string $filtername The filter name.
+ * @param int $read_write By default, stream_filter_prepend will
+ * attach the filter to the read filter chain
+ * if the file was opened for reading (i.e. File Mode:
+ * r, and/or +). The filter
+ * will also be attached to the write filter chain
+ * if the file was opened for writing (i.e. File Mode:
+ * w, a, and/or +).
+ * STREAM_FILTER_READ,
+ * STREAM_FILTER_WRITE, and/or
+ * STREAM_FILTER_ALL can also be passed to the
+ * read_write parameter to override this behavior.
+ * See stream_filter_append for an example of
+ * using this parameter.
+ * @param mixed $params This filter will be added with the specified params
+ * to the beginning of the list and will therefore be
+ * called first during stream operations. To add a filter to the end of the
+ * list, use stream_filter_append.
+ * @return resource Returns a resource on success. The resource can be
+ * used to refer to this filter instance during a call to
+ * stream_filter_remove.
+ *
+ * FALSE is returned if stream is not a resource or
+ * if filtername cannot be located.
+ * @throws StreamException
+ *
+ */
+function stream_filter_prepend($stream, string $filtername, int $read_write = null, $params = null)
+{
+ error_clear_last();
+ if ($params !== null) {
+ $result = \stream_filter_prepend($stream, $filtername, $read_write, $params);
+ } elseif ($read_write !== null) {
+ $result = \stream_filter_prepend($stream, $filtername, $read_write);
+ } else {
+ $result = \stream_filter_prepend($stream, $filtername);
+ }
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * stream_filter_register allows you to implement
+ * your own filter on any registered stream used with all the other
+ * filesystem functions (such as fopen,
+ * fread etc.).
+ *
+ * @param string $filtername The filter name to be registered.
+ * @param string $classname To implement a filter, you need to define a class as an extension of
+ * php_user_filter with a number of member
+ * functions. When performing read/write operations on the stream
+ * to which your filter is attached, PHP will pass the data through your
+ * filter (and any other filters attached to that stream) so that the
+ * data may be modified as desired. You must implement the methods
+ * exactly as described in php_user_filter - doing
+ * otherwise will lead to undefined behaviour.
+ * @throws StreamException
+ *
+ */
+function stream_filter_register(string $filtername, string $classname): void
+{
+ error_clear_last();
+ $result = \stream_filter_register($filtername, $classname);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Removes a stream filter previously added to a stream with
+ * stream_filter_prepend or
+ * stream_filter_append. Any data remaining in the
+ * filter's internal buffer will be flushed through to the next filter before
+ * removing it.
+ *
+ * @param resource $stream_filter The stream filter to be removed.
+ * @throws StreamException
+ *
+ */
+function stream_filter_remove($stream_filter): void
+{
+ error_clear_last();
+ $result = \stream_filter_remove($stream_filter);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Identical to file_get_contents, except that
+ * stream_get_contents operates on an already open
+ * stream resource and returns the remaining contents in a string, up to
+ * maxlength bytes and starting at the specified
+ * offset.
+ *
+ * @param resource $handle A stream resource (e.g. returned from fopen)
+ * @param int $maxlength The maximum bytes to read. Defaults to -1 (read all the remaining
+ * buffer).
+ * @param int $offset Seek to the specified offset before reading. If this number is negative,
+ * no seeking will occur and reading will start from the current position.
+ * @return string Returns a string.
+ * @throws StreamException
+ *
+ */
+function stream_get_contents($handle, int $maxlength = -1, int $offset = -1): string
+{
+ error_clear_last();
+ $result = \stream_get_contents($handle, $maxlength, $offset);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Determines if stream stream refers to a valid terminal type device.
+ * This is a more portable version of posix_isatty, since it works on Windows systems too.
+ *
+ * @param resource $stream
+ * @throws StreamException
+ *
+ */
+function stream_isatty($stream): void
+{
+ error_clear_last();
+ $result = \stream_isatty($stream);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Resolve filename against the include path according to the same rules as fopen/include.
+ *
+ * @param string $filename The filename to resolve.
+ * @return string Returns a string containing the resolved absolute filename.
+ * @throws StreamException
+ *
+ */
+function stream_resolve_include_path(string $filename): string
+{
+ error_clear_last();
+ $result = \stream_resolve_include_path($filename);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets blocking or non-blocking mode on a stream.
+ *
+ * This function works for any stream that supports non-blocking mode
+ * (currently, regular files and socket streams).
+ *
+ * @param resource $stream The stream.
+ * @param bool $mode If mode is FALSE, the given stream
+ * will be switched to non-blocking mode, and if TRUE, it
+ * will be switched to blocking mode. This affects calls like
+ * fgets and fread
+ * that read from the stream. In non-blocking mode an
+ * fgets call will always return right away
+ * while in blocking mode it will wait for data to become available
+ * on the stream.
+ * @throws StreamException
+ *
+ */
+function stream_set_blocking($stream, bool $mode): void
+{
+ error_clear_last();
+ $result = \stream_set_blocking($stream, $mode);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Sets the timeout value on stream,
+ * expressed in the sum of seconds and
+ * microseconds.
+ *
+ * When the stream times out, the 'timed_out' key of the array returned by
+ * stream_get_meta_data is set to TRUE, although no
+ * error/warning is generated.
+ *
+ * @param resource $stream The target stream.
+ * @param int $seconds The seconds part of the timeout to be set.
+ * @param int $microseconds The microseconds part of the timeout to be set.
+ * @throws StreamException
+ *
+ */
+function stream_set_timeout($stream, int $seconds, int $microseconds = 0): void
+{
+ error_clear_last();
+ $result = \stream_set_timeout($stream, $seconds, $microseconds);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Accept a connection on a socket previously created by
+ * stream_socket_server.
+ *
+ * @param resource $server_socket The server socket to accept a connection from.
+ * @param float $timeout Override the default socket accept timeout. Time should be given in
+ * seconds.
+ * @param string|null $peername Will be set to the name (address) of the client which connected, if
+ * included and available from the selected transport.
+ *
+ * Can also be determined later using
+ * stream_socket_get_name.
+ * @return resource Returns a stream to the accepted socket connection.
+ * @throws StreamException
+ *
+ */
+function stream_socket_accept($server_socket, float $timeout = null, ?string &$peername = null)
+{
+ error_clear_last();
+ if ($peername !== null) {
+ $result = \stream_socket_accept($server_socket, $timeout, $peername);
+ } elseif ($timeout !== null) {
+ $result = \stream_socket_accept($server_socket, $timeout);
+ } else {
+ $result = \stream_socket_accept($server_socket);
+ }
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Initiates a stream or datagram connection to the destination specified
+ * by remote_socket. The type of socket created
+ * is determined by the transport specified using standard URL formatting:
+ * transport://target. For Internet Domain sockets
+ * (AF_INET) such as TCP and UDP, the target portion
+ * of the remote_socket parameter should consist of
+ * a hostname or IP address followed by a colon and a port number. For Unix
+ * domain sockets, the target portion should point
+ * to the socket file on the filesystem.
+ *
+ * @param string $remote_socket Address to the socket to connect to.
+ * @param int $errno Will be set to the system level error number if connection fails.
+ * @param string $errstr Will be set to the system level error message if the connection fails.
+ * @param float $timeout Number of seconds until the connect() system call
+ * should timeout.
+ *
+ *
+ * This parameter only applies when not making asynchronous
+ * connection attempts.
+ *
+ *
+ *
+ *
+ * To set a timeout for reading/writing data over the socket, use the
+ * stream_set_timeout, as the
+ * timeout only applies while making connecting
+ * the socket.
+ *
+ *
+ *
+ * To set a timeout for reading/writing data over the socket, use the
+ * stream_set_timeout, as the
+ * timeout only applies while making connecting
+ * the socket.
+ * @param int $flags Bitmask field which may be set to any combination of connection flags.
+ * Currently the select of connection flags is limited to
+ * STREAM_CLIENT_CONNECT (default),
+ * STREAM_CLIENT_ASYNC_CONNECT and
+ * STREAM_CLIENT_PERSISTENT.
+ * @param resource $context A valid context resource created with stream_context_create.
+ * @return resource On success a stream resource is returned which may
+ * be used together with the other file functions (such as
+ * fgets, fgetss,
+ * fwrite, fclose, and
+ * feof), FALSE on failure.
+ * @throws StreamException
+ *
+ */
+function stream_socket_client(string $remote_socket, int &$errno = null, string &$errstr = null, float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, $context = null)
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \stream_socket_client($remote_socket, $errno, $errstr, $timeout, $flags, $context);
+ } elseif ($flags !== STREAM_CLIENT_CONNECT) {
+ $result = \stream_socket_client($remote_socket, $errno, $errstr, $timeout, $flags);
+ } elseif ($timeout !== null) {
+ $result = \stream_socket_client($remote_socket, $errno, $errstr, $timeout);
+ } else {
+ $result = \stream_socket_client($remote_socket, $errno, $errstr);
+ }
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * stream_socket_pair creates a pair of connected,
+ * indistinguishable socket streams. This function is commonly used in IPC
+ * (Inter-Process Communication).
+ *
+ * @param int $domain The protocol family to be used: STREAM_PF_INET,
+ * STREAM_PF_INET6 or
+ * STREAM_PF_UNIX
+ * @param int $type The type of communication to be used:
+ * STREAM_SOCK_DGRAM,
+ * STREAM_SOCK_RAW,
+ * STREAM_SOCK_RDM,
+ * STREAM_SOCK_SEQPACKET or
+ * STREAM_SOCK_STREAM
+ * @param int $protocol The protocol to be used: STREAM_IPPROTO_ICMP,
+ * STREAM_IPPROTO_IP,
+ * STREAM_IPPROTO_RAW,
+ * STREAM_IPPROTO_TCP or
+ * STREAM_IPPROTO_UDP
+ * @return resource[] Returns an array with the two socket resources on success.
+ * @throws StreamException
+ *
+ */
+function stream_socket_pair(int $domain, int $type, int $protocol): iterable
+{
+ error_clear_last();
+ $result = \stream_socket_pair($domain, $type, $protocol);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Creates a stream or datagram socket on the specified
+ * local_socket.
+ *
+ * This function only creates a socket, to begin accepting connections
+ * use stream_socket_accept.
+ *
+ * @param string $local_socket The type of socket created is determined by the transport specified
+ * using standard URL formatting: transport://target.
+ *
+ * For Internet Domain sockets (AF_INET) such as TCP and UDP, the
+ * target portion of the
+ * remote_socket parameter should consist of a
+ * hostname or IP address followed by a colon and a port number. For
+ * Unix domain sockets, the target portion should
+ * point to the socket file on the filesystem.
+ *
+ * Depending on the environment, Unix domain sockets may not be available.
+ * A list of available transports can be retrieved using
+ * stream_get_transports. See
+ * for a list of bulitin transports.
+ * @param int $errno If the optional errno and errstr
+ * arguments are present they will be set to indicate the actual system
+ * level error that occurred in the system-level socket(),
+ * bind(), and listen() calls. If
+ * the value returned in errno is
+ * 0 and the function returned FALSE, it is an
+ * indication that the error occurred before the bind()
+ * call. This is most likely due to a problem initializing the socket.
+ * Note that the errno and
+ * errstr arguments will always be passed by reference.
+ * @param string $errstr See errno description.
+ * @param int $flags A bitmask field which may be set to any combination of socket creation
+ * flags.
+ *
+ * For UDP sockets, you must use STREAM_SERVER_BIND as
+ * the flags parameter.
+ * @param resource $context
+ * @return resource Returns the created stream.
+ * @throws StreamException
+ *
+ */
+function stream_socket_server(string $local_socket, int &$errno = null, string &$errstr = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context = null)
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \stream_socket_server($local_socket, $errno, $errstr, $flags, $context);
+ } else {
+ $result = \stream_socket_server($local_socket, $errno, $errstr, $flags);
+ }
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Shutdowns (partially or not) a full-duplex connection.
+ *
+ * @param resource $stream An open stream (opened with stream_socket_client,
+ * for example)
+ * @param int $how One of the following constants: STREAM_SHUT_RD
+ * (disable further receptions), STREAM_SHUT_WR
+ * (disable further transmissions) or
+ * STREAM_SHUT_RDWR (disable further receptions and
+ * transmissions).
+ * @throws StreamException
+ *
+ */
+function stream_socket_shutdown($stream, int $how): void
+{
+ error_clear_last();
+ $result = \stream_socket_shutdown($stream, $how);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Tells whether the stream supports locking through
+ * flock.
+ *
+ * @param resource $stream The stream to check.
+ * @throws StreamException
+ *
+ */
+function stream_supports_lock($stream): void
+{
+ error_clear_last();
+ $result = \stream_supports_lock($stream);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Allows you to implement your own protocol handlers and streams for use
+ * with all the other filesystem functions (such as fopen,
+ * fread etc.).
+ *
+ * @param string $protocol The wrapper name to be registered.
+ * @param string $classname The classname which implements the protocol.
+ * @param int $flags Should be set to STREAM_IS_URL if
+ * protocol is a URL protocol. Default is 0, local
+ * stream.
+ * @throws StreamException
+ *
+ */
+function stream_wrapper_register(string $protocol, string $classname, int $flags = 0): void
+{
+ error_clear_last();
+ $result = \stream_wrapper_register($protocol, $classname, $flags);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Restores a built-in wrapper previously unregistered with
+ * stream_wrapper_unregister.
+ *
+ * @param string $protocol
+ * @throws StreamException
+ *
+ */
+function stream_wrapper_restore(string $protocol): void
+{
+ error_clear_last();
+ $result = \stream_wrapper_restore($protocol);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Allows you to disable an already defined stream wrapper. Once the wrapper
+ * has been disabled you may override it with a user-defined wrapper using
+ * stream_wrapper_register or reenable it later on with
+ * stream_wrapper_restore.
+ *
+ * @param string $protocol
+ * @throws StreamException
+ *
+ */
+function stream_wrapper_unregister(string $protocol): void
+{
+ error_clear_last();
+ $result = \stream_wrapper_unregister($protocol);
+ if ($result === false) {
+ throw StreamException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\StringsException;
+
+/**
+ * convert_uudecode decodes a uuencoded string.
+ *
+ * @param string $data The uuencoded data.
+ * @return string Returns the decoded data as a string.
+ * @throws StringsException
+ *
+ */
+function convert_uudecode(string $data): string
+{
+ error_clear_last();
+ $result = \convert_uudecode($data);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * convert_uuencode encodes a string using the uuencode
+ * algorithm.
+ *
+ * Uuencode translates all strings (including binary data) into printable
+ * characters, making them safe for network transmissions. Uuencoded data is
+ * about 35% larger than the original.
+ *
+ * @param string $data The data to be encoded.
+ * @return string Returns the uuencoded data.
+ * @throws StringsException
+ *
+ */
+function convert_uuencode(string $data): string
+{
+ error_clear_last();
+ $result = \convert_uuencode($data);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Decodes a hexadecimally encoded binary string.
+ *
+ * @param string $data Hexadecimal representation of data.
+ * @return string Returns the binary representation of the given data.
+ * @throws StringsException
+ *
+ */
+function hex2bin(string $data): string
+{
+ error_clear_last();
+ $result = \hex2bin($data);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Calculates the MD5 hash of the file specified by the
+ * filename parameter using the
+ * RSA Data Security, Inc.
+ * MD5 Message-Digest Algorithm, and returns that hash.
+ * The hash is a 32-character hexadecimal number.
+ *
+ * @param string $filename The filename
+ * @param bool $raw_output When TRUE, returns the digest in raw binary format with a length of
+ * 16.
+ * @return string Returns a string on success, FALSE otherwise.
+ * @throws StringsException
+ *
+ */
+function md5_file(string $filename, bool $raw_output = false): string
+{
+ error_clear_last();
+ $result = \md5_file($filename, $raw_output);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Calculates the metaphone key of str.
+ *
+ * Similar to soundex metaphone creates the same key for
+ * similar sounding words. It's more accurate than
+ * soundex as it knows the basic rules of English
+ * pronunciation. The metaphone generated keys are of variable length.
+ *
+ * Metaphone was developed by Lawrence Philips
+ * <lphilips at verity dot com>. It is described in ["Practical
+ * Algorithms for Programmers", Binstock & Rex, Addison Wesley,
+ * 1995].
+ *
+ * @param string $str The input string.
+ * @param int $phonemes This parameter restricts the returned metaphone key to
+ * phonemes characters in length.
+ * The default value of 0 means no restriction.
+ * @return string Returns the metaphone key as a string.
+ * @throws StringsException
+ *
+ */
+function metaphone(string $str, int $phonemes = 0): string
+{
+ error_clear_last();
+ $result = \metaphone($str, $phonemes);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param string $filename The filename of the file to hash.
+ * @param bool $raw_output When TRUE, returns the digest in raw binary format with a length of
+ * 20.
+ * @return string Returns a string on success, FALSE otherwise.
+ * @throws StringsException
+ *
+ */
+function sha1_file(string $filename, bool $raw_output = false): string
+{
+ error_clear_last();
+ $result = \sha1_file($filename, $raw_output);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Calculates the soundex key of str.
+ *
+ * Soundex keys have the property that words pronounced similarly
+ * produce the same soundex key, and can thus be used to simplify
+ * searches in databases where you know the pronunciation but not
+ * the spelling. This soundex function returns a string 4 characters
+ * long, starting with a letter.
+ *
+ * This particular soundex function is one described by Donald Knuth
+ * in "The Art Of Computer Programming, vol. 3: Sorting And
+ * Searching", Addison-Wesley (1973), pp. 391-392.
+ *
+ * @param string $str The input string.
+ * @return string Returns the soundex key as a string.
+ * @throws StringsException
+ *
+ */
+function soundex(string $str): string
+{
+ error_clear_last();
+ $result = \soundex($str);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns a string produced according to the formatting string
+ * format.
+ *
+ * @param string $format The format string is composed of zero or more directives:
+ * ordinary characters (excluding %) that are
+ * copied directly to the result and conversion
+ * specifications, each of which results in fetching its
+ * own parameter.
+ *
+ * A conversion specification follows this prototype:
+ * %[argnum$][flags][width][.precision]specifier.
+ *
+ * An integer followed by a dollar sign $,
+ * to specify which number argument to treat in the conversion.
+ *
+ *
+ * Flags
+ *
+ *
+ *
+ * Flag
+ * Description
+ *
+ *
+ *
+ *
+ * -
+ *
+ * Left-justify within the given field width;
+ * Right justification is the default
+ *
+ *
+ *
+ * +
+ *
+ * Prefix positive numbers with a plus sign
+ * +; Default only negative
+ * are prefixed with a negative sign.
+ *
+ *
+ *
+ * (space)
+ *
+ * Pads the result with spaces.
+ * This is the default.
+ *
+ *
+ *
+ * 0
+ *
+ * Only left-pads numbers with zeros.
+ * With s specifiers this can
+ * also right-pad with zeros.
+ *
+ *
+ *
+ * '(char)
+ *
+ * Pads the result with the character (char).
+ *
+ *
+ *
+ *
+ *
+ *
+ * An integer that says how many characters (minimum)
+ * this conversion should result in.
+ *
+ * A period . followed by an integer
+ * who's meaning depends on the specifier:
+ *
+ *
+ *
+ * For e, E,
+ * f and F
+ * specifiers: this is the number of digits to be printed
+ * after the decimal point (by default, this is 6).
+ *
+ *
+ *
+ *
+ * For g and G
+ * specifiers: this is the maximum number of significant
+ * digits to be printed.
+ *
+ *
+ *
+ *
+ * For s specifier: it acts as a cutoff point,
+ * setting a maximum character limit to the string.
+ *
+ *
+ *
+ *
+ *
+ * If the period is specified without an explicit value for precision,
+ * 0 is assumed.
+ *
+ *
+ *
+ *
+ * Specifiers
+ *
+ *
+ *
+ * Specifier
+ * Description
+ *
+ *
+ *
+ *
+ * %
+ *
+ * A literal percent character. No argument is required.
+ *
+ *
+ *
+ * b
+ *
+ * The argument is treated as an integer and presented
+ * as a binary number.
+ *
+ *
+ *
+ * c
+ *
+ * The argument is treated as an integer and presented
+ * as the character with that ASCII.
+ *
+ *
+ *
+ * d
+ *
+ * The argument is treated as an integer and presented
+ * as a (signed) decimal number.
+ *
+ *
+ *
+ * e
+ *
+ * The argument is treated as scientific notation (e.g. 1.2e+2).
+ * The precision specifier stands for the number of digits after the
+ * decimal point since PHP 5.2.1. In earlier versions, it was taken as
+ * number of significant digits (one less).
+ *
+ *
+ *
+ * E
+ *
+ * Like the e specifier but uses
+ * uppercase letter (e.g. 1.2E+2).
+ *
+ *
+ *
+ * f
+ *
+ * The argument is treated as a float and presented
+ * as a floating-point number (locale aware).
+ *
+ *
+ *
+ * F
+ *
+ * The argument is treated as a float and presented
+ * as a floating-point number (non-locale aware).
+ * Available as of PHP 5.0.3.
+ *
+ *
+ *
+ * g
+ *
+ *
+ * General format.
+ *
+ *
+ * Let P equal the precision if nonzero, 6 if the precision is omitted,
+ * or 1 if the precision is zero.
+ * Then, if a conversion with style E would have an exponent of X:
+ *
+ *
+ * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1).
+ * Otherwise, the conversion is with style e and precision P − 1.
+ *
+ *
+ *
+ *
+ * G
+ *
+ * Like the g specifier but uses
+ * E and f.
+ *
+ *
+ *
+ * o
+ *
+ * The argument is treated as an integer and presented
+ * as an octal number.
+ *
+ *
+ *
+ * s
+ *
+ * The argument is treated and presented as a string.
+ *
+ *
+ *
+ * u
+ *
+ * The argument is treated as an integer and presented
+ * as an unsigned decimal number.
+ *
+ *
+ *
+ * x
+ *
+ * The argument is treated as an integer and presented
+ * as a hexadecimal number (with lowercase letters).
+ *
+ *
+ *
+ * X
+ *
+ * The argument is treated as an integer and presented
+ * as a hexadecimal number (with uppercase letters).
+ *
+ *
+ *
+ *
+ *
+ *
+ * General format.
+ *
+ * Let P equal the precision if nonzero, 6 if the precision is omitted,
+ * or 1 if the precision is zero.
+ * Then, if a conversion with style E would have an exponent of X:
+ *
+ * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1).
+ * Otherwise, the conversion is with style e and precision P − 1.
+ *
+ * The c type specifier ignores padding and width
+ *
+ * Attempting to use a combination of the string and width specifiers with character sets that require more than one byte per character may result in unexpected results
+ *
+ * Variables will be co-erced to a suitable type for the specifier:
+ *
+ * Type Handling
+ *
+ *
+ *
+ * Type
+ * Specifiers
+ *
+ *
+ *
+ *
+ * string
+ * s
+ *
+ *
+ * integer
+ *
+ * d,
+ * u,
+ * c,
+ * o,
+ * x,
+ * X,
+ * b
+ *
+ *
+ *
+ * double
+ *
+ * g,
+ * G,
+ * e,
+ * E,
+ * f,
+ * F
+ *
+ *
+ *
+ *
+ *
+ * @param mixed $params
+ * @return string Returns a string produced according to the formatting string
+ * format.
+ * @throws StringsException
+ *
+ */
+function sprintf(string $format, ...$params): string
+{
+ error_clear_last();
+ if ($params !== []) {
+ $result = \sprintf($format, ...$params);
+ } else {
+ $result = \sprintf($format);
+ }
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns the portion of string specified by the
+ * start and length parameters.
+ *
+ * @param string $string The input string.
+ * @param int $start If start is non-negative, the returned string
+ * will start at the start'th position in
+ * string, counting from zero. For instance,
+ * in the string 'abcdef', the character at
+ * position 0 is 'a', the
+ * character at position 2 is
+ * 'c', and so forth.
+ *
+ * If start is negative, the returned string
+ * will start at the start'th character
+ * from the end of string.
+ *
+ * If string is less than
+ * start characters long, FALSE will be returned.
+ *
+ *
+ * Using a negative start
+ *
+ *
+ * ]]>
+ *
+ *
+ * @param int $length If length is given and is positive, the string
+ * returned will contain at most length characters
+ * beginning from start (depending on the length of
+ * string).
+ *
+ * If length is given and is negative, then that many
+ * characters will be omitted from the end of string
+ * (after the start position has been calculated when a
+ * start is negative). If
+ * start denotes the position of this truncation or
+ * beyond, FALSE will be returned.
+ *
+ * If length is given and is 0,
+ * FALSE or NULL, an empty string will be returned.
+ *
+ * If length is omitted, the substring starting from
+ * start until the end of the string will be
+ * returned.
+ * @return string Returns the extracted part of string;, or
+ * an empty string.
+ * @throws StringsException
+ *
+ */
+function substr(string $string, int $start, int $length = null): string
+{
+ error_clear_last();
+ if ($length !== null) {
+ $result = \substr($string, $start, $length);
+ } else {
+ $result = \substr($string, $start);
+ }
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Operates as sprintf but accepts an array of
+ * arguments, rather than a variable number of arguments.
+ *
+ * @param string $format The format string is composed of zero or more directives:
+ * ordinary characters (excluding %) that are
+ * copied directly to the result and conversion
+ * specifications, each of which results in fetching its
+ * own parameter.
+ *
+ * A conversion specification follows this prototype:
+ * %[argnum$][flags][width][.precision]specifier.
+ *
+ * An integer followed by a dollar sign $,
+ * to specify which number argument to treat in the conversion.
+ *
+ *
+ * Flags
+ *
+ *
+ *
+ * Flag
+ * Description
+ *
+ *
+ *
+ *
+ * -
+ *
+ * Left-justify within the given field width;
+ * Right justification is the default
+ *
+ *
+ *
+ * +
+ *
+ * Prefix positive numbers with a plus sign
+ * +; Default only negative
+ * are prefixed with a negative sign.
+ *
+ *
+ *
+ * (space)
+ *
+ * Pads the result with spaces.
+ * This is the default.
+ *
+ *
+ *
+ * 0
+ *
+ * Only left-pads numbers with zeros.
+ * With s specifiers this can
+ * also right-pad with zeros.
+ *
+ *
+ *
+ * '(char)
+ *
+ * Pads the result with the character (char).
+ *
+ *
+ *
+ *
+ *
+ *
+ * An integer that says how many characters (minimum)
+ * this conversion should result in.
+ *
+ * A period . followed by an integer
+ * who's meaning depends on the specifier:
+ *
+ *
+ *
+ * For e, E,
+ * f and F
+ * specifiers: this is the number of digits to be printed
+ * after the decimal point (by default, this is 6).
+ *
+ *
+ *
+ *
+ * For g and G
+ * specifiers: this is the maximum number of significant
+ * digits to be printed.
+ *
+ *
+ *
+ *
+ * For s specifier: it acts as a cutoff point,
+ * setting a maximum character limit to the string.
+ *
+ *
+ *
+ *
+ *
+ * If the period is specified without an explicit value for precision,
+ * 0 is assumed.
+ *
+ *
+ *
+ *
+ * Specifiers
+ *
+ *
+ *
+ * Specifier
+ * Description
+ *
+ *
+ *
+ *
+ * %
+ *
+ * A literal percent character. No argument is required.
+ *
+ *
+ *
+ * b
+ *
+ * The argument is treated as an integer and presented
+ * as a binary number.
+ *
+ *
+ *
+ * c
+ *
+ * The argument is treated as an integer and presented
+ * as the character with that ASCII.
+ *
+ *
+ *
+ * d
+ *
+ * The argument is treated as an integer and presented
+ * as a (signed) decimal number.
+ *
+ *
+ *
+ * e
+ *
+ * The argument is treated as scientific notation (e.g. 1.2e+2).
+ * The precision specifier stands for the number of digits after the
+ * decimal point since PHP 5.2.1. In earlier versions, it was taken as
+ * number of significant digits (one less).
+ *
+ *
+ *
+ * E
+ *
+ * Like the e specifier but uses
+ * uppercase letter (e.g. 1.2E+2).
+ *
+ *
+ *
+ * f
+ *
+ * The argument is treated as a float and presented
+ * as a floating-point number (locale aware).
+ *
+ *
+ *
+ * F
+ *
+ * The argument is treated as a float and presented
+ * as a floating-point number (non-locale aware).
+ * Available as of PHP 5.0.3.
+ *
+ *
+ *
+ * g
+ *
+ *
+ * General format.
+ *
+ *
+ * Let P equal the precision if nonzero, 6 if the precision is omitted,
+ * or 1 if the precision is zero.
+ * Then, if a conversion with style E would have an exponent of X:
+ *
+ *
+ * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1).
+ * Otherwise, the conversion is with style e and precision P − 1.
+ *
+ *
+ *
+ *
+ * G
+ *
+ * Like the g specifier but uses
+ * E and f.
+ *
+ *
+ *
+ * o
+ *
+ * The argument is treated as an integer and presented
+ * as an octal number.
+ *
+ *
+ *
+ * s
+ *
+ * The argument is treated and presented as a string.
+ *
+ *
+ *
+ * u
+ *
+ * The argument is treated as an integer and presented
+ * as an unsigned decimal number.
+ *
+ *
+ *
+ * x
+ *
+ * The argument is treated as an integer and presented
+ * as a hexadecimal number (with lowercase letters).
+ *
+ *
+ *
+ * X
+ *
+ * The argument is treated as an integer and presented
+ * as a hexadecimal number (with uppercase letters).
+ *
+ *
+ *
+ *
+ *
+ *
+ * General format.
+ *
+ * Let P equal the precision if nonzero, 6 if the precision is omitted,
+ * or 1 if the precision is zero.
+ * Then, if a conversion with style E would have an exponent of X:
+ *
+ * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1).
+ * Otherwise, the conversion is with style e and precision P − 1.
+ *
+ * The c type specifier ignores padding and width
+ *
+ * Attempting to use a combination of the string and width specifiers with character sets that require more than one byte per character may result in unexpected results
+ *
+ * Variables will be co-erced to a suitable type for the specifier:
+ *
+ * Type Handling
+ *
+ *
+ *
+ * Type
+ * Specifiers
+ *
+ *
+ *
+ *
+ * string
+ * s
+ *
+ *
+ * integer
+ *
+ * d,
+ * u,
+ * c,
+ * o,
+ * x,
+ * X,
+ * b
+ *
+ *
+ *
+ * double
+ *
+ * g,
+ * G,
+ * e,
+ * E,
+ * f,
+ * F
+ *
+ *
+ *
+ *
+ *
+ * @param array $args
+ * @return string Return array values as a formatted string according to
+ * format.
+ * @throws StringsException
+ *
+ */
+function vsprintf(string $format, array $args): string
+{
+ error_clear_last();
+ $result = \vsprintf($format, $args);
+ if ($result === false) {
+ throw StringsException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\SwooleException;
+
+/**
+ *
+ *
+ * @param string $filename The filename being written.
+ * @param string $content The content writing to the file.
+ * @param int $offset The offset.
+ * @param callable $callback
+ * @throws SwooleException
+ *
+ */
+function swoole_async_write(string $filename, string $content, int $offset = null, callable $callback = null): void
+{
+ error_clear_last();
+ if ($callback !== null) {
+ $result = \swoole_async_write($filename, $content, $offset, $callback);
+ } elseif ($offset !== null) {
+ $result = \swoole_async_write($filename, $content, $offset);
+ } else {
+ $result = \swoole_async_write($filename, $content);
+ }
+ if ($result === false) {
+ throw SwooleException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param string $filename The filename being written.
+ * @param string $content The content writing to the file.
+ * @param callable $callback
+ * @param int $flags
+ * @throws SwooleException
+ *
+ */
+function swoole_async_writefile(string $filename, string $content, callable $callback = null, int $flags = 0): void
+{
+ error_clear_last();
+ if ($flags !== 0) {
+ $result = \swoole_async_writefile($filename, $content, $callback, $flags);
+ } elseif ($callback !== null) {
+ $result = \swoole_async_writefile($filename, $content, $callback);
+ } else {
+ $result = \swoole_async_writefile($filename, $content);
+ }
+ if ($result === false) {
+ throw SwooleException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param callable $callback
+ * @throws SwooleException
+ *
+ */
+function swoole_event_defer(callable $callback): void
+{
+ error_clear_last();
+ $result = \swoole_event_defer($callback);
+ if ($result === false) {
+ throw SwooleException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $fd
+ * @throws SwooleException
+ *
+ */
+function swoole_event_del(int $fd): void
+{
+ error_clear_last();
+ $result = \swoole_event_del($fd);
+ if ($result === false) {
+ throw SwooleException::createFromPhpError();
+ }
+}
+
+
+/**
+ *
+ *
+ * @param int $fd
+ * @param string $data
+ * @throws SwooleException
+ *
+ */
+function swoole_event_write(int $fd, string $data): void
+{
+ error_clear_last();
+ $result = \swoole_event_write($fd, $data);
+ if ($result === false) {
+ throw SwooleException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\UodbcException;
+
+/**
+ * Toggles autocommit behaviour.
+ *
+ * By default, auto-commit is on for a connection. Disabling
+ * auto-commit is equivalent with starting a transaction.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param bool $OnOff If OnOff is TRUE, auto-commit is enabled, if
+ * it is FALSE auto-commit is disabled.
+ * @return mixed Without the OnOff parameter, this function returns
+ * auto-commit status for connection_id. Non-zero is
+ * returned if auto-commit is on, 0 if it is off, or FALSE if an error
+ * occurs.
+ *
+ * If OnOff is set, this function returns TRUE on
+ * success.
+ * @throws UodbcException
+ *
+ */
+function odbc_autocommit($connection_id, bool $OnOff = false)
+{
+ error_clear_last();
+ $result = \odbc_autocommit($connection_id, $OnOff);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Controls handling of binary column data. ODBC SQL types affected are
+ * BINARY, VARBINARY, and
+ * LONGVARBINARY.
+ * The default mode can be set using the
+ * uodbc.defaultbinmode php.ini directive.
+ *
+ * When binary SQL data is converted to character C data (ODBC_BINMODE_CONVERT), each byte
+ * (8 bits) of source data is represented as two ASCII characters.
+ * These characters are the ASCII character representation of the
+ * number in its hexadecimal form. For example, a binary
+ * 00000001 is converted to
+ * "01" and a binary 11111111
+ * is converted to "FF".
+ *
+ * While the handling of BINARY and VARBINARY
+ * columns only depend on the binmode, the handling of LONGVARBINARY
+ * columns also depends on the longreadlen as well:
+ *
+ * LONGVARBINARY handling
+ *
+ *
+ *
+ * binmode
+ * longreadlen
+ * result
+ *
+ *
+ *
+ *
+ * ODBC_BINMODE_PASSTHRU
+ * 0
+ * passthru
+ *
+ *
+ * ODBC_BINMODE_RETURN
+ * 0
+ * passthru
+ *
+ *
+ * ODBC_BINMODE_CONVERT
+ * 0
+ * passthru
+ *
+ *
+ * ODBC_BINMODE_PASSTHRU
+ * >0
+ * passthru
+ *
+ *
+ * ODBC_BINMODE_RETURN
+ * >0
+ * return as is
+ *
+ *
+ * ODBC_BINMODE_CONVERT
+ * >0
+ * return as char
+ *
+ *
+ *
+ *
+ *
+ * If odbc_fetch_into is used, passthru means that an
+ * empty string is returned for these columns.
+ * If odbc_result is used, passthru means that the data are
+ * sent directly to the client (i.e. printed).
+ *
+ * @param int $result_id The result identifier.
+ *
+ * If result_id is 0, the
+ * settings apply as default for new results.
+ * @param int $mode Possible values for mode are:
+ *
+ *
+ *
+ * ODBC_BINMODE_PASSTHRU: Passthru BINARY data
+ *
+ *
+ *
+ *
+ * ODBC_BINMODE_RETURN: Return as is
+ *
+ *
+ *
+ *
+ * ODBC_BINMODE_CONVERT: Convert to char and return
+ *
+ *
+ *
+ *
+ *
+ * Handling of binary long
+ * columns is also affected by odbc_longreadlen.
+ *
+ *
+ * @throws UodbcException
+ *
+ */
+function odbc_binmode(int $result_id, int $mode): void
+{
+ error_clear_last();
+ $result = \odbc_binmode($result_id, $mode);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Lists columns and associated privileges for the given table.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $table_name The table name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $column_name The column name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @return resource Returns an ODBC result identifier.
+ * This result identifier can be used to fetch a list of columns and
+ * associated privileges.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * COLUMN_NAME
+ * GRANTOR
+ * GRANTEE
+ * PRIVILEGE
+ * IS_GRANTABLE
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_columnprivileges($connection_id, string $catalog, string $schema, string $table_name, string $column_name)
+{
+ error_clear_last();
+ $result = \odbc_columnprivileges($connection_id, $catalog, $schema, $table_name, $column_name);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Lists all columns in the requested range.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $table_name The table name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $column_name The column name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * COLUMN_NAME
+ * DATA_TYPE
+ * TYPE_NAME
+ * COLUMN_SIZE
+ * BUFFER_LENGTH
+ * DECIMAL_DIGITS
+ * NUM_PREC_RADIX
+ * NULLABLE
+ * REMARKS
+ * COLUMN_DEF
+ * SQL_DATA_TYPE
+ * SQL_DATETIME_SUB
+ * CHAR_OCTET_LENGTH
+ * ORDINAL_POSITION
+ * IS_NULLABLE
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_columns($connection_id, string $catalog = null, string $schema = null, string $table_name = null, string $column_name = null)
+{
+ error_clear_last();
+ if ($column_name !== null) {
+ $result = \odbc_columns($connection_id, $catalog, $schema, $table_name, $column_name);
+ } elseif ($table_name !== null) {
+ $result = \odbc_columns($connection_id, $catalog, $schema, $table_name);
+ } elseif ($schema !== null) {
+ $result = \odbc_columns($connection_id, $catalog, $schema);
+ } elseif ($catalog !== null) {
+ $result = \odbc_columns($connection_id, $catalog);
+ } else {
+ $result = \odbc_columns($connection_id);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Commits all pending transactions on the connection.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @throws UodbcException
+ *
+ */
+function odbc_commit($connection_id): void
+{
+ error_clear_last();
+ $result = \odbc_commit($connection_id);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function will return the list of available DSN (after calling it
+ * several times).
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param int $fetch_type The fetch_type can be one of two constant types:
+ * SQL_FETCH_FIRST, SQL_FETCH_NEXT.
+ * Use SQL_FETCH_FIRST the first time this function is
+ * called, thereafter use the SQL_FETCH_NEXT.
+ * @return array Returns FALSE on error, an array upon success, and NULL after fetching
+ * the last available DSN.
+ * @throws UodbcException
+ *
+ */
+function odbc_data_source($connection_id, int $fetch_type): array
+{
+ error_clear_last();
+ $result = \odbc_data_source($connection_id, $fetch_type);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sends an SQL statement to the database server.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $query_string The SQL statement.
+ * @param int $flags This parameter is currently not used.
+ * @return resource Returns an ODBC result identifier if the SQL command was executed
+ * successfully.
+ * @throws UodbcException
+ *
+ */
+function odbc_exec($connection_id, string $query_string, int $flags = null)
+{
+ error_clear_last();
+ if ($flags !== null) {
+ $result = \odbc_exec($connection_id, $query_string, $flags);
+ } else {
+ $result = \odbc_exec($connection_id, $query_string);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Executes a statement prepared with odbc_prepare.
+ *
+ * @param resource $result_id The result id resource, from odbc_prepare.
+ * @param array $parameters_array Parameters in parameter_array will be
+ * substituted for placeholders in the prepared statement in order.
+ * Elements of this array will be converted to strings by calling this
+ * function.
+ *
+ * Any parameters in parameter_array which
+ * start and end with single quotes will be taken as the name of a
+ * file to read and send to the database server as the data for the
+ * appropriate placeholder.
+ * @throws UodbcException
+ *
+ */
+function odbc_execute($result_id, array $parameters_array = null): void
+{
+ error_clear_last();
+ if ($parameters_array !== null) {
+ $result = \odbc_execute($result_id, $parameters_array);
+ } else {
+ $result = \odbc_execute($result_id);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Fetch one result row into array.
+ *
+ * @param resource $result_id The result resource.
+ * @param array|null $result_array The result array
+ * that can be of any type since it will be converted to type
+ * array. The array will contain the column values starting at array
+ * index 0.
+ * @param int $rownumber The row number.
+ * @return int Returns the number of columns in the result;
+ * FALSE on error.
+ * @throws UodbcException
+ *
+ */
+function odbc_fetch_into($result_id, ?array &$result_array, int $rownumber = null): int
+{
+ error_clear_last();
+ if ($rownumber !== null) {
+ $result = \odbc_fetch_into($result_id, $result_array, $rownumber);
+ } else {
+ $result = \odbc_fetch_into($result_id, $result_array);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the length of the field referenced by number in the given result
+ * identifier.
+ *
+ * @param resource $result_id The result identifier.
+ * @param int $field_number The field number. Field numbering starts at 1.
+ * @return int Returns the field length.
+ * @throws UodbcException
+ *
+ */
+function odbc_field_len($result_id, int $field_number): int
+{
+ error_clear_last();
+ $result = \odbc_field_len($result_id, $field_number);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the name of the field occupying the given column number in the given
+ * result identifier.
+ *
+ * @param resource $result_id The result identifier.
+ * @param int $field_number The field number. Field numbering starts at 1.
+ * @return string Returns the field name as a string.
+ * @throws UodbcException
+ *
+ */
+function odbc_field_name($result_id, int $field_number): string
+{
+ error_clear_last();
+ $result = \odbc_field_name($result_id, $field_number);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the number of the column slot that corresponds to the named field in
+ * the given result identifier.
+ *
+ * @param resource $result_id The result identifier.
+ * @param string $field_name The field name.
+ * @return int Returns the field number as a integer.
+ * Field numbering starts at 1.
+ * @throws UodbcException
+ *
+ */
+function odbc_field_num($result_id, string $field_name): int
+{
+ error_clear_last();
+ $result = \odbc_field_num($result_id, $field_name);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the scale of the field referenced by number in the given result
+ * identifier.
+ *
+ * @param resource $result_id The result identifier.
+ * @param int $field_number The field number. Field numbering starts at 1.
+ * @return int Returns the field scale as a integer.
+ * @throws UodbcException
+ *
+ */
+function odbc_field_scale($result_id, int $field_number): int
+{
+ error_clear_last();
+ $result = \odbc_field_scale($result_id, $field_number);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets the SQL type of the field referenced by number in the given result
+ * identifier.
+ *
+ * @param resource $result_id The result identifier.
+ * @param int $field_number The field number. Field numbering starts at 1.
+ * @return string Returns the field type as a string.
+ * @throws UodbcException
+ *
+ */
+function odbc_field_type($result_id, int $field_number): string
+{
+ error_clear_last();
+ $result = \odbc_field_type($result_id, $field_number);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves a list of foreign keys in the specified table or a list of
+ * foreign keys in other tables that refer to the primary key in the
+ * specified table
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $pk_catalog The catalog ('qualifier' in ODBC 2 parlance) of the primary key table.
+ * @param string $pk_schema The schema ('owner' in ODBC 2 parlance) of the primary key table.
+ * @param string $pk_table The primary key table.
+ * @param string $fk_catalog The catalog ('qualifier' in ODBC 2 parlance) of the foreign key table.
+ * @param string $fk_schema The schema ('owner' in ODBC 2 parlance) of the foreign key table.
+ * @param string $fk_table The foreign key table.
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * PKTABLE_CAT
+ * PKTABLE_SCHEM
+ * PKTABLE_NAME
+ * PKCOLUMN_NAME
+ * FKTABLE_CAT
+ * FKTABLE_SCHEM
+ * FKTABLE_NAME
+ * FKCOLUMN_NAME
+ * KEY_SEQ
+ * UPDATE_RULE
+ * DELETE_RULE
+ * FK_NAME
+ * PK_NAME
+ * DEFERRABILITY
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_foreignkeys($connection_id, string $pk_catalog, string $pk_schema, string $pk_table, string $fk_catalog, string $fk_schema, string $fk_table)
+{
+ error_clear_last();
+ $result = \odbc_foreignkeys($connection_id, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Retrieves information about data types supported by the data source.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param int $data_type The data type, which can be used to restrict the information to a
+ * single data type.
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * TYPE_NAME
+ * DATA_TYPE
+ * PRECISION
+ * LITERAL_PREFIX
+ * LITERAL_SUFFIX
+ * CREATE_PARAMS
+ * NULLABLE
+ * CASE_SENSITIVE
+ * SEARCHABLE
+ * UNSIGNED_ATTRIBUTE
+ * MONEY
+ * AUTO_INCREMENT
+ * LOCAL_TYPE_NAME
+ * MINIMUM_SCALE
+ * MAXIMUM_SCALE
+ *
+ *
+ * The result set is ordered by DATA_TYPE and TYPE_NAME.
+ * @throws UodbcException
+ *
+ */
+function odbc_gettypeinfo($connection_id, int $data_type = null)
+{
+ error_clear_last();
+ if ($data_type !== null) {
+ $result = \odbc_gettypeinfo($connection_id, $data_type);
+ } else {
+ $result = \odbc_gettypeinfo($connection_id);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Controls handling of LONG, LONGVARCHAR and LONGVARBINARY columns.
+ * The default length can be set using the
+ * uodbc.defaultlrl php.ini directive.
+ *
+ * @param resource $result_id The result identifier.
+ * @param int $length The number of bytes returned to PHP is controlled by the parameter
+ * length. If it is set to 0, long column data is passed through to the
+ * client (i.e. printed) when retrieved with odbc_result.
+ * @throws UodbcException
+ *
+ */
+function odbc_longreadlen($result_id, int $length): void
+{
+ error_clear_last();
+ $result = \odbc_longreadlen($result_id, $length);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Prepares a statement for execution. The result identifier can be used
+ * later to execute the statement with odbc_execute.
+ *
+ * Some databases (such as IBM DB2, MS SQL Server, and Oracle) support
+ * stored procedures that accept parameters of type IN, INOUT, and OUT as
+ * defined by the ODBC specification. However, the Unified ODBC driver
+ * currently only supports parameters of type IN to stored procedures.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $query_string The query string statement being prepared.
+ * @return resource Returns an ODBC result identifier if the SQL command was prepared
+ * successfully.
+ * @throws UodbcException
+ *
+ */
+function odbc_prepare($connection_id, string $query_string)
+{
+ error_clear_last();
+ $result = \odbc_prepare($connection_id, $query_string);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Returns a result identifier that can be used to fetch the column names
+ * that comprise the primary key for a table.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * @param string $table
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * COLUMN_NAME
+ * KEY_SEQ
+ * PK_NAME
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_primarykeys($connection_id, string $catalog, string $schema, string $table)
+{
+ error_clear_last();
+ $result = \odbc_primarykeys($connection_id, $catalog, $schema, $table);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Prints all rows from a result identifier produced by
+ * odbc_exec. The result is printed in HTML table format.
+ * The data is not escaped.
+ *
+ * This function is not supposed to be used in production environments; it is
+ * merely meant for development purposes, to get a result set quickly rendered.
+ *
+ * @param resource $result_id The result identifier.
+ * @param string $format Additional overall table formatting.
+ * @return int Returns the number of rows in the result.
+ * @throws UodbcException
+ *
+ */
+function odbc_result_all($result_id, string $format = null): int
+{
+ error_clear_last();
+ if ($format !== null) {
+ $result = \odbc_result_all($result_id, $format);
+ } else {
+ $result = \odbc_result_all($result_id);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Get result data
+ *
+ * @param resource $result_id The ODBC resource.
+ * @param mixed $field The field name being retrieved. It can either be an integer containing
+ * the column number of the field you want; or it can be a string
+ * containing the name of the field.
+ * @return mixed Returns the string contents of the field, FALSE on error, NULL for
+ * NULL data, or TRUE for binary data.
+ * @throws UodbcException
+ *
+ */
+function odbc_result($result_id, $field)
+{
+ error_clear_last();
+ $result = \odbc_result($result_id, $field);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Rolls back all pending statements on the connection.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @throws UodbcException
+ *
+ */
+function odbc_rollback($connection_id): void
+{
+ error_clear_last();
+ $result = \odbc_rollback($connection_id);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function allows fiddling with the ODBC options for a
+ * particular connection or query result. It was written to help
+ * find work around to problems in quirky ODBC drivers. You should
+ * probably only use this function if you are an ODBC programmer and
+ * understand the effects the various options will have. You will
+ * certainly need a good ODBC reference to explain all the different
+ * options and values that can be used. Different driver versions
+ * support different options.
+ *
+ * Because the effects may vary depending on the ODBC driver, use of
+ * this function in scripts to be made publicly available is
+ * strongly discouraged. Also, some ODBC options are not available
+ * to this function because they must be set before the connection
+ * is established or the query is prepared. However, if on a
+ * particular job it can make PHP work so your boss doesn't tell you
+ * to use a commercial product, that's all that really
+ * matters.
+ *
+ * @param resource $id Is a connection id or result id on which to change the settings.
+ * For SQLSetConnectOption(), this is a connection id.
+ * For SQLSetStmtOption(), this is a result id.
+ * @param int $function Is the ODBC function to use. The value should be
+ * 1 for SQLSetConnectOption() and
+ * 2 for SQLSetStmtOption().
+ * @param int $option The option to set.
+ * @param int $param The value for the given option.
+ * @throws UodbcException
+ *
+ */
+function odbc_setoption($id, int $function, int $option, int $param): void
+{
+ error_clear_last();
+ $result = \odbc_setoption($id, $function, $option, $param);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Retrieves either the optimal set of columns that uniquely identifies a
+ * row in the table, or columns that are automatically updated when any
+ * value in the row is updated by a transaction.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param int $type
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * @param string $table The table.
+ * @param int $scope The scope, which orders the result set.
+ * One of SQL_SCOPE_CURROW, SQL_SCOPE_TRANSACTION
+ * or SQL_SCOPE_SESSION.
+ * @param int $nullable Determines whether to return special columns that can have a NULL value.
+ * One of SQL_NO_NULLS or SQL_NULLABLE .
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * SCOPE
+ * COLUMN_NAME
+ * DATA_TYPE
+ * TYPE_NAME
+ * COLUMN_SIZE
+ * BUFFER_LENGTH
+ * DECIMAL_DIGITS
+ * PSEUDO_COLUMN
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_specialcolumns($connection_id, int $type, string $catalog, string $schema, string $table, int $scope, int $nullable)
+{
+ error_clear_last();
+ $result = \odbc_specialcolumns($connection_id, $type, $catalog, $schema, $table, $scope, $nullable);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Get statistics about a table and its indexes.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * @param string $table_name The table name.
+ * @param int $unique The type of the index.
+ * One of SQL_INDEX_UNIQUE or SQL_INDEX_ALL.
+ * @param int $accuracy One of SQL_ENSURE or SQL_QUICK.
+ * The latter requests that the driver retrieve the CARDINALITY and
+ * PAGES only if they are readily available from the server.
+ * @return resource Returns an ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * NON_UNIQUE
+ * INDEX_QUALIFIER
+ * INDEX_NAME
+ * TYPE
+ * ORDINAL_POSITION
+ * COLUMN_NAME
+ * ASC_OR_DESC
+ * CARDINALITY
+ * PAGES
+ * FILTER_CONDITION
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_statistics($connection_id, string $catalog, string $schema, string $table_name, int $unique, int $accuracy)
+{
+ error_clear_last();
+ $result = \odbc_statistics($connection_id, $catalog, $schema, $table_name, $unique, $accuracy);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Lists tables in the requested range and the privileges associated
+ * with each table.
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $name The name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @return resource An ODBC result identifier.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * GRANTOR
+ * GRANTEE
+ * PRIVILEGE
+ * IS_GRANTABLE
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_tableprivileges($connection_id, string $catalog, string $schema, string $name)
+{
+ error_clear_last();
+ $result = \odbc_tableprivileges($connection_id, $catalog, $schema, $name);
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Lists all tables in the requested range.
+ *
+ * To support enumeration of qualifiers, owners, and table types,
+ * the following special semantics for the
+ * catalog, schema,
+ * name, and
+ * table_type are available:
+ *
+ *
+ *
+ * If catalog is a single percent
+ * character (%) and schema and
+ * name are empty strings, then the result
+ * set contains a list of valid qualifiers for the data
+ * source. (All columns except the TABLE_QUALIFIER column contain
+ * NULLs.)
+ *
+ *
+ *
+ *
+ * If schema is a single percent character
+ * (%) and catalog and
+ * name are empty strings, then the result
+ * set contains a list of valid owners for the data source. (All
+ * columns except the TABLE_OWNER column contain
+ * NULLs.)
+ *
+ *
+ *
+ *
+ * If table_type is a single percent
+ * character (%) and catalog,
+ * schema and name
+ * are empty strings, then the result set contains a list of
+ * valid table types for the data source. (All columns except the
+ * TABLE_TYPE column contain NULLs.)
+ *
+ *
+ *
+ *
+ * @param resource $connection_id The ODBC connection identifier,
+ * see odbc_connect for details.
+ * @param string $catalog The catalog ('qualifier' in ODBC 2 parlance).
+ * @param string $schema The schema ('owner' in ODBC 2 parlance).
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $name The name.
+ * This parameter accepts the following search patterns:
+ * % to match zero or more characters,
+ * and _ to match a single character.
+ * @param string $types If table_type is not an empty string, it
+ * must contain a list of comma-separated values for the types of
+ * interest; each value may be enclosed in single quotes (') or
+ * unquoted. For example, 'TABLE','VIEW' or TABLE, VIEW. If the
+ * data source does not support a specified table type,
+ * odbc_tables does not return any results for
+ * that type.
+ * @return resource Returns an ODBC result identifier containing the information.
+ *
+ * The result set has the following columns:
+ *
+ * TABLE_CAT
+ * TABLE_SCHEM
+ * TABLE_NAME
+ * TABLE_TYPE
+ * REMARKS
+ *
+ * Drivers can report additional columns.
+ * @throws UodbcException
+ *
+ */
+function odbc_tables($connection_id, string $catalog = null, string $schema = null, string $name = null, string $types = null)
+{
+ error_clear_last();
+ if ($types !== null) {
+ $result = \odbc_tables($connection_id, $catalog, $schema, $name, $types);
+ } elseif ($name !== null) {
+ $result = \odbc_tables($connection_id, $catalog, $schema, $name);
+ } elseif ($schema !== null) {
+ $result = \odbc_tables($connection_id, $catalog, $schema);
+ } elseif ($catalog !== null) {
+ $result = \odbc_tables($connection_id, $catalog);
+ } else {
+ $result = \odbc_tables($connection_id);
+ }
+ if ($result === false) {
+ throw UodbcException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\UopzException;
+
+/**
+ * Makes class extend parent
+ *
+ * @param string $class The name of the class to extend
+ * @param string $parent The name of the class to inherit
+ * @throws UopzException
+ *
+ */
+function uopz_extend(string $class, string $parent): void
+{
+ error_clear_last();
+ $result = \uopz_extend($class, $parent);
+ if ($result === false) {
+ throw UopzException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes class implement interface
+ *
+ * @param string $class
+ * @param string $interface
+ * @throws UopzException
+ *
+ */
+function uopz_implement(string $class, string $interface): void
+{
+ error_clear_last();
+ $result = \uopz_implement($class, $interface);
+ if ($result === false) {
+ throw UopzException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\UrlException;
+
+/**
+ * Decodes a base64 encoded data.
+ *
+ * @param string $data The encoded data.
+ * @param bool $strict If the strict parameter is set to TRUE
+ * then the base64_decode function will return
+ * FALSE if the input contains character from outside the base64
+ * alphabet. Otherwise invalid characters will be silently discarded.
+ * @return string Returns the decoded data. The returned data may be
+ * binary.
+ * @throws UrlException
+ *
+ */
+function base64_decode(string $data, bool $strict = false): string
+{
+ error_clear_last();
+ $result = \base64_decode($data, $strict);
+ if ($result === false) {
+ throw UrlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * get_headers returns an array with the headers sent
+ * by the server in response to a HTTP request.
+ *
+ * @param string $url The target URL.
+ * @param int $format If the optional format parameter is set to non-zero,
+ * get_headers parses the response and sets the
+ * array's keys.
+ * @param resource $context A valid context resource created with
+ * stream_context_create.
+ * @return array Returns an indexed or associative array with the headers.
+ * @throws UrlException
+ *
+ */
+function get_headers(string $url, int $format = 0, $context = null): array
+{
+ error_clear_last();
+ if ($context !== null) {
+ $result = \get_headers($url, $format, $context);
+ } else {
+ $result = \get_headers($url, $format);
+ }
+ if ($result === false) {
+ throw UrlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function parses a URL and returns an associative array containing any
+ * of the various components of the URL that are present.
+ * The values of the array elements are not URL decoded.
+ *
+ * This function is not meant to validate
+ * the given URL, it only breaks it up into the above listed parts. Partial
+ * URLs are also accepted, parse_url tries its best to
+ * parse them correctly.
+ *
+ * @param string $url The URL to parse. Invalid characters are replaced by
+ * _.
+ * @param int $component Specify one of PHP_URL_SCHEME,
+ * PHP_URL_HOST, PHP_URL_PORT,
+ * PHP_URL_USER, PHP_URL_PASS,
+ * PHP_URL_PATH, PHP_URL_QUERY
+ * or PHP_URL_FRAGMENT to retrieve just a specific
+ * URL component as a string (except when
+ * PHP_URL_PORT is given, in which case the return
+ * value will be an integer).
+ * @return mixed On seriously malformed URLs, parse_url.
+ *
+ * If the component parameter is omitted, an
+ * associative array is returned. At least one element will be
+ * present within the array. Potential keys within this array are:
+ *
+ *
+ *
+ * scheme - e.g. http
+ *
+ *
+ *
+ *
+ * host
+ *
+ *
+ *
+ *
+ * port
+ *
+ *
+ *
+ *
+ * user
+ *
+ *
+ *
+ *
+ * pass
+ *
+ *
+ *
+ *
+ * path
+ *
+ *
+ *
+ *
+ * query - after the question mark ?
+ *
+ *
+ *
+ *
+ * fragment - after the hashmark #
+ *
+ *
+ *
+ *
+ * If the component parameter is specified,
+ * parse_url returns a string (or an
+ * integer, in the case of PHP_URL_PORT)
+ * instead of an array. If the requested component doesn't exist
+ * within the given URL, NULL will be returned.
+ * @throws UrlException
+ *
+ */
+function parse_url(string $url, int $component = -1)
+{
+ error_clear_last();
+ $result = \parse_url($url, $component);
+ if ($result === false) {
+ throw UrlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\VarException;
+
+/**
+ * Set the type of variable var to
+ * type.
+ *
+ * @param mixed $var The variable being converted.
+ * @param string $type Possibles values of type are:
+ *
+ *
+ *
+ * "boolean" or "bool"
+ *
+ *
+ *
+ *
+ * "integer" or "int"
+ *
+ *
+ *
+ *
+ * "float" or "double"
+ *
+ *
+ *
+ *
+ * "string"
+ *
+ *
+ *
+ *
+ * "array"
+ *
+ *
+ *
+ *
+ * "object"
+ *
+ *
+ *
+ *
+ * "null"
+ *
+ *
+ *
+ * @throws VarException
+ *
+ */
+function settype(&$var, string $type): void
+{
+ error_clear_last();
+ $result = \settype($var, $type);
+ if ($result === false) {
+ throw VarException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\XdiffException;
+
+/**
+ * Makes a binary diff of two files and stores the result in a patch file.
+ * This function works with both text and binary files. Resulting patch
+ * file can be later applied using xdiff_file_bpatch/xdiff_string_bpatch.
+ *
+ * @param string $old_file Path to the first file. This file acts as "old" file.
+ * @param string $new_file Path to the second file. This file acts as "new" file.
+ * @param string $dest Path of the resulting patch file. Resulting file contains differences
+ * between "old" and "new" files. It is in binary format and is human-unreadable.
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_bdiff(string $old_file, string $new_file, string $dest): void
+{
+ error_clear_last();
+ $result = \xdiff_file_bdiff($old_file, $new_file, $dest);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Patches a file with a binary
+ * patch and stores the result in a file dest.
+ * This function accepts patches created both via xdiff_file_bdiff
+ * and xdiff_file_rabdiff functions or their string counterparts.
+ *
+ * @param string $file The original file.
+ * @param string $patch The binary patch file.
+ * @param string $dest Path of the resulting file.
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_bpatch(string $file, string $patch, string $dest): void
+{
+ error_clear_last();
+ $result = \xdiff_file_bpatch($file, $patch, $dest);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes a binary diff of two files and stores the result in a patch file.
+ * This function works with both text and binary files. Resulting patch
+ * file can be later applied using xdiff_file_bpatch.
+ *
+ * Starting with version 1.5.0 this function is an alias of xdiff_file_bdiff.
+ *
+ * @param string $old_file Path to the first file. This file acts as "old" file.
+ * @param string $new_file Path to the second file. This file acts as "new" file.
+ * @param string $dest Path of the resulting patch file. Resulting file contains differences
+ * between "old" and "new" files. It is in binary format and is human-unreadable.
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_diff_binary(string $old_file, string $new_file, string $dest): void
+{
+ error_clear_last();
+ $result = \xdiff_file_diff_binary($old_file, $new_file, $dest);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes an unified diff containing differences between old_file and
+ * new_file and stores it in dest file. The
+ * resulting file is human-readable. An optional context parameter
+ * specifies how many lines of context should be added around each change.
+ * Setting minimal parameter to true will result in outputting the shortest
+ * patch file possible (can take a long time).
+ *
+ * @param string $old_file Path to the first file. This file acts as "old" file.
+ * @param string $new_file Path to the second file. This file acts as "new" file.
+ * @param string $dest Path of the resulting patch file.
+ * @param int $context Indicates how many lines of context you want to include in diff
+ * result.
+ * @param bool $minimal Set this parameter to TRUE if you want to minimalize size of the result
+ * (can take a long time).
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_diff(string $old_file, string $new_file, string $dest, int $context = 3, bool $minimal = false): void
+{
+ error_clear_last();
+ $result = \xdiff_file_diff($old_file, $new_file, $dest, $context, $minimal);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Patches a file with a binary
+ * patch and stores the result in a file dest.
+ * This function accepts patches created both via xdiff_file_bdiff
+ * or xdiff_file_rabdiff functions or their string counterparts.
+ *
+ * Starting with version 1.5.0 this function is an alias of xdiff_file_bpatch.
+ *
+ * @param string $file The original file.
+ * @param string $patch The binary patch file.
+ * @param string $dest Path of the resulting file.
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_patch_binary(string $file, string $patch, string $dest): void
+{
+ error_clear_last();
+ $result = \xdiff_file_patch_binary($file, $patch, $dest);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Makes a binary diff of two files and stores the result in a patch file.
+ * The difference between this function and xdiff_file_bdiff is different
+ * algorithm used which should result in faster execution and smaller diff produced.
+ * This function works with both text and binary files. Resulting patch
+ * file can be later applied using xdiff_file_bpatch/xdiff_string_bpatch.
+ *
+ * For more details about differences between algorithm used please check libxdiff
+ * website.
+ *
+ * @param string $old_file Path to the first file. This file acts as "old" file.
+ * @param string $new_file Path to the second file. This file acts as "new" file.
+ * @param string $dest Path of the resulting patch file. Resulting file contains differences
+ * between "old" and "new" files. It is in binary format and is human-unreadable.
+ * @throws XdiffException
+ *
+ */
+function xdiff_file_rabdiff(string $old_file, string $new_file, string $dest): void
+{
+ error_clear_last();
+ $result = \xdiff_file_rabdiff($old_file, $new_file, $dest);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Patches a string str with a binary patch.
+ * This function accepts patches created both via xdiff_string_bdiff
+ * and xdiff_string_rabdiff functions or their file counterparts.
+ *
+ * @param string $str The original binary string.
+ * @param string $patch The binary patch string.
+ * @return string Returns the patched string.
+ * @throws XdiffException
+ *
+ */
+function xdiff_string_bpatch(string $str, string $patch): string
+{
+ error_clear_last();
+ $result = \xdiff_string_bpatch($str, $patch);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Patches a string str with a binary patch.
+ * This function accepts patches created both via xdiff_string_bdiff
+ * and xdiff_string_rabdiff functions or their file counterparts.
+ *
+ * Starting with version 1.5.0 this function is an alias of xdiff_string_bpatch.
+ *
+ * @param string $str The original binary string.
+ * @param string $patch The binary patch string.
+ * @return string Returns the patched string.
+ * @throws XdiffException
+ *
+ */
+function xdiff_string_patch_binary(string $str, string $patch): string
+{
+ error_clear_last();
+ $result = \xdiff_string_patch_binary($str, $patch);
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Patches a str string with an unified patch in patch parameter
+ * and returns the result. patch has to be an unified diff created by
+ * xdiff_file_diff/xdiff_string_diff function.
+ * An optional flags parameter specifies mode of operation. Any
+ * rejected parts of the patch will be stored inside error variable if
+ * it is provided.
+ *
+ * @param string $str The original string.
+ * @param string $patch The unified patch string. It has to be created using xdiff_string_diff,
+ * xdiff_file_diff functions or compatible tools.
+ * @param int $flags flags can be either
+ * XDIFF_PATCH_NORMAL (default mode, normal patch)
+ * or XDIFF_PATCH_REVERSE (reversed patch).
+ *
+ * Starting from version 1.5.0, you can also use binary OR to enable
+ * XDIFF_PATCH_IGNORESPACE flag.
+ * @param string|null $error If provided then rejected parts are stored inside this variable.
+ * @return string Returns the patched string.
+ * @throws XdiffException
+ *
+ */
+function xdiff_string_patch(string $str, string $patch, int $flags = null, ?string &$error = null): string
+{
+ error_clear_last();
+ if ($error !== null) {
+ $result = \xdiff_string_patch($str, $patch, $flags, $error);
+ } elseif ($flags !== null) {
+ $result = \xdiff_string_patch($str, $patch, $flags);
+ } else {
+ $result = \xdiff_string_patch($str, $patch);
+ }
+ if ($result === false) {
+ throw XdiffException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\XmlException;
+
+/**
+ * xml_parser_create_ns creates a new XML parser
+ * with XML namespace support and returns a resource handle referencing
+ * it to be used by the other XML functions.
+ *
+ * @param string $encoding The input encoding is automatically detected, so that the
+ * encoding parameter specifies only the output
+ * encoding. In PHP 5.0.0 and 5.0.1, the default output charset is
+ * ISO-8859-1, while in PHP 5.0.2 and upper is UTF-8. The supported
+ * encodings are ISO-8859-1, UTF-8 and
+ * US-ASCII.
+ * @param string $separator With a namespace aware parser tag parameters passed to the various
+ * handler functions will consist of namespace and tag name separated by
+ * the string specified in separator.
+ * @return resource Returns a resource handle for the new XML parser.
+ * @throws XmlException
+ *
+ */
+function xml_parser_create_ns(string $encoding = null, string $separator = ":")
+{
+ error_clear_last();
+ if ($separator !== ":") {
+ $result = \xml_parser_create_ns($encoding, $separator);
+ } elseif ($encoding !== null) {
+ $result = \xml_parser_create_ns($encoding);
+ } else {
+ $result = \xml_parser_create_ns();
+ }
+ if ($result === false) {
+ throw XmlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * xml_parser_create creates a new XML parser
+ * and returns a resource handle referencing it to be used by the
+ * other XML functions.
+ *
+ * @param string $encoding The optional encoding specifies the character
+ * encoding for the input/output in PHP 4. Starting from PHP 5, the input
+ * encoding is automatically detected, so that the
+ * encoding parameter specifies only the output
+ * encoding. In PHP 4, the default output encoding is the same as the
+ * input charset. If empty string is passed, the parser attempts to identify
+ * which encoding the document is encoded in by looking at the heading 3 or
+ * 4 bytes. In PHP 5.0.0 and 5.0.1, the default output charset is
+ * ISO-8859-1, while in PHP 5.0.2 and upper is UTF-8. The supported
+ * encodings are ISO-8859-1, UTF-8 and
+ * US-ASCII.
+ * @return resource Returns a resource handle for the new XML parser.
+ * @throws XmlException
+ *
+ */
+function xml_parser_create(string $encoding = null)
+{
+ error_clear_last();
+ if ($encoding !== null) {
+ $result = \xml_parser_create($encoding);
+ } else {
+ $result = \xml_parser_create();
+ }
+ if ($result === false) {
+ throw XmlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function allows to use parser inside
+ * object. All callback functions could be set with
+ * xml_set_element_handler etc and assumed to be
+ * methods of object.
+ *
+ * @param resource $parser A reference to the XML parser to use inside the object.
+ * @param object $object The object where to use the XML parser.
+ * @throws XmlException
+ *
+ */
+function xml_set_object($parser, object &$object): void
+{
+ error_clear_last();
+ $result = \xml_set_object($parser, $object);
+ if ($result === false) {
+ throw XmlException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\XmlrpcException;
+
+/**
+ * Sets xmlrpc type, base64 or datetime, for a PHP string value.
+ *
+ * @param string|\DateTime $value Value to set the type
+ * @param string $type 'base64' or 'datetime'
+ * @throws XmlrpcException
+ *
+ */
+function xmlrpc_set_type(&$value, string $type): void
+{
+ error_clear_last();
+ $result = \xmlrpc_set_type($value, $type);
+ if ($result === false) {
+ throw XmlrpcException::createFromPhpError();
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\YamlException;
+
+/**
+ * Convert all or part of a YAML document stream read from a file to a PHP variable.
+ *
+ * @param string $filename Path to the file.
+ * @param int $pos Document to extract from stream (-1 for all
+ * documents, 0 for first document, ...).
+ * @param int|null $ndocs If ndocs is provided, then it is filled with the
+ * number of documents found in stream.
+ * @param array $callbacks Content handlers for YAML nodes. Associative array of YAML
+ * tag => callable mappings. See
+ * parse callbacks for more
+ * details.
+ * @return mixed Returns the value encoded in input in appropriate
+ * PHP type. If pos is -1 an
+ * array will be returned with one entry for each document found
+ * in the stream.
+ * @throws YamlException
+ *
+ */
+function yaml_parse_file(string $filename, int $pos = 0, ?int &$ndocs = null, array $callbacks = null)
+{
+ error_clear_last();
+ $result = \yaml_parse_file($filename, $pos, $ndocs, $callbacks);
+ if ($result === false) {
+ throw YamlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Convert all or part of a YAML document stream read from a URL to a PHP variable.
+ *
+ * @param string $url url should be of the form "scheme://...". PHP
+ * will search for a protocol handler (also known as a wrapper) for that
+ * scheme. If no wrappers for that protocol are registered, PHP will emit
+ * a notice to help you track potential problems in your script and then
+ * continue as though filename specifies a regular file.
+ * @param int $pos Document to extract from stream (-1 for all
+ * documents, 0 for first document, ...).
+ * @param int|null $ndocs If ndocs is provided, then it is filled with the
+ * number of documents found in stream.
+ * @param array $callbacks Content handlers for YAML nodes. Associative array of YAML
+ * tag => callable mappings. See
+ * parse callbacks for more
+ * @return mixed Returns the value encoded in input in appropriate
+ * PHP type. If pos is
+ * -1 an array will be returned with one entry
+ * for each document found in the stream.
+ * @throws YamlException
+ *
+ */
+function yaml_parse_url(string $url, int $pos = 0, ?int &$ndocs = null, array $callbacks = null)
+{
+ error_clear_last();
+ $result = \yaml_parse_url($url, $pos, $ndocs, $callbacks);
+ if ($result === false) {
+ throw YamlException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Convert all or part of a YAML document stream to a PHP variable.
+ *
+ * @param string $input The string to parse as a YAML document stream.
+ * @param int $pos Document to extract from stream (-1 for all
+ * documents, 0 for first document, ...).
+ * @param int|null $ndocs If ndocs is provided, then it is filled with the
+ * number of documents found in stream.
+ * @param array $callbacks Content handlers for YAML nodes. Associative array of YAML
+ * tag => callable mappings. See
+ * parse callbacks for more
+ * details.
+ * @return mixed Returns the value encoded in input in appropriate
+ * PHP type. If pos is -1 an
+ * array will be returned with one entry for each document found
+ * in the stream.
+ * @throws YamlException
+ *
+ */
+function yaml_parse(string $input, int $pos = 0, ?int &$ndocs = null, array $callbacks = null)
+{
+ error_clear_last();
+ $result = \yaml_parse($input, $pos, $ndocs, $callbacks);
+ if ($result === false) {
+ throw YamlException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\YazException;
+
+/**
+ * This function invokes a CCL parser. It converts a given CCL FIND query to
+ * an RPN query which may be passed to the yaz_search
+ * function to perform a search.
+ *
+ * To define a set of valid CCL fields call yaz_ccl_conf
+ * prior to this function.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @param string $query The CCL FIND query.
+ * @param array|null $result If the function was executed successfully, this will be an array
+ * containing the valid RPN query under the key rpn.
+ *
+ * Upon failure, three indexes are set in this array to indicate the cause
+ * of failure:
+ *
+ *
+ *
+ * errorcode - the CCL error code (integer)
+ *
+ *
+ *
+ *
+ * errorstring - the CCL error string
+ *
+ *
+ *
+ *
+ * errorpos - approximate position in query of failure
+ * (integer is character position)
+ *
+ *
+ *
+ *
+ * errorcode - the CCL error code (integer)
+ *
+ * errorstring - the CCL error string
+ *
+ * errorpos - approximate position in query of failure
+ * (integer is character position)
+ * @throws YazException
+ *
+ */
+function yaz_ccl_parse($id, string $query, ?array &$result): void
+{
+ error_clear_last();
+ $result = \yaz_ccl_parse($id, $query, $result);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Closes the connection given by parameter id.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @throws YazException
+ *
+ */
+function yaz_close($id): void
+{
+ error_clear_last();
+ $result = \yaz_close($id);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function returns a connection resource on success, zero on
+ * failure.
+ *
+ * yaz_connect prepares for a connection to a
+ * Z39.50 server.
+ * This function is non-blocking and does not attempt to establish
+ * a connection - it merely prepares a connect to be performed later when
+ * yaz_wait is called.
+ *
+ * @param string $zurl A string that takes the form host[:port][/database].
+ * If port is omitted, port 210 is used. If database is omitted
+ * Default is used.
+ * @param mixed $options If given as a string, it is treated as the Z39.50 V2 authentication
+ * string (OpenAuth).
+ *
+ * If given as an array, the contents of the array serves as options.
+ *
+ *
+ * user
+ *
+ *
+ * Username for authentication.
+ *
+ *
+ *
+ *
+ * group
+ *
+ *
+ * Group for authentication.
+ *
+ *
+ *
+ *
+ * password
+ *
+ *
+ * Password for authentication.
+ *
+ *
+ *
+ *
+ * cookie
+ *
+ *
+ * Cookie for session (YAZ proxy).
+ *
+ *
+ *
+ *
+ * proxy
+ *
+ *
+ * Proxy for connection (YAZ proxy).
+ *
+ *
+ *
+ *
+ * persistent
+ *
+ *
+ * A boolean. If TRUE the connection is persistent; If FALSE the
+ * connection is not persistent. By default connections are persistent.
+ *
+ *
+ *
+ * If you open a persistent connection, you won't be able to close
+ * it later with yaz_close.
+ *
+ *
+ *
+ *
+ *
+ * piggyback
+ *
+ *
+ * A boolean. If TRUE piggyback is enabled for searches; If FALSE
+ * piggyback is disabled. By default piggyback is enabled.
+ *
+ *
+ * Enabling piggyback is more efficient and usually saves a
+ * network-round-trip for first time fetches of records. However, a
+ * few Z39.50 servers do not support piggyback or they ignore element
+ * set names. For those, piggyback should be disabled.
+ *
+ *
+ *
+ *
+ * charset
+ *
+ *
+ * A string that specifies character set to be used in Z39.50
+ * language and character set negotiation. Use strings such as:
+ * ISO-8859-1, UTF-8,
+ * UTF-16.
+ *
+ *
+ * Most Z39.50 servers do not support this feature (and thus, this is
+ * ignored). Many servers use the ISO-8859-1 encoding for queries and
+ * messages. MARC21/USMARC records are not affected by this setting.
+ *
+ *
+ *
+ *
+ *
+ * preferredMessageSize
+ *
+ *
+ * An integer that specifies the maximum byte size of all records
+ * to be returned by a target during retrieval. See the
+ * Z39.50 standard for more
+ * information.
+ *
+ *
+ *
+ * This option is supported in PECL YAZ 1.0.5 or later.
+ *
+ *
+ *
+ *
+ *
+ *
+ * maximumRecordSize
+ *
+ *
+ * An integer that specifies the maximum byte size of a single record
+ * to be returned by a target during retrieval. This
+ * entity is referred to as Exceptional-record-size in the
+ * Z39.50 standard.
+ *
+ *
+ *
+ * This option is supported in PECL YAZ 1.0.5 or later.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Username for authentication.
+ *
+ * Group for authentication.
+ *
+ * Password for authentication.
+ *
+ * Cookie for session (YAZ proxy).
+ *
+ * Proxy for connection (YAZ proxy).
+ *
+ * A boolean. If TRUE the connection is persistent; If FALSE the
+ * connection is not persistent. By default connections are persistent.
+ *
+ * If you open a persistent connection, you won't be able to close
+ * it later with yaz_close.
+ *
+ * A boolean. If TRUE piggyback is enabled for searches; If FALSE
+ * piggyback is disabled. By default piggyback is enabled.
+ *
+ * Enabling piggyback is more efficient and usually saves a
+ * network-round-trip for first time fetches of records. However, a
+ * few Z39.50 servers do not support piggyback or they ignore element
+ * set names. For those, piggyback should be disabled.
+ *
+ * A string that specifies character set to be used in Z39.50
+ * language and character set negotiation. Use strings such as:
+ * ISO-8859-1, UTF-8,
+ * UTF-16.
+ *
+ * Most Z39.50 servers do not support this feature (and thus, this is
+ * ignored). Many servers use the ISO-8859-1 encoding for queries and
+ * messages. MARC21/USMARC records are not affected by this setting.
+ *
+ * An integer that specifies the maximum byte size of all records
+ * to be returned by a target during retrieval. See the
+ * Z39.50 standard for more
+ * information.
+ *
+ * This option is supported in PECL YAZ 1.0.5 or later.
+ *
+ * An integer that specifies the maximum byte size of a single record
+ * to be returned by a target during retrieval. This
+ * entity is referred to as Exceptional-record-size in the
+ * Z39.50 standard.
+ *
+ * This option is supported in PECL YAZ 1.0.5 or later.
+ * @return mixed A connection resource on success, FALSE on error.
+ * @throws YazException
+ *
+ */
+function yaz_connect(string $zurl, $options = null)
+{
+ error_clear_last();
+ if ($options !== null) {
+ $result = \yaz_connect($zurl, $options);
+ } else {
+ $result = \yaz_connect($zurl);
+ }
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function allows you to change databases within a session by
+ * specifying one or more databases to be used in search, retrieval, etc.
+ * - overriding databases specified in call to
+ * yaz_connect.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @param string $databases A string containing one or more databases. Multiple databases are
+ * separated by a plus sign +.
+ * @throws YazException
+ *
+ */
+function yaz_database($id, string $databases): void
+{
+ error_clear_last();
+ $result = \yaz_database($id, $databases);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function sets the element set name for retrieval.
+ *
+ * Call this function before yaz_search or
+ * yaz_present to specify the element set name for
+ * records to be retrieved.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @param string $elementset Most servers support F (for full records) and
+ * B (for brief records).
+ * @throws YazException
+ *
+ */
+function yaz_element($id, string $elementset): void
+{
+ error_clear_last();
+ $result = \yaz_element($id, $elementset);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function prepares for retrieval of records after a successful search.
+ *
+ * The yaz_range function should be called prior to this
+ * function to specify the range of records to be retrieved.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @throws YazException
+ *
+ */
+function yaz_present($id): void
+{
+ error_clear_last();
+ $result = \yaz_present($id);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * yaz_search prepares for a search on the given
+ * connection.
+ *
+ * Like yaz_connect this function is non-blocking and
+ * only prepares for a search to be executed later when
+ * yaz_wait is called.
+ *
+ * @param resource $id The connection resource returned by yaz_connect.
+ * @param string $type This parameter represents the query type - only "rpn"
+ * is supported now in which case the third argument specifies a Type-1
+ * query in prefix query notation.
+ * @param string $query The RPN query is a textual representation of the Type-1 query as
+ * defined by the Z39.50 standard. However, in the text representation
+ * as used by YAZ a prefix notation is used, that is the operator
+ * precedes the operands. The query string is a sequence of tokens where
+ * white space is ignored unless surrounded by double quotes. Tokens beginning
+ * with an at-character (@) are considered operators,
+ * otherwise they are treated as search terms.
+ *
+ * You can find information about attributes at the
+ * Z39.50 Maintenance Agency
+ * site.
+ *
+ * If you would like to use a more friendly notation,
+ * use the CCL parser - functions yaz_ccl_conf and
+ * yaz_ccl_parse.
+ * @throws YazException
+ *
+ */
+function yaz_search($id, string $type, string $query): void
+{
+ error_clear_last();
+ $result = \yaz_search($id, $type, $query);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function carries out networked (blocked) activity for outstanding
+ * requests which have been prepared by the functions
+ * yaz_connect, yaz_search,
+ * yaz_present, yaz_scan and
+ * yaz_itemorder.
+ *
+ * yaz_wait returns when all servers have either
+ * completed all requests or aborted (in case of errors).
+ *
+ * @param array $options An associative array of options:
+ *
+ *
+ * timeout
+ *
+ *
+ * Sets timeout in seconds. If a server has not responded within the
+ * timeout it is considered dead and yaz_wait
+ * returns. The default value for timeout is 15 seconds.
+ *
+ *
+ *
+ *
+ * event
+ *
+ *
+ * A boolean.
+ *
+ *
+ *
+ *
+ *
+ * Sets timeout in seconds. If a server has not responded within the
+ * timeout it is considered dead and yaz_wait
+ * returns. The default value for timeout is 15 seconds.
+ *
+ * A boolean.
+ * @return mixed Returns TRUE on success.
+ * In event mode, returns resource.
+ * @throws YazException
+ *
+ */
+function yaz_wait(array &$options = null)
+{
+ error_clear_last();
+ $result = \yaz_wait($options);
+ if ($result === false) {
+ throw YazException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ZipException;
+
+/**
+ * Closes the specified directory entry.
+ *
+ * @param resource $zip_entry A directory entry previously opened zip_entry_open.
+ * @throws ZipException
+ *
+ */
+function zip_entry_close($zip_entry): void
+{
+ error_clear_last();
+ $result = \zip_entry_close($zip_entry);
+ if ($result === false) {
+ throw ZipException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Opens a directory entry in a zip file for reading.
+ *
+ * @param resource $zip A valid resource handle returned by zip_open.
+ * @param resource $zip_entry A directory entry returned by zip_read.
+ * @param string $mode Any of the modes specified in the documentation of
+ * fopen.
+ *
+ * Currently, mode is ignored and is always
+ * "rb". This is due to the fact that zip support
+ * in PHP is read only access.
+ * @throws ZipException
+ *
+ */
+function zip_entry_open($zip, $zip_entry, string $mode = null): void
+{
+ error_clear_last();
+ if ($mode !== null) {
+ $result = \zip_entry_open($zip, $zip_entry, $mode);
+ } else {
+ $result = \zip_entry_open($zip, $zip_entry);
+ }
+ if ($result === false) {
+ throw ZipException::createFromPhpError();
+ }
+}
+
+
+/**
+ * Reads from an open directory entry.
+ *
+ * @param resource $zip_entry A directory entry returned by zip_read.
+ * @param int $length The number of bytes to return.
+ *
+ * This should be the uncompressed length you wish to read.
+ * @return string Returns the data read, empty string on end of a file.
+ * @throws ZipException
+ *
+ */
+function zip_entry_read($zip_entry, int $length = 1024): string
+{
+ error_clear_last();
+ $result = \zip_entry_read($zip_entry, $length);
+ if ($result === false) {
+ throw ZipException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use Safe\Exceptions\ZlibException;
+
+/**
+ * Incrementally deflates data in the specified context.
+ *
+ * @param resource $context A context created with deflate_init.
+ * @param string $data A chunk of data to compress.
+ * @param int $flush_mode One of ZLIB_BLOCK,
+ * ZLIB_NO_FLUSH,
+ * ZLIB_PARTIAL_FLUSH,
+ * ZLIB_SYNC_FLUSH (default),
+ * ZLIB_FULL_FLUSH, ZLIB_FINISH.
+ * Normally you will want to set ZLIB_NO_FLUSH to
+ * maximize compression, and ZLIB_FINISH to terminate
+ * with the last chunk of data. See the zlib manual for a
+ * detailed description of these constants.
+ * @return string Returns a chunk of compressed data.
+ * @throws ZlibException
+ *
+ */
+function deflate_add($context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
+{
+ error_clear_last();
+ $result = \deflate_add($context, $data, $flush_mode);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Initializes an incremental deflate context using the specified
+ * encoding.
+ *
+ * Note that the window option here only sets the window size
+ * of the algorithm, differently from the zlib filters where the same parameter
+ * also sets the encoding to use; the encoding must be set with the
+ * encoding parameter.
+ *
+ * Limitation: there is currently no way to set the header information on a GZIP
+ * compressed stream, which are set as follows: GZIP signature
+ * (\x1f\x8B); compression method (\x08
+ * == DEFLATE); 6 zero bytes; the operating system set to the current system
+ * (\x00 = Windows, \x03 = Unix, etc.)
+ *
+ * @param int $encoding One of the ZLIB_ENCODING_* constants.
+ * @param array $options An associative array which may contain the following elements:
+ *
+ *
+ * level
+ *
+ *
+ * The compression level in range -1..9; defaults to -1.
+ *
+ *
+ *
+ *
+ * memory
+ *
+ *
+ * The compression memory level in range 1..9; defaults to 8.
+ *
+ *
+ *
+ *
+ * window
+ *
+ *
+ * The zlib window size (logarithmic) in range 8..15; defaults to 15.
+ *
+ *
+ *
+ *
+ * strategy
+ *
+ *
+ * One of ZLIB_FILTERED,
+ * ZLIB_HUFFMAN_ONLY, ZLIB_RLE,
+ * ZLIB_FIXED or
+ * ZLIB_DEFAULT_STRATEGY (the default).
+ *
+ *
+ *
+ *
+ * dictionary
+ *
+ *
+ * A string or an array of strings
+ * of the preset dictionary (default: no preset dictionary).
+ *
+ *
+ *
+ *
+ *
+ * The compression level in range -1..9; defaults to -1.
+ *
+ * The compression memory level in range 1..9; defaults to 8.
+ *
+ * The zlib window size (logarithmic) in range 8..15; defaults to 15.
+ *
+ * One of ZLIB_FILTERED,
+ * ZLIB_HUFFMAN_ONLY, ZLIB_RLE,
+ * ZLIB_FIXED or
+ * ZLIB_DEFAULT_STRATEGY (the default).
+ *
+ * A string or an array of strings
+ * of the preset dictionary (default: no preset dictionary).
+ * @return resource Returns a deflate context resource (zlib.deflate) on
+ * success.
+ * @throws ZlibException
+ *
+ */
+function deflate_init(int $encoding, array $options = null)
+{
+ error_clear_last();
+ $result = \deflate_init($encoding, $options);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Closes the given gz-file pointer.
+ *
+ * @param resource $zp The gz-file pointer. It must be valid, and must point to a file
+ * successfully opened by gzopen.
+ * @throws ZlibException
+ *
+ */
+function gzclose($zp): void
+{
+ error_clear_last();
+ $result = \gzclose($zp);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function compresses the given string using the ZLIB
+ * data format.
+ *
+ * For details on the ZLIB compression algorithm see the document
+ * "ZLIB Compressed Data Format
+ * Specification version 3.3" (RFC 1950).
+ *
+ * @param string $data The data to compress.
+ * @param int $level The level of compression. Can be given as 0 for no compression up to 9
+ * for maximum compression.
+ *
+ * If -1 is used, the default compression of the zlib library is used which is 6.
+ * @param int $encoding One of ZLIB_ENCODING_* constants.
+ * @return string The compressed string.
+ * @throws ZlibException
+ *
+ */
+function gzcompress(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_DEFLATE): string
+{
+ error_clear_last();
+ $result = \gzcompress($data, $level, $encoding);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns a decoded version of the input
+ * data.
+ *
+ * @param string $data The data to decode, encoded by gzencode.
+ * @param int $length The maximum length of data to decode.
+ * @return string The decoded string.
+ * @throws ZlibException
+ *
+ */
+function gzdecode(string $data, int $length = null): string
+{
+ error_clear_last();
+ if ($length !== null) {
+ $result = \gzdecode($data, $length);
+ } else {
+ $result = \gzdecode($data);
+ }
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function compresses the given string using the DEFLATE
+ * data format.
+ *
+ * For details on the DEFLATE compression algorithm see the document
+ * "DEFLATE Compressed Data Format
+ * Specification version 1.3" (RFC 1951).
+ *
+ * @param string $data The data to deflate.
+ * @param int $level The level of compression. Can be given as 0 for no compression up to 9
+ * for maximum compression. If not given, the default compression level will
+ * be the default compression level of the zlib library.
+ * @param int $encoding One of ZLIB_ENCODING_* constants.
+ * @return string The deflated string.
+ * @throws ZlibException
+ *
+ */
+function gzdeflate(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_RAW): string
+{
+ error_clear_last();
+ $result = \gzdeflate($data, $level, $encoding);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function returns a compressed version of the input
+ * data compatible with the output of the
+ * gzip program.
+ *
+ * For more information on the GZIP file format, see the document:
+ * GZIP file format specification
+ * version 4.3 (RFC 1952).
+ *
+ * @param string $data The data to encode.
+ * @param int $level The level of compression. Can be given as 0 for no compression up to 9
+ * for maximum compression. If not given, the default compression level will
+ * be the default compression level of the zlib library.
+ * @param int $encoding_mode The encoding mode. Can be FORCE_GZIP (the default)
+ * or FORCE_DEFLATE.
+ *
+ * Prior to PHP 5.4.0, using FORCE_DEFLATE results in
+ * a standard zlib deflated string (inclusive zlib headers) after a gzip
+ * file header but without the trailing crc32 checksum.
+ *
+ * In PHP 5.4.0 and later, FORCE_DEFLATE generates
+ * RFC 1950 compliant output, consisting of a zlib header, the deflated
+ * data, and an Adler checksum.
+ * @return string The encoded string.
+ * @throws ZlibException
+ *
+ */
+function gzencode(string $data, int $level = -1, int $encoding_mode = FORCE_GZIP): string
+{
+ error_clear_last();
+ $result = \gzencode($data, $level, $encoding_mode);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Gets a (uncompressed) string of up to length - 1 bytes read from the given
+ * file pointer. Reading ends when length - 1 bytes have been read, on a
+ * newline, or on EOF (whichever comes first).
+ *
+ * @param resource $zp The gz-file pointer. It must be valid, and must point to a file
+ * successfully opened by gzopen.
+ * @param int $length The length of data to get.
+ * @return string The uncompressed string.
+ * @throws ZlibException
+ *
+ */
+function gzgets($zp, int $length = null): string
+{
+ error_clear_last();
+ if ($length !== null) {
+ $result = \gzgets($zp, $length);
+ } else {
+ $result = \gzgets($zp);
+ }
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Identical to gzgets, except that
+ * gzgetss attempts to strip any HTML and PHP
+ * tags from the text it reads.
+ *
+ * @param resource $zp The gz-file pointer. It must be valid, and must point to a file
+ * successfully opened by gzopen.
+ * @param int $length The length of data to get.
+ * @param string $allowable_tags You can use this optional parameter to specify tags which should not
+ * be stripped.
+ * @return string The uncompressed and stripped string.
+ * @throws ZlibException
+ *
+ */
+function gzgetss($zp, int $length, string $allowable_tags = null): string
+{
+ error_clear_last();
+ if ($allowable_tags !== null) {
+ $result = \gzgetss($zp, $length, $allowable_tags);
+ } else {
+ $result = \gzgetss($zp, $length);
+ }
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * This function inflates a deflated string.
+ *
+ * @param string $data The data compressed by gzdeflate.
+ * @param int $length The maximum length of data to decode.
+ * @return string The original uncompressed data.
+ *
+ * The function will return an error if the uncompressed data is more than
+ * 32768 times the length of the compressed input data
+ * or more than the optional parameter length.
+ * @throws ZlibException
+ *
+ */
+function gzinflate(string $data, int $length = 0): string
+{
+ error_clear_last();
+ $result = \gzinflate($data, $length);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads to EOF on the given gz-file pointer from the current position and
+ * writes the (uncompressed) results to standard output.
+ *
+ * @param resource $zp The gz-file pointer. It must be valid, and must point to a file
+ * successfully opened by gzopen.
+ * @return int The number of uncompressed characters read from gz
+ * and passed through to the input.
+ * @throws ZlibException
+ *
+ */
+function gzpassthru($zp): int
+{
+ error_clear_last();
+ $result = \gzpassthru($zp);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Sets the file position indicator of the given gz-file pointer to the
+ * beginning of the file stream.
+ *
+ * @param resource $zp The gz-file pointer. It must be valid, and must point to a file
+ * successfully opened by gzopen.
+ * @throws ZlibException
+ *
+ */
+function gzrewind($zp): void
+{
+ error_clear_last();
+ $result = \gzrewind($zp);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+}
+
+
+/**
+ * This function uncompress a compressed string.
+ *
+ * @param string $data The data compressed by gzcompress.
+ * @param int $length The maximum length of data to decode.
+ * @return string The original uncompressed data.
+ *
+ * The function will return an error if the uncompressed data is more than
+ * 32768 times the length of the compressed input data
+ * or more than the optional parameter length.
+ * @throws ZlibException
+ *
+ */
+function gzuncompress(string $data, int $length = 0): string
+{
+ error_clear_last();
+ $result = \gzuncompress($data, $length);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ *
+ *
+ * @param resource $resource
+ * @return int Returns number of bytes read so far.
+ * @throws ZlibException
+ *
+ */
+function inflate_get_read_len($resource): int
+{
+ error_clear_last();
+ $result = \inflate_get_read_len($resource);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Usually returns either ZLIB_OK or ZLIB_STREAM_END.
+ *
+ * @param resource $resource
+ * @return int Returns decompression status.
+ * @throws ZlibException
+ *
+ */
+function inflate_get_status($resource): int
+{
+ error_clear_last();
+ $result = \inflate_get_status($resource);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Incrementally inflates encoded data in the specified context.
+ *
+ * Limitation: header information from GZIP compressed data are not made
+ * available.
+ *
+ * @param resource $context A context created with inflate_init.
+ * @param string $encoded_data A chunk of compressed data.
+ * @param int $flush_mode One of ZLIB_BLOCK,
+ * ZLIB_NO_FLUSH,
+ * ZLIB_PARTIAL_FLUSH,
+ * ZLIB_SYNC_FLUSH (default),
+ * ZLIB_FULL_FLUSH, ZLIB_FINISH.
+ * Normally you will want to set ZLIB_NO_FLUSH to
+ * maximize compression, and ZLIB_FINISH to terminate
+ * with the last chunk of data. See the zlib manual for a
+ * detailed description of these constants.
+ * @return string Returns a chunk of uncompressed data.
+ * @throws ZlibException
+ *
+ */
+function inflate_add($context, string $encoded_data, int $flush_mode = ZLIB_SYNC_FLUSH): string
+{
+ error_clear_last();
+ $result = \inflate_add($context, $encoded_data, $flush_mode);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Initialize an incremental inflate context with the specified
+ * encoding.
+ *
+ * @param int $encoding One of the ZLIB_ENCODING_* constants.
+ * @param array $options An associative array which may contain the following elements:
+ *
+ *
+ * level
+ *
+ *
+ * The compression level in range -1..9; defaults to -1.
+ *
+ *
+ *
+ *
+ * memory
+ *
+ *
+ * The compression memory level in range 1..9; defaults to 8.
+ *
+ *
+ *
+ *
+ * window
+ *
+ *
+ * The zlib window size (logarithmic) in range 8..15; defaults to 15.
+ *
+ *
+ *
+ *
+ * strategy
+ *
+ *
+ * One of ZLIB_FILTERED,
+ * ZLIB_HUFFMAN_ONLY, ZLIB_RLE,
+ * ZLIB_FIXED or
+ * ZLIB_DEFAULT_STRATEGY (the default).
+ *
+ *
+ *
+ *
+ * dictionary
+ *
+ *
+ * A string or an array of strings
+ * of the preset dictionary (default: no preset dictionary).
+ *
+ *
+ *
+ *
+ *
+ * The compression level in range -1..9; defaults to -1.
+ *
+ * The compression memory level in range 1..9; defaults to 8.
+ *
+ * The zlib window size (logarithmic) in range 8..15; defaults to 15.
+ *
+ * One of ZLIB_FILTERED,
+ * ZLIB_HUFFMAN_ONLY, ZLIB_RLE,
+ * ZLIB_FIXED or
+ * ZLIB_DEFAULT_STRATEGY (the default).
+ *
+ * A string or an array of strings
+ * of the preset dictionary (default: no preset dictionary).
+ * @return resource Returns an inflate context resource (zlib.inflate) on
+ * success.
+ * @throws ZlibException
+ *
+ */
+function inflate_init(int $encoding, array $options = null)
+{
+ error_clear_last();
+ $result = \inflate_init($encoding, $options);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Reads a file, decompresses it and writes it to standard output.
+ *
+ * readgzfile can be used to read a file which is not in
+ * gzip format; in this case readgzfile will directly
+ * read from the file without decompression.
+ *
+ * @param string $filename The file name. This file will be opened from the filesystem and its
+ * contents written to standard output.
+ * @param int $use_include_path You can set this optional parameter to 1, if you
+ * want to search for the file in the include_path too.
+ * @return int Returns the number of (uncompressed) bytes read from the file on success
+ * @throws ZlibException
+ *
+ */
+function readgzfile(string $filename, int $use_include_path = 0): int
+{
+ error_clear_last();
+ $result = \readgzfile($filename, $use_include_path);
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
+
+
+/**
+ * Uncompress any raw/gzip/zlib encoded data.
+ *
+ * @param string $data
+ * @param int $max_decoded_len
+ * @return string Returns the uncompressed data.
+ * @throws ZlibException
+ *
+ */
+function zlib_decode(string $data, int $max_decoded_len = null): string
+{
+ error_clear_last();
+ if ($max_decoded_len !== null) {
+ $result = \zlib_decode($data, $max_decoded_len);
+ } else {
+ $result = \zlib_decode($data);
+ }
+ if ($result === false) {
+ throw ZlibException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use DateInterval;
+use DateTimeInterface;
+use DateTimeZone;
+use Safe\Exceptions\DatetimeException;
+
+/** this class implements a safe version of the Datetime class */
+class DateTime extends \DateTime
+{
+ //switch from regular datetime to safe version
+ private static function createFromRegular(\DateTime $datetime): self
+ {
+ return new self($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimezone());
+ }
+
+ /**
+ * @param string $format
+ * @param string $time
+ * @param DateTimeZone|null $timezone
+ * @throws DatetimeException
+ */
+ public static function createFromFormat($format, $time, $timezone = null): self
+ {
+ $datetime = parent::createFromFormat($format, $time, $timezone);
+ if ($datetime === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($datetime);
+ }
+
+ /**
+ * @param DateTimeInterface $datetime2 The date to compare to.
+ * @param boolean $absolute [optional] Whether to return absolute difference.
+ * @return DateInterval The DateInterval object representing the difference between the two dates.
+ * @throws DatetimeException
+ */
+ public function diff($datetime2, $absolute = false): DateInterval
+ {
+ /** @var \DateInterval|false $result */
+ $result = parent::diff($datetime2, $absolute);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $modify A date/time string. Valid formats are explained in <a href="https://secure.php.net/manual/en/datetime.formats.php">Date and Time Formats</a>.
+ * @return DateTime Returns the DateTime object for method chaining.
+ * @throws DatetimeException
+ */
+ public function modify($modify): self
+ {
+ /** @var DateTime|false $result */
+ $result = parent::modify($modify);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+
+ /**
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @return DateTime
+ * @throws DatetimeException
+ */
+ public function setDate($year, $month, $day): self
+ {
+ /** @var DateTime|false $result */
+ $result = parent::setDate($year, $month, $day);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+namespace Safe;
+
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use DateTimeZone;
+use Safe\Exceptions\DatetimeException;
+
+/**
+ * This class is used to implement a safe version of the DatetimeImmutable class.
+ * While it technically overloads \DateTimeImmutable for typehint compatibility,
+ * it is actually used as a wrapper of \DateTimeImmutable, mostly to be able to overwrite functions like getTimestamp() while still being able to edit milliseconds via setTime().
+ */
+class DateTimeImmutable extends \DateTimeImmutable
+{
+ /**
+ * @var \DateTimeImmutable
+ */
+ private $innerDateTime;
+
+ /**
+ * DateTimeImmutable constructor.
+ * @param string $time
+ * @param DateTimeZone|null $timezone
+ * @throws \Exception
+ */
+ public function __construct($time = 'now', $timezone = null)
+ {
+ parent::__construct($time, $timezone);
+ $this->innerDateTime = new parent($time, $timezone);
+ }
+
+ //switch between regular datetime and safe version
+ public static function createFromRegular(\DateTimeImmutable $datetime): self
+ {
+ $safeDatetime = new self($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimezone()); //we need to also update the wrapper to not break the operators '<' and '>'
+ $safeDatetime->innerDateTime = $datetime; //to make sure we don't lose information because of the format().
+ return $safeDatetime;
+ }
+
+ //usefull if you need to switch back to regular DateTimeImmutable (for example when using DatePeriod)
+ public function getInnerDateTime(): \DateTimeImmutable
+ {
+ return $this->innerDateTime;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // overload functions with false errors
+
+ /**
+ * @param string $format
+ * @param string $time
+ * @param DateTimeZone|null $timezone
+ * @throws DatetimeException
+ */
+ public static function createFromFormat($format, $time, $timezone = null): self
+ {
+ $datetime = parent::createFromFormat($format, $time, $timezone);
+ if ($datetime === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($datetime);
+ }
+
+ /**
+ * @param string $format
+ * @return string
+ * @throws DatetimeException
+ */
+ public function format($format): string
+ {
+ /** @var string|false $result */
+ $result = $this->innerDateTime->format($format);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+
+ /**
+ * @param DateTimeInterface $datetime2
+ * @param bool $absolute
+ * @return DateInterval
+ * @throws DatetimeException
+ */
+ public function diff($datetime2, $absolute = false): DateInterval
+ {
+ /** @var \DateInterval|false $result */
+ $result = $this->innerDateTime->diff($datetime2, $absolute);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $modify
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function modify($modify): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->modify($modify);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
+ }
+
+ /**
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function setDate($year, $month, $day): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->setDate($year, $month, $day);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
+ }
+
+ /**
+ * @param int $year
+ * @param int $week
+ * @param int $day
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function setISODate($year, $week, $day = 1): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->setISODate($year, $week, $day);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
+ }
+
+ /**
+ * @param int $hour
+ * @param int $minute
+ * @param int $second
+ * @param int $microseconds
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function setTime($hour, $minute, $second = 0, $microseconds = 0): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->setTime($hour, $minute, $second, $microseconds);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result);
+ }
+
+ /**
+ * @param int $unixtimestamp
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function setTimestamp($unixtimestamp): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->setTimestamp($unixtimestamp);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result);
+ }
+
+ /**
+ * @param DateTimeZone $timezone
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function setTimezone($timezone): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->setTimezone($timezone);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result);
+ }
+
+ /**
+ * @param DateInterval $interval
+ * @return DateTimeImmutable
+ * @throws DatetimeException
+ */
+ public function sub($interval): self
+ {
+ /** @var \DateTimeImmutable|false $result */
+ $result = $this->innerDateTime->sub($interval);
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return self::createFromRegular($result);
+ }
+
+ /**
+ * @throws DatetimeException
+ */
+ public function getOffset(): int
+ {
+ /** @var int|false $result */
+ $result = $this->innerDateTime->getOffset();
+ if ($result === false) {
+ throw DatetimeException::createFromPhpError();
+ }
+ return $result;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //overload getters to use the inner datetime immutable instead of itself
+
+ /**
+ * @param DateInterval $interval
+ * @return DateTimeImmutable
+ */
+ public function add($interval): self
+ {
+ return self::createFromRegular($this->innerDateTime->add($interval));
+ }
+
+ /**
+ * @param DateTime $dateTime
+ * @return DateTimeImmutable
+ */
+ public static function createFromMutable($dateTime): self
+ {
+ return self::createFromRegular(parent::createFromMutable($dateTime));
+ }
+
+ /**
+ * @param mixed[] $array
+ * @return DateTimeImmutable
+ */
+ public static function __set_state($array): self
+ {
+ return self::createFromRegular(parent::__set_state($array));
+ }
+
+ public function getTimezone(): DateTimeZone
+ {
+ return $this->innerDateTime->getTimezone();
+ }
+
+ public function getTimestamp(): int
+ {
+ return $this->innerDateTime->getTimestamp();
+ }
+}
--- /dev/null
+<?php
+
+
+namespace Safe\Exceptions;
+
+class CurlException extends \Exception implements SafeExceptionInterface
+{
+ /**
+ * @param resource $ch
+ */
+ public static function createFromCurlResource($ch): self
+ {
+ return new self(\curl_error($ch), \curl_errno($ch));
+ }
+}
--- /dev/null
+<?php
+
+
+namespace Safe\Exceptions;
+
+class JsonException extends \Exception implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ return new self(\json_last_error_msg(), \json_last_error());
+ }
+}
--- /dev/null
+<?php
+
+
+namespace Safe\Exceptions;
+
+class OpensslException extends \Exception implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ return new self(\openssl_error_string() ?: '', 0);
+ }
+}
--- /dev/null
+<?php
+
+
+namespace Safe\Exceptions;
+
+class PcreException extends \Exception implements SafeExceptionInterface
+{
+ public static function createFromPhpError(): self
+ {
+ $errorMap = [
+ PREG_INTERNAL_ERROR => 'PREG_INTERNAL_ERROR: Internal error',
+ PREG_BACKTRACK_LIMIT_ERROR => 'PREG_BACKTRACK_LIMIT_ERROR: Backtrack limit reached',
+ PREG_RECURSION_LIMIT_ERROR => 'PREG_RECURSION_LIMIT_ERROR: Recursion limit reached',
+ PREG_BAD_UTF8_ERROR => 'PREG_BAD_UTF8_ERROR: Invalid UTF8 character',
+ PREG_BAD_UTF8_OFFSET_ERROR => 'PREG_BAD_UTF8_OFFSET_ERROR',
+ PREG_JIT_STACKLIMIT_ERROR => 'PREG_JIT_STACKLIMIT_ERROR',
+ ];
+ $errMsg = $errorMap[preg_last_error()] ?? 'Unknown PCRE error: '.preg_last_error();
+ return new self($errMsg, \preg_last_error());
+ }
+}
--- /dev/null
+<?php
+
+
+namespace Safe\Exceptions;
+
+interface SafeExceptionInterface extends \Throwable
+{
+
+}
--- /dev/null
+<?php
+/**
+ * This file contains all the functions that could not be dealt with automatically using the code generator.
+ * If you add a function in this list, do not forget to add it in the generator/config/specialCasesFunctions.php
+ *
+ */
+
+namespace Safe;
+
+use Safe\Exceptions\SocketsException;
+use const PREG_NO_ERROR;
+use Safe\Exceptions\ApcException;
+use Safe\Exceptions\ApcuException;
+use Safe\Exceptions\JsonException;
+use Safe\Exceptions\OpensslException;
+use Safe\Exceptions\PcreException;
+
+/**
+ * Wrapper for json_decode that throws when an error occurs.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws JsonException if the JSON cannot be decoded.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
+{
+ $data = \json_decode($json, $assoc, $depth, $options);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw JsonException::createFromPhpError();
+ }
+ return $data;
+}
+
+
+/**
+ * Fetchs a stored variable from the cache.
+ *
+ * @param mixed $key The key used to store the value (with
+ * apc_store). If an array is passed then each
+ * element is fetched and returned.
+ * @return mixed The stored variable or array of variables on success; FALSE on failure
+ * @throws ApcException
+ *
+ */
+function apc_fetch($key)
+{
+ error_clear_last();
+ $result = \apc_fetch($key, $success);
+ if ($success === false) {
+ throw ApcException::createFromPhpError();
+ }
+ return $result;
+}
+
+/**
+ * Fetchs an entry from the cache.
+ *
+ * @param string|string[] $key The key used to store the value (with
+ * apcu_store). If an array is passed then each
+ * element is fetched and returned.
+ * @return mixed The stored variable or array of variables on success
+ * @throws ApcuException
+ *
+ */
+function apcu_fetch($key)
+{
+ error_clear_last();
+ $result = \apcu_fetch($key, $success);
+ if ($success === false) {
+ throw ApcuException::createFromPhpError();
+ }
+ return $result;
+}
+
+/**
+ * Searches subject for matches to
+ * pattern and replaces them with
+ * replacement.
+ *
+ * @param mixed $pattern The pattern to search for. It can be either a string or an array with
+ * strings.
+ *
+ * Several PCRE modifiers
+ * are also available.
+ * @param mixed $replacement The string or an array with strings to replace. If this parameter is a
+ * string and the pattern parameter is an array,
+ * all patterns will be replaced by that string. If both
+ * pattern and replacement
+ * parameters are arrays, each pattern will be
+ * replaced by the replacement counterpart. If
+ * there are fewer elements in the replacement
+ * array than in the pattern array, any extra
+ * patterns will be replaced by an empty string.
+ *
+ * replacement may contain references of the form
+ * \\n or
+ * $n, with the latter form
+ * being the preferred one. Every such reference will be replaced by the text
+ * captured by the n'th parenthesized pattern.
+ * n can be from 0 to 99, and
+ * \\0 or $0 refers to the text matched
+ * by the whole pattern. Opening parentheses are counted from left to right
+ * (starting from 1) to obtain the number of the capturing subpattern.
+ * To use backslash in replacement, it must be doubled
+ * ("\\\\" PHP string).
+ *
+ * When working with a replacement pattern where a backreference is
+ * immediately followed by another number (i.e.: placing a literal number
+ * immediately after a matched pattern), you cannot use the familiar
+ * \\1 notation for your backreference.
+ * \\11, for example, would confuse
+ * preg_replace since it does not know whether you
+ * want the \\1 backreference followed by a literal
+ * 1, or the \\11 backreference
+ * followed by nothing. In this case the solution is to use
+ * ${1}1. This creates an isolated
+ * $1 backreference, leaving the 1
+ * as a literal.
+ *
+ * When using the deprecated e modifier, this function escapes
+ * some characters (namely ', ",
+ * \ and NULL) in the strings that replace the
+ * backreferences. This is done to ensure that no syntax errors arise
+ * from backreference usage with either single or double quotes (e.g.
+ * 'strlen(\'$1\')+strlen("$2")'). Make sure you are
+ * aware of PHP's string
+ * syntax to know exactly how the interpreted string will look.
+ * @param string|array|string[] $subject The string or an array with strings to search and replace.
+ *
+ * If subject is an array, then the search and
+ * replace is performed on every entry of subject,
+ * and the return value is an array as well.
+ * @param int $limit The maximum possible replacements for each pattern in each
+ * subject string. Defaults to
+ * -1 (no limit).
+ * @param int $count If specified, this variable will be filled with the number of
+ * replacements done.
+ * @return string|array|string[] preg_replace returns an array if the
+ * subject parameter is an array, or a string
+ * otherwise.
+ *
+ * If matches are found, the new subject will
+ * be returned, otherwise subject will be
+ * returned unchanged.
+ *
+ * @throws PcreException
+ *
+ */
+function preg_replace($pattern, $replacement, $subject, int $limit = -1, int &$count = null)
+{
+ error_clear_last();
+ $result = \preg_replace($pattern, $replacement, $subject, $limit, $count);
+ if (preg_last_error() !== PREG_NO_ERROR || $result === null) {
+ throw PcreException::createFromPhpError();
+ }
+ return $result;
+}
+
+/**
+ * @param resource|null $dir_handle
+ * @return string|false
+ * @deprecated
+ * This function is only in safe because the php documentation is wrong
+ */
+function readdir($dir_handle = null)
+{
+ if ($dir_handle !== null) {
+ $result = \readdir($dir_handle);
+ } else {
+ $result = \readdir();
+ }
+ return $result;
+}
+
+/**
+ * Encrypts given data with given method and key, returns a raw
+ * or base64 encoded string
+ *
+ * @param string $data The plaintext message data to be encrypted.
+ * @param string $method The cipher method. For a list of available cipher methods, use openssl_get_cipher_methods.
+ * @param string $key The key.
+ * @param int $options options is a bitwise disjunction of the flags
+ * OPENSSL_RAW_DATA and
+ * OPENSSL_ZERO_PADDING.
+ * @param string $iv A non-NULL Initialization Vector.
+ * @param string $tag The authentication tag passed by reference when using AEAD cipher mode (GCM or CCM).
+ * @param string $aad Additional authentication data.
+ * @param int $tag_length The length of the authentication tag. Its value can be between 4 and 16 for GCM mode.
+ * @return string Returns the encrypted string.
+ * @throws OpensslException
+ *
+ */
+function openssl_encrypt(string $data, string $method, string $key, int $options = 0, string $iv = "", string &$tag = "", string $aad = "", int $tag_length = 16): string
+{
+ error_clear_last();
+ // The $tag parameter is handled in a weird way by openssl_encrypt. It cannot be provided unless encoding is AEAD
+ if (func_num_args() <= 5) {
+ $result = \openssl_encrypt($data, $method, $key, $options, $iv);
+ } else {
+ $result = \openssl_encrypt($data, $method, $key, $options, $iv, $tag, $aad, $tag_length);
+ }
+ if ($result === false) {
+ throw OpensslException::createFromPhpError();
+ }
+ return $result;
+}
+
+/**
+ * The function socket_write writes to the
+ * socket from the given
+ * buffer.
+ *
+ * @param resource $socket
+ * @param string $buffer The buffer to be written.
+ * @param int $length The optional parameter length can specify an
+ * alternate length of bytes written to the socket. If this length is
+ * greater than the buffer length, it is silently truncated to the length
+ * of the buffer.
+ * @return int Returns the number of bytes successfully written to the socket.
+ * The error code can be retrieved with
+ * socket_last_error. This code may be passed to
+ * socket_strerror to get a textual explanation of the
+ * error.
+ * @throws SocketsException
+ *
+ */
+function socket_write($socket, string $buffer, int $length = 0): int
+{
+ error_clear_last();
+ $result = $length === 0 ? \socket_write($socket, $buffer) : \socket_write($socket, $buffer, $length);
+ if ($result === false) {
+ throw SocketsException::createFromPhpError();
+ }
+ return $result;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;
+use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+# This file configures rector/rector:~0.7.0 to replace all PHP functions with their equivalent "safe" functions
+return static function (ContainerConfigurator $containerConfigurator): void {
+ $services = $containerConfigurator->services();
+
+ $services->set(RenameFunctionRector::class)
+ ->call('configure', [[ RenameFunctionRector::OLD_FUNCTION_TO_NEW_FUNCTION => [
+ 'apache_getenv' => 'Safe\apache_getenv',
+ 'apache_get_version' => 'Safe\apache_get_version',
+ 'apache_request_headers' => 'Safe\apache_request_headers',
+ 'apache_reset_timeout' => 'Safe\apache_reset_timeout',
+ 'apache_response_headers' => 'Safe\apache_response_headers',
+ 'apache_setenv' => 'Safe\apache_setenv',
+ 'apcu_cache_info' => 'Safe\apcu_cache_info',
+ 'apcu_cas' => 'Safe\apcu_cas',
+ 'apcu_dec' => 'Safe\apcu_dec',
+ 'apcu_fetch' => 'Safe\apcu_fetch',
+ 'apcu_inc' => 'Safe\apcu_inc',
+ 'apcu_sma_info' => 'Safe\apcu_sma_info',
+ 'apc_fetch' => 'Safe\apc_fetch',
+ 'array_combine' => 'Safe\array_combine',
+ 'array_flip' => 'Safe\array_flip',
+ 'array_replace' => 'Safe\array_replace',
+ 'array_replace_recursive' => 'Safe\array_replace_recursive',
+ 'array_walk_recursive' => 'Safe\array_walk_recursive',
+ 'arsort' => 'Safe\arsort',
+ 'asort' => 'Safe\asort',
+ 'base64_decode' => 'Safe\base64_decode',
+ 'bzclose' => 'Safe\bzclose',
+ 'bzflush' => 'Safe\bzflush',
+ 'bzread' => 'Safe\bzread',
+ 'bzwrite' => 'Safe\bzwrite',
+ 'chdir' => 'Safe\chdir',
+ 'chgrp' => 'Safe\chgrp',
+ 'chmod' => 'Safe\chmod',
+ 'chown' => 'Safe\chown',
+ 'chroot' => 'Safe\chroot',
+ 'class_alias' => 'Safe\class_alias',
+ 'class_implements' => 'Safe\class_implements',
+ 'class_parents' => 'Safe\class_parents',
+ 'class_uses' => 'Safe\class_uses',
+ 'cli_set_process_title' => 'Safe\cli_set_process_title',
+ 'closelog' => 'Safe\closelog',
+ 'com_event_sink' => 'Safe\com_event_sink',
+ 'com_load_typelib' => 'Safe\com_load_typelib',
+ 'com_print_typeinfo' => 'Safe\com_print_typeinfo',
+ 'convert_uudecode' => 'Safe\convert_uudecode',
+ 'convert_uuencode' => 'Safe\convert_uuencode',
+ 'copy' => 'Safe\copy',
+ 'create_function' => 'Safe\create_function',
+ 'cubrid_free_result' => 'Safe\cubrid_free_result',
+ 'cubrid_get_charset' => 'Safe\cubrid_get_charset',
+ 'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
+ 'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
+ 'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
+ 'cubrid_insert_id' => 'Safe\cubrid_insert_id',
+ 'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
+ 'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
+ 'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
+ 'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
+ 'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
+ 'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
+ 'curl_escape' => 'Safe\curl_escape',
+ 'curl_exec' => 'Safe\curl_exec',
+ 'curl_getinfo' => 'Safe\curl_getinfo',
+ 'curl_init' => 'Safe\curl_init',
+ 'curl_multi_errno' => 'Safe\curl_multi_errno',
+ 'curl_multi_info_read' => 'Safe\curl_multi_info_read',
+ 'curl_multi_init' => 'Safe\curl_multi_init',
+ 'curl_setopt' => 'Safe\curl_setopt',
+ 'curl_share_errno' => 'Safe\curl_share_errno',
+ 'curl_share_setopt' => 'Safe\curl_share_setopt',
+ 'curl_unescape' => 'Safe\curl_unescape',
+ 'date' => 'Safe\date',
+ 'date_parse' => 'Safe\date_parse',
+ 'date_parse_from_format' => 'Safe\date_parse_from_format',
+ 'date_sunrise' => 'Safe\date_sunrise',
+ 'date_sunset' => 'Safe\date_sunset',
+ 'date_sun_info' => 'Safe\date_sun_info',
+ 'db2_autocommit' => 'Safe\db2_autocommit',
+ 'db2_bind_param' => 'Safe\db2_bind_param',
+ 'db2_client_info' => 'Safe\db2_client_info',
+ 'db2_close' => 'Safe\db2_close',
+ 'db2_commit' => 'Safe\db2_commit',
+ 'db2_execute' => 'Safe\db2_execute',
+ 'db2_free_result' => 'Safe\db2_free_result',
+ 'db2_free_stmt' => 'Safe\db2_free_stmt',
+ 'db2_get_option' => 'Safe\db2_get_option',
+ 'db2_pclose' => 'Safe\db2_pclose',
+ 'db2_rollback' => 'Safe\db2_rollback',
+ 'db2_server_info' => 'Safe\db2_server_info',
+ 'db2_set_option' => 'Safe\db2_set_option',
+ 'define' => 'Safe\define',
+ 'deflate_add' => 'Safe\deflate_add',
+ 'deflate_init' => 'Safe\deflate_init',
+ 'disk_free_space' => 'Safe\disk_free_space',
+ 'disk_total_space' => 'Safe\disk_total_space',
+ 'dl' => 'Safe\dl',
+ 'dns_get_record' => 'Safe\dns_get_record',
+ 'eio_busy' => 'Safe\eio_busy',
+ 'eio_chmod' => 'Safe\eio_chmod',
+ 'eio_chown' => 'Safe\eio_chown',
+ 'eio_close' => 'Safe\eio_close',
+ 'eio_custom' => 'Safe\eio_custom',
+ 'eio_dup2' => 'Safe\eio_dup2',
+ 'eio_event_loop' => 'Safe\eio_event_loop',
+ 'eio_fallocate' => 'Safe\eio_fallocate',
+ 'eio_fchmod' => 'Safe\eio_fchmod',
+ 'eio_fdatasync' => 'Safe\eio_fdatasync',
+ 'eio_fstat' => 'Safe\eio_fstat',
+ 'eio_fstatvfs' => 'Safe\eio_fstatvfs',
+ 'eio_fsync' => 'Safe\eio_fsync',
+ 'eio_ftruncate' => 'Safe\eio_ftruncate',
+ 'eio_futime' => 'Safe\eio_futime',
+ 'eio_grp' => 'Safe\eio_grp',
+ 'eio_lstat' => 'Safe\eio_lstat',
+ 'eio_mkdir' => 'Safe\eio_mkdir',
+ 'eio_mknod' => 'Safe\eio_mknod',
+ 'eio_nop' => 'Safe\eio_nop',
+ 'eio_readahead' => 'Safe\eio_readahead',
+ 'eio_readdir' => 'Safe\eio_readdir',
+ 'eio_readlink' => 'Safe\eio_readlink',
+ 'eio_rename' => 'Safe\eio_rename',
+ 'eio_rmdir' => 'Safe\eio_rmdir',
+ 'eio_seek' => 'Safe\eio_seek',
+ 'eio_sendfile' => 'Safe\eio_sendfile',
+ 'eio_stat' => 'Safe\eio_stat',
+ 'eio_statvfs' => 'Safe\eio_statvfs',
+ 'eio_symlink' => 'Safe\eio_symlink',
+ 'eio_sync' => 'Safe\eio_sync',
+ 'eio_syncfs' => 'Safe\eio_syncfs',
+ 'eio_sync_file_range' => 'Safe\eio_sync_file_range',
+ 'eio_truncate' => 'Safe\eio_truncate',
+ 'eio_unlink' => 'Safe\eio_unlink',
+ 'eio_utime' => 'Safe\eio_utime',
+ 'eio_write' => 'Safe\eio_write',
+ 'error_log' => 'Safe\error_log',
+ 'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
+ 'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
+ 'fclose' => 'Safe\fclose',
+ 'fflush' => 'Safe\fflush',
+ 'file' => 'Safe\file',
+ 'fileatime' => 'Safe\fileatime',
+ 'filectime' => 'Safe\filectime',
+ 'fileinode' => 'Safe\fileinode',
+ 'filemtime' => 'Safe\filemtime',
+ 'fileowner' => 'Safe\fileowner',
+ 'filesize' => 'Safe\filesize',
+ 'file_get_contents' => 'Safe\file_get_contents',
+ 'file_put_contents' => 'Safe\file_put_contents',
+ 'filter_input_array' => 'Safe\filter_input_array',
+ 'filter_var_array' => 'Safe\filter_var_array',
+ 'finfo_close' => 'Safe\finfo_close',
+ 'finfo_open' => 'Safe\finfo_open',
+ 'flock' => 'Safe\flock',
+ 'fopen' => 'Safe\fopen',
+ 'fputcsv' => 'Safe\fputcsv',
+ 'fread' => 'Safe\fread',
+ 'fsockopen' => 'Safe\fsockopen',
+ 'ftp_alloc' => 'Safe\ftp_alloc',
+ 'ftp_append' => 'Safe\ftp_append',
+ 'ftp_cdup' => 'Safe\ftp_cdup',
+ 'ftp_chdir' => 'Safe\ftp_chdir',
+ 'ftp_chmod' => 'Safe\ftp_chmod',
+ 'ftp_close' => 'Safe\ftp_close',
+ 'ftp_connect' => 'Safe\ftp_connect',
+ 'ftp_delete' => 'Safe\ftp_delete',
+ 'ftp_fget' => 'Safe\ftp_fget',
+ 'ftp_fput' => 'Safe\ftp_fput',
+ 'ftp_get' => 'Safe\ftp_get',
+ 'ftp_login' => 'Safe\ftp_login',
+ 'ftp_mkdir' => 'Safe\ftp_mkdir',
+ 'ftp_mlsd' => 'Safe\ftp_mlsd',
+ 'ftp_nlist' => 'Safe\ftp_nlist',
+ 'ftp_pasv' => 'Safe\ftp_pasv',
+ 'ftp_put' => 'Safe\ftp_put',
+ 'ftp_pwd' => 'Safe\ftp_pwd',
+ 'ftp_rename' => 'Safe\ftp_rename',
+ 'ftp_rmdir' => 'Safe\ftp_rmdir',
+ 'ftp_site' => 'Safe\ftp_site',
+ 'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
+ 'ftp_systype' => 'Safe\ftp_systype',
+ 'ftruncate' => 'Safe\ftruncate',
+ 'fwrite' => 'Safe\fwrite',
+ 'getallheaders' => 'Safe\getallheaders',
+ 'getcwd' => 'Safe\getcwd',
+ 'gethostname' => 'Safe\gethostname',
+ 'getimagesize' => 'Safe\getimagesize',
+ 'getlastmod' => 'Safe\getlastmod',
+ 'getmygid' => 'Safe\getmygid',
+ 'getmyinode' => 'Safe\getmyinode',
+ 'getmypid' => 'Safe\getmypid',
+ 'getmyuid' => 'Safe\getmyuid',
+ 'getopt' => 'Safe\getopt',
+ 'getprotobyname' => 'Safe\getprotobyname',
+ 'getprotobynumber' => 'Safe\getprotobynumber',
+ 'get_headers' => 'Safe\get_headers',
+ 'glob' => 'Safe\glob',
+ 'gmdate' => 'Safe\gmdate',
+ 'gmp_binomial' => 'Safe\gmp_binomial',
+ 'gmp_export' => 'Safe\gmp_export',
+ 'gmp_import' => 'Safe\gmp_import',
+ 'gmp_random_seed' => 'Safe\gmp_random_seed',
+ 'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
+ 'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
+ 'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
+ 'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
+ 'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
+ 'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
+ 'gnupg_setarmor' => 'Safe\gnupg_setarmor',
+ 'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
+ 'gzclose' => 'Safe\gzclose',
+ 'gzcompress' => 'Safe\gzcompress',
+ 'gzdecode' => 'Safe\gzdecode',
+ 'gzdeflate' => 'Safe\gzdeflate',
+ 'gzencode' => 'Safe\gzencode',
+ 'gzgets' => 'Safe\gzgets',
+ 'gzgetss' => 'Safe\gzgetss',
+ 'gzinflate' => 'Safe\gzinflate',
+ 'gzpassthru' => 'Safe\gzpassthru',
+ 'gzrewind' => 'Safe\gzrewind',
+ 'gzuncompress' => 'Safe\gzuncompress',
+ 'hash_hkdf' => 'Safe\hash_hkdf',
+ 'hash_update_file' => 'Safe\hash_update_file',
+ 'header_register_callback' => 'Safe\header_register_callback',
+ 'hex2bin' => 'Safe\hex2bin',
+ 'highlight_file' => 'Safe\highlight_file',
+ 'highlight_string' => 'Safe\highlight_string',
+ 'ibase_add_user' => 'Safe\ibase_add_user',
+ 'ibase_backup' => 'Safe\ibase_backup',
+ 'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
+ 'ibase_blob_create' => 'Safe\ibase_blob_create',
+ 'ibase_blob_get' => 'Safe\ibase_blob_get',
+ 'ibase_close' => 'Safe\ibase_close',
+ 'ibase_commit' => 'Safe\ibase_commit',
+ 'ibase_commit_ret' => 'Safe\ibase_commit_ret',
+ 'ibase_connect' => 'Safe\ibase_connect',
+ 'ibase_delete_user' => 'Safe\ibase_delete_user',
+ 'ibase_drop_db' => 'Safe\ibase_drop_db',
+ 'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
+ 'ibase_free_query' => 'Safe\ibase_free_query',
+ 'ibase_free_result' => 'Safe\ibase_free_result',
+ 'ibase_maintain_db' => 'Safe\ibase_maintain_db',
+ 'ibase_modify_user' => 'Safe\ibase_modify_user',
+ 'ibase_name_result' => 'Safe\ibase_name_result',
+ 'ibase_pconnect' => 'Safe\ibase_pconnect',
+ 'ibase_restore' => 'Safe\ibase_restore',
+ 'ibase_rollback' => 'Safe\ibase_rollback',
+ 'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
+ 'ibase_service_attach' => 'Safe\ibase_service_attach',
+ 'ibase_service_detach' => 'Safe\ibase_service_detach',
+ 'iconv' => 'Safe\iconv',
+ 'iconv_get_encoding' => 'Safe\iconv_get_encoding',
+ 'iconv_set_encoding' => 'Safe\iconv_set_encoding',
+ 'image2wbmp' => 'Safe\image2wbmp',
+ 'imageaffine' => 'Safe\imageaffine',
+ 'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
+ 'imageaffinematrixget' => 'Safe\imageaffinematrixget',
+ 'imagealphablending' => 'Safe\imagealphablending',
+ 'imageantialias' => 'Safe\imageantialias',
+ 'imagearc' => 'Safe\imagearc',
+ 'imagebmp' => 'Safe\imagebmp',
+ 'imagechar' => 'Safe\imagechar',
+ 'imagecharup' => 'Safe\imagecharup',
+ 'imagecolorat' => 'Safe\imagecolorat',
+ 'imagecolordeallocate' => 'Safe\imagecolordeallocate',
+ 'imagecolormatch' => 'Safe\imagecolormatch',
+ 'imageconvolution' => 'Safe\imageconvolution',
+ 'imagecopy' => 'Safe\imagecopy',
+ 'imagecopymerge' => 'Safe\imagecopymerge',
+ 'imagecopymergegray' => 'Safe\imagecopymergegray',
+ 'imagecopyresampled' => 'Safe\imagecopyresampled',
+ 'imagecopyresized' => 'Safe\imagecopyresized',
+ 'imagecreate' => 'Safe\imagecreate',
+ 'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
+ 'imagecreatefromgd' => 'Safe\imagecreatefromgd',
+ 'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
+ 'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
+ 'imagecreatefromgif' => 'Safe\imagecreatefromgif',
+ 'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
+ 'imagecreatefrompng' => 'Safe\imagecreatefrompng',
+ 'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
+ 'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
+ 'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
+ 'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
+ 'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
+ 'imagecrop' => 'Safe\imagecrop',
+ 'imagecropauto' => 'Safe\imagecropauto',
+ 'imagedashedline' => 'Safe\imagedashedline',
+ 'imagedestroy' => 'Safe\imagedestroy',
+ 'imageellipse' => 'Safe\imageellipse',
+ 'imagefill' => 'Safe\imagefill',
+ 'imagefilledarc' => 'Safe\imagefilledarc',
+ 'imagefilledellipse' => 'Safe\imagefilledellipse',
+ 'imagefilledpolygon' => 'Safe\imagefilledpolygon',
+ 'imagefilledrectangle' => 'Safe\imagefilledrectangle',
+ 'imagefilltoborder' => 'Safe\imagefilltoborder',
+ 'imagefilter' => 'Safe\imagefilter',
+ 'imageflip' => 'Safe\imageflip',
+ 'imagegammacorrect' => 'Safe\imagegammacorrect',
+ 'imagegd' => 'Safe\imagegd',
+ 'imagegd2' => 'Safe\imagegd2',
+ 'imagegif' => 'Safe\imagegif',
+ 'imagegrabscreen' => 'Safe\imagegrabscreen',
+ 'imagegrabwindow' => 'Safe\imagegrabwindow',
+ 'imagejpeg' => 'Safe\imagejpeg',
+ 'imagelayereffect' => 'Safe\imagelayereffect',
+ 'imageline' => 'Safe\imageline',
+ 'imageloadfont' => 'Safe\imageloadfont',
+ 'imageopenpolygon' => 'Safe\imageopenpolygon',
+ 'imagepng' => 'Safe\imagepng',
+ 'imagepolygon' => 'Safe\imagepolygon',
+ 'imagerectangle' => 'Safe\imagerectangle',
+ 'imagerotate' => 'Safe\imagerotate',
+ 'imagesavealpha' => 'Safe\imagesavealpha',
+ 'imagescale' => 'Safe\imagescale',
+ 'imagesetbrush' => 'Safe\imagesetbrush',
+ 'imagesetclip' => 'Safe\imagesetclip',
+ 'imagesetinterpolation' => 'Safe\imagesetinterpolation',
+ 'imagesetpixel' => 'Safe\imagesetpixel',
+ 'imagesetstyle' => 'Safe\imagesetstyle',
+ 'imagesetthickness' => 'Safe\imagesetthickness',
+ 'imagesettile' => 'Safe\imagesettile',
+ 'imagestring' => 'Safe\imagestring',
+ 'imagestringup' => 'Safe\imagestringup',
+ 'imagesx' => 'Safe\imagesx',
+ 'imagesy' => 'Safe\imagesy',
+ 'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
+ 'imagettfbbox' => 'Safe\imagettfbbox',
+ 'imagettftext' => 'Safe\imagettftext',
+ 'imagewbmp' => 'Safe\imagewbmp',
+ 'imagewebp' => 'Safe\imagewebp',
+ 'imagexbm' => 'Safe\imagexbm',
+ 'imap_append' => 'Safe\imap_append',
+ 'imap_check' => 'Safe\imap_check',
+ 'imap_clearflag_full' => 'Safe\imap_clearflag_full',
+ 'imap_close' => 'Safe\imap_close',
+ 'imap_createmailbox' => 'Safe\imap_createmailbox',
+ 'imap_deletemailbox' => 'Safe\imap_deletemailbox',
+ 'imap_fetchstructure' => 'Safe\imap_fetchstructure',
+ 'imap_gc' => 'Safe\imap_gc',
+ 'imap_headerinfo' => 'Safe\imap_headerinfo',
+ 'imap_mail' => 'Safe\imap_mail',
+ 'imap_mailboxmsginfo' => 'Safe\imap_mailboxmsginfo',
+ 'imap_mail_compose' => 'Safe\imap_mail_compose',
+ 'imap_mail_copy' => 'Safe\imap_mail_copy',
+ 'imap_mail_move' => 'Safe\imap_mail_move',
+ 'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
+ 'imap_num_msg' => 'Safe\imap_num_msg',
+ 'imap_open' => 'Safe\imap_open',
+ 'imap_renamemailbox' => 'Safe\imap_renamemailbox',
+ 'imap_savebody' => 'Safe\imap_savebody',
+ 'imap_setacl' => 'Safe\imap_setacl',
+ 'imap_setflag_full' => 'Safe\imap_setflag_full',
+ 'imap_set_quota' => 'Safe\imap_set_quota',
+ 'imap_sort' => 'Safe\imap_sort',
+ 'imap_subscribe' => 'Safe\imap_subscribe',
+ 'imap_thread' => 'Safe\imap_thread',
+ 'imap_timeout' => 'Safe\imap_timeout',
+ 'imap_undelete' => 'Safe\imap_undelete',
+ 'imap_unsubscribe' => 'Safe\imap_unsubscribe',
+ 'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
+ 'inet_ntop' => 'Safe\inet_ntop',
+ 'inflate_add' => 'Safe\inflate_add',
+ 'inflate_get_read_len' => 'Safe\inflate_get_read_len',
+ 'inflate_get_status' => 'Safe\inflate_get_status',
+ 'inflate_init' => 'Safe\inflate_init',
+ 'ingres_autocommit' => 'Safe\ingres_autocommit',
+ 'ingres_close' => 'Safe\ingres_close',
+ 'ingres_commit' => 'Safe\ingres_commit',
+ 'ingres_connect' => 'Safe\ingres_connect',
+ 'ingres_execute' => 'Safe\ingres_execute',
+ 'ingres_field_name' => 'Safe\ingres_field_name',
+ 'ingres_field_type' => 'Safe\ingres_field_type',
+ 'ingres_free_result' => 'Safe\ingres_free_result',
+ 'ingres_pconnect' => 'Safe\ingres_pconnect',
+ 'ingres_result_seek' => 'Safe\ingres_result_seek',
+ 'ingres_rollback' => 'Safe\ingres_rollback',
+ 'ingres_set_environment' => 'Safe\ingres_set_environment',
+ 'ini_get' => 'Safe\ini_get',
+ 'ini_set' => 'Safe\ini_set',
+ 'inotify_init' => 'Safe\inotify_init',
+ 'inotify_rm_watch' => 'Safe\inotify_rm_watch',
+ 'iptcembed' => 'Safe\iptcembed',
+ 'iptcparse' => 'Safe\iptcparse',
+ 'jdtounix' => 'Safe\jdtounix',
+ 'jpeg2wbmp' => 'Safe\jpeg2wbmp',
+ 'json_decode' => 'Safe\json_decode',
+ 'json_encode' => 'Safe\json_encode',
+ 'json_last_error_msg' => 'Safe\json_last_error_msg',
+ 'krsort' => 'Safe\krsort',
+ 'ksort' => 'Safe\ksort',
+ 'lchgrp' => 'Safe\lchgrp',
+ 'lchown' => 'Safe\lchown',
+ 'ldap_add' => 'Safe\ldap_add',
+ 'ldap_add_ext' => 'Safe\ldap_add_ext',
+ 'ldap_bind' => 'Safe\ldap_bind',
+ 'ldap_bind_ext' => 'Safe\ldap_bind_ext',
+ 'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
+ 'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
+ 'ldap_count_entries' => 'Safe\ldap_count_entries',
+ 'ldap_delete' => 'Safe\ldap_delete',
+ 'ldap_delete_ext' => 'Safe\ldap_delete_ext',
+ 'ldap_exop' => 'Safe\ldap_exop',
+ 'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
+ 'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
+ 'ldap_explode_dn' => 'Safe\ldap_explode_dn',
+ 'ldap_first_attribute' => 'Safe\ldap_first_attribute',
+ 'ldap_first_entry' => 'Safe\ldap_first_entry',
+ 'ldap_free_result' => 'Safe\ldap_free_result',
+ 'ldap_get_attributes' => 'Safe\ldap_get_attributes',
+ 'ldap_get_dn' => 'Safe\ldap_get_dn',
+ 'ldap_get_entries' => 'Safe\ldap_get_entries',
+ 'ldap_get_option' => 'Safe\ldap_get_option',
+ 'ldap_get_values' => 'Safe\ldap_get_values',
+ 'ldap_get_values_len' => 'Safe\ldap_get_values_len',
+ 'ldap_list' => 'Safe\ldap_list',
+ 'ldap_modify_batch' => 'Safe\ldap_modify_batch',
+ 'ldap_mod_add' => 'Safe\ldap_mod_add',
+ 'ldap_mod_add_ext' => 'Safe\ldap_mod_add_ext',
+ 'ldap_mod_del' => 'Safe\ldap_mod_del',
+ 'ldap_mod_del_ext' => 'Safe\ldap_mod_del_ext',
+ 'ldap_mod_replace' => 'Safe\ldap_mod_replace',
+ 'ldap_mod_replace_ext' => 'Safe\ldap_mod_replace_ext',
+ 'ldap_next_attribute' => 'Safe\ldap_next_attribute',
+ 'ldap_parse_exop' => 'Safe\ldap_parse_exop',
+ 'ldap_parse_result' => 'Safe\ldap_parse_result',
+ 'ldap_read' => 'Safe\ldap_read',
+ 'ldap_rename' => 'Safe\ldap_rename',
+ 'ldap_rename_ext' => 'Safe\ldap_rename_ext',
+ 'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
+ 'ldap_search' => 'Safe\ldap_search',
+ 'ldap_set_option' => 'Safe\ldap_set_option',
+ 'ldap_unbind' => 'Safe\ldap_unbind',
+ 'libxml_get_last_error' => 'Safe\libxml_get_last_error',
+ 'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
+ 'link' => 'Safe\link',
+ 'lzf_compress' => 'Safe\lzf_compress',
+ 'lzf_decompress' => 'Safe\lzf_decompress',
+ 'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
+ 'mailparse_msg_free' => 'Safe\mailparse_msg_free',
+ 'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
+ 'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
+ 'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
+ 'mb_chr' => 'Safe\mb_chr',
+ 'mb_detect_order' => 'Safe\mb_detect_order',
+ 'mb_encoding_aliases' => 'Safe\mb_encoding_aliases',
+ 'mb_eregi_replace' => 'Safe\mb_eregi_replace',
+ 'mb_ereg_replace' => 'Safe\mb_ereg_replace',
+ 'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
+ 'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
+ 'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
+ 'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
+ 'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
+ 'mb_http_output' => 'Safe\mb_http_output',
+ 'mb_internal_encoding' => 'Safe\mb_internal_encoding',
+ 'mb_ord' => 'Safe\mb_ord',
+ 'mb_parse_str' => 'Safe\mb_parse_str',
+ 'mb_regex_encoding' => 'Safe\mb_regex_encoding',
+ 'mb_send_mail' => 'Safe\mb_send_mail',
+ 'mb_split' => 'Safe\mb_split',
+ 'mb_str_split' => 'Safe\mb_str_split',
+ 'md5_file' => 'Safe\md5_file',
+ 'metaphone' => 'Safe\metaphone',
+ 'mime_content_type' => 'Safe\mime_content_type',
+ 'mkdir' => 'Safe\mkdir',
+ 'mktime' => 'Safe\mktime',
+ 'msg_queue_exists' => 'Safe\msg_queue_exists',
+ 'msg_receive' => 'Safe\msg_receive',
+ 'msg_remove_queue' => 'Safe\msg_remove_queue',
+ 'msg_send' => 'Safe\msg_send',
+ 'msg_set_queue' => 'Safe\msg_set_queue',
+ 'msql_affected_rows' => 'Safe\msql_affected_rows',
+ 'msql_close' => 'Safe\msql_close',
+ 'msql_connect' => 'Safe\msql_connect',
+ 'msql_create_db' => 'Safe\msql_create_db',
+ 'msql_data_seek' => 'Safe\msql_data_seek',
+ 'msql_db_query' => 'Safe\msql_db_query',
+ 'msql_drop_db' => 'Safe\msql_drop_db',
+ 'msql_field_len' => 'Safe\msql_field_len',
+ 'msql_field_name' => 'Safe\msql_field_name',
+ 'msql_field_seek' => 'Safe\msql_field_seek',
+ 'msql_field_table' => 'Safe\msql_field_table',
+ 'msql_field_type' => 'Safe\msql_field_type',
+ 'msql_free_result' => 'Safe\msql_free_result',
+ 'msql_pconnect' => 'Safe\msql_pconnect',
+ 'msql_query' => 'Safe\msql_query',
+ 'msql_select_db' => 'Safe\msql_select_db',
+ 'mysqli_get_cache_stats' => 'Safe\mysqli_get_cache_stats',
+ 'mysqli_get_client_stats' => 'Safe\mysqli_get_client_stats',
+ 'mysqlnd_ms_dump_servers' => 'Safe\mysqlnd_ms_dump_servers',
+ 'mysqlnd_ms_fabric_select_global' => 'Safe\mysqlnd_ms_fabric_select_global',
+ 'mysqlnd_ms_fabric_select_shard' => 'Safe\mysqlnd_ms_fabric_select_shard',
+ 'mysqlnd_ms_get_last_used_connection' => 'Safe\mysqlnd_ms_get_last_used_connection',
+ 'mysqlnd_qc_clear_cache' => 'Safe\mysqlnd_qc_clear_cache',
+ 'mysqlnd_qc_set_is_select' => 'Safe\mysqlnd_qc_set_is_select',
+ 'mysqlnd_qc_set_storage_handler' => 'Safe\mysqlnd_qc_set_storage_handler',
+ 'mysql_close' => 'Safe\mysql_close',
+ 'mysql_connect' => 'Safe\mysql_connect',
+ 'mysql_create_db' => 'Safe\mysql_create_db',
+ 'mysql_data_seek' => 'Safe\mysql_data_seek',
+ 'mysql_db_name' => 'Safe\mysql_db_name',
+ 'mysql_db_query' => 'Safe\mysql_db_query',
+ 'mysql_drop_db' => 'Safe\mysql_drop_db',
+ 'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
+ 'mysql_field_flags' => 'Safe\mysql_field_flags',
+ 'mysql_field_len' => 'Safe\mysql_field_len',
+ 'mysql_field_name' => 'Safe\mysql_field_name',
+ 'mysql_field_seek' => 'Safe\mysql_field_seek',
+ 'mysql_free_result' => 'Safe\mysql_free_result',
+ 'mysql_get_host_info' => 'Safe\mysql_get_host_info',
+ 'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
+ 'mysql_get_server_info' => 'Safe\mysql_get_server_info',
+ 'mysql_info' => 'Safe\mysql_info',
+ 'mysql_list_dbs' => 'Safe\mysql_list_dbs',
+ 'mysql_list_fields' => 'Safe\mysql_list_fields',
+ 'mysql_list_processes' => 'Safe\mysql_list_processes',
+ 'mysql_list_tables' => 'Safe\mysql_list_tables',
+ 'mysql_num_fields' => 'Safe\mysql_num_fields',
+ 'mysql_num_rows' => 'Safe\mysql_num_rows',
+ 'mysql_query' => 'Safe\mysql_query',
+ 'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
+ 'mysql_result' => 'Safe\mysql_result',
+ 'mysql_select_db' => 'Safe\mysql_select_db',
+ 'mysql_set_charset' => 'Safe\mysql_set_charset',
+ 'mysql_tablename' => 'Safe\mysql_tablename',
+ 'mysql_thread_id' => 'Safe\mysql_thread_id',
+ 'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
+ 'natcasesort' => 'Safe\natcasesort',
+ 'natsort' => 'Safe\natsort',
+ 'ob_end_clean' => 'Safe\ob_end_clean',
+ 'ob_end_flush' => 'Safe\ob_end_flush',
+ 'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
+ 'oci_bind_by_name' => 'Safe\oci_bind_by_name',
+ 'oci_cancel' => 'Safe\oci_cancel',
+ 'oci_close' => 'Safe\oci_close',
+ 'oci_commit' => 'Safe\oci_commit',
+ 'oci_connect' => 'Safe\oci_connect',
+ 'oci_define_by_name' => 'Safe\oci_define_by_name',
+ 'oci_execute' => 'Safe\oci_execute',
+ 'oci_fetch_all' => 'Safe\oci_fetch_all',
+ 'oci_field_name' => 'Safe\oci_field_name',
+ 'oci_field_precision' => 'Safe\oci_field_precision',
+ 'oci_field_scale' => 'Safe\oci_field_scale',
+ 'oci_field_size' => 'Safe\oci_field_size',
+ 'oci_field_type' => 'Safe\oci_field_type',
+ 'oci_field_type_raw' => 'Safe\oci_field_type_raw',
+ 'oci_free_descriptor' => 'Safe\oci_free_descriptor',
+ 'oci_free_statement' => 'Safe\oci_free_statement',
+ 'oci_new_collection' => 'Safe\oci_new_collection',
+ 'oci_new_connect' => 'Safe\oci_new_connect',
+ 'oci_new_cursor' => 'Safe\oci_new_cursor',
+ 'oci_new_descriptor' => 'Safe\oci_new_descriptor',
+ 'oci_num_fields' => 'Safe\oci_num_fields',
+ 'oci_num_rows' => 'Safe\oci_num_rows',
+ 'oci_parse' => 'Safe\oci_parse',
+ 'oci_pconnect' => 'Safe\oci_pconnect',
+ 'oci_result' => 'Safe\oci_result',
+ 'oci_rollback' => 'Safe\oci_rollback',
+ 'oci_server_version' => 'Safe\oci_server_version',
+ 'oci_set_action' => 'Safe\oci_set_action',
+ 'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
+ 'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
+ 'oci_set_client_info' => 'Safe\oci_set_client_info',
+ 'oci_set_db_operation' => 'Safe\oci_set_db_operation',
+ 'oci_set_edition' => 'Safe\oci_set_edition',
+ 'oci_set_module_name' => 'Safe\oci_set_module_name',
+ 'oci_set_prefetch' => 'Safe\oci_set_prefetch',
+ 'oci_statement_type' => 'Safe\oci_statement_type',
+ 'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
+ 'odbc_autocommit' => 'Safe\odbc_autocommit',
+ 'odbc_binmode' => 'Safe\odbc_binmode',
+ 'odbc_columnprivileges' => 'Safe\odbc_columnprivileges',
+ 'odbc_columns' => 'Safe\odbc_columns',
+ 'odbc_commit' => 'Safe\odbc_commit',
+ 'odbc_data_source' => 'Safe\odbc_data_source',
+ 'odbc_exec' => 'Safe\odbc_exec',
+ 'odbc_execute' => 'Safe\odbc_execute',
+ 'odbc_fetch_into' => 'Safe\odbc_fetch_into',
+ 'odbc_field_len' => 'Safe\odbc_field_len',
+ 'odbc_field_name' => 'Safe\odbc_field_name',
+ 'odbc_field_num' => 'Safe\odbc_field_num',
+ 'odbc_field_scale' => 'Safe\odbc_field_scale',
+ 'odbc_field_type' => 'Safe\odbc_field_type',
+ 'odbc_foreignkeys' => 'Safe\odbc_foreignkeys',
+ 'odbc_gettypeinfo' => 'Safe\odbc_gettypeinfo',
+ 'odbc_longreadlen' => 'Safe\odbc_longreadlen',
+ 'odbc_prepare' => 'Safe\odbc_prepare',
+ 'odbc_primarykeys' => 'Safe\odbc_primarykeys',
+ 'odbc_result' => 'Safe\odbc_result',
+ 'odbc_result_all' => 'Safe\odbc_result_all',
+ 'odbc_rollback' => 'Safe\odbc_rollback',
+ 'odbc_setoption' => 'Safe\odbc_setoption',
+ 'odbc_specialcolumns' => 'Safe\odbc_specialcolumns',
+ 'odbc_statistics' => 'Safe\odbc_statistics',
+ 'odbc_tableprivileges' => 'Safe\odbc_tableprivileges',
+ 'odbc_tables' => 'Safe\odbc_tables',
+ 'opcache_compile_file' => 'Safe\opcache_compile_file',
+ 'opcache_get_status' => 'Safe\opcache_get_status',
+ 'opendir' => 'Safe\opendir',
+ 'openlog' => 'Safe\openlog',
+ 'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
+ 'openssl_csr_export' => 'Safe\openssl_csr_export',
+ 'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
+ 'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
+ 'openssl_csr_new' => 'Safe\openssl_csr_new',
+ 'openssl_csr_sign' => 'Safe\openssl_csr_sign',
+ 'openssl_decrypt' => 'Safe\openssl_decrypt',
+ 'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
+ 'openssl_digest' => 'Safe\openssl_digest',
+ 'openssl_encrypt' => 'Safe\openssl_encrypt',
+ 'openssl_open' => 'Safe\openssl_open',
+ 'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
+ 'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
+ 'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
+ 'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
+ 'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
+ 'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
+ 'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
+ 'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
+ 'openssl_pkey_export' => 'Safe\openssl_pkey_export',
+ 'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
+ 'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
+ 'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
+ 'openssl_pkey_new' => 'Safe\openssl_pkey_new',
+ 'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
+ 'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
+ 'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
+ 'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
+ 'openssl_random_pseudo_bytes' => 'Safe\openssl_random_pseudo_bytes',
+ 'openssl_seal' => 'Safe\openssl_seal',
+ 'openssl_sign' => 'Safe\openssl_sign',
+ 'openssl_x509_export' => 'Safe\openssl_x509_export',
+ 'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
+ 'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
+ 'openssl_x509_read' => 'Safe\openssl_x509_read',
+ 'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
+ 'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
+ 'pack' => 'Safe\pack',
+ 'parse_ini_file' => 'Safe\parse_ini_file',
+ 'parse_ini_string' => 'Safe\parse_ini_string',
+ 'parse_url' => 'Safe\parse_url',
+ 'password_hash' => 'Safe\password_hash',
+ 'pcntl_exec' => 'Safe\pcntl_exec',
+ 'pcntl_getpriority' => 'Safe\pcntl_getpriority',
+ 'pcntl_setpriority' => 'Safe\pcntl_setpriority',
+ 'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
+ 'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
+ 'pcntl_strerror' => 'Safe\pcntl_strerror',
+ 'PDF_activate_item' => 'Safe\PDF_activate_item',
+ 'PDF_add_locallink' => 'Safe\PDF_add_locallink',
+ 'PDF_add_nameddest' => 'Safe\PDF_add_nameddest',
+ 'PDF_add_note' => 'Safe\PDF_add_note',
+ 'PDF_add_pdflink' => 'Safe\PDF_add_pdflink',
+ 'PDF_add_thumbnail' => 'Safe\PDF_add_thumbnail',
+ 'PDF_add_weblink' => 'Safe\PDF_add_weblink',
+ 'PDF_attach_file' => 'Safe\PDF_attach_file',
+ 'PDF_begin_layer' => 'Safe\PDF_begin_layer',
+ 'PDF_begin_page' => 'Safe\PDF_begin_page',
+ 'PDF_begin_page_ext' => 'Safe\PDF_begin_page_ext',
+ 'PDF_circle' => 'Safe\PDF_circle',
+ 'PDF_clip' => 'Safe\PDF_clip',
+ 'PDF_close' => 'Safe\PDF_close',
+ 'PDF_closepath' => 'Safe\PDF_closepath',
+ 'PDF_closepath_fill_stroke' => 'Safe\PDF_closepath_fill_stroke',
+ 'PDF_closepath_stroke' => 'Safe\PDF_closepath_stroke',
+ 'PDF_close_pdi' => 'Safe\PDF_close_pdi',
+ 'PDF_close_pdi_page' => 'Safe\PDF_close_pdi_page',
+ 'PDF_concat' => 'Safe\PDF_concat',
+ 'PDF_continue_text' => 'Safe\PDF_continue_text',
+ 'PDF_curveto' => 'Safe\PDF_curveto',
+ 'PDF_delete' => 'Safe\PDF_delete',
+ 'PDF_end_layer' => 'Safe\PDF_end_layer',
+ 'PDF_end_page' => 'Safe\PDF_end_page',
+ 'PDF_end_page_ext' => 'Safe\PDF_end_page_ext',
+ 'PDF_end_pattern' => 'Safe\PDF_end_pattern',
+ 'PDF_end_template' => 'Safe\PDF_end_template',
+ 'PDF_fill' => 'Safe\PDF_fill',
+ 'PDF_fill_stroke' => 'Safe\PDF_fill_stroke',
+ 'PDF_fit_image' => 'Safe\PDF_fit_image',
+ 'PDF_fit_pdi_page' => 'Safe\PDF_fit_pdi_page',
+ 'PDF_fit_textline' => 'Safe\PDF_fit_textline',
+ 'PDF_initgraphics' => 'Safe\PDF_initgraphics',
+ 'PDF_lineto' => 'Safe\PDF_lineto',
+ 'PDF_makespotcolor' => 'Safe\PDF_makespotcolor',
+ 'PDF_moveto' => 'Safe\PDF_moveto',
+ 'PDF_open_file' => 'Safe\PDF_open_file',
+ 'PDF_place_image' => 'Safe\PDF_place_image',
+ 'PDF_place_pdi_page' => 'Safe\PDF_place_pdi_page',
+ 'PDF_rect' => 'Safe\PDF_rect',
+ 'PDF_restore' => 'Safe\PDF_restore',
+ 'PDF_rotate' => 'Safe\PDF_rotate',
+ 'PDF_save' => 'Safe\PDF_save',
+ 'PDF_scale' => 'Safe\PDF_scale',
+ 'PDF_setcolor' => 'Safe\PDF_setcolor',
+ 'PDF_setdash' => 'Safe\PDF_setdash',
+ 'PDF_setdashpattern' => 'Safe\PDF_setdashpattern',
+ 'PDF_setflat' => 'Safe\PDF_setflat',
+ 'PDF_setfont' => 'Safe\PDF_setfont',
+ 'PDF_setgray' => 'Safe\PDF_setgray',
+ 'PDF_setgray_fill' => 'Safe\PDF_setgray_fill',
+ 'PDF_setgray_stroke' => 'Safe\PDF_setgray_stroke',
+ 'PDF_setlinejoin' => 'Safe\PDF_setlinejoin',
+ 'PDF_setlinewidth' => 'Safe\PDF_setlinewidth',
+ 'PDF_setmatrix' => 'Safe\PDF_setmatrix',
+ 'PDF_setmiterlimit' => 'Safe\PDF_setmiterlimit',
+ 'PDF_setrgbcolor' => 'Safe\PDF_setrgbcolor',
+ 'PDF_setrgbcolor_fill' => 'Safe\PDF_setrgbcolor_fill',
+ 'PDF_setrgbcolor_stroke' => 'Safe\PDF_setrgbcolor_stroke',
+ 'PDF_set_border_color' => 'Safe\PDF_set_border_color',
+ 'PDF_set_border_dash' => 'Safe\PDF_set_border_dash',
+ 'PDF_set_border_style' => 'Safe\PDF_set_border_style',
+ 'PDF_set_info' => 'Safe\PDF_set_info',
+ 'PDF_set_layer_dependency' => 'Safe\PDF_set_layer_dependency',
+ 'PDF_set_parameter' => 'Safe\PDF_set_parameter',
+ 'PDF_set_text_pos' => 'Safe\PDF_set_text_pos',
+ 'PDF_set_value' => 'Safe\PDF_set_value',
+ 'PDF_show' => 'Safe\PDF_show',
+ 'PDF_show_xy' => 'Safe\PDF_show_xy',
+ 'PDF_skew' => 'Safe\PDF_skew',
+ 'PDF_stroke' => 'Safe\PDF_stroke',
+ 'pg_cancel_query' => 'Safe\pg_cancel_query',
+ 'pg_client_encoding' => 'Safe\pg_client_encoding',
+ 'pg_close' => 'Safe\pg_close',
+ 'pg_connect' => 'Safe\pg_connect',
+ 'pg_connection_reset' => 'Safe\pg_connection_reset',
+ 'pg_convert' => 'Safe\pg_convert',
+ 'pg_copy_from' => 'Safe\pg_copy_from',
+ 'pg_copy_to' => 'Safe\pg_copy_to',
+ 'pg_dbname' => 'Safe\pg_dbname',
+ 'pg_delete' => 'Safe\pg_delete',
+ 'pg_end_copy' => 'Safe\pg_end_copy',
+ 'pg_execute' => 'Safe\pg_execute',
+ 'pg_field_name' => 'Safe\pg_field_name',
+ 'pg_field_table' => 'Safe\pg_field_table',
+ 'pg_field_type' => 'Safe\pg_field_type',
+ 'pg_flush' => 'Safe\pg_flush',
+ 'pg_free_result' => 'Safe\pg_free_result',
+ 'pg_host' => 'Safe\pg_host',
+ 'pg_insert' => 'Safe\pg_insert',
+ 'pg_last_error' => 'Safe\pg_last_error',
+ 'pg_last_notice' => 'Safe\pg_last_notice',
+ 'pg_last_oid' => 'Safe\pg_last_oid',
+ 'pg_lo_close' => 'Safe\pg_lo_close',
+ 'pg_lo_export' => 'Safe\pg_lo_export',
+ 'pg_lo_import' => 'Safe\pg_lo_import',
+ 'pg_lo_open' => 'Safe\pg_lo_open',
+ 'pg_lo_read' => 'Safe\pg_lo_read',
+ 'pg_lo_read_all' => 'Safe\pg_lo_read_all',
+ 'pg_lo_seek' => 'Safe\pg_lo_seek',
+ 'pg_lo_truncate' => 'Safe\pg_lo_truncate',
+ 'pg_lo_unlink' => 'Safe\pg_lo_unlink',
+ 'pg_lo_write' => 'Safe\pg_lo_write',
+ 'pg_meta_data' => 'Safe\pg_meta_data',
+ 'pg_options' => 'Safe\pg_options',
+ 'pg_parameter_status' => 'Safe\pg_parameter_status',
+ 'pg_pconnect' => 'Safe\pg_pconnect',
+ 'pg_ping' => 'Safe\pg_ping',
+ 'pg_port' => 'Safe\pg_port',
+ 'pg_prepare' => 'Safe\pg_prepare',
+ 'pg_put_line' => 'Safe\pg_put_line',
+ 'pg_query' => 'Safe\pg_query',
+ 'pg_query_params' => 'Safe\pg_query_params',
+ 'pg_result_error_field' => 'Safe\pg_result_error_field',
+ 'pg_result_seek' => 'Safe\pg_result_seek',
+ 'pg_select' => 'Safe\pg_select',
+ 'pg_send_execute' => 'Safe\pg_send_execute',
+ 'pg_send_prepare' => 'Safe\pg_send_prepare',
+ 'pg_send_query' => 'Safe\pg_send_query',
+ 'pg_send_query_params' => 'Safe\pg_send_query_params',
+ 'pg_socket' => 'Safe\pg_socket',
+ 'pg_trace' => 'Safe\pg_trace',
+ 'pg_tty' => 'Safe\pg_tty',
+ 'pg_update' => 'Safe\pg_update',
+ 'pg_version' => 'Safe\pg_version',
+ 'phpcredits' => 'Safe\phpcredits',
+ 'phpinfo' => 'Safe\phpinfo',
+ 'png2wbmp' => 'Safe\png2wbmp',
+ 'posix_access' => 'Safe\posix_access',
+ 'posix_getgrnam' => 'Safe\posix_getgrnam',
+ 'posix_getpgid' => 'Safe\posix_getpgid',
+ 'posix_initgroups' => 'Safe\posix_initgroups',
+ 'posix_kill' => 'Safe\posix_kill',
+ 'posix_mkfifo' => 'Safe\posix_mkfifo',
+ 'posix_mknod' => 'Safe\posix_mknod',
+ 'posix_setegid' => 'Safe\posix_setegid',
+ 'posix_seteuid' => 'Safe\posix_seteuid',
+ 'posix_setgid' => 'Safe\posix_setgid',
+ 'posix_setpgid' => 'Safe\posix_setpgid',
+ 'posix_setrlimit' => 'Safe\posix_setrlimit',
+ 'posix_setuid' => 'Safe\posix_setuid',
+ 'preg_match' => 'Safe\preg_match',
+ 'preg_match_all' => 'Safe\preg_match_all',
+ 'preg_replace' => 'Safe\preg_replace',
+ 'preg_split' => 'Safe\preg_split',
+ 'proc_get_status' => 'Safe\proc_get_status',
+ 'proc_nice' => 'Safe\proc_nice',
+ 'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
+ 'pspell_add_to_session' => 'Safe\pspell_add_to_session',
+ 'pspell_clear_session' => 'Safe\pspell_clear_session',
+ 'pspell_config_create' => 'Safe\pspell_config_create',
+ 'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
+ 'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
+ 'pspell_config_ignore' => 'Safe\pspell_config_ignore',
+ 'pspell_config_mode' => 'Safe\pspell_config_mode',
+ 'pspell_config_personal' => 'Safe\pspell_config_personal',
+ 'pspell_config_repl' => 'Safe\pspell_config_repl',
+ 'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
+ 'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
+ 'pspell_new' => 'Safe\pspell_new',
+ 'pspell_new_config' => 'Safe\pspell_new_config',
+ 'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
+ 'pspell_store_replacement' => 'Safe\pspell_store_replacement',
+ 'ps_add_launchlink' => 'Safe\ps_add_launchlink',
+ 'ps_add_locallink' => 'Safe\ps_add_locallink',
+ 'ps_add_note' => 'Safe\ps_add_note',
+ 'ps_add_pdflink' => 'Safe\ps_add_pdflink',
+ 'ps_add_weblink' => 'Safe\ps_add_weblink',
+ 'ps_arc' => 'Safe\ps_arc',
+ 'ps_arcn' => 'Safe\ps_arcn',
+ 'ps_begin_page' => 'Safe\ps_begin_page',
+ 'ps_begin_pattern' => 'Safe\ps_begin_pattern',
+ 'ps_begin_template' => 'Safe\ps_begin_template',
+ 'ps_circle' => 'Safe\ps_circle',
+ 'ps_clip' => 'Safe\ps_clip',
+ 'ps_close' => 'Safe\ps_close',
+ 'ps_closepath' => 'Safe\ps_closepath',
+ 'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
+ 'ps_close_image' => 'Safe\ps_close_image',
+ 'ps_continue_text' => 'Safe\ps_continue_text',
+ 'ps_curveto' => 'Safe\ps_curveto',
+ 'ps_delete' => 'Safe\ps_delete',
+ 'ps_end_page' => 'Safe\ps_end_page',
+ 'ps_end_pattern' => 'Safe\ps_end_pattern',
+ 'ps_end_template' => 'Safe\ps_end_template',
+ 'ps_fill' => 'Safe\ps_fill',
+ 'ps_fill_stroke' => 'Safe\ps_fill_stroke',
+ 'ps_get_parameter' => 'Safe\ps_get_parameter',
+ 'ps_hyphenate' => 'Safe\ps_hyphenate',
+ 'ps_include_file' => 'Safe\ps_include_file',
+ 'ps_lineto' => 'Safe\ps_lineto',
+ 'ps_moveto' => 'Safe\ps_moveto',
+ 'ps_new' => 'Safe\ps_new',
+ 'ps_open_file' => 'Safe\ps_open_file',
+ 'ps_place_image' => 'Safe\ps_place_image',
+ 'ps_rect' => 'Safe\ps_rect',
+ 'ps_restore' => 'Safe\ps_restore',
+ 'ps_rotate' => 'Safe\ps_rotate',
+ 'ps_save' => 'Safe\ps_save',
+ 'ps_scale' => 'Safe\ps_scale',
+ 'ps_setcolor' => 'Safe\ps_setcolor',
+ 'ps_setdash' => 'Safe\ps_setdash',
+ 'ps_setflat' => 'Safe\ps_setflat',
+ 'ps_setfont' => 'Safe\ps_setfont',
+ 'ps_setgray' => 'Safe\ps_setgray',
+ 'ps_setlinecap' => 'Safe\ps_setlinecap',
+ 'ps_setlinejoin' => 'Safe\ps_setlinejoin',
+ 'ps_setlinewidth' => 'Safe\ps_setlinewidth',
+ 'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
+ 'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
+ 'ps_setpolydash' => 'Safe\ps_setpolydash',
+ 'ps_set_border_color' => 'Safe\ps_set_border_color',
+ 'ps_set_border_dash' => 'Safe\ps_set_border_dash',
+ 'ps_set_border_style' => 'Safe\ps_set_border_style',
+ 'ps_set_info' => 'Safe\ps_set_info',
+ 'ps_set_parameter' => 'Safe\ps_set_parameter',
+ 'ps_set_text_pos' => 'Safe\ps_set_text_pos',
+ 'ps_set_value' => 'Safe\ps_set_value',
+ 'ps_shading' => 'Safe\ps_shading',
+ 'ps_shading_pattern' => 'Safe\ps_shading_pattern',
+ 'ps_shfill' => 'Safe\ps_shfill',
+ 'ps_show' => 'Safe\ps_show',
+ 'ps_show2' => 'Safe\ps_show2',
+ 'ps_show_xy' => 'Safe\ps_show_xy',
+ 'ps_show_xy2' => 'Safe\ps_show_xy2',
+ 'ps_stroke' => 'Safe\ps_stroke',
+ 'ps_symbol' => 'Safe\ps_symbol',
+ 'ps_translate' => 'Safe\ps_translate',
+ 'putenv' => 'Safe\putenv',
+ 'readfile' => 'Safe\readfile',
+ 'readgzfile' => 'Safe\readgzfile',
+ 'readline_add_history' => 'Safe\readline_add_history',
+ 'readline_callback_handler_install' => 'Safe\readline_callback_handler_install',
+ 'readline_clear_history' => 'Safe\readline_clear_history',
+ 'readline_completion_function' => 'Safe\readline_completion_function',
+ 'readline_read_history' => 'Safe\readline_read_history',
+ 'readline_write_history' => 'Safe\readline_write_history',
+ 'readlink' => 'Safe\readlink',
+ 'realpath' => 'Safe\realpath',
+ 'register_tick_function' => 'Safe\register_tick_function',
+ 'rename' => 'Safe\rename',
+ 'rewind' => 'Safe\rewind',
+ 'rewinddir' => 'Safe\rewinddir',
+ 'rmdir' => 'Safe\rmdir',
+ 'rpmaddtag' => 'Safe\rpmaddtag',
+ 'rrd_create' => 'Safe\rrd_create',
+ 'rsort' => 'Safe\rsort',
+ 'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
+ 'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
+ 'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
+ 'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
+ 'scandir' => 'Safe\scandir',
+ 'sem_acquire' => 'Safe\sem_acquire',
+ 'sem_get' => 'Safe\sem_get',
+ 'sem_release' => 'Safe\sem_release',
+ 'sem_remove' => 'Safe\sem_remove',
+ 'session_abort' => 'Safe\session_abort',
+ 'session_decode' => 'Safe\session_decode',
+ 'session_destroy' => 'Safe\session_destroy',
+ 'session_regenerate_id' => 'Safe\session_regenerate_id',
+ 'session_reset' => 'Safe\session_reset',
+ 'session_unset' => 'Safe\session_unset',
+ 'session_write_close' => 'Safe\session_write_close',
+ 'settype' => 'Safe\settype',
+ 'set_include_path' => 'Safe\set_include_path',
+ 'set_time_limit' => 'Safe\set_time_limit',
+ 'sha1_file' => 'Safe\sha1_file',
+ 'shmop_delete' => 'Safe\shmop_delete',
+ 'shmop_read' => 'Safe\shmop_read',
+ 'shmop_write' => 'Safe\shmop_write',
+ 'shm_put_var' => 'Safe\shm_put_var',
+ 'shm_remove' => 'Safe\shm_remove',
+ 'shm_remove_var' => 'Safe\shm_remove_var',
+ 'shuffle' => 'Safe\shuffle',
+ 'simplexml_import_dom' => 'Safe\simplexml_import_dom',
+ 'simplexml_load_file' => 'Safe\simplexml_load_file',
+ 'simplexml_load_string' => 'Safe\simplexml_load_string',
+ 'sleep' => 'Safe\sleep',
+ 'socket_accept' => 'Safe\socket_accept',
+ 'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
+ 'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
+ 'socket_bind' => 'Safe\socket_bind',
+ 'socket_connect' => 'Safe\socket_connect',
+ 'socket_create' => 'Safe\socket_create',
+ 'socket_create_listen' => 'Safe\socket_create_listen',
+ 'socket_create_pair' => 'Safe\socket_create_pair',
+ 'socket_export_stream' => 'Safe\socket_export_stream',
+ 'socket_getpeername' => 'Safe\socket_getpeername',
+ 'socket_getsockname' => 'Safe\socket_getsockname',
+ 'socket_get_option' => 'Safe\socket_get_option',
+ 'socket_import_stream' => 'Safe\socket_import_stream',
+ 'socket_listen' => 'Safe\socket_listen',
+ 'socket_read' => 'Safe\socket_read',
+ 'socket_send' => 'Safe\socket_send',
+ 'socket_sendmsg' => 'Safe\socket_sendmsg',
+ 'socket_sendto' => 'Safe\socket_sendto',
+ 'socket_set_block' => 'Safe\socket_set_block',
+ 'socket_set_nonblock' => 'Safe\socket_set_nonblock',
+ 'socket_set_option' => 'Safe\socket_set_option',
+ 'socket_shutdown' => 'Safe\socket_shutdown',
+ 'socket_write' => 'Safe\socket_write',
+ 'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
+ 'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
+ 'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
+ 'sodium_crypto_pwhash' => 'Safe\sodium_crypto_pwhash',
+ 'sodium_crypto_pwhash_str' => 'Safe\sodium_crypto_pwhash_str',
+ 'solr_get_version' => 'Safe\solr_get_version',
+ 'sort' => 'Safe\sort',
+ 'soundex' => 'Safe\soundex',
+ 'spl_autoload_register' => 'Safe\spl_autoload_register',
+ 'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
+ 'sprintf' => 'Safe\sprintf',
+ 'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
+ 'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
+ 'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
+ 'sqlsrv_close' => 'Safe\sqlsrv_close',
+ 'sqlsrv_commit' => 'Safe\sqlsrv_commit',
+ 'sqlsrv_configure' => 'Safe\sqlsrv_configure',
+ 'sqlsrv_execute' => 'Safe\sqlsrv_execute',
+ 'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
+ 'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
+ 'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
+ 'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
+ 'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
+ 'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
+ 'sqlsrv_query' => 'Safe\sqlsrv_query',
+ 'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
+ 'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
+ 'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
+ 'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
+ 'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
+ 'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
+ 'ssh2_auth_password' => 'Safe\ssh2_auth_password',
+ 'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
+ 'ssh2_connect' => 'Safe\ssh2_connect',
+ 'ssh2_disconnect' => 'Safe\ssh2_disconnect',
+ 'ssh2_exec' => 'Safe\ssh2_exec',
+ 'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
+ 'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
+ 'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
+ 'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
+ 'ssh2_scp_send' => 'Safe\ssh2_scp_send',
+ 'ssh2_sftp' => 'Safe\ssh2_sftp',
+ 'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
+ 'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
+ 'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
+ 'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
+ 'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
+ 'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
+ 'stream_context_set_params' => 'Safe\stream_context_set_params',
+ 'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
+ 'stream_filter_append' => 'Safe\stream_filter_append',
+ 'stream_filter_prepend' => 'Safe\stream_filter_prepend',
+ 'stream_filter_register' => 'Safe\stream_filter_register',
+ 'stream_filter_remove' => 'Safe\stream_filter_remove',
+ 'stream_get_contents' => 'Safe\stream_get_contents',
+ 'stream_isatty' => 'Safe\stream_isatty',
+ 'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
+ 'stream_set_blocking' => 'Safe\stream_set_blocking',
+ 'stream_set_timeout' => 'Safe\stream_set_timeout',
+ 'stream_socket_accept' => 'Safe\stream_socket_accept',
+ 'stream_socket_client' => 'Safe\stream_socket_client',
+ 'stream_socket_pair' => 'Safe\stream_socket_pair',
+ 'stream_socket_server' => 'Safe\stream_socket_server',
+ 'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
+ 'stream_supports_lock' => 'Safe\stream_supports_lock',
+ 'stream_wrapper_register' => 'Safe\stream_wrapper_register',
+ 'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
+ 'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
+ 'strptime' => 'Safe\strptime',
+ 'strtotime' => 'Safe\strtotime',
+ 'substr' => 'Safe\substr',
+ 'swoole_async_write' => 'Safe\swoole_async_write',
+ 'swoole_async_writefile' => 'Safe\swoole_async_writefile',
+ 'swoole_event_defer' => 'Safe\swoole_event_defer',
+ 'swoole_event_del' => 'Safe\swoole_event_del',
+ 'swoole_event_write' => 'Safe\swoole_event_write',
+ 'symlink' => 'Safe\symlink',
+ 'syslog' => 'Safe\syslog',
+ 'system' => 'Safe\system',
+ 'tempnam' => 'Safe\tempnam',
+ 'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
+ 'time_nanosleep' => 'Safe\time_nanosleep',
+ 'time_sleep_until' => 'Safe\time_sleep_until',
+ 'tmpfile' => 'Safe\tmpfile',
+ 'touch' => 'Safe\touch',
+ 'uasort' => 'Safe\uasort',
+ 'uksort' => 'Safe\uksort',
+ 'unlink' => 'Safe\unlink',
+ 'unpack' => 'Safe\unpack',
+ 'uopz_extend' => 'Safe\uopz_extend',
+ 'uopz_implement' => 'Safe\uopz_implement',
+ 'usort' => 'Safe\usort',
+ 'virtual' => 'Safe\virtual',
+ 'vsprintf' => 'Safe\vsprintf',
+ 'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
+ 'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
+ 'xdiff_file_diff' => 'Safe\xdiff_file_diff',
+ 'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
+ 'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
+ 'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
+ 'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
+ 'xdiff_string_patch' => 'Safe\xdiff_string_patch',
+ 'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
+ 'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
+ 'xml_parser_create' => 'Safe\xml_parser_create',
+ 'xml_parser_create_ns' => 'Safe\xml_parser_create_ns',
+ 'xml_set_object' => 'Safe\xml_set_object',
+ 'yaml_parse' => 'Safe\yaml_parse',
+ 'yaml_parse_file' => 'Safe\yaml_parse_file',
+ 'yaml_parse_url' => 'Safe\yaml_parse_url',
+ 'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
+ 'yaz_close' => 'Safe\yaz_close',
+ 'yaz_connect' => 'Safe\yaz_connect',
+ 'yaz_database' => 'Safe\yaz_database',
+ 'yaz_element' => 'Safe\yaz_element',
+ 'yaz_present' => 'Safe\yaz_present',
+ 'yaz_search' => 'Safe\yaz_search',
+ 'yaz_wait' => 'Safe\yaz_wait',
+ 'zip_entry_close' => 'Safe\zip_entry_close',
+ 'zip_entry_open' => 'Safe\zip_entry_open',
+ 'zip_entry_read' => 'Safe\zip_entry_read',
+ 'zlib_decode' => 'Safe\zlib_decode',
+]]]);
+};
--- /dev/null
+MIT License
+
+Copyright (c) 2018 Spomky-Labs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+{
+ "name": "web-auth/cose-lib",
+ "type": "library",
+ "license": "MIT",
+ "description": "CBOR Object Signing and Encryption (COSE) For PHP",
+ "keywords": ["COSE", "RFC8152"],
+ "homepage": "https://github.com/web-auth",
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/cose/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2",
+ "ext-json": "*",
+ "ext-openssl": "*",
+ "ext-mbstring": "*",
+ "fgrosse/phpasn1": "^2.1",
+ "beberlei/assert": "^3.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "Cose\\": "src/"
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm;
+
+interface Algorithm
+{
+ public static function identifier(): int;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+final class HS256 extends Hmac
+{
+ public const ID = 5;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): string
+ {
+ return 'sha256';
+ }
+
+ protected function getSignatureLength(): int
+ {
+ return 256;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+final class HS256Truncated64 extends Hmac
+{
+ public const ID = 4;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): string
+ {
+ return 'sha256';
+ }
+
+ protected function getSignatureLength(): int
+ {
+ return 64;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+final class HS384 extends Hmac
+{
+ public const ID = 6;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): string
+ {
+ return 'sha384';
+ }
+
+ protected function getSignatureLength(): int
+ {
+ return 384;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+final class HS512 extends Hmac
+{
+ public const ID = 7;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): string
+ {
+ return 'sha512';
+ }
+
+ protected function getSignatureLength(): int
+ {
+ return 512;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+use Assert\Assertion;
+use Cose\Key\Key;
+
+abstract class Hmac implements Mac
+{
+ public function hash(string $data, Key $key): string
+ {
+ $this->checKey($key);
+ $signature = hash_hmac($this->getHashAlgorithm(), $data, $key->get(-1), true);
+
+ return mb_substr($signature, 0, intdiv($this->getSignatureLength(), 8), '8bit');
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ return hash_equals($this->hash($data, $key), $signature);
+ }
+
+ abstract protected function getHashAlgorithm(): string;
+
+ abstract protected function getSignatureLength(): int;
+
+ private function checKey(Key $key): void
+ {
+ Assertion::eq($key->type(), 4, 'Invalid key. Must be of type symmetric');
+ Assertion::true($key->has(-1), 'Invalid key. The value of the key is missing');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Mac;
+
+use Cose\Algorithm\Algorithm;
+use Cose\Key\Key;
+
+interface Mac extends Algorithm
+{
+ public function hash(string $data, Key $key): string;
+
+ public function verify(string $data, Key $key, string $signature): bool;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm;
+
+use function array_key_exists;
+use Assert\Assertion;
+
+class Manager
+{
+ /**
+ * @var Algorithm[]
+ */
+ private $algorithms = [];
+
+ public function add(Algorithm $algorithm): void
+ {
+ $identifier = $algorithm::identifier();
+ $this->algorithms[$identifier] = $algorithm;
+ }
+
+ public function list(): iterable
+ {
+ yield from array_keys($this->algorithms);
+ }
+
+ /**
+ * @return Algorithm[]
+ */
+ public function all(): iterable
+ {
+ yield from $this->algorithms;
+ }
+
+ public function has(int $identifier): bool
+ {
+ return array_key_exists($identifier, $this->algorithms);
+ }
+
+ public function get(int $identifier): Algorithm
+ {
+ Assertion::true($this->has($identifier), 'Unsupported algorithm');
+
+ return $this->algorithms[$identifier];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm;
+
+use Assert\Assertion;
+
+class ManagerFactory
+{
+ /**
+ * @var Algorithm[]
+ */
+ private $algorithms = [];
+
+ public function add(string $alias, Algorithm $algorithm): void
+ {
+ $this->algorithms[$alias] = $algorithm;
+ }
+
+ public function list(): iterable
+ {
+ yield from array_keys($this->algorithms);
+ }
+
+ public function all(): iterable
+ {
+ yield from array_keys($this->algorithms);
+ }
+
+ public function create(array $aliases): Manager
+ {
+ $manager = new Manager();
+ foreach ($aliases as $alias) {
+ Assertion::keyExists($this->algorithms, $alias, sprintf('The algorithm with alias "%s" is not supported', $alias));
+ $manager->add($this->algorithms[$alias]);
+ }
+
+ return $manager;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use Assert\Assertion;
+use Cose\Algorithm\Signature\Signature;
+use Cose\Key\Ec2Key;
+use Cose\Key\Key;
+
+abstract class ECDSA implements Signature
+{
+ public function sign(string $data, Key $key): string
+ {
+ $key = $this->handleKey($key);
+ openssl_sign($data, $signature, $key->asPEM(), $this->getHashAlgorithm());
+
+ return ECSignature::fromAsn1($signature, $this->getSignaturePartLength());
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $key = $this->handleKey($key);
+ $publicKey = $key->toPublic();
+ $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength());
+
+ return 1 === openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm());
+ }
+
+ abstract protected function getCurve(): int;
+
+ abstract protected function getHashAlgorithm(): int;
+
+ abstract protected function getSignaturePartLength(): int;
+
+ private function handleKey(Key $key): Ec2Key
+ {
+ $key = new Ec2Key($key->getData());
+ Assertion::eq($key->curve(), $this->getCurve(), 'This key cannot be used with this algorithm');
+
+ return $key;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use function bin2hex;
+use function dechex;
+use function hexdec;
+use InvalidArgumentException;
+use function mb_strlen;
+use function mb_substr;
+use function str_pad;
+use const STR_PAD_LEFT;
+
+/**
+ * @internal
+ */
+final class ECSignature
+{
+ private const ASN1_SEQUENCE = '30';
+ private const ASN1_INTEGER = '02';
+ private const ASN1_MAX_SINGLE_BYTE = 128;
+ private const ASN1_LENGTH_2BYTES = '81';
+ private const ASN1_BIG_INTEGER_LIMIT = '7f';
+ private const ASN1_NEGATIVE_INTEGER = '00';
+ private const BYTE_SIZE = 2;
+
+ public static function toAsn1(string $signature, int $length): string
+ {
+ $signature = bin2hex($signature);
+
+ if (self::octetLength($signature) !== $length) {
+ throw new InvalidArgumentException('Invalid signature length.');
+ }
+
+ $pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit'));
+ $pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit'));
+
+ $lengthR = self::octetLength($pointR);
+ $lengthS = self::octetLength($pointS);
+
+ $totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
+ $lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
+
+ $bin = hex2bin(
+ self::ASN1_SEQUENCE
+ .$lengthPrefix.dechex($totalLength)
+ .self::ASN1_INTEGER.dechex($lengthR).$pointR
+ .self::ASN1_INTEGER.dechex($lengthS).$pointS
+ );
+ if (false === $bin) {
+ throw new InvalidArgumentException('Unable to convert into ASN.1');
+ }
+
+ return $bin;
+ }
+
+ public static function fromAsn1(string $signature, int $length): string
+ {
+ $message = bin2hex($signature);
+ $position = 0;
+
+ if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
+ throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
+ }
+
+ // @phpstan-ignore-next-line
+ if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
+ $position += self::BYTE_SIZE;
+ }
+
+ $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
+ $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
+
+ $bin = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT));
+ if (false === $bin) {
+ throw new InvalidArgumentException('Unable to convert from ASN.1');
+ }
+
+ return $bin;
+ }
+
+ private static function octetLength(string $data): int
+ {
+ return intdiv(mb_strlen($data, '8bit'), self::BYTE_SIZE);
+ }
+
+ private static function preparePositiveInteger(string $data): string
+ {
+ if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
+ return self::ASN1_NEGATIVE_INTEGER.$data;
+ }
+
+ while (
+ self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit')
+ && mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT
+ ) {
+ $data = mb_substr($data, 2, null, '8bit');
+ }
+
+ return $data;
+ }
+
+ private static function readAsn1Content(string $message, int &$position, int $length): string
+ {
+ $content = mb_substr($message, $position, $length, '8bit');
+ $position += $length;
+
+ return $content;
+ }
+
+ private static function readAsn1Integer(string $message, int &$position): string
+ {
+ if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
+ throw new InvalidArgumentException('Invalid data. Should contain an integer.');
+ }
+
+ $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
+
+ return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
+ }
+
+ private static function retrievePositiveInteger(string $data): string
+ {
+ while (
+ self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit')
+ && mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT
+ ) {
+ $data = mb_substr($data, 2, null, '8bit');
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use Cose\Key\Ec2Key;
+
+final class ES256 extends ECDSA
+{
+ public const ID = -7;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA256;
+ }
+
+ protected function getCurve(): int
+ {
+ return Ec2Key::CURVE_P256;
+ }
+
+ protected function getSignaturePartLength(): int
+ {
+ return 64;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use Cose\Key\Ec2Key;
+
+final class ES256K extends ECDSA
+{
+ public const ID = -46;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA256;
+ }
+
+ protected function getCurve(): int
+ {
+ return Ec2Key::CURVE_P256K;
+ }
+
+ protected function getSignaturePartLength(): int
+ {
+ return 64;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use Cose\Key\Ec2Key;
+
+final class ES384 extends ECDSA
+{
+ public const ID = -35;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA384;
+ }
+
+ protected function getCurve(): int
+ {
+ return Ec2Key::CURVE_P384;
+ }
+
+ protected function getSignaturePartLength(): int
+ {
+ return 96;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\ECDSA;
+
+use Cose\Key\Ec2Key;
+
+final class ES512 extends ECDSA
+{
+ public const ID = -36;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA512;
+ }
+
+ protected function getCurve(): int
+ {
+ return Ec2Key::CURVE_P521;
+ }
+
+ protected function getSignaturePartLength(): int
+ {
+ return 132;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\EdDSA;
+
+use Cose\Key\Key;
+
+final class ED256 extends EdDSA
+{
+ public const ID = -260;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ public function sign(string $data, Key $key): string
+ {
+ $hashedData = hash('sha256', $data, true);
+
+ return parent::sign($hashedData, $key);
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $hashedData = hash('sha256', $data, true);
+
+ return parent::verify($hashedData, $key, $signature);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\EdDSA;
+
+use Cose\Key\Key;
+
+final class ED512 extends EdDSA
+{
+ public const ID = -261;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ public function sign(string $data, Key $key): string
+ {
+ $hashedData = hash('sha512', $data, true);
+
+ return parent::sign($hashedData, $key);
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $hashedData = hash('sha512', $data, true);
+
+ return parent::verify($hashedData, $key, $signature);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\EdDSA;
+
+final class Ed25519 extends EdDSA
+{
+ public const ID = -8;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\EdDSA;
+
+use Assert\Assertion;
+use Cose\Algorithm\Signature\Signature;
+use Cose\Algorithms;
+use Cose\Key\Key;
+use Cose\Key\OkpKey;
+use InvalidArgumentException;
+use function sodium_crypto_sign_detached;
+use function sodium_crypto_sign_verify_detached;
+
+class EdDSA implements Signature
+{
+ public function sign(string $data, Key $key): string
+ {
+ $key = $this->handleKey($key);
+ Assertion::true($key->isPrivate(), 'The key is not private');
+
+ $x = $key->x();
+ $d = $key->d();
+ $secret = $d.$x;
+
+ switch ($key->curve()) {
+ case OkpKey::CURVE_ED25519:
+ return sodium_crypto_sign_detached($data, $secret);
+ default:
+ throw new InvalidArgumentException('Unsupported curve');
+ }
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $key = $this->handleKey($key);
+
+ switch ($key->curve()) {
+ case OkpKey::CURVE_ED25519:
+ return sodium_crypto_sign_verify_detached($signature, $data, $key->x());
+ default:
+ throw new InvalidArgumentException('Unsupported curve');
+ }
+ }
+
+ public static function identifier(): int
+ {
+ return Algorithms::COSE_ALGORITHM_EdDSA;
+ }
+
+ private function handleKey(Key $key): OkpKey
+ {
+ return new OkpKey($key->getData());
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+use Cose\Hash;
+
+final class PS256 extends PSSRSA
+{
+ public const ID = -37;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): Hash
+ {
+ return Hash::sha256();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+use Cose\Hash;
+
+final class PS384 extends PSSRSA
+{
+ public const ID = -38;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): Hash
+ {
+ return Hash::sha384();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+use Cose\Hash;
+
+final class PS512 extends PSSRSA
+{
+ public const ID = -39;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): Hash
+ {
+ return Hash::sha512();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+use function ceil;
+use function chr;
+use Cose\Algorithm\Signature\Signature;
+use Cose\BigInteger;
+use Cose\Hash;
+use Cose\Key\Key;
+use Cose\Key\RsaKey;
+use function hash_equals;
+use InvalidArgumentException;
+use function mb_strlen;
+use function mb_substr;
+use function ord;
+use function random_bytes;
+use RuntimeException;
+use function str_pad;
+use function str_repeat;
+
+/**
+ * @internal
+ */
+abstract class PSSRSA implements Signature
+{
+ public function sign(string $data, Key $key): string
+ {
+ $key = $this->handleKey($key);
+ $modulusLength = mb_strlen($key->n(), '8bit');
+
+ $em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm());
+ $message = BigInteger::createFromBinaryString($em);
+ $signature = $this->exponentiate($key, $message);
+
+ return $this->convertIntegerToOctetString($signature, $modulusLength);
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $key = $this->handleKey($key);
+ $modulusLength = mb_strlen($key->n(), '8bit');
+
+ if (mb_strlen($signature, '8bit') !== $modulusLength) {
+ throw new InvalidArgumentException('Invalid modulus length');
+ }
+ $s2 = BigInteger::createFromBinaryString($signature);
+ $m2 = $this->exponentiate($key, $s2);
+ $em = $this->convertIntegerToOctetString($m2, $modulusLength);
+ $modBits = 8 * $modulusLength;
+
+ return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm());
+ }
+
+ /**
+ * Exponentiate with or without Chinese Remainder Theorem.
+ * Operation with primes 'p' and 'q' is appox. 2x faster.
+ */
+ public function exponentiate(RsaKey $key, BigInteger $c): BigInteger
+ {
+ if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(BigInteger::createFromBinaryString($key->n())) > 0) {
+ throw new RuntimeException();
+ }
+ if ($key->isPublic() || !$key->hasPrimes() || !$key->hasExponents() || !$key->hasCoefficient()) {
+ return $c->modPow(BigInteger::createFromBinaryString($key->e()), BigInteger::createFromBinaryString($key->n()));
+ }
+
+ [$p, $q] = $key->primes();
+ [$dP, $dQ] = $key->exponents();
+ $qInv = BigInteger::createFromBinaryString($key->QInv());
+
+ $m1 = $c->modPow($dP, $p);
+ $m2 = $c->modPow($dQ, $q);
+ $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
+
+ return $m2->add($h->multiply($q));
+ }
+
+ abstract protected function getHashAlgorithm(): Hash;
+
+ private function handleKey(Key $key): RsaKey
+ {
+ return new RsaKey($key->getData());
+ }
+
+ private function convertIntegerToOctetString(BigInteger $x, int $xLen): string
+ {
+ $x = $x->toBytes();
+ if (mb_strlen($x, '8bit') > $xLen) {
+ throw new RuntimeException('Unable to convert the integer');
+ }
+
+ return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
+ }
+
+ /**
+ * MGF1.
+ */
+ private function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
+ {
+ $t = '';
+ $count = ceil($maskLen / $mgfHash->getLength());
+ for ($i = 0; $i < $count; ++$i) {
+ $c = pack('N', $i);
+ $t .= $mgfHash->hash($mgfSeed.$c);
+ }
+
+ return mb_substr($t, 0, $maskLen, '8bit');
+ }
+
+ /**
+ * EMSA-PSS-ENCODE.
+ */
+ private function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
+ {
+ $emLen = ($modulusLength + 1) >> 3;
+ $sLen = $hash->getLength();
+ $mHash = $hash->hash($message);
+ if ($emLen <= $hash->getLength() + $sLen + 2) {
+ throw new RuntimeException();
+ }
+ $salt = random_bytes($sLen);
+ $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
+ $h = $hash->hash($m2);
+ $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
+ $db = $ps.chr(1).$salt;
+ $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
+ $maskedDB = $db ^ $dbMask;
+ $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
+
+ return $maskedDB.$h.chr(0xBC);
+ }
+
+ /**
+ * EMSA-PSS-VERIFY.
+ */
+ private function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
+ {
+ $emLen = ($emBits + 1) >> 3;
+ $sLen = $hash->getLength();
+ $mHash = $hash->hash($m);
+ if ($emLen < $hash->getLength() + $sLen + 2) {
+ throw new InvalidArgumentException();
+ }
+ if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
+ throw new InvalidArgumentException();
+ }
+ $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
+ $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
+ $temp = chr(0xFF << ($emBits & 7));
+ if ((~$maskedDB[0] & $temp) !== $temp) {
+ throw new InvalidArgumentException();
+ }
+ $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
+ $db = $maskedDB ^ $dbMask;
+ $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
+ $temp = $emLen - $hash->getLength() - $sLen - 2;
+ if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
+ throw new InvalidArgumentException();
+ }
+ if (1 !== ord($db[$temp])) {
+ throw new InvalidArgumentException();
+ }
+ $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
+ $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
+ $h2 = $hash->hash($m2);
+
+ return hash_equals($h, $h2);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+final class RS1 extends RSA
+{
+ public const ID = -65535;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA1;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+final class RS256 extends RSA
+{
+ public const ID = -257;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA256;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+final class RS384 extends RSA
+{
+ public const ID = -258;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA384;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+final class RS512 extends RSA
+{
+ public const ID = -259;
+
+ public static function identifier(): int
+ {
+ return self::ID;
+ }
+
+ protected function getHashAlgorithm(): int
+ {
+ return OPENSSL_ALGO_SHA512;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature\RSA;
+
+use Assert\Assertion;
+use Cose\Algorithm\Signature\Signature;
+use Cose\Key\Key;
+use Cose\Key\RsaKey;
+use InvalidArgumentException;
+
+abstract class RSA implements Signature
+{
+ public function sign(string $data, Key $key): string
+ {
+ $key = $this->handleKey($key);
+ Assertion::true($key->isPrivate(), 'The key is not private');
+
+ if (false === openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm())) {
+ throw new InvalidArgumentException('Unable to sign the data');
+ }
+
+ return $signature;
+ }
+
+ public function verify(string $data, Key $key, string $signature): bool
+ {
+ $key = $this->handleKey($key);
+
+ return 1 === openssl_verify($data, $signature, $key->asPem(), $this->getHashAlgorithm());
+ }
+
+ abstract protected function getHashAlgorithm(): int;
+
+ private function handleKey(Key $key): RsaKey
+ {
+ return new RsaKey($key->getData());
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Algorithm\Signature;
+
+use Cose\Algorithm\Algorithm;
+use Cose\Key\Key;
+
+interface Signature extends Algorithm
+{
+ public function sign(string $data, Key $key): string;
+
+ public function verify(string $data, Key $key, string $signature): bool;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose;
+
+use Assert\Assertion;
+use Assert\AssertionFailedException;
+
+/**
+ * @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ */
+abstract class Algorithms
+{
+ public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33;
+ public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32;
+ public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31;
+ public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30;
+ public const COSE_ALGORITHM_AES_MAC_256_128 = 26;
+ public const COSE_ALGORITHM_AES_MAC_128_128 = 25;
+ public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24;
+ public const COSE_ALGORITHM_AES_MAC_256_64 = 15;
+ public const COSE_ALGORITHM_AES_MAC_128_64 = 14;
+ public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13;
+ public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12;
+ public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11;
+ public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10;
+ public const COSE_ALGORITHM_HS512 = 7;
+ public const COSE_ALGORITHM_HS384 = 6;
+ public const COSE_ALGORITHM_HS256 = 5;
+ public const COSE_ALGORITHM_HS256_64 = 4;
+ public const COSE_ALGORITHM_A256GCM = 3;
+ public const COSE_ALGORITHM_A192GCM = 2;
+ public const COSE_ALGORITHM_A128GCM = 1;
+ public const COSE_ALGORITHM_A128KW = -3;
+ public const COSE_ALGORITHM_A192KW = -4;
+ public const COSE_ALGORITHM_A256KW = -5;
+ public const COSE_ALGORITHM_DIRECT = -6;
+ public const COSE_ALGORITHM_ES256 = -7;
+ public const COSE_ALGORITHM_EdDSA = -8;
+ public const COSE_ALGORITHM_ED256 = -260;
+ public const COSE_ALGORITHM_ED512 = -261;
+ public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10;
+ public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11;
+ public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12;
+ public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13;
+ public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25;
+ public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26;
+ public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27;
+ public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28;
+ public const COSE_ALGORITHM_ECDH_ES_A128KW = -29;
+ public const COSE_ALGORITHM_ECDH_ES_A192KW = -30;
+ public const COSE_ALGORITHM_ECDH_ES_A256KW = -31;
+ public const COSE_ALGORITHM_ECDH_SS_A128KW = -32;
+ public const COSE_ALGORITHM_ECDH_SS_A192KW = -33;
+ public const COSE_ALGORITHM_ECDH_SS_A256KW = -34;
+ public const COSE_ALGORITHM_ES384 = -35;
+ public const COSE_ALGORITHM_ES512 = -36;
+ public const COSE_ALGORITHM_PS256 = -37;
+ public const COSE_ALGORITHM_PS384 = -38;
+ public const COSE_ALGORITHM_PS512 = -39;
+ public const COSE_ALGORITHM_RSAES_OAEP = -40;
+ public const COSE_ALGORITHM_RSAES_OAEP_256 = -41;
+ public const COSE_ALGORITHM_RSAES_OAEP_512 = -42;
+ public const COSE_ALGORITHM_ES256K = -46;
+ public const COSE_ALGORITHM_RS256 = -257;
+ public const COSE_ALGORITHM_RS384 = -258;
+ public const COSE_ALGORITHM_RS512 = -259;
+ public const COSE_ALGORITHM_RS1 = -65535;
+
+ public const COSE_ALGORITHM_MAP = [
+ self::COSE_ALGORITHM_ES256 => OPENSSL_ALGO_SHA256,
+ self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384,
+ self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512,
+ self::COSE_ALGORITHM_RS256 => OPENSSL_ALGO_SHA256,
+ self::COSE_ALGORITHM_RS384 => OPENSSL_ALGO_SHA384,
+ self::COSE_ALGORITHM_RS512 => OPENSSL_ALGO_SHA512,
+ self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1,
+ ];
+
+ public const COSE_HASH_MAP = [
+ self::COSE_ALGORITHM_ES256K => 'sha256',
+ self::COSE_ALGORITHM_ES256 => 'sha256',
+ self::COSE_ALGORITHM_ES384 => 'sha384',
+ self::COSE_ALGORITHM_ES512 => 'sha512',
+ self::COSE_ALGORITHM_RS256 => 'sha256',
+ self::COSE_ALGORITHM_RS384 => 'sha384',
+ self::COSE_ALGORITHM_RS512 => 'sha512',
+ self::COSE_ALGORITHM_PS256 => 'sha256',
+ self::COSE_ALGORITHM_PS384 => 'sha384',
+ self::COSE_ALGORITHM_PS512 => 'sha512',
+ self::COSE_ALGORITHM_RS1 => 'sha1',
+ ];
+
+ /**
+ * @throws AssertionFailedException
+ */
+ public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int
+ {
+ Assertion::keyExists(self::COSE_ALGORITHM_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported');
+
+ return self::COSE_ALGORITHM_MAP[$algorithmIdentifier];
+ }
+
+ /**
+ * @throws AssertionFailedException
+ */
+ public static function getHashAlgorithmFor(int $algorithmIdentifier): string
+ {
+ Assertion::keyExists(self::COSE_HASH_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported');
+
+ return self::COSE_HASH_MAP[$algorithmIdentifier];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose;
+
+use Brick\Math\BigInteger as BrickBigInteger;
+use function chr;
+use function hex2bin;
+use InvalidArgumentException;
+use function unpack;
+
+/**
+ * @internal
+ */
+class BigInteger
+{
+ /**
+ * Holds the BigInteger's value.
+ *
+ * @var BrickBigInteger
+ */
+ private $value;
+
+ private function __construct(BrickBigInteger $value)
+ {
+ $this->value = $value;
+ }
+
+ public static function createFromBinaryString(string $value): self
+ {
+ $res = unpack('H*', $value);
+ if (false === $res) {
+ throw new InvalidArgumentException('Unable to convert the data from binary');
+ }
+ $data = current($res);
+
+ return new self(BrickBigInteger::fromBase($data, 16));
+ }
+
+ public static function createFromDecimal(int $value): self
+ {
+ return new self(BrickBigInteger::of($value));
+ }
+
+ /**
+ * Converts a BigInteger to a binary string.
+ */
+ public function toBytes(): string
+ {
+ if ($this->value->isEqualTo(BrickBigInteger::zero())) {
+ return '';
+ }
+
+ $temp = $this->value->toBase(16);
+ $temp = 0 !== (mb_strlen($temp, '8bit') & 1) ? '0'.$temp : $temp;
+ $temp = hex2bin($temp);
+ if (false === $temp) {
+ throw new InvalidArgumentException('Unable to convert the data into binary');
+ }
+
+ return ltrim($temp, chr(0));
+ }
+
+ /**
+ * Adds two BigIntegers.
+ *
+ * @param BigInteger $y
+ *
+ * @return BigInteger
+ */
+ public function add(self $y): self
+ {
+ $value = $this->value->plus($y->value);
+
+ return new self($value);
+ }
+
+ /**
+ * Subtracts two BigIntegers.
+ *
+ * @param BigInteger $y
+ *
+ * @return BigInteger
+ */
+ public function subtract(self $y): self
+ {
+ $value = $this->value->minus($y->value);
+
+ return new self($value);
+ }
+
+ /**
+ * Multiplies two BigIntegers.
+ *
+ * @param BigInteger $x
+ *
+ * @return BigInteger
+ */
+ public function multiply(self $x): self
+ {
+ $value = $this->value->multipliedBy($x->value);
+
+ return new self($value);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BigInteger $e
+ * @param BigInteger $n
+ *
+ * @return BigInteger
+ */
+ public function modPow(self $e, self $n): self
+ {
+ $value = $this->value->modPow($e->value, $n->value);
+
+ return new self($value);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BigInteger $d
+ *
+ * @return BigInteger
+ */
+ public function mod(self $d): self
+ {
+ $value = $this->value->mod($d->value);
+
+ return new self($value);
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * @param BigInteger $y
+ */
+ public function compare(self $y): int
+ {
+ return $this->value->compareTo($y->value);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose;
+
+/**
+ * @internal
+ */
+class Hash
+{
+ /**
+ * Hash Parameter.
+ *
+ * @var string
+ */
+ private $hash;
+
+ /**
+ * DER encoding T.
+ *
+ * @var string
+ */
+ private $t;
+
+ /**
+ * Hash Length.
+ *
+ * @var int
+ */
+ private $length;
+
+ private function __construct(string $hash, int $length, string $t)
+ {
+ $this->hash = $hash;
+ $this->length = $length;
+ $this->t = $t;
+ }
+
+ /**
+ * @return Hash
+ */
+ public static function sha1(): self
+ {
+ return new self('sha1', 20, "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14");
+ }
+
+ /**
+ * @return Hash
+ */
+ public static function sha256(): self
+ {
+ return new self('sha256', 32, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20");
+ }
+
+ /**
+ * @return Hash
+ */
+ public static function sha384(): self
+ {
+ return new self('sha384', 48, "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30");
+ }
+
+ /**
+ * @return Hash
+ */
+ public static function sha512(): self
+ {
+ return new self('sha512', 64, "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40");
+ }
+
+ public function getLength(): int
+ {
+ return $this->length;
+ }
+
+ /**
+ * Compute the HMAC.
+ */
+ public function hash(string $text): string
+ {
+ return hash($this->hash, $text, true);
+ }
+
+ public function name(): string
+ {
+ return $this->hash;
+ }
+
+ public function t(): string
+ {
+ return $this->t;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Key;
+
+use function array_key_exists;
+use Assert\Assertion;
+use FG\ASN1\ExplicitlyTaggedObject;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\Integer;
+use FG\ASN1\Universal\ObjectIdentifier;
+use FG\ASN1\Universal\OctetString;
+use FG\ASN1\Universal\Sequence;
+
+class Ec2Key extends Key
+{
+ public const CURVE_P256 = 1;
+ public const CURVE_P256K = 8;
+ public const CURVE_P384 = 2;
+ public const CURVE_P521 = 3;
+
+ public const DATA_CURVE = -1;
+ public const DATA_X = -2;
+ public const DATA_Y = -3;
+ public const DATA_D = -4;
+
+ private const SUPPORTED_CURVES = [
+ self::CURVE_P256,
+ self::CURVE_P256K,
+ self::CURVE_P384,
+ self::CURVE_P521,
+ ];
+
+ private const NAMED_CURVE_OID = [
+ self::CURVE_P256 => '1.2.840.10045.3.1.7', // NIST P-256 / secp256r1
+ self::CURVE_P256K => '1.3.132.0.10', // NIST P-256K / secp256k1
+ self::CURVE_P384 => '1.3.132.0.34', // NIST P-384 / secp384r1
+ self::CURVE_P521 => '1.3.132.0.35', // NIST P-521 / secp521r1
+ ];
+
+ private const CURVE_KEY_LENGTH = [
+ self::CURVE_P256 => 32,
+ self::CURVE_P256K => 32,
+ self::CURVE_P384 => 48,
+ self::CURVE_P521 => 66,
+ ];
+
+ public function __construct(array $data)
+ {
+ parent::__construct($data);
+ Assertion::eq($data[self::TYPE], self::TYPE_EC2, 'Invalid EC2 key. The key type does not correspond to an EC2 key');
+ Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing');
+ Assertion::keyExists($data, self::DATA_X, 'Invalid EC2 key. The x coordinate is missing');
+ Assertion::keyExists($data, self::DATA_Y, 'Invalid EC2 key. The y coordinate is missing');
+ Assertion::length($data[self::DATA_X], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for x coordinate', null, '8bit');
+ Assertion::length($data[self::DATA_Y], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for y coordinate', null, '8bit');
+ Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported');
+ }
+
+ public function toPublic(): self
+ {
+ $data = $this->getData();
+ unset($data[self::DATA_D]);
+
+ return new self($data);
+ }
+
+ public function x(): string
+ {
+ return $this->get(self::DATA_X);
+ }
+
+ public function y(): string
+ {
+ return $this->get(self::DATA_Y);
+ }
+
+ public function isPrivate(): bool
+ {
+ return array_key_exists(self::DATA_D, $this->getData());
+ }
+
+ public function d(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private');
+
+ return $this->get(self::DATA_D);
+ }
+
+ public function curve(): int
+ {
+ return (int) $this->get(self::DATA_CURVE);
+ }
+
+ public function asPEM(): string
+ {
+ if ($this->isPrivate()) {
+ $der = new Sequence(
+ new Integer(1),
+ new OctetString(bin2hex($this->d())),
+ new ExplicitlyTaggedObject(0, new ObjectIdentifier($this->getCurveOid())),
+ new ExplicitlyTaggedObject(1, new BitString(bin2hex($this->getUncompressedCoordinates())))
+ );
+
+ return $this->pem('EC PRIVATE KEY', $der->getBinary());
+ }
+
+ $der = new Sequence(
+ new Sequence(
+ new ObjectIdentifier('1.2.840.10045.2.1'),
+ new ObjectIdentifier($this->getCurveOid())
+ ),
+ new BitString(bin2hex($this->getUncompressedCoordinates()))
+ );
+
+ return $this->pem('PUBLIC KEY', $der->getBinary());
+ }
+
+ public function getUncompressedCoordinates(): string
+ {
+ return "\x04".$this->x().$this->y();
+ }
+
+ private function getCurveOid(): string
+ {
+ return self::NAMED_CURVE_OID[$this->curve()];
+ }
+
+ private function pem(string $type, string $der): string
+ {
+ return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)).
+ chunk_split(base64_encode($der), 64, "\n").
+ sprintf("-----END %s-----\n", mb_strtoupper($type));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Key;
+
+use function array_key_exists;
+use Assert\Assertion;
+
+class Key
+{
+ public const TYPE = 1;
+ public const TYPE_OKP = 1;
+ public const TYPE_EC2 = 2;
+ public const TYPE_RSA = 3;
+ public const TYPE_OCT = 4;
+ public const KID = 2;
+ public const ALG = 3;
+ public const KEY_OPS = 4;
+ public const BASE_IV = 5;
+
+ /**
+ * @var array
+ */
+ private $data;
+
+ public function __construct(array $data)
+ {
+ Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined');
+ $this->data = $data;
+ }
+
+ public static function createFromData(array $data): self
+ {
+ Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined');
+ switch ($data[self::TYPE]) {
+ case 1:
+ return new OkpKey($data);
+ case 2:
+ return new Ec2Key($data);
+ case 3:
+ return new RsaKey($data);
+ case 4:
+ return new SymmetricKey($data);
+ default:
+ return new self($data);
+ }
+ }
+
+ /**
+ * @return int|string
+ */
+ public function type()
+ {
+ return $this->data[self::TYPE];
+ }
+
+ public function alg(): int
+ {
+ return (int) $this->get(self::ALG);
+ }
+
+ public function getData(): array
+ {
+ return $this->data;
+ }
+
+ public function has(int $key): bool
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(int $key)
+ {
+ Assertion::keyExists($this->data, $key, sprintf('The key has no data at index %d', $key));
+
+ return $this->data[$key];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Key;
+
+use function array_key_exists;
+use Assert\Assertion;
+
+class OkpKey extends Key
+{
+ public const CURVE_X25519 = 4;
+ public const CURVE_X448 = 5;
+ public const CURVE_ED25519 = 6;
+ public const CURVE_ED448 = 7;
+
+ public const DATA_CURVE = -1;
+ public const DATA_X = -2;
+ public const DATA_D = -4;
+
+ private const SUPPORTED_CURVES = [
+ self::CURVE_X25519,
+ self::CURVE_X448,
+ self::CURVE_ED25519,
+ self::CURVE_ED448,
+ ];
+
+ public function __construct(array $data)
+ {
+ parent::__construct($data);
+ Assertion::eq($data[self::TYPE], self::TYPE_OKP, 'Invalid OKP key. The key type does not correspond to an OKP key');
+ Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing');
+ Assertion::keyExists($data, self::DATA_X, 'Invalid OKP key. The x coordinate is missing');
+ Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported');
+ }
+
+ public function x(): string
+ {
+ return $this->get(self::DATA_X);
+ }
+
+ public function isPrivate(): bool
+ {
+ return array_key_exists(self::DATA_D, $this->getData());
+ }
+
+ public function d(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private');
+
+ return $this->get(self::DATA_D);
+ }
+
+ public function curve(): int
+ {
+ return (int) $this->get(self::DATA_CURVE);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Key;
+
+use function array_key_exists;
+use Assert\Assertion;
+use Brick\Math\BigInteger;
+use FG\ASN1\Universal\BitString;
+use FG\ASN1\Universal\Integer;
+use FG\ASN1\Universal\NullObject;
+use FG\ASN1\Universal\ObjectIdentifier;
+use FG\ASN1\Universal\Sequence;
+use InvalidArgumentException;
+
+class RsaKey extends Key
+{
+ public const DATA_N = -1;
+ public const DATA_E = -2;
+ public const DATA_D = -3;
+ public const DATA_P = -4;
+ public const DATA_Q = -5;
+ public const DATA_DP = -6;
+ public const DATA_DQ = -7;
+ public const DATA_QI = -8;
+ public const DATA_OTHER = -9;
+ public const DATA_RI = -10;
+ public const DATA_DI = -11;
+ public const DATA_TI = -12;
+
+ public function __construct(array $data)
+ {
+ parent::__construct($data);
+ Assertion::eq($data[self::TYPE], self::TYPE_RSA, 'Invalid RSA key. The key type does not correspond to a RSA key');
+ Assertion::keyExists($data, self::DATA_N, 'Invalid RSA key. The modulus is missing');
+ Assertion::keyExists($data, self::DATA_E, 'Invalid RSA key. The exponent is missing');
+ }
+
+ public function n(): string
+ {
+ return $this->get(self::DATA_N);
+ }
+
+ public function e(): string
+ {
+ return $this->get(self::DATA_E);
+ }
+
+ public function d(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_D);
+ }
+
+ public function p(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_P);
+ }
+
+ public function q(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_Q);
+ }
+
+ public function dP(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_DP);
+ }
+
+ public function dQ(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_DQ);
+ }
+
+ public function QInv(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_QI);
+ }
+
+ public function other(): array
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_OTHER);
+ }
+
+ public function rI(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_RI);
+ }
+
+ public function dI(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_DI);
+ }
+
+ public function tI(): string
+ {
+ Assertion::true($this->isPrivate(), 'The key is not private.');
+
+ return $this->get(self::DATA_TI);
+ }
+
+ public function hasPrimes(): bool
+ {
+ return $this->has(self::DATA_P) && $this->has(self::DATA_Q);
+ }
+
+ public function primes(): array
+ {
+ return [
+ $this->p(),
+ $this->q(),
+ ];
+ }
+
+ public function hasExponents(): bool
+ {
+ return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ);
+ }
+
+ public function exponents(): array
+ {
+ return [
+ $this->dP(),
+ $this->dQ(),
+ ];
+ }
+
+ public function hasCoefficient(): bool
+ {
+ return $this->has(self::DATA_QI);
+ }
+
+ public function isPublic(): bool
+ {
+ return !$this->isPrivate();
+ }
+
+ public function isPrivate(): bool
+ {
+ return array_key_exists(self::DATA_D, $this->getData());
+ }
+
+ public function asPem(): string
+ {
+ Assertion::false($this->isPrivate(), 'Unsupported for private keys.');
+ $bitSring = new Sequence(
+ new Integer($this->fromBase64ToInteger($this->n())),
+ new Integer($this->fromBase64ToInteger($this->e()))
+ );
+
+ $der = new Sequence(
+ new Sequence(
+ new ObjectIdentifier('1.2.840.113549.1.1.1'),
+ new NullObject()
+ ),
+ new BitString(bin2hex($bitSring->getBinary()))
+ );
+
+ return $this->pem('PUBLIC KEY', $der->getBinary());
+ }
+
+ private function fromBase64ToInteger(string $value): string
+ {
+ $data = unpack('H*', $value);
+ if (false === $data) {
+ throw new InvalidArgumentException('Unable to convert to an integer');
+ }
+
+ $hex = current($data);
+
+ return BigInteger::fromBase($hex, 16)->toBase(10);
+ }
+
+ private function pem(string $type, string $der): string
+ {
+ return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)).
+ chunk_split(base64_encode($der), 64, "\n").
+ sprintf("-----END %s-----\n", mb_strtoupper($type));
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose\Key;
+
+use Assert\Assertion;
+
+class SymmetricKey extends Key
+{
+ public const DATA_K = -1;
+
+ public function __construct(array $data)
+ {
+ parent::__construct($data);
+ Assertion::eq($data[self::TYPE], self::TYPE_OCT, 'Invalid symmetric key. The key type does not correspond to a symmetric key');
+ Assertion::keyExists($data, self::DATA_K, 'Invalid symmetric key. The parameter "k" is missing');
+ }
+
+ public function k(): string
+ {
+ return $this->get(self::DATA_K);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Cose;
+
+class Verifier
+{
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2018 Spomky-Labs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+{
+ "name": "web-auth/metadata-service",
+ "type": "library",
+ "license": "MIT",
+ "description": "Metadata Service for FIDO2/Webauthn",
+ "keywords": ["FIDO", "FIDO2", "webauthn"],
+ "homepage": "https://github.com/web-auth",
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/metadata-service/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2",
+ "ext-json": "*",
+ "beberlei/assert": "^3.2",
+ "league/uri": "^6.0",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log-implementation": "Recommended to receive logs from the library"
+ },
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\MetadataService\\": "src/"
+ }
+ },
+ "suggest": {
+ "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
+ "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources"
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+
+abstract class AbstractDescriptor implements JsonSerializable
+{
+ /**
+ * @var int|null
+ */
+ private $maxRetries;
+
+ /**
+ * @var int|null
+ */
+ private $blockSlowdown;
+
+ public function __construct(?int $maxRetries = null, ?int $blockSlowdown = null)
+ {
+ Assertion::greaterOrEqualThan($maxRetries, 0, Utils::logicException('Invalid data. The value of "maxRetries" must be a positive integer'));
+ Assertion::greaterOrEqualThan($blockSlowdown, 0, Utils::logicException('Invalid data. The value of "blockSlowdown" must be a positive integer'));
+
+ $this->maxRetries = $maxRetries;
+ $this->blockSlowdown = $blockSlowdown;
+ }
+
+ public function getMaxRetries(): ?int
+ {
+ return $this->maxRetries;
+ }
+
+ public function getBlockSlowdown(): ?int
+ {
+ return $this->blockSlowdown;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+abstract class AuthenticatorStatus
+{
+ public const NOT_FIDO_CERTIFIED = 'NOT_FIDO_CERTIFIED';
+ public const FIDO_CERTIFIED = 'FIDO_CERTIFIED';
+ public const USER_VERIFICATION_BYPASS = 'USER_VERIFICATION_BYPASS';
+ public const ATTESTATION_KEY_COMPROMISE = 'ATTESTATION_KEY_COMPROMISE';
+ public const USER_KEY_REMOTE_COMPROMISE = 'USER_KEY_REMOTE_COMPROMISE';
+ public const USER_KEY_PHYSICAL_COMPROMISE = 'USER_KEY_PHYSICAL_COMPROMISE';
+ public const UPDATE_AVAILABLE = 'UPDATE_AVAILABLE';
+ public const REVOKED = 'REVOKED';
+ public const SELF_ASSERTION_SUBMITTED = 'SELF_ASSERTION_SUBMITTED';
+ public const FIDO_CERTIFIED_L1 = 'FIDO_CERTIFIED_L1';
+ public const FIDO_CERTIFIED_L1plus = 'FIDO_CERTIFIED_L1plus';
+ public const FIDO_CERTIFIED_L2 = 'FIDO_CERTIFIED_L2';
+ public const FIDO_CERTIFIED_L2plus = 'FIDO_CERTIFIED_L2plus';
+ public const FIDO_CERTIFIED_L3 = 'FIDO_CERTIFIED_L3';
+ public const FIDO_CERTIFIED_L3plus = 'FIDO_CERTIFIED_L3plus';
+ public const FIDO_CERTIFIED_L4 = 'FIDO_CERTIFIED_L4';
+ public const FIDO_CERTIFIED_L5 = 'FIDO_CERTIFIED_L5';
+
+ public static function list(): array
+ {
+ return [
+ self::NOT_FIDO_CERTIFIED,
+ self::FIDO_CERTIFIED,
+ self::USER_VERIFICATION_BYPASS,
+ self::ATTESTATION_KEY_COMPROMISE,
+ self::USER_KEY_REMOTE_COMPROMISE,
+ self::USER_KEY_PHYSICAL_COMPROMISE,
+ self::UPDATE_AVAILABLE,
+ self::REVOKED,
+ self::SELF_ASSERTION_SUBMITTED,
+ self::FIDO_CERTIFIED_L1,
+ self::FIDO_CERTIFIED_L1plus,
+ self::FIDO_CERTIFIED_L2,
+ self::FIDO_CERTIFIED_L2plus,
+ self::FIDO_CERTIFIED_L3,
+ self::FIDO_CERTIFIED_L3plus,
+ self::FIDO_CERTIFIED_L4,
+ self::FIDO_CERTIFIED_L5,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+
+class BiometricAccuracyDescriptor extends AbstractDescriptor
+{
+ /**
+ * @var float|null
+ */
+ private $FAR;
+
+ /**
+ * @var float|null
+ */
+ private $FRR;
+
+ /**
+ * @var float|null
+ */
+ private $EER;
+
+ /**
+ * @var float|null
+ */
+ private $FAAR;
+
+ /**
+ * @var int|null
+ */
+ private $maxReferenceDataSets;
+
+ public function __construct(?float $FAR, ?float $FRR, ?float $EER, ?float $FAAR, ?int $maxReferenceDataSets, ?int $maxRetries = null, ?int $blockSlowdown = null)
+ {
+ Assertion::greaterOrEqualThan($maxReferenceDataSets, 0, Utils::logicException('Invalid data. The value of "maxReferenceDataSets" must be a positive integer'));
+ $this->FRR = $FRR;
+ $this->FAR = $FAR;
+ $this->EER = $EER;
+ $this->FAAR = $FAAR;
+ $this->maxReferenceDataSets = $maxReferenceDataSets;
+ parent::__construct($maxRetries, $blockSlowdown);
+ }
+
+ public function getFAR(): ?float
+ {
+ return $this->FAR;
+ }
+
+ public function getFRR(): ?float
+ {
+ return $this->FRR;
+ }
+
+ public function getEER(): ?float
+ {
+ return $this->EER;
+ }
+
+ public function getFAAR(): ?float
+ {
+ return $this->FAAR;
+ }
+
+ public function getMaxReferenceDataSets(): ?int
+ {
+ return $this->maxReferenceDataSets;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ return new self(
+ $data['FAR'] ?? null,
+ $data['FRR'] ?? null,
+ $data['EER'] ?? null,
+ $data['FAAR'] ?? null,
+ $data['maxReferenceDataSets'] ?? null,
+ $data['maxRetries'] ?? null,
+ $data['blockSlowdown'] ?? null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'FAR' => $this->FAR,
+ 'FRR' => $this->FRR,
+ 'EER' => $this->EER,
+ 'FAAR' => $this->FAAR,
+ 'maxReferenceDataSets' => $this->maxReferenceDataSets,
+ 'maxRetries' => $this->getMaxRetries(),
+ 'blockSlowdown' => $this->getBlockSlowdown(),
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use JsonSerializable;
+
+class BiometricStatusReport implements JsonSerializable
+{
+ /**
+ * @var int
+ */
+ private $certLevel;
+
+ /**
+ * @var int
+ */
+ private $modality;
+
+ /**
+ * @var string|null
+ */
+ private $effectiveDate;
+
+ /**
+ * @var string|null
+ */
+ private $certificationDescriptor;
+
+ /**
+ * @var string|null
+ */
+ private $certificateNumber;
+
+ /**
+ * @var string|null
+ */
+ private $certificationPolicyVersion;
+
+ /**
+ * @var string|null
+ */
+ private $certificationRequirementsVersion;
+
+ public function getCertLevel(): int
+ {
+ return $this->certLevel;
+ }
+
+ public function getModality(): int
+ {
+ return $this->modality;
+ }
+
+ public function getEffectiveDate(): ?string
+ {
+ return $this->effectiveDate;
+ }
+
+ public function getCertificationDescriptor(): ?string
+ {
+ return $this->certificationDescriptor;
+ }
+
+ public function getCertificateNumber(): ?string
+ {
+ return $this->certificateNumber;
+ }
+
+ public function getCertificationPolicyVersion(): ?string
+ {
+ return $this->certificationPolicyVersion;
+ }
+
+ public function getCertificationRequirementsVersion(): ?string
+ {
+ return $this->certificationRequirementsVersion;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $object = new self();
+ $object->certLevel = $data['certLevel'] ?? null;
+ $object->modality = $data['modality'] ?? null;
+ $object->effectiveDate = $data['effectiveDate'] ?? null;
+ $object->certificationDescriptor = $data['certificationDescriptor'] ?? null;
+ $object->certificateNumber = $data['certificateNumber'] ?? null;
+ $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null;
+ $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null;
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'certLevel' => $this->certLevel,
+ 'modality' => $this->modality,
+ 'effectiveDate' => $this->effectiveDate,
+ 'certificationDescriptor' => $this->certificationDescriptor,
+ 'certificateNumber' => $this->certificateNumber,
+ 'certificationPolicyVersion' => $this->certificationPolicyVersion,
+ 'certificationRequirementsVersion' => $this->certificationRequirementsVersion,
+ ];
+
+ return array_filter($data, static function ($var): bool {return null !== $var; });
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+
+class CodeAccuracyDescriptor extends AbstractDescriptor
+{
+ /**
+ * @var int
+ */
+ private $base;
+
+ /**
+ * @var int
+ */
+ private $minLength;
+
+ public function __construct(int $base, int $minLength, ?int $maxRetries = null, ?int $blockSlowdown = null)
+ {
+ Assertion::greaterOrEqualThan($base, 0, Utils::logicException('Invalid data. The value of "base" must be a positive integer'));
+ Assertion::greaterOrEqualThan($minLength, 0, Utils::logicException('Invalid data. The value of "minLength" must be a positive integer'));
+ $this->base = $base;
+ $this->minLength = $minLength;
+ parent::__construct($maxRetries, $blockSlowdown);
+ }
+
+ public function getBase(): int
+ {
+ return $this->base;
+ }
+
+ public function getMinLength(): int
+ {
+ return $this->minLength;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ Assertion::keyExists($data, 'base', Utils::logicException('The parameter "base" is missing'));
+ Assertion::keyExists($data, 'minLength', Utils::logicException('The parameter "minLength" is missing'));
+
+ return new self(
+ $data['base'],
+ $data['minLength'],
+ $data['maxRetries'] ?? null,
+ $data['blockSlowdown'] ?? null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'base' => $this->base,
+ 'minLength' => $this->minLength,
+ 'maxRetries' => $this->getMaxRetries(),
+ 'blockSlowdown' => $this->getBlockSlowdown(),
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class DisplayPNGCharacteristicsDescriptor implements JsonSerializable
+{
+ /**
+ * @var int
+ */
+ private $width;
+
+ /**
+ * @var int
+ */
+ private $height;
+
+ /**
+ * @var int
+ */
+ private $bitDepth;
+
+ /**
+ * @var int
+ */
+ private $colorType;
+
+ /**
+ * @var int
+ */
+ private $compression;
+
+ /**
+ * @var int
+ */
+ private $filter;
+
+ /**
+ * @var int
+ */
+ private $interlace;
+
+ /**
+ * @var RgbPaletteEntry[]
+ */
+ private $plte = [];
+
+ public function __construct(int $width, int $height, int $bitDepth, int $colorType, int $compression, int $filter, int $interlace)
+ {
+ Assertion::greaterOrEqualThan($width, 0, Utils::logicException('Invalid width'));
+ Assertion::greaterOrEqualThan($height, 0, Utils::logicException('Invalid height'));
+ Assertion::range($bitDepth, 0, 254, Utils::logicException('Invalid bit depth'));
+ Assertion::range($colorType, 0, 254, Utils::logicException('Invalid color type'));
+ Assertion::range($compression, 0, 254, Utils::logicException('Invalid compression'));
+ Assertion::range($filter, 0, 254, Utils::logicException('Invalid filter'));
+ Assertion::range($interlace, 0, 254, Utils::logicException('Invalid interlace'));
+
+ $this->width = $width;
+ $this->height = $height;
+ $this->bitDepth = $bitDepth;
+ $this->colorType = $colorType;
+ $this->compression = $compression;
+ $this->filter = $filter;
+ $this->interlace = $interlace;
+ }
+
+ public function addPalette(RgbPaletteEntry $rgbPaletteEntry): self
+ {
+ $this->plte[] = $rgbPaletteEntry;
+
+ return $this;
+ }
+
+ public function getWidth(): int
+ {
+ return $this->width;
+ }
+
+ public function getHeight(): int
+ {
+ return $this->height;
+ }
+
+ public function getBitDepth(): int
+ {
+ return $this->bitDepth;
+ }
+
+ public function getColorType(): int
+ {
+ return $this->colorType;
+ }
+
+ public function getCompression(): int
+ {
+ return $this->compression;
+ }
+
+ public function getFilter(): int
+ {
+ return $this->filter;
+ }
+
+ public function getInterlace(): int
+ {
+ return $this->interlace;
+ }
+
+ /**
+ * @return RgbPaletteEntry[]
+ */
+ public function getPlte(): array
+ {
+ return $this->plte;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ foreach (['width', 'compression', 'height', 'bitDepth', 'colorType', 'compression', 'filter', 'interlace'] as $key) {
+ Assertion::keyExists($data, $key, sprintf('Invalid data. The key "%s" is missing', $key));
+ }
+ $object = new self(
+ $data['width'],
+ $data['height'],
+ $data['bitDepth'],
+ $data['colorType'],
+ $data['compression'],
+ $data['filter'],
+ $data['interlace']
+ );
+ if (isset($data['plte'])) {
+ $plte = $data['plte'];
+ Assertion::isArray($plte, Utils::logicException('Invalid "plte" parameter'));
+ foreach ($plte as $item) {
+ $object->addPalette(RgbPaletteEntry::createFromArray($item));
+ }
+ }
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'width' => $this->width,
+ 'height' => $this->height,
+ 'bitDepth' => $this->bitDepth,
+ 'colorType' => $this->colorType,
+ 'compression' => $this->compression,
+ 'filter' => $this->filter,
+ 'interlace' => $this->interlace,
+ 'plte' => $this->plte,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use function Safe\json_decode;
+use function Safe\sprintf;
+
+class DistantSingleMetadata extends SingleMetadata
+{
+ /**
+ * @var ClientInterface
+ */
+ private $httpClient;
+
+ /**
+ * @var RequestFactoryInterface
+ */
+ private $requestFactory;
+
+ /**
+ * @var array
+ */
+ private $additionalHeaders;
+
+ /**
+ * @var string
+ */
+ private $uri;
+
+ /**
+ * @var bool
+ */
+ private $isBase64Encoded;
+
+ public function __construct(string $uri, bool $isBase64Encoded, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalHeaders = [])
+ {
+ parent::__construct($uri, $isBase64Encoded); //Useless
+ $this->uri = $uri;
+ $this->isBase64Encoded = $isBase64Encoded;
+ $this->httpClient = $httpClient;
+ $this->requestFactory = $requestFactory;
+ $this->additionalHeaders = $additionalHeaders;
+ }
+
+ public function getMetadataStatement(): MetadataStatement
+ {
+ $payload = $this->fetch();
+ $json = $this->isBase64Encoded ? Base64Url::decode($payload) : $payload;
+ $data = json_decode($json, true);
+
+ return MetadataStatement::createFromArray($data);
+ }
+
+ private function fetch(): string
+ {
+ $request = $this->requestFactory->createRequest('GET', $this->uri);
+ foreach ($this->additionalHeaders as $k => $v) {
+ $request = $request->withHeader($k, $v);
+ }
+ $response = $this->httpClient->sendRequest($request);
+ Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
+ $content = $response->getBody()->getContents();
+ Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
+
+ return $content;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class EcdaaTrustAnchor implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ private $X;
+
+ /**
+ * @var string
+ */
+ private $Y;
+
+ /**
+ * @var string
+ */
+ private $c;
+
+ /**
+ * @var string
+ */
+ private $sx;
+
+ /**
+ * @var string
+ */
+ private $sy;
+
+ /**
+ * @var string
+ */
+ private $G1Curve;
+
+ public function __construct(string $X, string $Y, string $c, string $sx, string $sy, string $G1Curve)
+ {
+ $this->X = $X;
+ $this->Y = $Y;
+ $this->c = $c;
+ $this->sx = $sx;
+ $this->sy = $sy;
+ $this->G1Curve = $G1Curve;
+ }
+
+ public function getX(): string
+ {
+ return $this->X;
+ }
+
+ public function getY(): string
+ {
+ return $this->Y;
+ }
+
+ public function getC(): string
+ {
+ return $this->c;
+ }
+
+ public function getSx(): string
+ {
+ return $this->sx;
+ }
+
+ public function getSy(): string
+ {
+ return $this->sy;
+ }
+
+ public function getG1Curve(): string
+ {
+ return $this->G1Curve;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ foreach (['X', 'Y', 'c', 'sx', 'sy', 'G1Curve'] as $key) {
+ Assertion::keyExists($data, $key, sprintf('Invalid data. The key "%s" is missing', $key));
+ }
+
+ return new self(
+ Base64Url::decode($data['X']),
+ Base64Url::decode($data['Y']),
+ Base64Url::decode($data['c']),
+ Base64Url::decode($data['sx']),
+ Base64Url::decode($data['sy']),
+ $data['G1Curve']
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'X' => Base64Url::encode($this->X),
+ 'Y' => Base64Url::encode($this->Y),
+ 'c' => Base64Url::encode($this->c),
+ 'sx' => Base64Url::encode($this->sx),
+ 'sy' => Base64Url::encode($this->sy),
+ 'G1Curve' => $this->G1Curve,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use function array_key_exists;
+use Assert\Assertion;
+use JsonSerializable;
+
+class ExtensionDescriptor implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ private $id;
+
+ /**
+ * @var int|null
+ */
+ private $tag;
+
+ /**
+ * @var string|null
+ */
+ private $data;
+
+ /**
+ * @var bool
+ */
+ private $fail_if_unknown;
+
+ public function __construct(string $id, ?int $tag, ?string $data, bool $fail_if_unknown)
+ {
+ if (null !== $tag) {
+ Assertion::greaterOrEqualThan($tag, 0, Utils::logicException('Invalid data. The parameter "tag" shall be a positive integer'));
+ }
+ $this->id = $id;
+ $this->tag = $tag;
+ $this->data = $data;
+ $this->fail_if_unknown = $fail_if_unknown;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getTag(): ?int
+ {
+ return $this->tag;
+ }
+
+ public function getData(): ?string
+ {
+ return $this->data;
+ }
+
+ public function isFailIfUnknown(): bool
+ {
+ return $this->fail_if_unknown;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ Assertion::keyExists($data, 'id', Utils::logicException('Invalid data. The parameter "id" is missing'));
+ Assertion::string($data['id'], Utils::logicException('Invalid data. The parameter "id" shall be a string'));
+ Assertion::keyExists($data, 'fail_if_unknown', Utils::logicException('Invalid data. The parameter "fail_if_unknown" is missing'));
+ Assertion::boolean($data['fail_if_unknown'], Utils::logicException('Invalid data. The parameter "fail_if_unknown" shall be a boolean'));
+ if (array_key_exists('tag', $data)) {
+ Assertion::integer($data['tag'], Utils::logicException('Invalid data. The parameter "tag" shall be a positive integer'));
+ }
+ if (array_key_exists('data', $data)) {
+ Assertion::string($data['data'], Utils::logicException('Invalid data. The parameter "data" shall be a string'));
+ }
+
+ return new self(
+ $data['id'],
+ $data['tag'] ?? null,
+ $data['data'] ?? null,
+ $data['fail_if_unknown']
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $result = [
+ 'id' => $this->id,
+ 'tag' => $this->tag,
+ 'data' => $this->data,
+ 'fail_if_unknown' => $this->fail_if_unknown,
+ ];
+
+ return Utils::filterNullValues($result);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function count;
+use InvalidArgumentException;
+use function is_array;
+use Jose\Component\KeyManagement\JWKFactory;
+use Jose\Component\Signature\Algorithm\ES256;
+use Jose\Component\Signature\Serializer\CompactSerializer;
+use League\Uri\UriString;
+use LogicException;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use function Safe\json_decode;
+use function Safe\sprintf;
+use Throwable;
+use Webauthn\CertificateToolbox;
+
+class MetadataService
+{
+ /**
+ * @var ClientInterface
+ */
+ private $httpClient;
+
+ /**
+ * @var RequestFactoryInterface
+ */
+ private $requestFactory;
+
+ /**
+ * @var array
+ */
+ private $additionalQueryStringValues;
+
+ /**
+ * @var array
+ */
+ private $additionalHeaders;
+
+ /**
+ * @var string
+ */
+ private $serviceUri;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(string $serviceUri, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalQueryStringValues = [], array $additionalHeaders = [], ?LoggerInterface $logger = null)
+ {
+ if (0 !== count($additionalQueryStringValues)) {
+ @trigger_error('The argument "additionalQueryStringValues" is deprecated since version 3.3 and will be removed in 4.0. Please set an empty array instead and us the method `addQueryStringValues`.', E_USER_DEPRECATED);
+ }
+ if (0 !== count($additionalQueryStringValues)) {
+ @trigger_error('The argument "additionalHeaders" is deprecated since version 3.3 and will be removed in 4.0. Please set an empty array instead and us the method `addHeaders`.', E_USER_DEPRECATED);
+ }
+ if (null !== $logger) {
+ @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger" instead.', E_USER_DEPRECATED);
+ }
+ $this->serviceUri = $serviceUri;
+ $this->httpClient = $httpClient;
+ $this->requestFactory = $requestFactory;
+ $this->additionalQueryStringValues = $additionalQueryStringValues;
+ $this->additionalHeaders = $additionalHeaders;
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public function addQueryStringValues(array $additionalQueryStringValues): self
+ {
+ $this->additionalQueryStringValues = $additionalQueryStringValues;
+
+ return $this;
+ }
+
+ public function addHeaders(array $additionalHeaders): self
+ {
+ $this->additionalHeaders = $additionalHeaders;
+
+ return $this;
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ public function has(string $aaguid): bool
+ {
+ try {
+ $toc = $this->fetchMetadataTOCPayload();
+ } catch (Throwable $e) {
+ return false;
+ }
+ foreach ($toc->getEntries() as $entry) {
+ if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function get(string $aaguid): MetadataStatement
+ {
+ $toc = $this->fetchMetadataTOCPayload();
+ foreach ($toc->getEntries() as $entry) {
+ if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) {
+ $mds = $this->fetchMetadataStatementFor($entry);
+ $mds
+ ->setStatusReports($entry->getStatusReports())
+ ->setRootCertificates($toc->getRootCertificates())
+ ;
+
+ return $mds;
+ }
+ }
+
+ throw new InvalidArgumentException(sprintf('The Metadata Statement with AAGUID "%s" is missing', $aaguid));
+ }
+
+ /**
+ * @deprecated This method is deprecated since v3.3 and will be removed in v4.0
+ */
+ public function getMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement
+ {
+ return $this->fetchMetadataStatementFor($entry, $hashingFunction);
+ }
+
+ public function fetchMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement
+ {
+ $this->logger->info('Trying to get the metadata statement for a given entry', ['entry' => $entry]);
+ try {
+ $hash = $entry->getHash();
+ $url = $entry->getUrl();
+ if (null === $hash || null === $url) {
+ throw new LogicException('The Metadata Statement has not been published');
+ }
+ $uri = $this->buildUri($url);
+ $result = $this->fetchMetadataStatement($uri, true, $hash, $hashingFunction);
+ $this->logger->info('The metadata statement exists');
+ $this->logger->debug('Metadata Statement', ['mds' => $result]);
+
+ return $result;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ /**
+ * @deprecated This method is deprecated since v3.3 and will be removed in v4.0
+ */
+ public function getMetadataTOCPayload(): MetadataTOCPayload
+ {
+ return $this->fetchMetadataTOCPayload();
+ }
+
+ private function fetchMetadataTOCPayload(): MetadataTOCPayload
+ {
+ $this->logger->info('Trying to get the metadata service TOC payload');
+ try {
+ $uri = $this->buildUri($this->serviceUri);
+ $toc = $this->fetchTableOfContent($uri);
+ $this->logger->info('The TOC payload has been received');
+ $this->logger->debug('TOC payload', ['toc' => $toc]);
+
+ return $toc;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ private function buildUri(string $uri): string
+ {
+ $parsedUri = UriString::parse($uri);
+ $queryString = $parsedUri['query'];
+ $query = [];
+ if (null !== $queryString) {
+ parse_str($queryString, $query);
+ }
+ foreach ($this->additionalQueryStringValues as $k => $v) {
+ if (!isset($query[$k])) {
+ $query[$k] = $v;
+ continue;
+ }
+ if (!is_array($query[$k])) {
+ $query[$k] = [$query[$k], $v];
+ continue;
+ }
+ $query[$k][] = $v;
+ }
+ $parsedUri['query'] = 0 === count($query) ? null : http_build_query($query, '', '&', PHP_QUERY_RFC3986);
+
+ return UriString::build($parsedUri);
+ }
+
+ private function fetchTableOfContent(string $uri): MetadataTOCPayload
+ {
+ $content = $this->fetch($uri);
+ $rootCertificates = [];
+ $payload = $this->getJwsPayload($content, $rootCertificates);
+ $data = json_decode($payload, true);
+
+ $toc = MetadataTOCPayload::createFromArray($data);
+ $toc->setRootCertificates($rootCertificates);
+
+ return $toc;
+ }
+
+ private function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, string $hash = '', string $hashingFunction = 'sha256'): MetadataStatement
+ {
+ $payload = $this->fetch($uri);
+ if ('' !== $hash) {
+ Assertion::true(hash_equals($hash, hash($hashingFunction, $payload, true)), 'The hash cannot be verified. The metadata statement shall be rejected');
+ }
+ $json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload;
+ $data = json_decode($json, true);
+
+ return MetadataStatement::createFromArray($data);
+ }
+
+ private function fetch(string $uri): string
+ {
+ $request = $this->requestFactory->createRequest('GET', $uri);
+ foreach ($this->additionalHeaders as $k => $v) {
+ $request = $request->withHeader($k, $v);
+ }
+ $response = $this->httpClient->sendRequest($request);
+ Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
+ $content = $response->getBody()->getContents();
+ Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
+
+ return $content;
+ }
+
+ private function getJwsPayload(string $token, array &$rootCertificates): string
+ {
+ $jws = (new CompactSerializer())->unserialize($token);
+ Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.');
+ $signature = $jws->getSignature(0);
+ $payload = $jws->getPayload();
+ Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.');
+ $header = $signature->getProtectedHeader();
+ Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.');
+ Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".');
+ Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.');
+ Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.');
+ $key = JWKFactory::createFromX5C($header['x5c']);
+ $rootCertificates = array_map(static function (string $x509): string {
+ return CertificateToolbox::fixPEMStructure($x509);
+ }, $header['x5c']);
+ $algorithm = new ES256();
+ $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature());
+ Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.');
+
+ return $jws->getPayload();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use InvalidArgumentException;
+use JsonSerializable;
+use function Safe\json_decode;
+use function Safe\sprintf;
+
+class MetadataStatement implements JsonSerializable
+{
+ public const KEY_PROTECTION_SOFTWARE = 0x0001;
+ public const KEY_PROTECTION_HARDWARE = 0x0002;
+ public const KEY_PROTECTION_TEE = 0x0004;
+ public const KEY_PROTECTION_SECURE_ELEMENT = 0x0008;
+ public const KEY_PROTECTION_REMOTE_HANDLE = 0x0010;
+
+ public const MATCHER_PROTECTION_SOFTWARE = 0x0001;
+ public const MATCHER_PROTECTION_TEE = 0x0002;
+ public const MATCHER_PROTECTION_ON_CHIP = 0x0004;
+
+ public const ATTACHMENT_HINT_INTERNAL = 0x0001;
+ public const ATTACHMENT_HINT_EXTERNAL = 0x0002;
+ public const ATTACHMENT_HINT_WIRED = 0x0004;
+ public const ATTACHMENT_HINT_WIRELESS = 0x0008;
+ public const ATTACHMENT_HINT_NFC = 0x0010;
+ public const ATTACHMENT_HINT_BLUETOOTH = 0x0020;
+ public const ATTACHMENT_HINT_NETWORK = 0x0040;
+ public const ATTACHMENT_HINT_READY = 0x0080;
+ public const ATTACHMENT_HINT_WIFI_DIRECT = 0x0100;
+
+ public const TRANSACTION_CONFIRMATION_DISPLAY_ANY = 0x0001;
+ public const TRANSACTION_CONFIRMATION_DISPLAY_PRIVILEGED_SOFTWARE = 0x0002;
+ public const TRANSACTION_CONFIRMATION_DISPLAY_TEE = 0x0004;
+ public const TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE = 0x0008;
+ public const TRANSACTION_CONFIRMATION_DISPLAY_REMOTE = 0x0010;
+
+ public const ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW = 0x0001;
+ public const ALG_SIGN_SECP256R1_ECDSA_SHA256_DER = 0x0002;
+ public const ALG_SIGN_RSASSA_PSS_SHA256_RAW = 0x0003;
+ public const ALG_SIGN_RSASSA_PSS_SHA256_DER = 0x0004;
+ public const ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW = 0x0005;
+ public const ALG_SIGN_SECP256K1_ECDSA_SHA256_DER = 0x0006;
+ public const ALG_SIGN_SM2_SM3_RAW = 0x0007;
+ public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW = 0x0008;
+ public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER = 0x0009;
+ public const ALG_SIGN_RSASSA_PSS_SHA384_RAW = 0x000A;
+ public const ALG_SIGN_RSASSA_PSS_SHA512_RAW = 0x000B;
+ public const ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW = 0x000C;
+ public const ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW = 0x000D;
+ public const ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW = 0x000E;
+ public const ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW = 0x000F;
+ public const ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW = 0x0010;
+ public const ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW = 0x0011;
+ public const ALG_SIGN_ED25519_EDDSA_SHA256_RAW = 0x0012;
+
+ public const ALG_KEY_ECC_X962_RAW = 0x0100;
+ public const ALG_KEY_ECC_X962_DER = 0x0101;
+ public const ALG_KEY_RSA_2048_RAW = 0x0102;
+ public const ALG_KEY_RSA_2048_DER = 0x0103;
+ public const ALG_KEY_COSE = 0x0104;
+
+ public const ATTESTATION_BASIC_FULL = 0x3E07;
+ public const ATTESTATION_BASIC_SURROGATE = 0x3E08;
+ public const ATTESTATION_ECDAA = 0x3E09;
+ public const ATTESTATION_ATTCA = 0x3E0A;
+
+ /**
+ * @var string|null
+ */
+ private $legalHeader;
+
+ /**
+ * @var string|null
+ */
+ private $aaid;
+
+ /**
+ * @var string|null
+ */
+ private $aaguid;
+ /**
+ * @var string[]
+ */
+ private $attestationCertificateKeyIdentifiers = [];
+
+ /**
+ * @var string
+ */
+ private $description;
+
+ /**
+ * @var string[]
+ */
+ private $alternativeDescriptions = [];
+
+ /**
+ * @var int
+ */
+ private $authenticatorVersion;
+
+ /**
+ * @var string
+ */
+ private $protocolFamily;
+
+ /**
+ * @var Version[]
+ */
+ private $upv = [];
+
+ /**
+ * @var string|null
+ */
+ private $assertionScheme;
+
+ /**
+ * @var int|null
+ */
+ private $authenticationAlgorithm;
+
+ /**
+ * @var int[]
+ */
+ private $authenticationAlgorithms = [];
+
+ /**
+ * @var int|null
+ */
+ private $publicKeyAlgAndEncoding;
+
+ /**
+ * @var int[]
+ */
+ private $publicKeyAlgAndEncodings = [];
+
+ /**
+ * @var int[]
+ */
+ private $attestationTypes = [];
+
+ /**
+ * @var VerificationMethodANDCombinations[]
+ */
+ private $userVerificationDetails = [];
+
+ /**
+ * @var int
+ */
+ private $keyProtection;
+
+ /**
+ * @var bool|null
+ */
+ private $isKeyRestricted;
+
+ /**
+ * @var bool|null
+ */
+ private $isFreshUserVerificationRequired;
+
+ /**
+ * @var int
+ */
+ private $matcherProtection;
+
+ /**
+ * @var int|null
+ */
+ private $cryptoStrength;
+
+ /**
+ * @var string|null
+ */
+ private $operatingEnv;
+
+ /**
+ * @var int
+ */
+ private $attachmentHint = 0;
+
+ /**
+ * @var bool|null
+ */
+ private $isSecondFactorOnly;
+
+ /**
+ * @var int
+ */
+ private $tcDisplay;
+
+ /**
+ * @var string|null
+ */
+ private $tcDisplayContentType;
+
+ /**
+ * @var DisplayPNGCharacteristicsDescriptor[]
+ */
+ private $tcDisplayPNGCharacteristics = [];
+
+ /**
+ * @var string[]
+ */
+ private $attestationRootCertificates = [];
+
+ /**
+ * @var EcdaaTrustAnchor[]
+ */
+ private $ecdaaTrustAnchors = [];
+
+ /**
+ * @var string|null
+ */
+ private $icon;
+
+ /**
+ * @var ExtensionDescriptor[]
+ */
+ private $supportedExtensions = [];
+
+ /**
+ * @var array<int, StatusReport>
+ */
+ private $statusReports = [];
+
+ /**
+ * @var string[]
+ */
+ private $rootCertificates = [];
+
+ public static function createFromString(string $statement): self
+ {
+ $data = json_decode($statement, true);
+ Assertion::isArray($data, 'Invalid Metadata Statement');
+
+ return self::createFromArray($data);
+ }
+
+ public function getLegalHeader(): ?string
+ {
+ return $this->legalHeader;
+ }
+
+ public function getAaid(): ?string
+ {
+ return $this->aaid;
+ }
+
+ public function getAaguid(): ?string
+ {
+ return $this->aaguid;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAttestationCertificateKeyIdentifiers(): array
+ {
+ return $this->attestationCertificateKeyIdentifiers;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAlternativeDescriptions(): array
+ {
+ return $this->alternativeDescriptions;
+ }
+
+ public function getAuthenticatorVersion(): int
+ {
+ return $this->authenticatorVersion;
+ }
+
+ public function getProtocolFamily(): string
+ {
+ return $this->protocolFamily;
+ }
+
+ /**
+ * @return Version[]
+ */
+ public function getUpv(): array
+ {
+ return $this->upv;
+ }
+
+ public function getAssertionScheme(): ?string
+ {
+ return $this->assertionScheme;
+ }
+
+ public function getAuthenticationAlgorithm(): ?int
+ {
+ return $this->authenticationAlgorithm;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getAuthenticationAlgorithms(): array
+ {
+ return $this->authenticationAlgorithms;
+ }
+
+ public function getPublicKeyAlgAndEncoding(): ?int
+ {
+ return $this->publicKeyAlgAndEncoding;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getPublicKeyAlgAndEncodings(): array
+ {
+ return $this->publicKeyAlgAndEncodings;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getAttestationTypes(): array
+ {
+ return $this->attestationTypes;
+ }
+
+ /**
+ * @return VerificationMethodANDCombinations[]
+ */
+ public function getUserVerificationDetails(): array
+ {
+ return $this->userVerificationDetails;
+ }
+
+ public function getKeyProtection(): int
+ {
+ return $this->keyProtection;
+ }
+
+ public function isKeyRestricted(): ?bool
+ {
+ return (bool) $this->isKeyRestricted;
+ }
+
+ public function isFreshUserVerificationRequired(): ?bool
+ {
+ return (bool) $this->isFreshUserVerificationRequired;
+ }
+
+ public function getMatcherProtection(): int
+ {
+ return $this->matcherProtection;
+ }
+
+ public function getCryptoStrength(): ?int
+ {
+ return $this->cryptoStrength;
+ }
+
+ public function getOperatingEnv(): ?string
+ {
+ return $this->operatingEnv;
+ }
+
+ public function getAttachmentHint(): int
+ {
+ return $this->attachmentHint;
+ }
+
+ public function isSecondFactorOnly(): ?bool
+ {
+ return (bool) $this->isSecondFactorOnly;
+ }
+
+ public function getTcDisplay(): int
+ {
+ return $this->tcDisplay;
+ }
+
+ public function getTcDisplayContentType(): ?string
+ {
+ return $this->tcDisplayContentType;
+ }
+
+ /**
+ * @return DisplayPNGCharacteristicsDescriptor[]
+ */
+ public function getTcDisplayPNGCharacteristics(): array
+ {
+ return $this->tcDisplayPNGCharacteristics;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAttestationRootCertificates(): array
+ {
+ return $this->attestationRootCertificates;
+ }
+
+ /**
+ * @return EcdaaTrustAnchor[]
+ */
+ public function getEcdaaTrustAnchors(): array
+ {
+ return $this->ecdaaTrustAnchors;
+ }
+
+ public function getIcon(): ?string
+ {
+ return $this->icon;
+ }
+
+ /**
+ * @return ExtensionDescriptor[]
+ */
+ public function getSupportedExtensions(): array
+ {
+ return $this->supportedExtensions;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $object = new self();
+ foreach (['description', 'protocolFamily'] as $key) {
+ if (!isset($data[$key])) {
+ throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key));
+ }
+ }
+ $object->legalHeader = $data['legalHeader'] ?? null;
+ $object->aaid = $data['aaid'] ?? null;
+ $object->aaguid = $data['aaguid'] ?? null;
+ $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? [];
+ $object->description = $data['description'];
+ $object->alternativeDescriptions = $data['alternativeDescriptions'] ?? [];
+ $object->authenticatorVersion = $data['authenticatorVersion'] ?? 0;
+ $object->protocolFamily = $data['protocolFamily'];
+ if (isset($data['upv'])) {
+ $upv = $data['upv'];
+ Assertion::isArray($upv, 'Invalid Metadata Statement');
+ foreach ($upv as $value) {
+ Assertion::isArray($value, 'Invalid Metadata Statement');
+ $object->upv[] = Version::createFromArray($value);
+ }
+ }
+ $object->assertionScheme = $data['assertionScheme'] ?? null;
+ $object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null;
+ $object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? [];
+ $object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null;
+ $object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? [];
+ $object->attestationTypes = $data['attestationTypes'] ?? [];
+ if (isset($data['userVerificationDetails'])) {
+ $userVerificationDetails = $data['userVerificationDetails'];
+ Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement');
+ foreach ($userVerificationDetails as $value) {
+ Assertion::isArray($value, 'Invalid Metadata Statement');
+ $object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value);
+ }
+ }
+ $object->keyProtection = $data['keyProtection'] ?? 0;
+ $object->isKeyRestricted = $data['isKeyRestricted'] ?? null;
+ $object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null;
+ $object->matcherProtection = $data['matcherProtection'] ?? 0;
+ $object->cryptoStrength = $data['cryptoStrength'] ?? null;
+ $object->operatingEnv = $data['operatingEnv'] ?? null;
+ $object->attachmentHint = $data['attachmentHint'] ?? 0;
+ $object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null;
+ $object->tcDisplay = $data['tcDisplay'] ?? 0;
+ $object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null;
+ if (isset($data['tcDisplayPNGCharacteristics'])) {
+ $tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics'];
+ Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement');
+ foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) {
+ Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement');
+ $object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic);
+ }
+ }
+ $object->attestationRootCertificates = $data['attestationRootCertificates'] ?? [];
+ $object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? [];
+ $object->icon = $data['icon'] ?? null;
+ if (isset($data['supportedExtensions'])) {
+ $supportedExtensions = $data['supportedExtensions'];
+ Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement');
+ foreach ($supportedExtensions as $supportedExtension) {
+ Assertion::isArray($supportedExtension, 'Invalid Metadata Statement');
+ $object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension);
+ }
+ }
+ $object->rootCertificates = $data['rootCertificates'] ?? [];
+ if (isset($data['statusReports'])) {
+ $reports = $data['statusReports'];
+ Assertion::isArray($reports, 'Invalid Metadata Statement');
+ foreach ($reports as $report) {
+ Assertion::isArray($report, 'Invalid Metadata Statement');
+ $object->statusReports[] = StatusReport::createFromArray($report);
+ }
+ }
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'legalHeader' => $this->legalHeader,
+ 'aaid' => $this->aaid,
+ 'aaguid' => $this->aaguid,
+ 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
+ 'description' => $this->description,
+ 'alternativeDescriptions' => $this->alternativeDescriptions,
+ 'authenticatorVersion' => $this->authenticatorVersion,
+ 'protocolFamily' => $this->protocolFamily,
+ 'upv' => $this->upv,
+ 'assertionScheme' => $this->assertionScheme,
+ 'authenticationAlgorithm' => $this->authenticationAlgorithm,
+ 'authenticationAlgorithms' => $this->authenticationAlgorithms,
+ 'publicKeyAlgAndEncoding' => $this->publicKeyAlgAndEncoding,
+ 'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings,
+ 'attestationTypes' => $this->attestationTypes,
+ 'userVerificationDetails' => $this->userVerificationDetails,
+ 'keyProtection' => $this->keyProtection,
+ 'isKeyRestricted' => $this->isKeyRestricted,
+ 'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired,
+ 'matcherProtection' => $this->matcherProtection,
+ 'cryptoStrength' => $this->cryptoStrength,
+ 'operatingEnv' => $this->operatingEnv,
+ 'attachmentHint' => $this->attachmentHint,
+ 'isSecondFactorOnly' => $this->isSecondFactorOnly,
+ 'tcDisplay' => $this->tcDisplay,
+ 'tcDisplayContentType' => $this->tcDisplayContentType,
+ 'tcDisplayPNGCharacteristics' => array_map(static function (DisplayPNGCharacteristicsDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->tcDisplayPNGCharacteristics),
+ 'attestationRootCertificates' => $this->attestationRootCertificates,
+ 'ecdaaTrustAnchors' => array_map(static function (EcdaaTrustAnchor $object): array {
+ return $object->jsonSerialize();
+ }, $this->ecdaaTrustAnchors),
+ 'icon' => $this->icon,
+ 'supportedExtensions' => array_map(static function (ExtensionDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->supportedExtensions),
+ 'rootCertificates' => $this->rootCertificates,
+ 'statusReports' => $this->statusReports,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+
+ /**
+ * @return StatusReport[]
+ */
+ public function getStatusReports(): array
+ {
+ return $this->statusReports;
+ }
+
+ /**
+ * @param StatusReport[] $statusReports
+ */
+ public function setStatusReports(array $statusReports): self
+ {
+ $this->statusReports = $statusReports;
+
+ return $this;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getRootCertificates(): array
+ {
+ return $this->rootCertificates;
+ }
+
+ /**
+ * @param string[] $rootCertificates
+ */
+ public function setRootCertificates(array $rootCertificates): self
+ {
+ $this->rootCertificates = $rootCertificates;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use Jose\Component\KeyManagement\JWKFactory;
+use Jose\Component\Signature\Algorithm\ES256;
+use Jose\Component\Signature\Serializer\CompactSerializer;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use function Safe\json_decode;
+use function Safe\sprintf;
+
+/**
+ * @deprecated This class is deprecated since v3.3 and will be removed in v4.0
+ */
+class MetadataStatementFetcher
+{
+ public static function fetchTableOfContent(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): MetadataTOCPayload
+ {
+ $content = self::fetch($uri, $client, $requestFactory, $additionalHeaders);
+ $payload = self::getJwsPayload($content);
+ $data = json_decode($payload, true);
+
+ return MetadataTOCPayload::createFromArray($data);
+ }
+
+ public static function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = [], string $hash = '', string $hashingFunction = 'sha256'): MetadataStatement
+ {
+ $payload = self::fetch($uri, $client, $requestFactory, $additionalHeaders);
+ if ('' !== $hash) {
+ Assertion::true(hash_equals($hash, hash($hashingFunction, $payload, true)), 'The hash cannot be verified. The metadata statement shall be rejected');
+ }
+ $json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload;
+ $data = json_decode($json, true);
+
+ return MetadataStatement::createFromArray($data);
+ }
+
+ private static function fetch(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): string
+ {
+ $request = $requestFactory->createRequest('GET', $uri);
+ foreach ($additionalHeaders as $k => $v) {
+ $request = $request->withHeader($k, $v);
+ }
+ $response = $client->sendRequest($request);
+ Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
+ $content = $response->getBody()->getContents();
+ Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
+
+ return $content;
+ }
+
+ private static function getJwsPayload(string $token): string
+ {
+ $jws = (new CompactSerializer())->unserialize($token);
+ Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.');
+ $signature = $jws->getSignature(0);
+ $payload = $jws->getPayload();
+ Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.');
+ $header = $signature->getProtectedHeader();
+ Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.');
+ Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".');
+ Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.');
+ Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.');
+ $key = JWKFactory::createFromX5C($header['x5c']);
+ $algorithm = new ES256();
+ $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature());
+ Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.');
+
+ return $jws->getPayload();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+interface MetadataStatementRepository
+{
+ public function findOneByAAGUID(string $aaguid): ?MetadataStatement;
+
+ /**
+ * @deprecated This method is deprecated since v3.3 and will be removed in v4.0. Please use the method "getStatusReports()" provided by the MetadataStatement object
+ *
+ * @return StatusReport[]
+ */
+ public function findStatusReportsByAAGUID(string $aaguid): array;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use function array_key_exists;
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class MetadataTOCPayload implements JsonSerializable
+{
+ /**
+ * @var string|null
+ */
+ private $legalHeader;
+
+ /**
+ * @var int
+ */
+ private $no;
+
+ /**
+ * @var string
+ */
+ private $nextUpdate;
+
+ /**
+ * @var MetadataTOCPayloadEntry[]
+ */
+ private $entries = [];
+
+ /**
+ * @var string[]
+ */
+ private $rootCertificates;
+
+ public function __construct(int $no, string $nextUpdate, ?string $legalHeader = null)
+ {
+ $this->no = $no;
+ $this->nextUpdate = $nextUpdate;
+ $this->legalHeader = $legalHeader;
+ }
+
+ public function addEntry(MetadataTOCPayloadEntry $entry): self
+ {
+ $this->entries[] = $entry;
+
+ return $this;
+ }
+
+ public function getLegalHeader(): ?string
+ {
+ return $this->legalHeader;
+ }
+
+ public function getNo(): int
+ {
+ return $this->no;
+ }
+
+ public function getNextUpdate(): string
+ {
+ return $this->nextUpdate;
+ }
+
+ /**
+ * @return MetadataTOCPayloadEntry[]
+ */
+ public function getEntries(): array
+ {
+ return $this->entries;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ foreach (['no', 'nextUpdate', 'entries'] as $key) {
+ Assertion::keyExists($data, $key, Utils::logicException(sprintf('Invalid data. The parameter "%s" is missing', $key)));
+ }
+ Assertion::integer($data['no'], Utils::logicException('Invalid data. The parameter "no" shall be an integer'));
+ Assertion::string($data['nextUpdate'], Utils::logicException('Invalid data. The parameter "nextUpdate" shall be a string'));
+ Assertion::isArray($data['entries'], Utils::logicException('Invalid data. The parameter "entries" shall be a n array of entries'));
+ if (array_key_exists('legalHeader', $data)) {
+ Assertion::string($data['legalHeader'], Utils::logicException('Invalid data. The parameter "legalHeader" shall be a string'));
+ }
+ $object = new self(
+ $data['no'],
+ $data['nextUpdate'],
+ $data['legalHeader'] ?? null
+ );
+ foreach ($data['entries'] as $k => $entry) {
+ $object->addEntry(MetadataTOCPayloadEntry::createFromArray($entry));
+ }
+ $object->rootCertificates = $data['rootCertificates'] ?? [];
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'legalHeader' => $this->legalHeader,
+ 'nextUpdate' => $this->nextUpdate,
+ 'no' => $this->no,
+ 'entries' => array_map(static function (MetadataTOCPayloadEntry $object): array {
+ return $object->jsonSerialize();
+ }, $this->entries),
+ 'rootCertificates' => $this->rootCertificates,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getRootCertificates(): array
+ {
+ return $this->rootCertificates;
+ }
+
+ /**
+ * @param string[] $rootCertificates
+ */
+ public function setRootCertificates(array $rootCertificates): self
+ {
+ $this->rootCertificates = $rootCertificates;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function count;
+use JsonSerializable;
+use LogicException;
+
+class MetadataTOCPayloadEntry implements JsonSerializable
+{
+ /**
+ * @var string|null
+ */
+ private $aaid;
+
+ /**
+ * @var string|null
+ */
+ private $aaguid;
+
+ /**
+ * @var string[]
+ */
+ private $attestationCertificateKeyIdentifiers = [];
+
+ /**
+ * @var string|null
+ */
+ private $hash;
+
+ /**
+ * @var string|null
+ */
+ private $url;
+
+ /**
+ * @var StatusReport[]
+ */
+ private $statusReports = [];
+
+ /**
+ * @var string
+ */
+ private $timeOfLastStatusChange;
+
+ /**
+ * @var string
+ */
+ private $rogueListURL;
+
+ /**
+ * @var string
+ */
+ private $rogueListHash;
+
+ public function __construct(?string $aaid, ?string $aaguid, array $attestationCertificateKeyIdentifiers, ?string $hash, ?string $url, string $timeOfLastStatusChange, ?string $rogueListURL, ?string $rogueListHash)
+ {
+ if (null !== $aaid && null !== $aaguid) {
+ throw new LogicException('Authenticators cannot support both AAID and AAGUID');
+ }
+ if (null === $aaid && null === $aaguid && 0 === count($attestationCertificateKeyIdentifiers)) {
+ throw new LogicException('If neither AAID nor AAGUID are set, the attestation certificate identifier list shall not be empty');
+ }
+ foreach ($attestationCertificateKeyIdentifiers as $attestationCertificateKeyIdentifier) {
+ Assertion::string($attestationCertificateKeyIdentifier, Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
+ Assertion::notEmpty($attestationCertificateKeyIdentifier, Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
+ Assertion::regex($attestationCertificateKeyIdentifier, '/^[0-9a-f]+$/', Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
+ }
+ $this->aaid = $aaid;
+ $this->aaguid = $aaguid;
+ $this->attestationCertificateKeyIdentifiers = $attestationCertificateKeyIdentifiers;
+ $this->hash = Base64Url::decode($hash);
+ $this->url = $url;
+ $this->timeOfLastStatusChange = $timeOfLastStatusChange;
+ $this->rogueListURL = $rogueListURL;
+ $this->rogueListHash = $rogueListHash;
+ }
+
+ public function getAaid(): ?string
+ {
+ return $this->aaid;
+ }
+
+ public function getAaguid(): ?string
+ {
+ return $this->aaguid;
+ }
+
+ public function getAttestationCertificateKeyIdentifiers(): array
+ {
+ return $this->attestationCertificateKeyIdentifiers;
+ }
+
+ public function getHash(): ?string
+ {
+ return $this->hash;
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
+ public function addStatusReports(StatusReport $statusReport): self
+ {
+ $this->statusReports[] = $statusReport;
+
+ return $this;
+ }
+
+ /**
+ * @return StatusReport[]
+ */
+ public function getStatusReports(): array
+ {
+ return $this->statusReports;
+ }
+
+ public function getTimeOfLastStatusChange(): string
+ {
+ return $this->timeOfLastStatusChange;
+ }
+
+ public function getRogueListURL(): string
+ {
+ return $this->rogueListURL;
+ }
+
+ public function getRogueListHash(): string
+ {
+ return $this->rogueListHash;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ Assertion::keyExists($data, 'timeOfLastStatusChange', Utils::logicException('Invalid data. The parameter "timeOfLastStatusChange" is missing'));
+ Assertion::keyExists($data, 'statusReports', Utils::logicException('Invalid data. The parameter "statusReports" is missing'));
+ Assertion::isArray($data['statusReports'], Utils::logicException('Invalid data. The parameter "statusReports" shall be an array of StatusReport objects'));
+ $object = new self(
+ $data['aaid'] ?? null,
+ $data['aaguid'] ?? null,
+ $data['attestationCertificateKeyIdentifiers'] ?? [],
+ $data['hash'] ?? null,
+ $data['url'] ?? null,
+ $data['timeOfLastStatusChange'],
+ $data['rogueListURL'] ?? null,
+ $data['rogueListHash'] ?? null
+ );
+ foreach ($data['statusReports'] as $statusReport) {
+ $object->addStatusReports(StatusReport::createFromArray($statusReport));
+ }
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'aaid' => $this->aaid,
+ 'aaguid' => $this->aaguid,
+ 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
+ 'hash' => Base64Url::encode($this->hash),
+ 'url' => $this->url,
+ 'statusReports' => array_map(static function (StatusReport $object): array {
+ return $object->jsonSerialize();
+ }, $this->statusReports),
+ 'timeOfLastStatusChange' => $this->timeOfLastStatusChange,
+ 'rogueListURL' => $this->rogueListURL,
+ 'rogueListHash' => $this->rogueListHash,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use function array_key_exists;
+use Assert\Assertion;
+use function Safe\sprintf;
+
+class PatternAccuracyDescriptor extends AbstractDescriptor
+{
+ /**
+ * @var int
+ */
+ private $minComplexity;
+
+ public function __construct(int $minComplexity, ?int $maxRetries = null, ?int $blockSlowdown = null)
+ {
+ Assertion::greaterOrEqualThan($minComplexity, 0, Utils::logicException('Invalid data. The value of "minComplexity" must be a positive integer'));
+ $this->minComplexity = $minComplexity;
+ parent::__construct($maxRetries, $blockSlowdown);
+ }
+
+ public function getMinComplexity(): int
+ {
+ return $this->minComplexity;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ Assertion::keyExists($data, 'minComplexity', Utils::logicException('The key "minComplexity" is missing'));
+ foreach (['minComplexity', 'maxRetries', 'blockSlowdown'] as $key) {
+ if (array_key_exists($key, $data)) {
+ Assertion::integer($data[$key], Utils::logicException(sprintf('Invalid data. The value of "%s" must be a positive integer', $key)));
+ }
+ }
+
+ return new self(
+ $data['minComplexity'],
+ $data['maxRetries'] ?? null,
+ $data['blockSlowdown'] ?? null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'minComplexity' => $this->minComplexity,
+ 'maxRetries' => $this->getMaxRetries(),
+ 'blockSlowdown' => $this->getBlockSlowdown(),
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class RgbPaletteEntry implements JsonSerializable
+{
+ /**
+ * @var int
+ */
+ private $r;
+
+ /**
+ * @var int
+ */
+ private $g;
+
+ /**
+ * @var int
+ */
+ private $b;
+
+ public function __construct(int $r, int $g, int $b)
+ {
+ Assertion::range($r, 0, 255, Utils::logicException('The key "r" is invalid'));
+ Assertion::range($g, 0, 255, Utils::logicException('The key "g" is invalid'));
+ Assertion::range($b, 0, 255, Utils::logicException('The key "b" is invalid'));
+ $this->r = $r;
+ $this->g = $g;
+ $this->b = $b;
+ }
+
+ public function getR(): int
+ {
+ return $this->r;
+ }
+
+ public function getG(): int
+ {
+ return $this->g;
+ }
+
+ public function getB(): int
+ {
+ return $this->b;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ foreach (['r', 'g', 'b'] as $key) {
+ Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key));
+ Assertion::integer($data[$key], sprintf('The key "%s" is invalid', $key));
+ }
+
+ return new self(
+ $data['r'],
+ $data['g'],
+ $data['b']
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ return [
+ 'r' => $this->r,
+ 'g' => $this->g,
+ 'b' => $this->b,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+
+class RogueListEntry implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ private $sk;
+
+ /**
+ * @var string
+ */
+ private $date;
+
+ public function __construct(string $sk, string $date)
+ {
+ $this->sk = $sk;
+ $this->date = $date;
+ }
+
+ public function getSk(): string
+ {
+ return $this->sk;
+ }
+
+ public function getDate(): ?string
+ {
+ return $this->date;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ Assertion::keyExists($data, 'sk', 'The key "sk" is missing');
+ Assertion::string($data['sk'], 'The key "sk" is invalid');
+ Assertion::keyExists($data, 'date', 'The key "date" is missing');
+ Assertion::string($data['date'], 'The key "date" is invalid');
+
+ return new self(
+ $data['sk'],
+ $data['date']
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ return [
+ 'sk' => $this->sk,
+ 'date' => $this->date,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use function Safe\base64_decode;
+use function Safe\json_decode;
+
+class SingleMetadata
+{
+ /**
+ * @var MetadataStatement
+ */
+ private $statement;
+ /**
+ * @var string
+ */
+ private $data;
+ /**
+ * @var bool
+ */
+ private $isBase64Encoded;
+
+ public function __construct(string $data, bool $isBase64Encoded)
+ {
+ $this->data = $data;
+ $this->isBase64Encoded = $isBase64Encoded;
+ }
+
+ public function getMetadataStatement(): MetadataStatement
+ {
+ if (null === $this->statement) {
+ $json = $this->data;
+ if ($this->isBase64Encoded) {
+ $json = base64_decode($this->data, true);
+ }
+ $statement = json_decode($json, true);
+ $this->statement = MetadataStatement::createFromArray($statement);
+ }
+
+ return $this->statement;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use function in_array;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class StatusReport implements JsonSerializable
+{
+ /**
+ * @var string
+ *
+ * @see AuthenticatorStatus
+ */
+ private $status;
+
+ /**
+ * @var string|null
+ */
+ private $effectiveDate;
+
+ /**
+ * @var string|null
+ */
+ private $certificate;
+
+ /**
+ * @var string|null
+ */
+ private $url;
+
+ /**
+ * @var string|null
+ */
+ private $certificationDescriptor;
+
+ /**
+ * @var string|null
+ */
+ private $certificateNumber;
+
+ /**
+ * @var string|null
+ */
+ private $certificationPolicyVersion;
+
+ /**
+ * @var string|null
+ */
+ private $certificationRequirementsVersion;
+
+ public function __construct(string $status, ?string $effectiveDate, ?string $certificate, ?string $url, ?string $certificationDescriptor, ?string $certificateNumber, ?string $certificationPolicyVersion, ?string $certificationRequirementsVersion)
+ {
+ Assertion::inArray($status, AuthenticatorStatus::list(), Utils::logicException('The value of the key "status" is not acceptable'));
+
+ $this->status = $status;
+ $this->effectiveDate = $effectiveDate;
+ $this->certificate = $certificate;
+ $this->url = $url;
+ $this->certificationDescriptor = $certificationDescriptor;
+ $this->certificateNumber = $certificateNumber;
+ $this->certificationPolicyVersion = $certificationPolicyVersion;
+ $this->certificationRequirementsVersion = $certificationRequirementsVersion;
+ }
+
+ public function isCompromised(): bool
+ {
+ return in_array($this->status, [
+ AuthenticatorStatus::ATTESTATION_KEY_COMPROMISE,
+ AuthenticatorStatus::USER_KEY_PHYSICAL_COMPROMISE,
+ AuthenticatorStatus::USER_KEY_REMOTE_COMPROMISE,
+ AuthenticatorStatus::USER_VERIFICATION_BYPASS,
+ ], true);
+ }
+
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ public function getEffectiveDate(): ?string
+ {
+ return $this->effectiveDate;
+ }
+
+ public function getCertificate(): ?string
+ {
+ return $this->certificate;
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
+ public function getCertificationDescriptor(): ?string
+ {
+ return $this->certificationDescriptor;
+ }
+
+ public function getCertificateNumber(): ?string
+ {
+ return $this->certificateNumber;
+ }
+
+ public function getCertificationPolicyVersion(): ?string
+ {
+ return $this->certificationPolicyVersion;
+ }
+
+ public function getCertificationRequirementsVersion(): ?string
+ {
+ return $this->certificationRequirementsVersion;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ Assertion::keyExists($data, 'status', Utils::logicException('The key "status" is missing'));
+ foreach (['effectiveDate', 'certificate', 'url', 'certificationDescriptor', 'certificateNumber', 'certificationPolicyVersion', 'certificationRequirementsVersion'] as $key) {
+ if (isset($data[$key])) {
+ Assertion::nullOrString($data[$key], Utils::logicException(sprintf('The value of the key "%s" is invalid', $key)));
+ }
+ }
+
+ return new self(
+ $data['status'],
+ $data['effectiveDate'] ?? null,
+ $data['certificate'] ?? null,
+ $data['url'] ?? null,
+ $data['certificationDescriptor'] ?? null,
+ $data['certificateNumber'] ?? null,
+ $data['certificationPolicyVersion'] ?? null,
+ $data['certificationRequirementsVersion'] ?? null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'status' => $this->status,
+ 'effectiveDate' => $this->effectiveDate,
+ 'certificate' => $this->certificate,
+ 'url' => $this->url,
+ 'certificationDescriptor' => $this->certificationDescriptor,
+ 'certificateNumber' => $this->certificateNumber,
+ 'certificationPolicyVersion' => $this->certificationPolicyVersion,
+ 'certificationRequirementsVersion' => $this->certificationRequirementsVersion,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use LogicException;
+use Throwable;
+
+/**
+ * @internal
+ */
+abstract class Utils
+{
+ public static function logicException(string $message, ?Throwable $previousException = null): callable
+ {
+ return static function () use ($message, $previousException): LogicException {
+ return new LogicException($message, 0, $previousException);
+ };
+ }
+
+ public static function filterNullValues(array $data): array
+ {
+ return array_filter($data, static function ($var): bool {return null !== $var; });
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+
+class VerificationMethodANDCombinations implements JsonSerializable
+{
+ /**
+ * @var VerificationMethodDescriptor[]
+ */
+ private $verificationMethods = [];
+
+ public function addVerificationMethodDescriptor(VerificationMethodDescriptor $verificationMethodDescriptor): self
+ {
+ $this->verificationMethods[] = $verificationMethodDescriptor;
+
+ return $this;
+ }
+
+ /**
+ * @return VerificationMethodDescriptor[]
+ */
+ public function getVerificationMethods(): array
+ {
+ return $this->verificationMethods;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $object = new self();
+
+ foreach ($data as $datum) {
+ Assertion::isArray($datum, Utils::logicException('Invalid data'));
+ $object->addVerificationMethodDescriptor(VerificationMethodDescriptor::createFromArray($datum));
+ }
+
+ return $object;
+ }
+
+ public function jsonSerialize(): array
+ {
+ return array_map(static function (VerificationMethodDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->verificationMethods);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class VerificationMethodDescriptor implements JsonSerializable
+{
+ public const USER_VERIFY_PRESENCE = 0x00000001;
+ public const USER_VERIFY_FINGERPRINT = 0x00000002;
+ public const USER_VERIFY_PASSCODE = 0x00000004;
+ public const USER_VERIFY_VOICEPRINT = 0x00000008;
+ public const USER_VERIFY_FACEPRINT = 0x00000010;
+ public const USER_VERIFY_LOCATION = 0x00000020;
+ public const USER_VERIFY_EYEPRINT = 0x00000040;
+ public const USER_VERIFY_PATTERN = 0x00000080;
+ public const USER_VERIFY_HANDPRINT = 0x00000100;
+ public const USER_VERIFY_NONE = 0x00000200;
+ public const USER_VERIFY_ALL = 0x00000400;
+
+ /**
+ * @var int
+ */
+ private $userVerification;
+
+ /**
+ * @var CodeAccuracyDescriptor|null
+ */
+ private $caDesc;
+
+ /**
+ * @var BiometricAccuracyDescriptor|null
+ */
+ private $baDesc;
+
+ /**
+ * @var PatternAccuracyDescriptor|null
+ */
+ private $paDesc;
+
+ public function __construct(int $userVerification, ?CodeAccuracyDescriptor $caDesc = null, ?BiometricAccuracyDescriptor $baDesc = null, ?PatternAccuracyDescriptor $paDesc = null)
+ {
+ Assertion::greaterOrEqualThan($userVerification, 0, Utils::logicException('The parameter "userVerification" is invalid'));
+ $this->userVerification = $userVerification;
+ $this->caDesc = $caDesc;
+ $this->baDesc = $baDesc;
+ $this->paDesc = $paDesc;
+ }
+
+ public function getUserVerification(): int
+ {
+ return $this->userVerification;
+ }
+
+ public function userPresence(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_PRESENCE);
+ }
+
+ public function fingerprint(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_FINGERPRINT);
+ }
+
+ public function passcode(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_PASSCODE);
+ }
+
+ public function voicePrint(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_VOICEPRINT);
+ }
+
+ public function facePrint(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_FACEPRINT);
+ }
+
+ public function location(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_LOCATION);
+ }
+
+ public function eyePrint(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_EYEPRINT);
+ }
+
+ public function pattern(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_PATTERN);
+ }
+
+ public function handprint(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_HANDPRINT);
+ }
+
+ public function none(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_NONE);
+ }
+
+ public function all(): bool
+ {
+ return 0 !== ($this->userVerification & self::USER_VERIFY_ALL);
+ }
+
+ public function getCaDesc(): ?CodeAccuracyDescriptor
+ {
+ return $this->caDesc;
+ }
+
+ public function getBaDesc(): ?BiometricAccuracyDescriptor
+ {
+ return $this->baDesc;
+ }
+
+ public function getPaDesc(): ?PatternAccuracyDescriptor
+ {
+ return $this->paDesc;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ Assertion::keyExists($data, 'userVerification', Utils::logicException('The parameter "userVerification" is missing'));
+ Assertion::integer($data['userVerification'], Utils::logicException('The parameter "userVerification" is invalid'));
+ foreach (['caDesc', 'baDesc', 'paDesc'] as $key) {
+ if (isset($data[$key])) {
+ Assertion::isArray($data[$key], Utils::logicException(sprintf('Invalid parameter "%s"', $key)));
+ }
+ }
+
+ return new self(
+ $data['userVerification'],
+ isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null,
+ isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null,
+ isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'userVerification' => $this->userVerification,
+ 'caDesc' => null === $this->caDesc ? null : $this->caDesc->jsonSerialize(),
+ 'baDesc' => null === $this->baDesc ? null : $this->baDesc->jsonSerialize(),
+ 'paDesc' => null === $this->paDesc ? null : $this->paDesc->jsonSerialize(),
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\MetadataService;
+
+use function array_key_exists;
+use Assert\Assertion;
+use JsonSerializable;
+use LogicException;
+use function Safe\sprintf;
+
+class Version implements JsonSerializable
+{
+ /**
+ * @var int|null
+ */
+ private $major;
+
+ /**
+ * @var int|null
+ */
+ private $minor;
+
+ public function __construct(?int $major, ?int $minor)
+ {
+ if (null === $major && null === $minor) {
+ throw new LogicException('Invalid data. Must contain at least one item');
+ }
+ Assertion::greaterOrEqualThan($major, 0, Utils::logicException('Invalid argument "major"'));
+ Assertion::greaterOrEqualThan($minor, 0, Utils::logicException('Invalid argument "minor"'));
+
+ $this->major = $major;
+ $this->minor = $minor;
+ }
+
+ public function getMajor(): ?int
+ {
+ return $this->major;
+ }
+
+ public function getMinor(): ?int
+ {
+ return $this->minor;
+ }
+
+ public static function createFromArray(array $data): self
+ {
+ $data = Utils::filterNullValues($data);
+ foreach (['major', 'minor'] as $key) {
+ if (array_key_exists($key, $data)) {
+ Assertion::integer($data[$key], sprintf('Invalid value for key "%s"', $key));
+ }
+ }
+
+ return new self(
+ $data['major'] ?? null,
+ $data['minor'] ?? null
+ );
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = [
+ 'major' => $this->major,
+ 'minor' => $this->minor,
+ ];
+
+ return Utils::filterNullValues($data);
+ }
+}
--- /dev/null
+MIT License
+
+Copyright (c) 2018 Spomky-Labs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+{
+ "name": "web-auth/webauthn-lib",
+ "type": "library",
+ "license": "MIT",
+ "description": "FIDO2/Webauthn Support For PHP",
+ "keywords": ["FIDO", "FIDO2", "webauthn"],
+ "homepage": "https://github.com/web-auth",
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/web-auth/webauthn-library/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2",
+ "ext-json": "*",
+ "ext-openssl": "*",
+ "ext-mbstring": "*",
+ "beberlei/assert": "^3.2",
+ "fgrosse/phpasn1": "^2.1",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0",
+ "psr/log": "^1.1",
+ "ramsey/uuid": "^3.8|^4.0",
+ "spomky-labs/base64url": "^2.0",
+ "spomky-labs/cbor-php": "^1.0|^2.0",
+ "symfony/process": "^3.0|^4.0|^5.0",
+ "thecodingmachine/safe": "^1.1",
+ "web-auth/cose-lib": "self.version",
+ "web-auth/metadata-service": "self.version"
+ },
+ "autoload": {
+ "psr-4": {
+ "Webauthn\\": "src/"
+ }
+ },
+ "suggest": {
+ "psr/log-implementation": "Recommended to receive logs from the library",
+ "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
+ "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support"
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use CBOR\Decoder;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Algorithms;
+use Cose\Key\Ec2Key;
+use Cose\Key\Key;
+use Cose\Key\RsaKey;
+use function count;
+use FG\ASN1\ASNObject;
+use FG\ASN1\ExplicitlyTaggedObject;
+use FG\ASN1\Universal\OctetString;
+use FG\ASN1\Universal\Sequence;
+use function Safe\hex2bin;
+use function Safe\openssl_pkey_get_public;
+use function Safe\sprintf;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\StringStream;
+use Webauthn\TrustPath\CertificateTrustPath;
+
+final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport
+{
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ public function __construct()
+ {
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ }
+
+ public function name(): string
+ {
+ return 'android-key';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
+ foreach (['sig', 'x5c', 'alg'] as $key) {
+ Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
+ }
+ $certificates = $attestation['attStmt']['x5c'];
+ Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
+
+ return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
+
+ $certificates = $trustPath->getCertificates();
+
+ //Decode leaf attestation certificate
+ $leaf = $certificates[0];
+ $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
+
+ $signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
+ $alg = $attestationStatement->get('alg');
+
+ return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg));
+ }
+
+ private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void
+ {
+ $resource = openssl_pkey_get_public($certificate);
+ $details = openssl_pkey_get_details($resource);
+ Assertion::isArray($details, 'Unable to read the certificate');
+
+ //Check that authData publicKey matches the public key in the attestation certificate
+ $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'No attested credential data found');
+ $publicKeyData = $attestedCredentialData->getCredentialPublicKey();
+ Assertion::notNull($publicKeyData, 'No attested public key found');
+ $publicDataStream = new StringStream($publicKeyData);
+ $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
+ Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
+ $publicDataStream->close();
+ $publicKey = Key::createFromData($coseKey);
+
+ Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
+ Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key');
+
+ /*---------------------------*/
+ $certDetails = openssl_x509_parse($certificate);
+
+ //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions
+ Assertion::isArray($certDetails, 'The certificate is not valid');
+ Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
+ Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
+ Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing');
+ $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17'];
+ $extensionAsAsn1 = ASNObject::fromBinary($extension);
+ Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ $objects = $extensionAsAsn1->getChildren();
+
+ //Check that attestationChallenge is set to the clientDataHash.
+ Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid');
+
+ //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag.
+ Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ $softwareEnforcedFlags = $objects[6];
+ Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags);
+
+ Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ $teeEnforcedFlags = $objects[6];
+ Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
+ $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags);
+ }
+
+ private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void
+ {
+ foreach ($sequence->getChildren() as $tag) {
+ Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag');
+ /* @var ExplicitlyTaggedObject $tag */
+ Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found');
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use InvalidArgumentException;
+use Jose\Component\Core\Algorithm as AlgorithmInterface;
+use Jose\Component\Core\AlgorithmManager;
+use Jose\Component\Core\Util\JsonConverter;
+use Jose\Component\KeyManagement\JWKFactory;
+use Jose\Component\Signature\Algorithm;
+use Jose\Component\Signature\JWS;
+use Jose\Component\Signature\JWSVerifier;
+use Jose\Component\Signature\Serializer\CompactSerializer;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use RuntimeException;
+use function Safe\json_decode;
+use function Safe\sprintf;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\TrustPath\CertificateTrustPath;
+
+final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport
+{
+ /**
+ * @var string|null
+ */
+ private $apiKey;
+
+ /**
+ * @var ClientInterface|null
+ */
+ private $client;
+
+ /**
+ * @var CompactSerializer
+ */
+ private $jwsSerializer;
+
+ /**
+ * @var JWSVerifier|null
+ */
+ private $jwsVerifier;
+
+ /**
+ * @var RequestFactoryInterface|null
+ */
+ private $requestFactory;
+
+ /**
+ * @var int
+ */
+ private $leeway;
+
+ /**
+ * @var int
+ */
+ private $maxAge;
+
+ public function __construct(?ClientInterface $client = null, ?string $apiKey = null, ?RequestFactoryInterface $requestFactory = null, ?int $leeway = null, ?int $maxAge = null)
+ {
+ if (!class_exists(Algorithm\RS256::class)) {
+ throw new RuntimeException('The algorithm RS256 is missing. Did you forget to install the package web-token/jwt-signature-algorithm-rsa?');
+ }
+ if (!class_exists(JWKFactory::class)) {
+ throw new RuntimeException('The class Jose\Component\KeyManagement\JWKFactory is missing. Did you forget to install the package web-token/jwt-key-mgmt?');
+ }
+ if (null !== $client) {
+ @trigger_error('The argument "client" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
+ }
+ if (null !== $apiKey) {
+ @trigger_error('The argument "apiKey" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
+ }
+ if (null !== $requestFactory) {
+ @trigger_error('The argument "requestFactory" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
+ }
+ if (null !== $maxAge) {
+ @trigger_error('The argument "maxAge" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setMaxAge".', E_USER_DEPRECATED);
+ }
+ if (null !== $leeway) {
+ @trigger_error('The argument "leeway" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setLeeway".', E_USER_DEPRECATED);
+ }
+ $this->jwsSerializer = new CompactSerializer();
+ $this->initJwsVerifier();
+
+ //To be removed in 4.0
+ $this->leeway = $leeway ?? 0;
+ $this->maxAge = $maxAge ?? 60000;
+ $this->apiKey = $apiKey;
+ $this->client = $client;
+ $this->requestFactory = $requestFactory;
+ }
+
+ public function enableApiVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): self
+ {
+ $this->apiKey = $apiKey;
+ $this->client = $client;
+ $this->requestFactory = $requestFactory;
+
+ return $this;
+ }
+
+ public function setMaxAge(int $maxAge): self
+ {
+ $this->maxAge = $maxAge;
+
+ return $this;
+ }
+
+ public function setLeeway(int $leeway): self
+ {
+ $this->leeway = $leeway;
+
+ return $this;
+ }
+
+ public function name(): string
+ {
+ return 'android-safetynet';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
+ foreach (['ver', 'response'] as $key) {
+ Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
+ Assertion::notEmpty($attestation['attStmt'][$key], sprintf('The attestation statement value "%s" is empty.', $key));
+ }
+ $jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']);
+ $jwsHeader = $jws->getSignature(0)->getProtectedHeader();
+ Assertion::keyExists($jwsHeader, 'x5c', 'The response in the attestation statement must contain a "x5c" header.');
+ Assertion::notEmpty($jwsHeader['x5c'], 'The "x5c" parameter in the attestation statement response must contain at least one certificate.');
+ $certificates = $this->convertCertificatesToPem($jwsHeader['x5c']);
+ $attestation['attStmt']['jws'] = $jws;
+
+ return AttestationStatement::createBasic(
+ $this->name(),
+ $attestation['attStmt'],
+ new CertificateTrustPath($certificates)
+ );
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
+ $certificates = $trustPath->getCertificates();
+ $firstCertificate = current($certificates);
+ Assertion::string($firstCertificate, 'No certificate');
+
+ $parsedCertificate = openssl_x509_parse($firstCertificate);
+ Assertion::isArray($parsedCertificate, 'Invalid attestation object');
+ Assertion::keyExists($parsedCertificate, 'subject', 'Invalid attestation object');
+ Assertion::keyExists($parsedCertificate['subject'], 'CN', 'Invalid attestation object');
+ Assertion::eq($parsedCertificate['subject']['CN'], 'attest.android.com', 'Invalid attestation object');
+
+ /** @var JWS $jws */
+ $jws = $attestationStatement->get('jws');
+ $payload = $jws->getPayload();
+ $this->validatePayload($payload, $clientDataJSONHash, $authenticatorData);
+
+ //Check the signature
+ $this->validateSignature($jws, $trustPath);
+
+ //Check against Google service
+ $this->validateUsingGoogleApi($attestationStatement);
+
+ return true;
+ }
+
+ private function validatePayload(?string $payload, string $clientDataJSONHash, AuthenticatorData $authenticatorData): void
+ {
+ Assertion::notNull($payload, 'Invalid attestation object');
+ $payload = JsonConverter::decode($payload);
+ Assertion::isArray($payload, 'Invalid attestation object');
+ Assertion::keyExists($payload, 'nonce', 'Invalid attestation object. "nonce" is missing.');
+ Assertion::eq($payload['nonce'], base64_encode(hash('sha256', $authenticatorData->getAuthData().$clientDataJSONHash, true)), 'Invalid attestation object. Invalid nonce');
+ Assertion::keyExists($payload, 'ctsProfileMatch', 'Invalid attestation object. "ctsProfileMatch" is missing.');
+ Assertion::true($payload['ctsProfileMatch'], 'Invalid attestation object. "ctsProfileMatch" value is false.');
+ Assertion::keyExists($payload, 'timestampMs', 'Invalid attestation object. Timestamp is missing.');
+ Assertion::integer($payload['timestampMs'], 'Invalid attestation object. Timestamp shall be an integer.');
+ $currentTime = time() * 1000;
+ Assertion::lessOrEqualThan($payload['timestampMs'], $currentTime + $this->leeway, sprintf('Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs']));
+ Assertion::lessOrEqualThan($currentTime - $payload['timestampMs'], $this->maxAge, sprintf('Invalid attestation object. Too old. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs']));
+ }
+
+ private function validateSignature(JWS $jws, CertificateTrustPath $trustPath): void
+ {
+ $jwk = JWKFactory::createFromCertificate($trustPath->getCertificates()[0]);
+ $isValid = $this->jwsVerifier->verifyWithKey($jws, $jwk, 0);
+ Assertion::true($isValid, 'Invalid response signature');
+ }
+
+ private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void
+ {
+ if (null === $this->client || null === $this->apiKey || null === $this->requestFactory) {
+ return;
+ }
+ $uri = sprintf('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s', urlencode($this->apiKey));
+ $requestBody = sprintf('{"signedAttestation":"%s"}', $attestationStatement->get('response'));
+ $request = $this->requestFactory->createRequest('POST', $uri);
+ $request = $request->withHeader('content-type', 'application/json');
+ $request->getBody()->write($requestBody);
+
+ $response = $this->client->sendRequest($request);
+ $this->checkGoogleApiResponse($response);
+ $responseBody = $this->getResponseBody($response);
+ $responseBodyJson = json_decode($responseBody, true);
+ Assertion::keyExists($responseBodyJson, 'isValidSignature', 'Invalid response.');
+ Assertion::boolean($responseBodyJson['isValidSignature'], 'Invalid response.');
+ Assertion::true($responseBodyJson['isValidSignature'], 'Invalid response.');
+ }
+
+ private function getResponseBody(ResponseInterface $response): string
+ {
+ $responseBody = '';
+ $response->getBody()->rewind();
+ while (true) {
+ $tmp = $response->getBody()->read(1024);
+ if ('' === $tmp) {
+ break;
+ }
+ $responseBody .= $tmp;
+ }
+
+ return $responseBody;
+ }
+
+ private function checkGoogleApiResponse(ResponseInterface $response): void
+ {
+ Assertion::eq(200, $response->getStatusCode(), 'Request did not succeeded');
+ Assertion::true($response->hasHeader('content-type'), 'Unrecognized response');
+
+ foreach ($response->getHeader('content-type') as $header) {
+ if (0 === mb_strpos($header, 'application/json')) {
+ return;
+ }
+ }
+
+ throw new InvalidArgumentException('Unrecognized response');
+ }
+
+ /**
+ * @param string[] $certificates
+ *
+ * @return string[]
+ */
+ private function convertCertificatesToPem(array $certificates): array
+ {
+ foreach ($certificates as $k => $v) {
+ $certificates[$k] = CertificateToolbox::fixPEMStructure($v);
+ }
+
+ return $certificates;
+ }
+
+ private function initJwsVerifier(): void
+ {
+ $algorithmClasses = [
+ Algorithm\RS256::class, Algorithm\RS384::class, Algorithm\RS512::class,
+ Algorithm\PS256::class, Algorithm\PS384::class, Algorithm\PS512::class,
+ Algorithm\ES256::class, Algorithm\ES384::class, Algorithm\ES512::class,
+ Algorithm\EdDSA::class,
+ ];
+ /* @var AlgorithmInterface[] $algorithms */
+ $algorithms = [];
+ foreach ($algorithmClasses as $algorithm) {
+ if (class_exists($algorithm)) {
+ /* @var AlgorithmInterface $algorithm */
+ $algorithms[] = new $algorithm();
+ }
+ }
+ $algorithmManager = new AlgorithmManager($algorithms);
+ $this->jwsVerifier = new JWSVerifier($algorithmManager);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use CBOR\Decoder;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Key\Ec2Key;
+use Cose\Key\Key;
+use Cose\Key\RsaKey;
+use function count;
+use function Safe\openssl_pkey_get_public;
+use function Safe\sprintf;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\StringStream;
+use Webauthn\TrustPath\CertificateTrustPath;
+
+final class AppleAttestationStatementSupport implements AttestationStatementSupport
+{
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ public function __construct()
+ {
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ }
+
+ public function name(): string
+ {
+ return 'apple';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
+ foreach (['x5c'] as $key) {
+ Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
+ }
+ $certificates = $attestation['attStmt']['x5c'];
+ Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
+
+ return AttestationStatement::createAnonymizationCA($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
+
+ $certificates = $trustPath->getCertificates();
+
+ //Decode leaf attestation certificate
+ $leaf = $certificates[0];
+
+ $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
+
+ return true;
+ }
+
+ private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void
+ {
+ $resource = openssl_pkey_get_public($certificate);
+ $details = openssl_pkey_get_details($resource);
+ Assertion::isArray($details, 'Unable to read the certificate');
+
+ //Check that authData publicKey matches the public key in the attestation certificate
+ $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'No attested credential data found');
+ $publicKeyData = $attestedCredentialData->getCredentialPublicKey();
+ Assertion::notNull($publicKeyData, 'No attested public key found');
+ $publicDataStream = new StringStream($publicKeyData);
+ $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
+ Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
+ $publicDataStream->close();
+ $publicKey = Key::createFromData($coseKey);
+
+ Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
+
+ //We check the attested key corresponds to the key in the certificate
+ Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key');
+
+ /*---------------------------*/
+ $certDetails = openssl_x509_parse($certificate);
+
+ //Find Apple Extension with OID “1.2.840.113635.100.8.2” in certificate extensions
+ Assertion::isArray($certDetails, 'The certificate is not valid');
+ Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
+ Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
+ Assertion::keyExists($certDetails['extensions'], '1.2.840.113635.100.8.2', 'The certificate extension "1.2.840.113635.100.8.2" is missing');
+ $extension = $certDetails['extensions']['1.2.840.113635.100.8.2'];
+
+ $nonceToHash = $authenticatorData->getAuthData().$clientDataHash;
+ $nonce = hash('sha256', $nonceToHash);
+
+ //'3024a1220420' corresponds to the Sequence+Explicitly Tagged Object + Octet Object
+ Assertion::eq('3024a1220420'.$nonce, bin2hex($extension), 'The client data hash is not valid');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Webauthn\AuthenticatorData;
+use Webauthn\MetadataService\MetadataStatement;
+
+class AttestationObject
+{
+ /**
+ * @var string
+ */
+ private $rawAttestationObject;
+ /**
+ * @var AttestationStatement
+ */
+ private $attStmt;
+ /**
+ * @var AuthenticatorData
+ */
+ private $authData;
+
+ /**
+ * @var MetadataStatement|null
+ */
+ private $metadataStatement;
+
+ public function __construct(string $rawAttestationObject, AttestationStatement $attStmt, AuthenticatorData $authData, ?MetadataStatement $metadataStatement = null)
+ {
+ if (null !== $metadataStatement) {
+ @trigger_error('The argument "metadataStatement" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatement".', E_USER_DEPRECATED);
+ }
+ $this->rawAttestationObject = $rawAttestationObject;
+ $this->attStmt = $attStmt;
+ $this->authData = $authData;
+ $this->metadataStatement = $metadataStatement;
+ }
+
+ public function getRawAttestationObject(): string
+ {
+ return $this->rawAttestationObject;
+ }
+
+ public function getAttStmt(): AttestationStatement
+ {
+ return $this->attStmt;
+ }
+
+ public function setAttStmt(AttestationStatement $attStmt): void
+ {
+ $this->attStmt = $attStmt;
+ }
+
+ public function getAuthData(): AuthenticatorData
+ {
+ return $this->authData;
+ }
+
+ public function getMetadataStatement(): ?MetadataStatement
+ {
+ return $this->metadataStatement;
+ }
+
+ public function setMetadataStatement(MetadataStatement $metadataStatement): self
+ {
+ $this->metadataStatement = $metadataStatement;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use CBOR\Decoder;
+use CBOR\MapObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use function ord;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Ramsey\Uuid\Uuid;
+use function Safe\sprintf;
+use function Safe\unpack;
+use Throwable;
+use Webauthn\AttestedCredentialData;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
+use Webauthn\AuthenticatorData;
+use Webauthn\MetadataService\MetadataStatementRepository;
+use Webauthn\StringStream;
+
+class AttestationObjectLoader
+{
+ private const FLAG_AT = 0b01000000;
+ private const FLAG_ED = 0b10000000;
+
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ /**
+ * @var AttestationStatementSupportManager
+ */
+ private $attestationStatementSupportManager;
+
+ /**
+ * @var LoggerInterface|null
+ */
+ private $logger;
+
+ public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null)
+ {
+ if (null !== $metadataStatementRepository) {
+ @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.2 and will be removed in 4.0. Please set `null` instead.', E_USER_DEPRECATED);
+ }
+ if (null !== $logger) {
+ @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger" instead.', E_USER_DEPRECATED);
+ }
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ $this->attestationStatementSupportManager = $attestationStatementSupportManager;
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public static function create(AttestationStatementSupportManager $attestationStatementSupportManager): self
+ {
+ return new self($attestationStatementSupportManager);
+ }
+
+ public function load(string $data): AttestationObject
+ {
+ try {
+ $this->logger->info('Trying to load the data', ['data' => $data]);
+ $decodedData = Base64Url::decode($data);
+ $stream = new StringStream($decodedData);
+ $parsed = $this->decoder->decode($stream);
+
+ $this->logger->info('Loading the Attestation Statement');
+ $attestationObject = $parsed->getNormalizedData();
+ Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.');
+ $stream->close();
+ Assertion::isArray($attestationObject, 'Invalid attestation object');
+ Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object');
+ Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object');
+ Assertion::keyExists($attestationObject, 'attStmt', 'Invalid attestation object');
+ $authData = $attestationObject['authData'];
+
+ $attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']);
+ $attestationStatement = $attestationStatementSupport->load($attestationObject);
+ $this->logger->info('Attestation Statement loaded');
+ $this->logger->debug('Attestation Statement loaded', ['attestationStatement' => $attestationStatement]);
+
+ $authDataStream = new StringStream($authData);
+ $rp_id_hash = $authDataStream->read(32);
+ $flags = $authDataStream->read(1);
+ $signCount = $authDataStream->read(4);
+ $signCount = unpack('N', $signCount)[1];
+ $this->logger->debug(sprintf('Signature counter: %d', $signCount));
+
+ $attestedCredentialData = null;
+ if (0 !== (ord($flags) & self::FLAG_AT)) {
+ $this->logger->info('Attested Credential Data is present');
+ $aaguid = Uuid::fromBytes($authDataStream->read(16));
+ $credentialLength = $authDataStream->read(2);
+ $credentialLength = unpack('n', $credentialLength)[1];
+ $credentialId = $authDataStream->read($credentialLength);
+ $credentialPublicKey = $this->decoder->decode($authDataStream);
+ Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
+ $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
+ $this->logger->info('Attested Credential Data loaded');
+ $this->logger->debug('Attested Credential Data loaded', ['at' => $attestedCredentialData]);
+ }
+
+ $extension = null;
+ if (0 !== (ord($flags) & self::FLAG_ED)) {
+ $this->logger->info('Extension Data loaded');
+ $extension = $this->decoder->decode($authDataStream);
+ $extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
+ $this->logger->info('Extension Data loaded');
+ $this->logger->debug('Extension Data loaded', ['ed' => $extension]);
+ }
+ Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.');
+ $authDataStream->close();
+
+ $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
+ $attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData);
+ $this->logger->info('Attestation Object loaded');
+ $this->logger->debug('Attestation Object', ['ed' => $attestationObject]);
+
+ return $attestationObject;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use function array_key_exists;
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\sprintf;
+use Webauthn\TrustPath\TrustPath;
+use Webauthn\TrustPath\TrustPathLoader;
+
+class AttestationStatement implements JsonSerializable
+{
+ public const TYPE_NONE = 'none';
+ public const TYPE_BASIC = 'basic';
+ public const TYPE_SELF = 'self';
+ public const TYPE_ATTCA = 'attca';
+ public const TYPE_ECDAA = 'ecdaa';
+ public const TYPE_ANONCA = 'anonca';
+
+ /**
+ * @var string
+ */
+ private $fmt;
+
+ /**
+ * @var mixed[]
+ */
+ private $attStmt;
+
+ /**
+ * @var TrustPath
+ */
+ private $trustPath;
+
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public function __construct(string $fmt, array $attStmt, string $type, TrustPath $trustPath)
+ {
+ $this->fmt = $fmt;
+ $this->attStmt = $attStmt;
+ $this->type = $type;
+ $this->trustPath = $trustPath;
+ }
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath);
+ }
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath);
+ }
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath);
+ }
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath);
+ }
+
+ /**
+ * @param mixed[] $attStmt
+ */
+ public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath);
+ }
+
+ public static function createAnonymizationCA(string $fmt, array $attStmt, TrustPath $trustPath): self
+ {
+ return new self($fmt, $attStmt, self::TYPE_ANONCA, $trustPath);
+ }
+
+ public function getFmt(): string
+ {
+ return $this->fmt;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function getAttStmt(): array
+ {
+ return $this->attStmt;
+ }
+
+ public function has(string $key): bool
+ {
+ return array_key_exists($key, $this->attStmt);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(string $key)
+ {
+ Assertion::true($this->has($key), sprintf('The attestation statement has no key "%s".', $key));
+
+ return $this->attStmt[$key];
+ }
+
+ public function getTrustPath(): TrustPath
+ {
+ return $this->trustPath;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param mixed[] $data
+ */
+ public static function createFromArray(array $data): self
+ {
+ foreach (['fmt', 'attStmt', 'trustPath', 'type'] as $key) {
+ Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key));
+ }
+
+ return new self(
+ $data['fmt'],
+ $data['attStmt'],
+ $data['type'],
+ TrustPathLoader::loadTrustPath($data['trustPath'])
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'fmt' => $this->fmt,
+ 'attStmt' => $this->attStmt,
+ 'trustPath' => $this->trustPath->jsonSerialize(),
+ 'type' => $this->type,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Webauthn\AuthenticatorData;
+
+interface AttestationStatementSupport
+{
+ public function name(): string;
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement;
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use function array_key_exists;
+use Assert\Assertion;
+use function Safe\sprintf;
+
+class AttestationStatementSupportManager
+{
+ /**
+ * @var AttestationStatementSupport[]
+ */
+ private $attestationStatementSupports = [];
+
+ public function add(AttestationStatementSupport $attestationStatementSupport): void
+ {
+ $this->attestationStatementSupports[$attestationStatementSupport->name()] = $attestationStatementSupport;
+ }
+
+ public function has(string $name): bool
+ {
+ return array_key_exists($name, $this->attestationStatementSupports);
+ }
+
+ public function get(string $name): AttestationStatementSupport
+ {
+ Assertion::true($this->has($name), sprintf('The attestation statement format "%s" is not supported.', $name));
+
+ return $this->attestationStatementSupports[$name];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use CBOR\Decoder;
+use CBOR\MapObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Key\Ec2Key;
+use InvalidArgumentException;
+use function Safe\openssl_pkey_get_public;
+use function Safe\sprintf;
+use Throwable;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\StringStream;
+use Webauthn\TrustPath\CertificateTrustPath;
+
+final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport
+{
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ public function __construct()
+ {
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ }
+
+ public function name(): string
+ {
+ return 'fido-u2f';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
+ foreach (['sig', 'x5c'] as $key) {
+ Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
+ }
+ $certificates = $attestation['attStmt']['x5c'];
+ Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
+ Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.');
+ Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
+
+ reset($certificates);
+ $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
+ $this->checkCertificate($certificates[0]);
+
+ return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ Assertion::eq(
+ $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(),
+ '00000000-0000-0000-0000-000000000000',
+ 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"'
+ );
+ $trustPath = $attestationStatement->getTrustPath();
+ Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
+ $dataToVerify = "\0";
+ $dataToVerify .= $authenticatorData->getRpIdHash();
+ $dataToVerify .= $clientDataJSONHash;
+ $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId();
+ $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey());
+
+ return 1 === openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256);
+ }
+
+ private function extractPublicKey(?string $publicKey): string
+ {
+ Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.');
+
+ $publicKeyStream = new StringStream($publicKey);
+ $coseKey = $this->decoder->decode($publicKeyStream);
+ Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
+ $publicKeyStream->close();
+ Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
+
+ $coseKey = $coseKey->getNormalizedData();
+ $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]);
+
+ return "\x04".$ec2Key->x().$ec2Key->y();
+ }
+
+ private function checkCertificate(string $publicKey): void
+ {
+ try {
+ $resource = openssl_pkey_get_public($publicKey);
+ $details = openssl_pkey_get_details($resource);
+ } catch (Throwable $throwable) {
+ throw new InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable);
+ }
+ Assertion::isArray($details, 'Invalid certificate or certificate chain');
+ Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain');
+ Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain');
+ Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain');
+ Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain');
+ Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use function count;
+use Webauthn\AuthenticatorData;
+use Webauthn\TrustPath\EmptyTrustPath;
+
+final class NoneAttestationStatementSupport implements AttestationStatementSupport
+{
+ public function name(): string
+ {
+ return 'none';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::noContent($attestation['attStmt'], 'Invalid attestation object');
+
+ return AttestationStatement::createNone($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ return 0 === count($attestationStatement->getAttStmt());
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use function array_key_exists;
+use Assert\Assertion;
+use CBOR\Decoder;
+use CBOR\MapObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Algorithm\Manager;
+use Cose\Algorithm\Signature\Signature;
+use Cose\Algorithms;
+use Cose\Key\Key;
+use function in_array;
+use InvalidArgumentException;
+use function is_array;
+use RuntimeException;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\StringStream;
+use Webauthn\TrustPath\CertificateTrustPath;
+use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
+use Webauthn\TrustPath\EmptyTrustPath;
+use Webauthn\Util\CoseSignatureFixer;
+
+final class PackedAttestationStatementSupport implements AttestationStatementSupport
+{
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ /**
+ * @var Manager
+ */
+ private $algorithmManager;
+
+ public function __construct(Manager $algorithmManager)
+ {
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ $this->algorithmManager = $algorithmManager;
+ }
+
+ public function name(): string
+ {
+ return 'packed';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.');
+ Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.');
+ Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.');
+ switch (true) {
+ case array_key_exists('x5c', $attestation['attStmt']):
+ return $this->loadBasicType($attestation);
+ case array_key_exists('ecdaaKeyId', $attestation['attStmt']):
+ return $this->loadEcdaaType($attestation['attStmt']);
+ default:
+ return $this->loadEmptyType($attestation);
+ }
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ switch (true) {
+ case $trustPath instanceof CertificateTrustPath:
+ return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath);
+ case $trustPath instanceof EcdaaKeyIdTrustPath:
+ return $this->processWithECDAA();
+ case $trustPath instanceof EmptyTrustPath:
+ return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData);
+ default:
+ throw new InvalidArgumentException('Unsupported attestation statement');
+ }
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ private function loadBasicType(array $attestation): AttestationStatement
+ {
+ $certificates = $attestation['attStmt']['x5c'];
+ Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+ $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
+
+ return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
+ }
+
+ private function loadEcdaaType(array $attestation): AttestationStatement
+ {
+ $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId'];
+ Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.');
+
+ return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId']));
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ private function loadEmptyType(array $attestation): AttestationStatement
+ {
+ return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
+ }
+
+ private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
+ {
+ $parsed = openssl_x509_parse($attestnCert);
+ Assertion::isArray($parsed, 'Invalid certificate');
+
+ //Check version
+ Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
+
+ //Check subject field
+ Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"');
+
+ //Check extensions
+ Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing');
+
+ //Check certificate is not a CA cert
+ Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false');
+
+ $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'No attested credential available');
+
+ // id-fido-gen-ce-aaguid OID check
+ Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
+ }
+
+ private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool
+ {
+ $certificates = $trustPath->getCertificates();
+
+ // Check leaf certificate
+ $this->checkCertificate($certificates[0], $authenticatorData);
+
+ // Get the COSE algorithm identifier and the corresponding OpenSSL one
+ $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
+ $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
+
+ // Verification of the signature
+ $signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
+ $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
+
+ return 1 === $result;
+ }
+
+ private function processWithECDAA(): bool
+ {
+ throw new RuntimeException('ECDAA not supported');
+ }
+
+ private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'No attested credential available');
+ $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
+ Assertion::notNull($credentialPublicKey, 'No credential public key available');
+ $publicKeyStream = new StringStream($credentialPublicKey);
+ $publicKey = $this->decoder->decode($publicKeyStream);
+ Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
+ $publicKeyStream->close();
+ Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
+ $publicKey = $publicKey->getNormalizedData(false);
+ $publicKey = new Key($publicKey);
+ Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.');
+
+ $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash;
+ $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg'));
+ if (!$algorithm instanceof Signature) {
+ throw new RuntimeException('Invalid algorithm');
+ }
+ $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm);
+
+ return $algorithm->verify($dataToVerify, $publicKey, $signature);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AttestationStatement;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use CBOR\Decoder;
+use CBOR\MapObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Algorithms;
+use Cose\Key\Ec2Key;
+use Cose\Key\Key;
+use Cose\Key\OkpKey;
+use Cose\Key\RsaKey;
+use function count;
+use function in_array;
+use InvalidArgumentException;
+use function is_array;
+use RuntimeException;
+use Safe\DateTimeImmutable;
+use function Safe\sprintf;
+use function Safe\unpack;
+use Webauthn\AuthenticatorData;
+use Webauthn\CertificateToolbox;
+use Webauthn\StringStream;
+use Webauthn\TrustPath\CertificateTrustPath;
+use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
+
+final class TPMAttestationStatementSupport implements AttestationStatementSupport
+{
+ public function name(): string
+ {
+ return 'tpm';
+ }
+
+ /**
+ * @param mixed[] $attestation
+ */
+ public function load(array $attestation): AttestationStatement
+ {
+ Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
+ Assertion::keyNotExists($attestation['attStmt'], 'ecdaaKeyId', 'ECDAA not supported');
+ foreach (['ver', 'ver', 'sig', 'alg', 'certInfo', 'pubArea'] as $key) {
+ Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
+ }
+ Assertion::eq('2.0', $attestation['attStmt']['ver'], 'Invalid attestation object');
+
+ $certInfo = $this->checkCertInfo($attestation['attStmt']['certInfo']);
+ Assertion::eq('8017', bin2hex($certInfo['type']), 'Invalid attestation object');
+
+ $pubArea = $this->checkPubArea($attestation['attStmt']['pubArea']);
+ $pubAreaAttestedNameAlg = mb_substr($certInfo['attestedName'], 0, 2, '8bit');
+ $pubAreaHash = hash($this->getTPMHash($pubAreaAttestedNameAlg), $attestation['attStmt']['pubArea'], true);
+ $attestedName = $pubAreaAttestedNameAlg.$pubAreaHash;
+ Assertion::eq($attestedName, $certInfo['attestedName'], 'Invalid attested name');
+
+ $attestation['attStmt']['parsedCertInfo'] = $certInfo;
+ $attestation['attStmt']['parsedPubArea'] = $pubArea;
+
+ $certificates = CertificateToolbox::convertAllDERToPEM($attestation['attStmt']['x5c']);
+ Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
+
+ return AttestationStatement::createAttCA(
+ $this->name(),
+ $attestation['attStmt'],
+ new CertificateTrustPath($certificates)
+ );
+ }
+
+ public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $attToBeSigned = $authenticatorData->getAuthData().$clientDataJSONHash;
+ $attToBeSignedHash = hash(Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), $attToBeSigned, true);
+ Assertion::eq($attestationStatement->get('parsedCertInfo')['extraData'], $attToBeSignedHash, 'Invalid attestation hash');
+ $this->checkUniquePublicKey(
+ $attestationStatement->get('parsedPubArea')['unique'],
+ $authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()
+ );
+
+ switch (true) {
+ case $attestationStatement->getTrustPath() instanceof CertificateTrustPath:
+ return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData);
+ case $attestationStatement->getTrustPath() instanceof EcdaaKeyIdTrustPath:
+ return $this->processWithECDAA();
+ default:
+ throw new InvalidArgumentException('Unsupported attestation statement');
+ }
+ }
+
+ private function checkUniquePublicKey(string $unique, string $cborPublicKey): void
+ {
+ $cborDecoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ $publicKey = $cborDecoder->decode(new StringStream($cborPublicKey));
+ Assertion::isInstanceOf($publicKey, MapObject::class, 'Invalid public key');
+ $key = new Key($publicKey->getNormalizedData(false));
+
+ switch ($key->type()) {
+ case Key::TYPE_OKP:
+ $uniqueFromKey = (new OkpKey($key->getData()))->x();
+ break;
+ case Key::TYPE_EC2:
+ $ec2Key = new Ec2Key($key->getData());
+ $uniqueFromKey = "\x04".$ec2Key->x().$ec2Key->y();
+ break;
+ case Key::TYPE_RSA:
+ $uniqueFromKey = (new RsaKey($key->getData()))->n();
+ break;
+ default:
+ throw new InvalidArgumentException('Invalid or unsupported key type.');
+ }
+
+ Assertion::eq($unique, $uniqueFromKey, 'Invalid pubArea.unique value');
+ }
+
+ /**
+ * @return mixed[]
+ */
+ private function checkCertInfo(string $data): array
+ {
+ $certInfo = new StringStream($data);
+
+ $magic = $certInfo->read(4);
+ Assertion::eq('ff544347', bin2hex($magic), 'Invalid attestation object');
+
+ $type = $certInfo->read(2);
+
+ $qualifiedSignerLength = unpack('n', $certInfo->read(2))[1];
+ $qualifiedSigner = $certInfo->read($qualifiedSignerLength); //Ignored
+
+ $extraDataLength = unpack('n', $certInfo->read(2))[1];
+ $extraData = $certInfo->read($extraDataLength);
+
+ $clockInfo = $certInfo->read(17); //Ignore
+
+ $firmwareVersion = $certInfo->read(8);
+
+ $attestedNameLength = unpack('n', $certInfo->read(2))[1];
+ $attestedName = $certInfo->read($attestedNameLength);
+
+ $attestedQualifiedNameLength = unpack('n', $certInfo->read(2))[1];
+ $attestedQualifiedName = $certInfo->read($attestedQualifiedNameLength); //Ignore
+ Assertion::true($certInfo->isEOF(), 'Invalid certificate information. Presence of extra bytes.');
+ $certInfo->close();
+
+ return [
+ 'magic' => $magic,
+ 'type' => $type,
+ 'qualifiedSigner' => $qualifiedSigner,
+ 'extraData' => $extraData,
+ 'clockInfo' => $clockInfo,
+ 'firmwareVersion' => $firmwareVersion,
+ 'attestedName' => $attestedName,
+ 'attestedQualifiedName' => $attestedQualifiedName,
+ ];
+ }
+
+ /**
+ * @return mixed[]
+ */
+ private function checkPubArea(string $data): array
+ {
+ $pubArea = new StringStream($data);
+
+ $type = $pubArea->read(2);
+
+ $nameAlg = $pubArea->read(2);
+
+ $objectAttributes = $pubArea->read(4);
+
+ $authPolicyLength = unpack('n', $pubArea->read(2))[1];
+ $authPolicy = $pubArea->read($authPolicyLength);
+
+ $parameters = $this->getParameters($type, $pubArea);
+
+ $uniqueLength = unpack('n', $pubArea->read(2))[1];
+ $unique = $pubArea->read($uniqueLength);
+ Assertion::true($pubArea->isEOF(), 'Invalid public area. Presence of extra bytes.');
+ $pubArea->close();
+
+ return [
+ 'type' => $type,
+ 'nameAlg' => $nameAlg,
+ 'objectAttributes' => $objectAttributes,
+ 'authPolicy' => $authPolicy,
+ 'parameters' => $parameters,
+ 'unique' => $unique,
+ ];
+ }
+
+ /**
+ * @return mixed[]
+ */
+ private function getParameters(string $type, StringStream $stream): array
+ {
+ switch (bin2hex($type)) {
+ case '0001':
+ case '0014':
+ case '0016':
+ return [
+ 'symmetric' => $stream->read(2),
+ 'scheme' => $stream->read(2),
+ 'keyBits' => unpack('n', $stream->read(2))[1],
+ 'exponent' => $this->getExponent($stream->read(4)),
+ ];
+ case '0018':
+ return [
+ 'symmetric' => $stream->read(2),
+ 'scheme' => $stream->read(2),
+ 'curveId' => $stream->read(2),
+ 'kdf' => $stream->read(2),
+ ];
+ default:
+ throw new InvalidArgumentException('Unsupported type');
+ }
+ }
+
+ private function getExponent(string $exponent): string
+ {
+ return '00000000' === bin2hex($exponent) ? Base64Url::decode('AQAB') : $exponent;
+ }
+
+ private function getTPMHash(string $nameAlg): string
+ {
+ switch (bin2hex($nameAlg)) {
+ case '0004':
+ return 'sha1'; //: "TPM_ALG_SHA1",
+ case '000b':
+ return 'sha256'; //: "TPM_ALG_SHA256",
+ case '000c':
+ return 'sha384'; //: "TPM_ALG_SHA384",
+ case '000d':
+ return 'sha512'; //: "TPM_ALG_SHA512",
+ default:
+ throw new InvalidArgumentException('Unsupported hash algorithm');
+ }
+ }
+
+ private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
+
+ $certificates = $trustPath->getCertificates();
+
+ // Check certificate CA chain and returns the Attestation Certificate
+ $this->checkCertificate($certificates[0], $authenticatorData);
+
+ // Get the COSE algorithm identifier and the corresponding OpenSSL one
+ $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
+ $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
+
+ $result = openssl_verify($attestationStatement->get('certInfo'), $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
+
+ return 1 === $result;
+ }
+
+ private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
+ {
+ $parsed = openssl_x509_parse($attestnCert);
+ Assertion::isArray($parsed, 'Invalid certificate');
+
+ //Check version
+ Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
+
+ //Check subject field is empty
+ Assertion::false(!isset($parsed['subject']) || !is_array($parsed['subject']) || 0 !== count($parsed['subject']), 'Invalid certificate name. The Subject should be empty');
+
+ // Check period of validity
+ Assertion::keyExists($parsed, 'validFrom_time_t', 'Invalid certificate start date.');
+ Assertion::integer($parsed['validFrom_time_t'], 'Invalid certificate start date.');
+ $startDate = (new DateTimeImmutable())->setTimestamp($parsed['validFrom_time_t']);
+ Assertion::true($startDate < new DateTimeImmutable(), 'Invalid certificate start date.');
+
+ Assertion::keyExists($parsed, 'validTo_time_t', 'Invalid certificate end date.');
+ Assertion::integer($parsed['validTo_time_t'], 'Invalid certificate end date.');
+ $endDate = (new DateTimeImmutable())->setTimestamp($parsed['validTo_time_t']);
+ Assertion::true($endDate > new DateTimeImmutable(), 'Invalid certificate end date.');
+
+ //Check extensions
+ Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing');
+
+ //Check subjectAltName
+ Assertion::false(!isset($parsed['extensions']['subjectAltName']), 'The "subjectAltName" is missing');
+
+ //Check extendedKeyUsage
+ Assertion::false(!isset($parsed['extensions']['extendedKeyUsage']), 'The "subjectAltName" is missing');
+ Assertion::eq($parsed['extensions']['extendedKeyUsage'], '2.23.133.8.3', 'The "extendedKeyUsage" is invalid');
+
+ // id-fido-gen-ce-aaguid OID check
+ Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($authenticatorData->getAttestedCredentialData()->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
+ }
+
+ private function processWithECDAA(): bool
+ {
+ throw new RuntimeException('ECDAA not supported');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use JsonSerializable;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+use function Safe\base64_decode;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#sec-attested-credential-data
+ */
+class AttestedCredentialData implements JsonSerializable
+{
+ /**
+ * @var UuidInterface
+ */
+ private $aaguid;
+
+ /**
+ * @var string
+ */
+ private $credentialId;
+
+ /**
+ * @var string|null
+ */
+ private $credentialPublicKey;
+
+ public function __construct(UuidInterface $aaguid, string $credentialId, ?string $credentialPublicKey)
+ {
+ $this->aaguid = $aaguid;
+ $this->credentialId = $credentialId;
+ $this->credentialPublicKey = $credentialPublicKey;
+ }
+
+ public function getAaguid(): UuidInterface
+ {
+ return $this->aaguid;
+ }
+
+ public function setAaguid(UuidInterface $aaguid): void
+ {
+ $this->aaguid = $aaguid;
+ }
+
+ public function getCredentialId(): string
+ {
+ return $this->credentialId;
+ }
+
+ public function getCredentialPublicKey(): ?string
+ {
+ return $this->credentialPublicKey;
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ Assertion::keyExists($json, 'aaguid', 'Invalid input. "aaguid" is missing.');
+ Assertion::keyExists($json, 'credentialId', 'Invalid input. "credentialId" is missing.');
+ switch (true) {
+ case 36 === mb_strlen($json['aaguid'], '8bit'):
+ $uuid = Uuid::fromString($json['aaguid']);
+ break;
+ default: // Kept for compatibility with old format
+ $decoded = base64_decode($json['aaguid'], true);
+ $uuid = Uuid::fromBytes($decoded);
+ }
+ $credentialId = base64_decode($json['credentialId'], true);
+
+ $credentialPublicKey = null;
+ if (isset($json['credentialPublicKey'])) {
+ $credentialPublicKey = base64_decode($json['credentialPublicKey'], true);
+ }
+
+ return new self(
+ $uuid,
+ $credentialId,
+ $credentialPublicKey
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $result = [
+ 'aaguid' => $this->aaguid->toString(),
+ 'credentialId' => base64_encode($this->credentialId),
+ ];
+ if (null !== $this->credentialPublicKey) {
+ $result['credentialPublicKey'] = base64_encode($this->credentialPublicKey);
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+use JsonSerializable;
+
+class AuthenticationExtension implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ private $name;
+
+ /**
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * @param mixed $value
+ */
+ public function __construct(string $name, $value)
+ {
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ public function name(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function value()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function jsonSerialize()
+ {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+use function array_key_exists;
+use ArrayIterator;
+use Assert\Assertion;
+use function count;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+use JsonSerializable;
+use function Safe\sprintf;
+
+class AuthenticationExtensionsClientInputs implements JsonSerializable, Countable, IteratorAggregate
+{
+ /**
+ * @var AuthenticationExtension[]
+ */
+ private $extensions = [];
+
+ public function add(AuthenticationExtension $extension): void
+ {
+ $this->extensions[$extension->name()] = $extension;
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ $object = new self();
+ foreach ($json as $k => $v) {
+ $object->add(new AuthenticationExtension($k, $v));
+ }
+
+ return $object;
+ }
+
+ public function has(string $key): bool
+ {
+ return array_key_exists($key, $this->extensions);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(string $key)
+ {
+ Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key));
+
+ return $this->extensions[$key];
+ }
+
+ /**
+ * @return AuthenticationExtension[]
+ */
+ public function jsonSerialize(): array
+ {
+ return array_map(static function (AuthenticationExtension $object) {
+ return $object->jsonSerialize();
+ }, $this->extensions);
+ }
+
+ /**
+ * @return Iterator<string, AuthenticationExtension>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->extensions);
+ }
+
+ public function count(int $mode = COUNT_NORMAL): int
+ {
+ return count($this->extensions, $mode);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+use function array_key_exists;
+use ArrayIterator;
+use Assert\Assertion;
+use function count;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+use JsonSerializable;
+use function Safe\json_decode;
+use function Safe\sprintf;
+
+class AuthenticationExtensionsClientOutputs implements JsonSerializable, Countable, IteratorAggregate
+{
+ /**
+ * @var AuthenticationExtension[]
+ */
+ private $extensions = [];
+
+ public function add(AuthenticationExtension $extension): void
+ {
+ $this->extensions[$extension->name()] = $extension;
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ $object = new self();
+ foreach ($json as $k => $v) {
+ $object->add(new AuthenticationExtension($k, $v));
+ }
+
+ return $object;
+ }
+
+ public function has(string $key): bool
+ {
+ return array_key_exists($key, $this->extensions);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(string $key)
+ {
+ Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key));
+
+ return $this->extensions[$key];
+ }
+
+ /**
+ * @return AuthenticationExtension[]
+ */
+ public function jsonSerialize(): array
+ {
+ return array_map(static function (AuthenticationExtension $object) {
+ return $object->jsonSerialize();
+ }, $this->extensions);
+ }
+
+ /**
+ * @return Iterator<string, AuthenticationExtension>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->extensions);
+ }
+
+ public function count(int $mode = COUNT_NORMAL): int
+ {
+ return count($this->extensions, $mode);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+use Assert\Assertion;
+use CBOR\CBORObject;
+use CBOR\MapObject;
+
+abstract class AuthenticationExtensionsClientOutputsLoader
+{
+ public static function load(CBORObject $object): AuthenticationExtensionsClientOutputs
+ {
+ Assertion::isInstanceOf($object, MapObject::class, 'Invalid extension object');
+ $data = $object->getNormalizedData();
+ $extensions = new AuthenticationExtensionsClientOutputs();
+ foreach ($data as $key => $value) {
+ Assertion::string($key, 'Invalid extension key');
+ $extensions->add(new AuthenticationExtension($key, $value));
+ }
+
+ return $extensions;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+interface ExtensionOutputChecker
+{
+ /**
+ * @throws ExtensionOutputError
+ */
+ public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+class ExtensionOutputCheckerHandler
+{
+ /**
+ * @var ExtensionOutputChecker[]
+ */
+ private $checkers = [];
+
+ public function add(ExtensionOutputChecker $checker): void
+ {
+ $this->checkers[] = $checker;
+ }
+
+ /**
+ * @throws ExtensionOutputError
+ */
+ public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void
+ {
+ foreach ($this->checkers as $checker) {
+ $checker->check($inputs, $outputs);
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\AuthenticationExtensions;
+
+use Exception;
+use Throwable;
+
+class ExtensionOutputError extends Exception
+{
+ /**
+ * @var AuthenticationExtension
+ */
+ private $authenticationExtension;
+
+ public function __construct(AuthenticationExtension $authenticationExtension, string $message = '', int $code = 0, Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ $this->authenticationExtension = $authenticationExtension;
+ }
+
+ public function getAuthenticationExtension(): AuthenticationExtension
+ {
+ return $this->authenticationExtension;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function Safe\base64_decode;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#authenticatorassertionresponse
+ */
+class AuthenticatorAssertionResponse extends AuthenticatorResponse
+{
+ /**
+ * @var AuthenticatorData
+ */
+ private $authenticatorData;
+
+ /**
+ * @var string
+ */
+ private $signature;
+
+ /**
+ * @var string|null
+ */
+ private $userHandle;
+
+ public function __construct(CollectedClientData $clientDataJSON, AuthenticatorData $authenticatorData, string $signature, ?string $userHandle)
+ {
+ parent::__construct($clientDataJSON);
+ $this->authenticatorData = $authenticatorData;
+ $this->signature = $signature;
+ $this->userHandle = $userHandle;
+ }
+
+ public function getAuthenticatorData(): AuthenticatorData
+ {
+ return $this->authenticatorData;
+ }
+
+ public function getSignature(): string
+ {
+ return $this->signature;
+ }
+
+ public function getUserHandle(): ?string
+ {
+ if (null === $this->userHandle || '' === $this->userHandle) {
+ return $this->userHandle;
+ }
+
+ return base64_decode($this->userHandle, true);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use CBOR\Decoder;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use Cose\Algorithm\Manager;
+use Cose\Algorithm\Signature\Signature;
+use Cose\Key\Key;
+use function count;
+use function in_array;
+use function is_string;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use function Safe\parse_url;
+use Throwable;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
+use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
+use Webauthn\Counter\CounterChecker;
+use Webauthn\Counter\ThrowExceptionIfInvalid;
+use Webauthn\TokenBinding\TokenBindingHandler;
+use Webauthn\Util\CoseSignatureFixer;
+
+class AuthenticatorAssertionResponseValidator
+{
+ /**
+ * @var PublicKeyCredentialSourceRepository
+ */
+ private $publicKeyCredentialSourceRepository;
+
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ /**
+ * @var TokenBindingHandler
+ */
+ private $tokenBindingHandler;
+
+ /**
+ * @var ExtensionOutputCheckerHandler
+ */
+ private $extensionOutputCheckerHandler;
+
+ /**
+ * @var Manager|null
+ */
+ private $algorithmManager;
+
+ /**
+ * @var CounterChecker
+ */
+ private $counterChecker;
+
+ /**
+ * @var LoggerInterface|null
+ */
+ private $logger;
+
+ public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, Manager $algorithmManager, ?CounterChecker $counterChecker = null, ?LoggerInterface $logger = null)
+ {
+ if (null !== $logger) {
+ @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED);
+ }
+ if (null !== $counterChecker) {
+ @trigger_error('The argument "counterChecker" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setCounterChecker".', E_USER_DEPRECATED);
+ }
+ $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository;
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ $this->tokenBindingHandler = $tokenBindingHandler;
+ $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
+ $this->algorithmManager = $algorithmManager;
+ $this->counterChecker = $counterChecker ?? new ThrowExceptionIfInvalid();
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ /**
+ * @see https://www.w3.org/TR/webauthn/#verifying-assertion
+ */
+ public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle, array $securedRelyingPartyId = []): PublicKeyCredentialSource
+ {
+ try {
+ $this->logger->info('Checking the authenticator assertion response', [
+ 'credentialId' => $credentialId,
+ 'authenticatorAssertionResponse' => $authenticatorAssertionResponse,
+ 'publicKeyCredentialRequestOptions' => $publicKeyCredentialRequestOptions,
+ 'host' => $request->getUri()->getHost(),
+ 'userHandle' => $userHandle,
+ ]);
+ /** @see 7.2.1 */
+ if (0 !== count($publicKeyCredentialRequestOptions->getAllowCredentials())) {
+ Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.');
+ }
+
+ /** @see 7.2.2 */
+ $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId);
+ Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.');
+
+ /** @see 7.2.3 */
+ $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData();
+ $credentialUserHandle = $publicKeyCredentialSource->getUserHandle();
+ $responseUserHandle = $authenticatorAssertionResponse->getUserHandle();
+
+ /** @see 7.2.2 User Handle*/
+ if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated,
+ Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle');
+ if (null !== $responseUserHandle && '' !== $responseUserHandle) {
+ Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle');
+ }
+ } else {
+ Assertion::notEmpty($responseUserHandle, 'User handle is mandatory');
+ Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle');
+ }
+
+ $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
+ $isU2F = U2FPublicKey::isU2FKey($credentialPublicKey);
+ if ($isU2F) {
+ $credentialPublicKey = U2FPublicKey::createCOSEKey($credentialPublicKey);
+ }
+ Assertion::notNull($credentialPublicKey, 'No public key available.');
+ $stream = new StringStream($credentialPublicKey);
+ $credentialPublicKeyStream = $this->decoder->decode($stream);
+ Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.');
+ $stream->close();
+
+ /** @see 7.2.4 */
+ /** @see 7.2.5 */
+ //Nothing to do. Use of objects directly
+
+ /** @see 7.2.6 */
+ $C = $authenticatorAssertionResponse->getClientDataJSON();
+
+ /** @see 7.2.7 */
+ Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".');
+
+ /** @see 7.2.8 */
+ Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.');
+
+ /** @see 7.2.9 */
+ $rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost();
+ $facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions());
+ $parsedRelyingPartyId = parse_url($C->getOrigin());
+ Assertion::isArray($parsedRelyingPartyId, 'Invalid origin');
+ if (!in_array($facetId, $securedRelyingPartyId, true)) {
+ $scheme = $parsedRelyingPartyId['scheme'] ?? '';
+ Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.');
+ }
+ $clientDataRpId = $parsedRelyingPartyId['host'] ?? '';
+ Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.');
+ $rpIdLength = mb_strlen($facetId);
+ Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.');
+
+ /** @see 7.2.10 */
+ if (null !== $C->getTokenBinding()) {
+ $this->tokenBindingHandler->check($C->getTokenBinding(), $request);
+ }
+
+ $expectedRpIdHash = $isU2F ? $C->getOrigin() : $facetId;
+ // u2f response has full origin in rpIdHash
+ /** @see 7.2.11 */
+ $rpIdHash = hash('sha256', $expectedRpIdHash, true);
+ Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.');
+
+ /** @see 7.2.12 */
+ Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present');
+ /** @see 7.2.13 */
+ if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) {
+ Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.');
+ }
+
+ /** @see 7.2.14 */
+ $extensionsClientOutputs = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions();
+ if (null !== $extensionsClientOutputs) {
+ $this->extensionOutputCheckerHandler->check(
+ $publicKeyCredentialRequestOptions->getExtensions(),
+ $extensionsClientOutputs
+ );
+ }
+
+ /** @see 7.2.15 */
+ $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true);
+
+ /** @see 7.2.16 */
+ $dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash;
+ $signature = $authenticatorAssertionResponse->getSignature();
+ $coseKey = new Key($credentialPublicKeyStream->getNormalizedData());
+ $algorithm = $this->algorithmManager->get($coseKey->alg());
+ Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm');
+ $signature = CoseSignatureFixer::fix($signature, $algorithm);
+ Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.');
+
+ /** @see 7.2.17 */
+ $storedCounter = $publicKeyCredentialSource->getCounter();
+ $responseCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount();
+ if (0 !== $responseCounter || 0 !== $storedCounter) {
+ $this->counterChecker->check($publicKeyCredentialSource, $responseCounter);
+ }
+ $publicKeyCredentialSource->setCounter($responseCounter);
+ $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
+
+ /** @see 7.2.18 */
+ //All good. We can continue.
+ $this->logger->info('The assertion is valid');
+ $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]);
+
+ return $publicKeyCredentialSource;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ public function setCounterChecker(CounterChecker $counterChecker): self
+ {
+ $this->counterChecker = $counterChecker;
+
+ return $this;
+ }
+
+ /**
+ * @param array<PublicKeyCredentialDescriptor> $allowedCredentials
+ */
+ private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool
+ {
+ foreach ($allowedCredentials as $allowedCredential) {
+ if (hash_equals($allowedCredential->getId(), $credentialId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string
+ {
+ if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) {
+ return $rpId;
+ }
+ $appId = $authenticationExtensionsClientInputs->get('appid')->value();
+ $wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value();
+ if (!is_string($appId) || true !== $wasUsed) {
+ return $rpId;
+ }
+
+ return $appId;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Webauthn\AttestationStatement\AttestationObject;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#authenticatorattestationresponse
+ */
+class AuthenticatorAttestationResponse extends AuthenticatorResponse
+{
+ /**
+ * @var AttestationObject
+ */
+ private $attestationObject;
+
+ public function __construct(CollectedClientData $clientDataJSON, AttestationObject $attestationObject)
+ {
+ parent::__construct($clientDataJSON);
+ $this->attestationObject = $attestationObject;
+ }
+
+ public function getAttestationObject(): AttestationObject
+ {
+ return $this->attestationObject;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use function count;
+use function in_array;
+use InvalidArgumentException;
+use function is_string;
+use LogicException;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Ramsey\Uuid\Uuid;
+use function Safe\parse_url;
+use function Safe\sprintf;
+use Throwable;
+use Webauthn\AttestationStatement\AttestationObject;
+use Webauthn\AttestationStatement\AttestationStatement;
+use Webauthn\AttestationStatement\AttestationStatementSupportManager;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
+use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
+use Webauthn\CertificateChainChecker\CertificateChainChecker;
+use Webauthn\MetadataService\MetadataStatement;
+use Webauthn\MetadataService\MetadataStatementRepository;
+use Webauthn\MetadataService\StatusReport;
+use Webauthn\TokenBinding\TokenBindingHandler;
+use Webauthn\TrustPath\CertificateTrustPath;
+use Webauthn\TrustPath\EmptyTrustPath;
+
+class AuthenticatorAttestationResponseValidator
+{
+ /**
+ * @var AttestationStatementSupportManager
+ */
+ private $attestationStatementSupportManager;
+
+ /**
+ * @var PublicKeyCredentialSourceRepository
+ */
+ private $publicKeyCredentialSource;
+
+ /**
+ * @var TokenBindingHandler
+ */
+ private $tokenBindingHandler;
+
+ /**
+ * @var ExtensionOutputCheckerHandler
+ */
+ private $extensionOutputCheckerHandler;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var MetadataStatementRepository|null
+ */
+ private $metadataStatementRepository;
+
+ /**
+ * @var CertificateChainChecker|null
+ */
+ private $certificateChainChecker;
+
+ public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, PublicKeyCredentialSourceRepository $publicKeyCredentialSource, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null)
+ {
+ if (null !== $logger) {
+ @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED);
+ }
+ if (null !== $metadataStatementRepository) {
+ @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED);
+ }
+ $this->attestationStatementSupportManager = $attestationStatementSupportManager;
+ $this->publicKeyCredentialSource = $publicKeyCredentialSource;
+ $this->tokenBindingHandler = $tokenBindingHandler;
+ $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
+ $this->metadataStatementRepository = $metadataStatementRepository;
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ public function setCertificateChainChecker(CertificateChainChecker $certificateChainChecker): self
+ {
+ $this->certificateChainChecker = $certificateChainChecker;
+
+ return $this;
+ }
+
+ public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self
+ {
+ $this->metadataStatementRepository = $metadataStatementRepository;
+
+ return $this;
+ }
+
+ /**
+ * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential
+ */
+ public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request, array $securedRelyingPartyId = []): PublicKeyCredentialSource
+ {
+ try {
+ $this->logger->info('Checking the authenticator attestation response', [
+ 'authenticatorAttestationResponse' => $authenticatorAttestationResponse,
+ 'publicKeyCredentialCreationOptions' => $publicKeyCredentialCreationOptions,
+ 'host' => $request->getUri()->getHost(),
+ ]);
+ /** @see 7.1.1 */
+ //Nothing to do
+
+ /** @see 7.1.2 */
+ $C = $authenticatorAttestationResponse->getClientDataJSON();
+
+ /** @see 7.1.3 */
+ Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".');
+
+ /** @see 7.1.4 */
+ Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.');
+
+ /** @see 7.1.5 */
+ $rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost();
+ $facetId = $this->getFacetId($rpId, $publicKeyCredentialCreationOptions->getExtensions(), $authenticatorAttestationResponse->getAttestationObject()->getAuthData()->getExtensions());
+
+ $parsedRelyingPartyId = parse_url($C->getOrigin());
+ Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin()));
+ Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.');
+ $clientDataRpId = $parsedRelyingPartyId['host'] ?? '';
+ Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.');
+ $rpIdLength = mb_strlen($facetId);
+ Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.');
+
+ if (!in_array($facetId, $securedRelyingPartyId, true)) {
+ $scheme = $parsedRelyingPartyId['scheme'] ?? '';
+ Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.');
+ }
+
+ /** @see 7.1.6 */
+ if (null !== $C->getTokenBinding()) {
+ $this->tokenBindingHandler->check($C->getTokenBinding(), $request);
+ }
+
+ /** @see 7.1.7 */
+ $clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true);
+
+ /** @see 7.1.8 */
+ $attestationObject = $authenticatorAttestationResponse->getAttestationObject();
+
+ /** @see 7.1.9 */
+ $rpIdHash = hash('sha256', $facetId, true);
+ Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.');
+
+ /** @see 7.1.10 */
+ Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present');
+ /** @see 7.1.11 */
+ if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) {
+ Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.');
+ }
+
+ /** @see 7.1.12 */
+ $extensionsClientOutputs = $attestationObject->getAuthData()->getExtensions();
+ if (null !== $extensionsClientOutputs) {
+ $this->extensionOutputCheckerHandler->check(
+ $publicKeyCredentialCreationOptions->getExtensions(),
+ $extensionsClientOutputs
+ );
+ }
+
+ /** @see 7.1.13 */
+ $this->checkMetadataStatement($publicKeyCredentialCreationOptions, $attestationObject);
+ $fmt = $attestationObject->getAttStmt()->getFmt();
+ Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.');
+
+ /** @see 7.1.14 */
+ $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt);
+ Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.');
+
+ /** @see 7.1.15 */
+ /** @see 7.1.16 */
+ /** @see 7.1.17 */
+ Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.');
+ $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'There is no attested credential data.');
+ $credentialId = $attestedCredentialData->getCredentialId();
+ Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.');
+
+ /** @see 7.1.18 */
+ /** @see 7.1.19 */
+ $publicKeyCredentialSource = $this->createPublicKeyCredentialSource(
+ $credentialId,
+ $attestedCredentialData,
+ $attestationObject,
+ $publicKeyCredentialCreationOptions->getUser()->getId()
+ );
+ $this->logger->info('The attestation is valid');
+ $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]);
+
+ return $publicKeyCredentialSource;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ private function checkCertificateChain(AttestationStatement $attestationStatement, ?MetadataStatement $metadataStatement): void
+ {
+ $trustPath = $attestationStatement->getTrustPath();
+ if (!$trustPath instanceof CertificateTrustPath) {
+ return;
+ }
+ $authenticatorCertificates = $trustPath->getCertificates();
+
+ if (null === $metadataStatement) {
+ // @phpstan-ignore-next-line
+ null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, [], null);
+
+ return;
+ }
+
+ $metadataStatementCertificates = $metadataStatement->getAttestationRootCertificates();
+ $rootStatementCertificates = $metadataStatement->getRootCertificates();
+ foreach ($metadataStatementCertificates as $key => $metadataStatementCertificate) {
+ $metadataStatementCertificates[$key] = CertificateToolbox::fixPEMStructure($metadataStatementCertificate);
+ }
+ $trustedCertificates = array_merge(
+ $metadataStatementCertificates,
+ $rootStatementCertificates
+ );
+
+ // @phpstan-ignore-next-line
+ null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates, $trustedCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, $trustedCertificates);
+ }
+
+ private function checkMetadataStatement(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, AttestationObject $attestationObject): void
+ {
+ $attestationStatement = $attestationObject->getAttStmt();
+ $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData();
+ Assertion::notNull($attestedCredentialData, 'No attested credential data found');
+ $aaguid = $attestedCredentialData->getAaguid()->toString();
+ if (PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE === $publicKeyCredentialCreationOptions->getAttestation()) {
+ $this->logger->debug('No attestation is asked.');
+ //No attestation is asked. We shall ensure that the data is anonymous.
+ if (
+ '00000000-0000-0000-0000-000000000000' === $aaguid
+ && (AttestationStatement::TYPE_NONE === $attestationStatement->getType() || AttestationStatement::TYPE_SELF === $attestationStatement->getType())) {
+ $this->logger->debug('The Attestation Statement is anonymous.');
+ $this->checkCertificateChain($attestationStatement, null);
+
+ return;
+ }
+ $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [
+ 'aaguid' => $aaguid,
+ 'AttestationStatement' => $attestationStatement,
+ ]);
+ $attestedCredentialData->setAaguid(
+ Uuid::fromString('00000000-0000-0000-0000-000000000000')
+ );
+ $attestationObject->setAttStmt(AttestationStatement::createNone('none', [], new EmptyTrustPath()));
+
+ return;
+ }
+ if (AttestationStatement::TYPE_NONE === $attestationStatement->getType()) {
+ $this->logger->debug('No attestation returned.');
+ //No attestation is returned. We shall ensure that the AAGUID is a null one.
+ if ('00000000-0000-0000-0000-000000000000' !== $aaguid) {
+ $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [
+ 'aaguid' => $aaguid,
+ 'AttestationStatement' => $attestationStatement,
+ ]);
+ $attestedCredentialData->setAaguid(
+ Uuid::fromString('00000000-0000-0000-0000-000000000000')
+ );
+
+ return;
+ }
+
+ return;
+ }
+
+ //The MDS Repository is mandatory here
+ Assertion::notNull($this->metadataStatementRepository, 'The Metadata Statement Repository is mandatory when requesting attestation objects.');
+ $metadataStatement = $this->metadataStatementRepository->findOneByAAGUID($aaguid);
+
+ // We check the last status report
+ $this->checkStatusReport(null === $metadataStatement ? [] : $metadataStatement->getStatusReports());
+
+ // We check the certificate chain (if any)
+ $this->checkCertificateChain($attestationStatement, $metadataStatement);
+
+ // If no Attestation Statement has been returned or if null AAGUID (=00000000-0000-0000-0000-000000000000)
+ // => nothing to check
+ if ('00000000-0000-0000-0000-000000000000' === $aaguid || AttestationStatement::TYPE_NONE === $attestationStatement->getType()) {
+ return;
+ }
+
+ // At this point, the Metadata Statement is mandatory
+ Assertion::notNull($metadataStatement, sprintf('The Metadata Statement for the AAGUID "%s" is missing', $aaguid));
+
+ // Check Attestation Type is allowed
+ if (0 !== count($metadataStatement->getAttestationTypes())) {
+ $type = $this->getAttestationType($attestationStatement);
+ Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator');
+ }
+ }
+
+ /**
+ * @param StatusReport[] $statusReports
+ */
+ private function checkStatusReport(array $statusReports): void
+ {
+ if (0 !== count($statusReports)) {
+ $lastStatusReport = end($statusReports);
+ if ($lastStatusReport->isCompromised()) {
+ throw new LogicException('The authenticator is compromised and cannot be used');
+ }
+ }
+ }
+
+ private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource
+ {
+ return new PublicKeyCredentialSource(
+ $credentialId,
+ PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
+ [],
+ $attestationObject->getAttStmt()->getType(),
+ $attestationObject->getAttStmt()->getTrustPath(),
+ $attestedCredentialData->getAaguid(),
+ $attestedCredentialData->getCredentialPublicKey(),
+ $userHandle,
+ $attestationObject->getAuthData()->getSignCount()
+ );
+ }
+
+ private function getAttestationType(AttestationStatement $attestationStatement): int
+ {
+ switch ($attestationStatement->getType()) {
+ case AttestationStatement::TYPE_BASIC:
+ return MetadataStatement::ATTESTATION_BASIC_FULL;
+ case AttestationStatement::TYPE_SELF:
+ return MetadataStatement::ATTESTATION_BASIC_SURROGATE;
+ case AttestationStatement::TYPE_ATTCA:
+ return MetadataStatement::ATTESTATION_ATTCA;
+ case AttestationStatement::TYPE_ECDAA:
+ return MetadataStatement::ATTESTATION_ECDAA;
+ default:
+ throw new InvalidArgumentException('Invalid attestation type');
+ }
+ }
+
+ private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string
+ {
+ if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) {
+ return $rpId;
+ }
+ $appId = $authenticationExtensionsClientInputs->get('appid')->value();
+ $wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value();
+ if (!is_string($appId) || true !== $wasUsed) {
+ return $rpId;
+ }
+
+ return $appId;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function ord;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#sec-authenticator-data
+ */
+class AuthenticatorData
+{
+ private const FLAG_UP = 0b00000001;
+ private const FLAG_RFU1 = 0b00000010;
+ private const FLAG_UV = 0b00000100;
+ private const FLAG_RFU2 = 0b00111000;
+ private const FLAG_AT = 0b01000000;
+ private const FLAG_ED = 0b10000000;
+ /**
+ * @var string
+ */
+ protected $authData;
+
+ /**
+ * @var string
+ */
+ protected $rpIdHash;
+
+ /**
+ * @var string
+ */
+ protected $flags;
+
+ /**
+ * @var int
+ */
+ protected $signCount;
+
+ /**
+ * @var AttestedCredentialData|null
+ */
+ protected $attestedCredentialData;
+
+ /**
+ * @var AuthenticationExtensionsClientOutputs|null
+ */
+ protected $extensions;
+
+ public function __construct(string $authData, string $rpIdHash, string $flags, int $signCount, ?AttestedCredentialData $attestedCredentialData, ?AuthenticationExtensionsClientOutputs $extensions)
+ {
+ $this->rpIdHash = $rpIdHash;
+ $this->flags = $flags;
+ $this->signCount = $signCount;
+ $this->attestedCredentialData = $attestedCredentialData;
+ $this->extensions = $extensions;
+ $this->authData = $authData;
+ }
+
+ public function getAuthData(): string
+ {
+ return $this->authData;
+ }
+
+ public function getRpIdHash(): string
+ {
+ return $this->rpIdHash;
+ }
+
+ public function isUserPresent(): bool
+ {
+ return 0 !== (ord($this->flags) & self::FLAG_UP) ? true : false;
+ }
+
+ public function isUserVerified(): bool
+ {
+ return 0 !== (ord($this->flags) & self::FLAG_UV) ? true : false;
+ }
+
+ public function hasAttestedCredentialData(): bool
+ {
+ return 0 !== (ord($this->flags) & self::FLAG_AT) ? true : false;
+ }
+
+ public function hasExtensions(): bool
+ {
+ return 0 !== (ord($this->flags) & self::FLAG_ED) ? true : false;
+ }
+
+ public function getReservedForFutureUse1(): int
+ {
+ return ord($this->flags) & self::FLAG_RFU1;
+ }
+
+ public function getReservedForFutureUse2(): int
+ {
+ return ord($this->flags) & self::FLAG_RFU2;
+ }
+
+ public function getSignCount(): int
+ {
+ return $this->signCount;
+ }
+
+ public function getAttestedCredentialData(): ?AttestedCredentialData
+ {
+ return $this->attestedCredentialData;
+ }
+
+ public function getExtensions(): ?AuthenticationExtensionsClientOutputs
+ {
+ return null !== $this->extensions && $this->hasExtensions() ? $this->extensions : null;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#authenticatorresponse
+ */
+abstract class AuthenticatorResponse
+{
+ /**
+ * @var CollectedClientData
+ */
+ private $clientDataJSON;
+
+ public function __construct(CollectedClientData $clientDataJSON)
+ {
+ $this->clientDataJSON = $clientDataJSON;
+ }
+
+ public function getClientDataJSON(): CollectedClientData
+ {
+ return $this->clientDataJSON;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\json_decode;
+
+class AuthenticatorSelectionCriteria implements JsonSerializable
+{
+ public const AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE = null;
+ public const AUTHENTICATOR_ATTACHMENT_PLATFORM = 'platform';
+ public const AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM = 'cross-platform';
+
+ public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required';
+ public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred';
+ public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged';
+
+ public const RESIDENT_KEY_REQUIREMENT_NONE = null;
+ public const RESIDENT_KEY_REQUIREMENT_REQUIRED = 'required';
+ public const RESIDENT_KEY_REQUIREMENT_PREFERRED = 'preferred';
+ public const RESIDENT_KEY_REQUIREMENT_DISCOURAGED = 'discouraged';
+
+ /**
+ * @var string|null
+ */
+ private $authenticatorAttachment;
+
+ /**
+ * @var bool
+ */
+ private $requireResidentKey;
+
+ /**
+ * @var string
+ */
+ private $userVerification;
+
+ /**
+ * @var string|null
+ */
+ private $residentKey;
+
+ public function __construct(?string $authenticatorAttachment = null, ?bool $requireResidentKey = null, ?string $userVerification = null, ?string $residentKey = null)
+ {
+ if (null !== $authenticatorAttachment) {
+ @trigger_error('The argument "authenticatorAttachment" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorAttachment".', E_USER_DEPRECATED);
+ }
+ if (null !== $requireResidentKey) {
+ @trigger_error('The argument "requireResidentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRequireResidentKey".', E_USER_DEPRECATED);
+ }
+ if (null !== $userVerification) {
+ @trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED);
+ }
+ if (null !== $residentKey) {
+ @trigger_error('The argument "residentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setResidentKey".', E_USER_DEPRECATED);
+ }
+ $this->authenticatorAttachment = $authenticatorAttachment;
+ $this->requireResidentKey = $requireResidentKey ?? false;
+ $this->userVerification = $userVerification ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED;
+ $this->residentKey = $residentKey ?? self::RESIDENT_KEY_REQUIREMENT_NONE;
+ }
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ public function setAuthenticatorAttachment(?string $authenticatorAttachment): self
+ {
+ $this->authenticatorAttachment = $authenticatorAttachment;
+
+ return $this;
+ }
+
+ public function setRequireResidentKey(bool $requireResidentKey): self
+ {
+ $this->requireResidentKey = $requireResidentKey;
+
+ return $this;
+ }
+
+ public function setUserVerification(string $userVerification): self
+ {
+ $this->userVerification = $userVerification;
+
+ return $this;
+ }
+
+ public function setResidentKey(?string $residentKey): self
+ {
+ $this->residentKey = $residentKey;
+
+ return $this;
+ }
+
+ public function getAuthenticatorAttachment(): ?string
+ {
+ return $this->authenticatorAttachment;
+ }
+
+ public function isRequireResidentKey(): bool
+ {
+ return $this->requireResidentKey;
+ }
+
+ public function getUserVerification(): string
+ {
+ return $this->userVerification;
+ }
+
+ public function getResidentKey(): ?string
+ {
+ return $this->residentKey;
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ return self::create()
+ ->setAuthenticatorAttachment($json['authenticatorAttachment'] ?? null)
+ ->setRequireResidentKey($json['requireResidentKey'] ?? false)
+ ->setUserVerification($json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED)
+ ->setResidentKey($json['residentKey'] ?? self::RESIDENT_KEY_REQUIREMENT_NONE)
+ ;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'requireResidentKey' => $this->requireResidentKey,
+ 'userVerification' => $this->userVerification,
+ ];
+ if (null !== $this->authenticatorAttachment) {
+ $json['authenticatorAttachment'] = $this->authenticatorAttachment;
+ }
+ if (null !== $this->residentKey) {
+ $json['residentKey'] = $this->residentKey;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\CertificateChainChecker;
+
+interface CertificateChainChecker
+{
+ /**
+ * @param string[] $authenticatorCertificates
+ * @param string[] $trustedCertificates
+ */
+ public function check(array $authenticatorCertificates, array $trustedCertificates): void;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\CertificateChainChecker;
+
+use Assert\Assertion;
+use function count;
+use InvalidArgumentException;
+use function is_int;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use RuntimeException;
+use Safe\Exceptions\FilesystemException;
+use function Safe\file_put_contents;
+use function Safe\ksort;
+use function Safe\mkdir;
+use function Safe\rename;
+use function Safe\sprintf;
+use function Safe\tempnam;
+use function Safe\unlink;
+use Symfony\Component\Process\Process;
+
+final class OpenSSLCertificateChainChecker implements CertificateChainChecker
+{
+ /**
+ * @var ClientInterface
+ */
+ private $client;
+
+ /**
+ * @var RequestFactoryInterface
+ */
+ private $requestFactory;
+
+ /**
+ * @var string[]
+ */
+ private $rootCertificates = [];
+
+ public function __construct(ClientInterface $client, RequestFactoryInterface $requestFactory)
+ {
+ $this->client = $client;
+ $this->requestFactory = $requestFactory;
+ }
+
+ public function addRootCertificate(string $certificate): self
+ {
+ $this->rootCertificates[] = $certificate;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $authenticatorCertificates
+ * @param string[] $trustedCertificates
+ */
+ public function check(array $authenticatorCertificates, array $trustedCertificates): void
+ {
+ if (0 === count($trustedCertificates)) {
+ $this->checkCertificatesValidity($authenticatorCertificates, true);
+
+ return;
+ }
+ $this->checkCertificatesValidity($authenticatorCertificates, false);
+
+ $hasCrls = false;
+ $processArguments = ['-no-CAfile', '-no-CApath'];
+
+ $caDirname = $this->createTemporaryDirectory();
+ $processArguments[] = '--CApath';
+ $processArguments[] = $caDirname;
+
+ foreach ($trustedCertificates as $certificate) {
+ $this->saveToTemporaryFile($caDirname, $certificate, 'webauthn-trusted-', '.pem');
+ $crl = $this->getCrls($certificate);
+ if ('' !== $crl) {
+ $hasCrls = true;
+ $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-trusted-crl-', '.crl');
+ }
+ }
+
+ $rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
+ $rehashProcess->run();
+ while ($rehashProcess->isRunning()) {
+ //Just wait
+ }
+ if (!$rehashProcess->isSuccessful()) {
+ throw new InvalidArgumentException('Invalid certificate or certificate chain');
+ }
+
+ $filenames = [];
+ $leafCertificate = array_shift($authenticatorCertificates);
+ $leafFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
+ $crl = $this->getCrls($leafCertificate);
+ if ('' !== $crl) {
+ $hasCrls = true;
+ $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-leaf-crl-', '.pem');
+ }
+ $filenames[] = $leafFilename;
+
+ foreach ($authenticatorCertificates as $certificate) {
+ $untrustedFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
+ $crl = $this->getCrls($certificate);
+ if ('' !== $crl) {
+ $hasCrls = true;
+ $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-untrusted-crl-', '.pem');
+ }
+ $processArguments[] = '-untrusted';
+ $processArguments[] = $untrustedFilename;
+ $filenames[] = $untrustedFilename;
+ }
+
+ $processArguments[] = $leafFilename;
+ if ($hasCrls) {
+ array_unshift($processArguments, '-crl_check');
+ array_unshift($processArguments, '-crl_check_all');
+ //array_unshift($processArguments, '-crl_download');
+ array_unshift($processArguments, '-extended_crl');
+ }
+ array_unshift($processArguments, 'openssl', 'verify');
+
+ $process = new Process($processArguments);
+ $process->run();
+ while ($process->isRunning()) {
+ //Just wait
+ }
+
+ foreach ($filenames as $filename) {
+ try {
+ unlink($filename);
+ } catch (FilesystemException $e) {
+ continue;
+ }
+ }
+ $this->deleteDirectory($caDirname);
+
+ if (!$process->isSuccessful()) {
+ throw new InvalidArgumentException('Invalid certificate or certificate chain');
+ }
+ }
+
+ /**
+ * @param string[] $certificates
+ */
+ private function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
+ {
+ foreach ($certificates as $certificate) {
+ $parsed = openssl_x509_parse($certificate);
+ Assertion::isArray($parsed, 'Unable to read the certificate');
+ if (false === $allowRootCertificate) {
+ $this->checkRootCertificate($parsed);
+ }
+
+ Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
+ Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
+ Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
+ Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
+ }
+ }
+
+ /**
+ * @param array<string, mixed> $parsed
+ */
+ private function checkRootCertificate(array $parsed): void
+ {
+ Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
+ Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
+ $subject = $parsed['subject'];
+ $issuer = $parsed['issuer'];
+ ksort($subject);
+ ksort($issuer);
+ Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
+ }
+
+ private function createTemporaryDirectory(): string
+ {
+ $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
+ if (file_exists($caDir)) {
+ unlink($caDir);
+ }
+ mkdir($caDir);
+ if (!is_dir($caDir)) {
+ throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
+ }
+
+ return $caDir;
+ }
+
+ private function deleteDirectory(string $dirname): void
+ {
+ $rehashProcess = new Process(['rm', '-rf', $dirname]);
+ $rehashProcess->run();
+ while ($rehashProcess->isRunning()) {
+ //Just wait
+ }
+ }
+
+ private function saveToTemporaryFile(string $folder, string $certificate, string $prefix, string $suffix): string
+ {
+ $filename = tempnam($folder, $prefix);
+ rename($filename, $filename.$suffix);
+ file_put_contents($filename.$suffix, $certificate, FILE_APPEND);
+
+ return $filename.$suffix;
+ }
+
+ private function getCrls(string $certificate): string
+ {
+ $parsed = openssl_x509_parse($certificate);
+ if (false === $parsed || !isset($parsed['extensions']['crlDistributionPoints'])) {
+ return '';
+ }
+ $endpoint = $parsed['extensions']['crlDistributionPoints'];
+ $pos = mb_strpos($endpoint, 'URI:');
+ if (!is_int($pos)) {
+ return '';
+ }
+
+ $endpoint = trim(mb_substr($endpoint, $pos + 4));
+ $request = $this->requestFactory->createRequest('GET', $endpoint);
+ $response = $this->client->sendRequest($request);
+
+ if (200 !== $response->getStatusCode()) {
+ return '';
+ }
+
+ return $response->getBody()->getContents();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use function count;
+use function in_array;
+use InvalidArgumentException;
+use RuntimeException;
+use Safe\Exceptions\FilesystemException;
+use function Safe\file_put_contents;
+use function Safe\ksort;
+use function Safe\mkdir;
+use function Safe\rename;
+use function Safe\sprintf;
+use function Safe\tempnam;
+use function Safe\unlink;
+use Symfony\Component\Process\Process;
+
+class CertificateToolbox
+{
+ /**
+ * @deprecated "This method is deprecated since v3.3 and will be removed en v4.0. Please use Webauthn\CertificateChainChecker\CertificateChainChecker instead"
+ *
+ * @param string[] $authenticatorCertificates
+ * @param string[] $trustedCertificates
+ */
+ public static function checkChain(array $authenticatorCertificates, array $trustedCertificates = []): void
+ {
+ if (0 === count($trustedCertificates)) {
+ self::checkCertificatesValidity($authenticatorCertificates, true);
+
+ return;
+ }
+ self::checkCertificatesValidity($authenticatorCertificates, false);
+
+ $processArguments = ['-no-CAfile', '-no-CApath'];
+
+ $caDirname = self::createTemporaryDirectory();
+ $processArguments[] = '--CApath';
+ $processArguments[] = $caDirname;
+
+ foreach ($trustedCertificates as $certificate) {
+ self::prepareCertificate($caDirname, $certificate, 'webauthn-trusted-', '.pem');
+ }
+
+ $rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
+ $rehashProcess->run();
+ while ($rehashProcess->isRunning()) {
+ //Just wait
+ }
+ if (!$rehashProcess->isSuccessful()) {
+ throw new InvalidArgumentException('Invalid certificate or certificate chain');
+ }
+
+ $filenames = [];
+ $leafCertificate = array_shift($authenticatorCertificates);
+ $leafFilename = self::prepareCertificate(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
+ $filenames[] = $leafFilename;
+
+ foreach ($authenticatorCertificates as $certificate) {
+ $untrustedFilename = self::prepareCertificate(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
+ $processArguments[] = '-untrusted';
+ $processArguments[] = $untrustedFilename;
+ $filenames[] = $untrustedFilename;
+ }
+
+ $processArguments[] = $leafFilename;
+ array_unshift($processArguments, 'openssl', 'verify');
+
+ $process = new Process($processArguments);
+ $process->run();
+ while ($process->isRunning()) {
+ //Just wait
+ }
+
+ foreach ($filenames as $filename) {
+ try {
+ unlink($filename);
+ } catch (FilesystemException $e) {
+ continue;
+ }
+ }
+ self::deleteDirectory($caDirname);
+
+ if (!$process->isSuccessful()) {
+ throw new InvalidArgumentException('Invalid certificate or certificate chain');
+ }
+ }
+
+ public static function fixPEMStructure(string $certificate, string $type = 'CERTIFICATE'): string
+ {
+ $pemCert = '-----BEGIN '.$type.'-----'.PHP_EOL;
+ $pemCert .= chunk_split($certificate, 64, PHP_EOL);
+ $pemCert .= '-----END '.$type.'-----'.PHP_EOL;
+
+ return $pemCert;
+ }
+
+ public static function convertDERToPEM(string $certificate, string $type = 'CERTIFICATE'): string
+ {
+ $derCertificate = self::unusedBytesFix($certificate);
+
+ return self::fixPEMStructure(base64_encode($derCertificate), $type);
+ }
+
+ /**
+ * @param string[] $certificates
+ *
+ * @return string[]
+ */
+ public static function convertAllDERToPEM(array $certificates, string $type = 'CERTIFICATE'): array
+ {
+ $certs = [];
+ foreach ($certificates as $publicKey) {
+ $certs[] = self::convertDERToPEM($publicKey, $type);
+ }
+
+ return $certs;
+ }
+
+ private static function unusedBytesFix(string $certificate): string
+ {
+ $certificateHash = hash('sha256', $certificate);
+ if (in_array($certificateHash, self::getCertificateHashes(), true)) {
+ $certificate[mb_strlen($certificate, '8bit') - 257] = "\0";
+ }
+
+ return $certificate;
+ }
+
+ /**
+ * @param string[] $certificates
+ */
+ private static function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
+ {
+ foreach ($certificates as $certificate) {
+ $parsed = openssl_x509_parse($certificate);
+ Assertion::isArray($parsed, 'Unable to read the certificate');
+ if (false === $allowRootCertificate) {
+ self::checkRootCertificate($parsed);
+ }
+
+ Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
+ Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
+ Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
+ Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
+ }
+ }
+
+ /**
+ * @param array<string, mixed> $parsed
+ */
+ private static function checkRootCertificate(array $parsed): void
+ {
+ Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
+ Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
+ $subject = $parsed['subject'];
+ $issuer = $parsed['issuer'];
+ ksort($subject);
+ ksort($issuer);
+ Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
+ }
+
+ /**
+ * @return string[]
+ */
+ private static function getCertificateHashes(): array
+ {
+ return [
+ '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
+ 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
+ '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
+ 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
+ '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
+ 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511',
+ ];
+ }
+
+ private static function createTemporaryDirectory(): string
+ {
+ $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
+ if (file_exists($caDir)) {
+ unlink($caDir);
+ }
+ mkdir($caDir);
+ if (!is_dir($caDir)) {
+ throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
+ }
+
+ return $caDir;
+ }
+
+ private static function deleteDirectory(string $dirname): void
+ {
+ $rehashProcess = new Process(['rm', '-rf', $dirname]);
+ $rehashProcess->run();
+ while ($rehashProcess->isRunning()) {
+ //Just wait
+ }
+ }
+
+ private static function prepareCertificate(string $folder, string $certificate, string $prefix, string $suffix): string
+ {
+ $untrustedFilename = tempnam($folder, $prefix);
+ rename($untrustedFilename, $untrustedFilename.$suffix);
+ file_put_contents($untrustedFilename.$suffix, $certificate, FILE_APPEND);
+ file_put_contents($untrustedFilename.$suffix, PHP_EOL, FILE_APPEND);
+
+ return $untrustedFilename.$suffix;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function array_key_exists;
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use InvalidArgumentException;
+use function Safe\json_decode;
+use function Safe\sprintf;
+use Webauthn\TokenBinding\TokenBinding;
+
+class CollectedClientData
+{
+ /**
+ * @var string
+ */
+ private $rawData;
+
+ /**
+ * @var mixed[]
+ */
+ private $data;
+
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @var string
+ */
+ private $challenge;
+
+ /**
+ * @var string
+ */
+ private $origin;
+
+ /**
+ * @var mixed[]|null
+ */
+ private $tokenBinding;
+
+ /**
+ * @param mixed[] $data
+ */
+ public function __construct(string $rawData, array $data)
+ {
+ $this->type = $this->findData($data, 'type');
+ $this->challenge = $this->findData($data, 'challenge', true, true);
+ $this->origin = $this->findData($data, 'origin');
+ $this->tokenBinding = $this->findData($data, 'tokenBinding', false);
+ $this->rawData = $rawData;
+ $this->data = $data;
+ }
+
+ public static function createFormJson(string $data): self
+ {
+ $rawData = Base64Url::decode($data);
+ $json = json_decode($rawData, true);
+ Assertion::isArray($json, 'Invalid collected client data');
+
+ return new self($rawData, $json);
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getChallenge(): string
+ {
+ return $this->challenge;
+ }
+
+ public function getOrigin(): string
+ {
+ return $this->origin;
+ }
+
+ public function getTokenBinding(): ?TokenBinding
+ {
+ return null === $this->tokenBinding ? null : TokenBinding::createFormArray($this->tokenBinding);
+ }
+
+ public function getRawData(): string
+ {
+ return $this->rawData;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function all(): array
+ {
+ return array_keys($this->data);
+ }
+
+ public function has(string $key): bool
+ {
+ return array_key_exists($key, $this->data);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(string $key)
+ {
+ if (!$this->has($key)) {
+ throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key));
+ }
+
+ return $this->data[$key];
+ }
+
+ /**
+ * @param mixed[] $json
+ *
+ * @return mixed|null
+ */
+ private function findData(array $json, string $key, bool $isRequired = true, bool $isB64 = false)
+ {
+ if (!array_key_exists($key, $json)) {
+ if ($isRequired) {
+ throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key));
+ }
+
+ return;
+ }
+
+ return $isB64 ? Base64Url::decode($json[$key]) : $json[$key];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\Counter;
+
+use Webauthn\PublicKeyCredentialSource;
+
+interface CounterChecker
+{
+ public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\Counter;
+
+use Assert\Assertion;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Throwable;
+use Webauthn\PublicKeyCredentialSource;
+
+final class ThrowExceptionIfInvalid implements CounterChecker
+{
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(?LoggerInterface $logger = null)
+ {
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void
+ {
+ try {
+ Assertion::greaterThan($currentCounter, $publicKeyCredentialSource->getCounter(), 'Invalid counter.');
+ } catch (Throwable $throwable) {
+ $this->logger->error('The counter is invalid', [
+ 'current' => $currentCounter,
+ 'new' => $publicKeyCredentialSource->getCounter(),
+ ]);
+ throw $throwable;
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+/**
+ * @see https://w3c.github.io/webappsec-credential-management/#credential
+ */
+abstract class Credential
+{
+ /**
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var string
+ */
+ protected $type;
+
+ public function __construct(string $id, string $type)
+ {
+ $this->id = $id;
+ $this->type = $type;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function Safe\json_encode;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#iface-pkcredential
+ */
+class PublicKeyCredential extends Credential
+{
+ /**
+ * @var string
+ */
+ protected $rawId;
+
+ /**
+ * @var AuthenticatorResponse
+ */
+ protected $response;
+
+ public function __construct(string $id, string $type, string $rawId, AuthenticatorResponse $response)
+ {
+ parent::__construct($id, $type);
+ $this->rawId = $rawId;
+ $this->response = $response;
+ }
+
+ public function __toString()
+ {
+ return json_encode($this);
+ }
+
+ public function getRawId(): string
+ {
+ return $this->rawId;
+ }
+
+ public function getResponse(): AuthenticatorResponse
+ {
+ return $this->response;
+ }
+
+ /**
+ * @param string[] $transport
+ */
+ public function getPublicKeyCredentialDescriptor(array $transport = []): PublicKeyCredentialDescriptor
+ {
+ return new PublicKeyCredentialDescriptor($this->getType(), $this->getRawId(), $transport);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function count;
+use function Safe\json_decode;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+
+class PublicKeyCredentialCreationOptions extends PublicKeyCredentialOptions
+{
+ public const ATTESTATION_CONVEYANCE_PREFERENCE_NONE = 'none';
+ public const ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT = 'indirect';
+ public const ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT = 'direct';
+ public const ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE = 'enterprise';
+
+ /**
+ * @var PublicKeyCredentialRpEntity
+ */
+ private $rp;
+
+ /**
+ * @var PublicKeyCredentialUserEntity
+ */
+ private $user;
+
+ /**
+ * @var PublicKeyCredentialParameters[]
+ */
+ private $pubKeyCredParams = [];
+
+ /**
+ * @var PublicKeyCredentialDescriptor[]
+ */
+ private $excludeCredentials = [];
+
+ /**
+ * @var AuthenticatorSelectionCriteria
+ */
+ private $authenticatorSelection;
+
+ /**
+ * @var string
+ */
+ private $attestation;
+
+ /**
+ * @param PublicKeyCredentialParameters[] $pubKeyCredParams
+ * @param PublicKeyCredentialDescriptor[] $excludeCredentials
+ */
+ public function __construct(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams, ?int $timeout = null, array $excludeCredentials = [], ?AuthenticatorSelectionCriteria $authenticatorSelection = null, ?string $attestation = null, ?AuthenticationExtensionsClientInputs $extensions = null)
+ {
+ if (0 !== count($excludeCredentials)) {
+ @trigger_error('The argument "excludeCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "excludeCredentials" or "excludeCredential".', E_USER_DEPRECATED);
+ }
+ if (null !== $authenticatorSelection) {
+ @trigger_error('The argument "authenticatorSelection" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorSelection".', E_USER_DEPRECATED);
+ }
+ if (null !== $attestation) {
+ @trigger_error('The argument "attestation" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAttestation".', E_USER_DEPRECATED);
+ }
+ parent::__construct($challenge, $timeout, $extensions);
+ $this->rp = $rp;
+ $this->user = $user;
+ $this->pubKeyCredParams = $pubKeyCredParams;
+ $this->authenticatorSelection = $authenticatorSelection ?? new AuthenticatorSelectionCriteria();
+ $this->attestation = $attestation ?? self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
+ $this->excludeCredentials($excludeCredentials)
+ ;
+ }
+
+ /**
+ * @param PublicKeyCredentialParameters[] $pubKeyCredParams
+ */
+ public static function create(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams): self
+ {
+ return new self($rp, $user, $challenge, $pubKeyCredParams);
+ }
+
+ public function addPubKeyCredParam(PublicKeyCredentialParameters $pubKeyCredParam): self
+ {
+ $this->pubKeyCredParams[] = $pubKeyCredParam;
+
+ return $this;
+ }
+
+ /**
+ * @param PublicKeyCredentialParameters[] $pubKeyCredParams
+ */
+ public function addPubKeyCredParams(array $pubKeyCredParams): self
+ {
+ foreach ($pubKeyCredParams as $pubKeyCredParam) {
+ $this->addPubKeyCredParam($pubKeyCredParam);
+ }
+
+ return $this;
+ }
+
+ public function excludeCredential(PublicKeyCredentialDescriptor $excludeCredential): self
+ {
+ $this->excludeCredentials[] = $excludeCredential;
+
+ return $this;
+ }
+
+ /**
+ * @param PublicKeyCredentialDescriptor[] $excludeCredentials
+ */
+ public function excludeCredentials(array $excludeCredentials): self
+ {
+ foreach ($excludeCredentials as $excludeCredential) {
+ $this->excludeCredential($excludeCredential);
+ }
+
+ return $this;
+ }
+
+ public function setAuthenticatorSelection(AuthenticatorSelectionCriteria $authenticatorSelection): self
+ {
+ $this->authenticatorSelection = $authenticatorSelection;
+
+ return $this;
+ }
+
+ public function setAttestation(string $attestation): self
+ {
+ Assertion::inArray($attestation, [
+ self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
+ self::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT,
+ self::ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT,
+ self::ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE,
+ ], 'Invalid attestation conveyance mode');
+ $this->attestation = $attestation;
+
+ return $this;
+ }
+
+ public function getRp(): PublicKeyCredentialRpEntity
+ {
+ return $this->rp;
+ }
+
+ public function getUser(): PublicKeyCredentialUserEntity
+ {
+ return $this->user;
+ }
+
+ /**
+ * @return PublicKeyCredentialParameters[]
+ */
+ public function getPubKeyCredParams(): array
+ {
+ return $this->pubKeyCredParams;
+ }
+
+ /**
+ * @return PublicKeyCredentialDescriptor[]
+ */
+ public function getExcludeCredentials(): array
+ {
+ return $this->excludeCredentials;
+ }
+
+ public function getAuthenticatorSelection(): AuthenticatorSelectionCriteria
+ {
+ return $this->authenticatorSelection;
+ }
+
+ public function getAttestation(): string
+ {
+ return $this->attestation;
+ }
+
+ public static function createFromString(string $data): PublicKeyCredentialOptions
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ public static function createFromArray(array $json): PublicKeyCredentialOptions
+ {
+ Assertion::keyExists($json, 'rp', 'Invalid input. "rp" is missing.');
+ Assertion::keyExists($json, 'pubKeyCredParams', 'Invalid input. "pubKeyCredParams" is missing.');
+ Assertion::isArray($json['pubKeyCredParams'], 'Invalid input. "pubKeyCredParams" is not an array.');
+ Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.');
+ Assertion::keyExists($json, 'attestation', 'Invalid input. "attestation" is missing.');
+ Assertion::keyExists($json, 'user', 'Invalid input. "user" is missing.');
+ Assertion::keyExists($json, 'authenticatorSelection', 'Invalid input. "authenticatorSelection" is missing.');
+
+ $pubKeyCredParams = [];
+ foreach ($json['pubKeyCredParams'] as $pubKeyCredParam) {
+ $pubKeyCredParams[] = PublicKeyCredentialParameters::createFromArray($pubKeyCredParam);
+ }
+ $excludeCredentials = [];
+ if (isset($json['excludeCredentials'])) {
+ foreach ($json['excludeCredentials'] as $excludeCredential) {
+ $excludeCredentials[] = PublicKeyCredentialDescriptor::createFromArray($excludeCredential);
+ }
+ }
+
+ return self::create(
+ PublicKeyCredentialRpEntity::createFromArray($json['rp']),
+ PublicKeyCredentialUserEntity::createFromArray($json['user']),
+ Base64Url::decode($json['challenge']),
+ $pubKeyCredParams
+ )
+ ->excludeCredentials($excludeCredentials)
+ ->setAuthenticatorSelection(AuthenticatorSelectionCriteria::createFromArray($json['authenticatorSelection']))
+ ->setAttestation($json['attestation'])
+ ->setTimeout($json['timeout'] ?? null)
+ ->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs())
+ ;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'rp' => $this->rp->jsonSerialize(),
+ 'pubKeyCredParams' => array_map(static function (PublicKeyCredentialParameters $object): array {
+ return $object->jsonSerialize();
+ }, $this->pubKeyCredParams),
+ 'challenge' => Base64Url::encode($this->challenge),
+ 'attestation' => $this->attestation,
+ 'user' => $this->user->jsonSerialize(),
+ 'authenticatorSelection' => $this->authenticatorSelection->jsonSerialize(),
+ ];
+
+ if (0 !== count($this->excludeCredentials)) {
+ $json['excludeCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->excludeCredentials);
+ }
+
+ if (0 !== $this->extensions->count()) {
+ $json['extensions'] = $this->extensions;
+ }
+
+ if (null !== $this->timeout) {
+ $json['timeout'] = $this->timeout;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function count;
+use JsonSerializable;
+use function Safe\json_decode;
+
+class PublicKeyCredentialDescriptor implements JsonSerializable
+{
+ public const CREDENTIAL_TYPE_PUBLIC_KEY = 'public-key';
+
+ public const AUTHENTICATOR_TRANSPORT_USB = 'usb';
+ public const AUTHENTICATOR_TRANSPORT_NFC = 'nfc';
+ public const AUTHENTICATOR_TRANSPORT_BLE = 'ble';
+ public const AUTHENTICATOR_TRANSPORT_INTERNAL = 'internal';
+
+ /**
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var string[]
+ */
+ protected $transports;
+
+ /**
+ * @param string[] $transports
+ */
+ public function __construct(string $type, string $id, array $transports = [])
+ {
+ $this->type = $type;
+ $this->id = $id;
+ $this->transports = $transports;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getTransports(): array
+ {
+ return $this->transports;
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.');
+ Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.');
+
+ return new self(
+ $json['type'],
+ Base64Url::decode($json['id']),
+ $json['transports'] ?? []
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'type' => $this->type,
+ 'id' => Base64Url::encode($this->id),
+ ];
+ if (0 !== count($this->transports)) {
+ $json['transports'] = $this->transports;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function array_key_exists;
+use ArrayIterator;
+use Assert\Assertion;
+use function count;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+use JsonSerializable;
+use function Safe\json_decode;
+
+class PublicKeyCredentialDescriptorCollection implements JsonSerializable, Countable, IteratorAggregate
+{
+ /**
+ * @var PublicKeyCredentialDescriptor[]
+ */
+ private $publicKeyCredentialDescriptors = [];
+
+ public function add(PublicKeyCredentialDescriptor $publicKeyCredentialDescriptor): void
+ {
+ $this->publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->getId()] = $publicKeyCredentialDescriptor;
+ }
+
+ public function has(string $id): bool
+ {
+ return array_key_exists($id, $this->publicKeyCredentialDescriptors);
+ }
+
+ public function remove(string $id): void
+ {
+ if (!$this->has($id)) {
+ return;
+ }
+
+ unset($this->publicKeyCredentialDescriptors[$id]);
+ }
+
+ /**
+ * @return Iterator<string, PublicKeyCredentialDescriptor>
+ */
+ public function getIterator(): Iterator
+ {
+ return new ArrayIterator($this->publicKeyCredentialDescriptors);
+ }
+
+ public function count(int $mode = COUNT_NORMAL): int
+ {
+ return count($this->publicKeyCredentialDescriptors, $mode);
+ }
+
+ /**
+ * @return array[]
+ */
+ public function jsonSerialize(): array
+ {
+ return array_map(static function (PublicKeyCredentialDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->publicKeyCredentialDescriptors);
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ $collection = new self();
+ foreach ($json as $item) {
+ $collection->add(PublicKeyCredentialDescriptor::createFromArray($item));
+ }
+
+ return $collection;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use JsonSerializable;
+
+abstract class PublicKeyCredentialEntity implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var string|null
+ */
+ protected $icon;
+
+ public function __construct(string $name, ?string $icon)
+ {
+ $this->name = $name;
+ $this->icon = $icon;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getIcon(): ?string
+ {
+ return $this->icon;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'name' => $this->name,
+ ];
+ if (null !== $this->icon) {
+ $json['icon'] = $this->icon;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use function array_key_exists;
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use CBOR\Decoder;
+use CBOR\MapObject;
+use CBOR\OtherObject\OtherObjectManager;
+use CBOR\Tag\TagObjectManager;
+use InvalidArgumentException;
+use function ord;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Ramsey\Uuid\Uuid;
+use function Safe\json_decode;
+use function Safe\sprintf;
+use function Safe\unpack;
+use Throwable;
+use Webauthn\AttestationStatement\AttestationObjectLoader;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
+
+class PublicKeyCredentialLoader
+{
+ private const FLAG_AT = 0b01000000;
+ private const FLAG_ED = 0b10000000;
+
+ /**
+ * @var AttestationObjectLoader
+ */
+ private $attestationObjectLoader;
+
+ /**
+ * @var Decoder
+ */
+ private $decoder;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(AttestationObjectLoader $attestationObjectLoader, ?LoggerInterface $logger = null)
+ {
+ if (null !== $logger) {
+ @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED);
+ }
+ $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
+ $this->attestationObjectLoader = $attestationObjectLoader;
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public static function create(AttestationObjectLoader $attestationObjectLoader): self
+ {
+ return new self($attestationObjectLoader);
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public function loadArray(array $json): PublicKeyCredential
+ {
+ $this->logger->info('Trying to load data from an array', ['data' => $json]);
+ try {
+ foreach (['id', 'rawId', 'type'] as $key) {
+ Assertion::keyExists($json, $key, sprintf('The parameter "%s" is missing', $key));
+ Assertion::string($json[$key], sprintf('The parameter "%s" shall be a string', $key));
+ }
+ Assertion::keyExists($json, 'response', 'The parameter "response" is missing');
+ Assertion::isArray($json['response'], 'The parameter "response" shall be an array');
+ Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type']));
+
+ $id = Base64Url::decode($json['id']);
+ $rawId = Base64Url::decode($json['rawId']);
+ Assertion::true(hash_equals($id, $rawId));
+
+ $publicKeyCredential = new PublicKeyCredential(
+ $json['id'],
+ $json['type'],
+ $rawId,
+ $this->createResponse($json['response'])
+ );
+ $this->logger->info('The data has been loaded');
+ $this->logger->debug('Public Key Credential', ['publicKeyCredential' => $publicKeyCredential]);
+
+ return $publicKeyCredential;
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ public function load(string $data): PublicKeyCredential
+ {
+ $this->logger->info('Trying to load data from a string', ['data' => $data]);
+ try {
+ $json = json_decode($data, true);
+
+ return $this->loadArray($json);
+ } catch (Throwable $throwable) {
+ $this->logger->error('An error occurred', [
+ 'exception' => $throwable,
+ ]);
+ throw $throwable;
+ }
+ }
+
+ /**
+ * @param mixed[] $response
+ */
+ private function createResponse(array $response): AuthenticatorResponse
+ {
+ Assertion::keyExists($response, 'clientDataJSON', 'Invalid data. The parameter "clientDataJSON" is missing');
+ Assertion::string($response['clientDataJSON'], 'Invalid data. The parameter "clientDataJSON" is invalid');
+ switch (true) {
+ case array_key_exists('attestationObject', $response):
+ Assertion::string($response['attestationObject'], 'Invalid data. The parameter "attestationObject " is invalid');
+ $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']);
+
+ return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject);
+ case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response):
+ $authData = Base64Url::decode($response['authenticatorData']);
+
+ $authDataStream = new StringStream($authData);
+ $rp_id_hash = $authDataStream->read(32);
+ $flags = $authDataStream->read(1);
+ $signCount = $authDataStream->read(4);
+ $signCount = unpack('N', $signCount)[1];
+
+ $attestedCredentialData = null;
+ if (0 !== (ord($flags) & self::FLAG_AT)) {
+ $aaguid = Uuid::fromBytes($authDataStream->read(16));
+ $credentialLength = $authDataStream->read(2);
+ $credentialLength = unpack('n', $credentialLength)[1];
+ $credentialId = $authDataStream->read($credentialLength);
+ $credentialPublicKey = $this->decoder->decode($authDataStream);
+ Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
+ $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
+ }
+
+ $extension = null;
+ if (0 !== (ord($flags) & self::FLAG_ED)) {
+ $extension = $this->decoder->decode($authDataStream);
+ $extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
+ }
+ Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.');
+ $authDataStream->close();
+ $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
+
+ return new AuthenticatorAssertionResponse(
+ CollectedClientData::createFormJson($response['clientDataJSON']),
+ $authenticatorData,
+ Base64Url::decode($response['signature']),
+ $response['userHandle'] ?? null
+ );
+ default:
+ throw new InvalidArgumentException('Unable to create the response object');
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use JsonSerializable;
+use Webauthn\AuthenticationExtensions\AuthenticationExtension;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+
+abstract class PublicKeyCredentialOptions implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ protected $challenge;
+
+ /**
+ * @var int|null
+ */
+ protected $timeout;
+
+ /**
+ * @var AuthenticationExtensionsClientInputs
+ */
+ protected $extensions;
+
+ public function __construct(string $challenge, ?int $timeout = null, ?AuthenticationExtensionsClientInputs $extensions = null)
+ {
+ if (null !== $timeout) {
+ @trigger_error('The argument "timeout" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setTimeout".', E_USER_DEPRECATED);
+ }
+ if (null !== $extensions) {
+ @trigger_error('The argument "extensions" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addExtension" or "addExtensions".', E_USER_DEPRECATED);
+ }
+ $this->challenge = $challenge;
+ $this->setTimeout($timeout);
+ $this->extensions = $extensions ?? new AuthenticationExtensionsClientInputs();
+ }
+
+ public function setTimeout(?int $timeout): self
+ {
+ $this->timeout = $timeout;
+
+ return $this;
+ }
+
+ public function addExtension(AuthenticationExtension $extension): self
+ {
+ $this->extensions->add($extension);
+
+ return $this;
+ }
+
+ /**
+ * @param AuthenticationExtension[] $extensions
+ */
+ public function addExtensions(array $extensions): self
+ {
+ foreach ($extensions as $extension) {
+ $this->addExtension($extension);
+ }
+
+ return $this;
+ }
+
+ public function setExtensions(AuthenticationExtensionsClientInputs $extensions): self
+ {
+ $this->extensions = $extensions;
+
+ return $this;
+ }
+
+ public function getChallenge(): string
+ {
+ return $this->challenge;
+ }
+
+ public function getTimeout(): ?int
+ {
+ return $this->timeout;
+ }
+
+ public function getExtensions(): AuthenticationExtensionsClientInputs
+ {
+ return $this->extensions;
+ }
+
+ abstract public static function createFromString(string $data): self;
+
+ /**
+ * @param mixed[] $json
+ */
+ abstract public static function createFromArray(array $json): self;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use JsonSerializable;
+use function Safe\json_decode;
+
+class PublicKeyCredentialParameters implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @var int
+ */
+ private $alg;
+
+ public function __construct(string $type, int $alg)
+ {
+ $this->type = $type;
+ $this->alg = $alg;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getAlg(): int
+ {
+ return $this->alg;
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.');
+ Assertion::string($json['type'], 'Invalid input. "type" is not a string.');
+ Assertion::keyExists($json, 'alg', 'Invalid input. "alg" is missing.');
+ Assertion::integer($json['alg'], 'Invalid input. "alg" is not an integer.');
+
+ return new self(
+ $json['type'],
+ $json['alg']
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => $this->type,
+ 'alg' => $this->alg,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function count;
+use function Safe\json_decode;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+
+class PublicKeyCredentialRequestOptions extends PublicKeyCredentialOptions
+{
+ public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required';
+ public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred';
+ public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged';
+
+ /**
+ * @var string|null
+ */
+ private $rpId;
+
+ /**
+ * @var PublicKeyCredentialDescriptor[]
+ */
+ private $allowCredentials = [];
+
+ /**
+ * @var string|null
+ */
+ private $userVerification;
+
+ /**
+ * @param PublicKeyCredentialDescriptor[] $allowCredentials
+ */
+ public function __construct(string $challenge, ?int $timeout = null, ?string $rpId = null, array $allowCredentials = [], ?string $userVerification = null, ?AuthenticationExtensionsClientInputs $extensions = null)
+ {
+ if (0 !== count($allowCredentials)) {
+ @trigger_error('The argument "allowCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addAllowedCredentials" or "addAllowedCredential".', E_USER_DEPRECATED);
+ }
+ if (null !== $rpId) {
+ @trigger_error('The argument "rpId" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRpId".', E_USER_DEPRECATED);
+ }
+ if (null !== $userVerification) {
+ @trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED);
+ }
+ parent::__construct($challenge, $timeout, $extensions);
+ $this
+ ->setRpId($rpId)
+ ->allowCredentials($allowCredentials)
+ ->setUserVerification($userVerification)
+ ;
+ }
+
+ public static function create(string $challenge): self
+ {
+ return new self($challenge);
+ }
+
+ public function setRpId(?string $rpId): self
+ {
+ $this->rpId = $rpId;
+
+ return $this;
+ }
+
+ public function allowCredential(PublicKeyCredentialDescriptor $allowCredential): self
+ {
+ $this->allowCredentials[] = $allowCredential;
+
+ return $this;
+ }
+
+ /**
+ * @param PublicKeyCredentialDescriptor[] $allowCredentials
+ */
+ public function allowCredentials(array $allowCredentials): self
+ {
+ foreach ($allowCredentials as $allowCredential) {
+ $this->allowCredential($allowCredential);
+ }
+
+ return $this;
+ }
+
+ public function setUserVerification(?string $userVerification): self
+ {
+ if (null === $userVerification) {
+ $this->rpId = null;
+
+ return $this;
+ }
+ Assertion::inArray($userVerification, [
+ self::USER_VERIFICATION_REQUIREMENT_REQUIRED,
+ self::USER_VERIFICATION_REQUIREMENT_PREFERRED,
+ self::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
+ ], 'Invalid user verification requirement');
+ $this->userVerification = $userVerification;
+
+ return $this;
+ }
+
+ public function getRpId(): ?string
+ {
+ return $this->rpId;
+ }
+
+ /**
+ * @return PublicKeyCredentialDescriptor[]
+ */
+ public function getAllowCredentials(): array
+ {
+ return $this->allowCredentials;
+ }
+
+ public function getUserVerification(): ?string
+ {
+ return $this->userVerification;
+ }
+
+ public static function createFromString(string $data): PublicKeyCredentialOptions
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): PublicKeyCredentialOptions
+ {
+ Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.');
+
+ $allowCredentials = [];
+ $allowCredentialList = $json['allowCredentials'] ?? [];
+ foreach ($allowCredentialList as $allowCredential) {
+ $allowCredentials[] = PublicKeyCredentialDescriptor::createFromArray($allowCredential);
+ }
+
+ return self::create(Base64Url::decode($json['challenge']))
+ ->setRpId($json['rpId'] ?? null)
+ ->allowCredentials($allowCredentials)
+ ->setUserVerification($json['userVerification'] ?? null)
+ ->setTimeout($json['timeout'] ?? null)
+ ->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs())
+ ;
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'challenge' => Base64Url::encode($this->challenge),
+ ];
+
+ if (null !== $this->rpId) {
+ $json['rpId'] = $this->rpId;
+ }
+
+ if (null !== $this->userVerification) {
+ $json['userVerification'] = $this->userVerification;
+ }
+
+ if (0 !== count($this->allowCredentials)) {
+ $json['allowCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array {
+ return $object->jsonSerialize();
+ }, $this->allowCredentials);
+ }
+
+ if (0 !== $this->extensions->count()) {
+ $json['extensions'] = $this->extensions->jsonSerialize();
+ }
+
+ if (null !== $this->timeout) {
+ $json['timeout'] = $this->timeout;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+
+class PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity
+{
+ /**
+ * @var string|null
+ */
+ protected $id;
+
+ public function __construct(string $name, ?string $id = null, ?string $icon = null)
+ {
+ parent::__construct($name, $icon);
+ $this->id = $id;
+ }
+
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.');
+
+ return new self(
+ $json['name'],
+ $json['id'] ?? null,
+ $json['icon'] ?? null
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = parent::jsonSerialize();
+ if (null !== $this->id) {
+ $json['id'] = $this->id;
+ }
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use InvalidArgumentException;
+use JsonSerializable;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+use function Safe\base64_decode;
+use function Safe\sprintf;
+use Throwable;
+use Webauthn\TrustPath\TrustPath;
+use Webauthn\TrustPath\TrustPathLoader;
+
+/**
+ * @see https://www.w3.org/TR/webauthn/#iface-pkcredential
+ */
+class PublicKeyCredentialSource implements JsonSerializable
+{
+ /**
+ * @var string
+ */
+ protected $publicKeyCredentialId;
+
+ /**
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * @var string[]
+ */
+ protected $transports;
+
+ /**
+ * @var string
+ */
+ protected $attestationType;
+
+ /**
+ * @var TrustPath
+ */
+ protected $trustPath;
+
+ /**
+ * @var UuidInterface
+ */
+ protected $aaguid;
+
+ /**
+ * @var string
+ */
+ protected $credentialPublicKey;
+
+ /**
+ * @var string
+ */
+ protected $userHandle;
+
+ /**
+ * @var int
+ */
+ protected $counter;
+
+ /**
+ * @var array|null
+ */
+ protected $otherUI;
+
+ /**
+ * @param string[] $transports
+ */
+ public function __construct(string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, UuidInterface $aaguid, string $credentialPublicKey, string $userHandle, int $counter, ?array $otherUI = null)
+ {
+ $this->publicKeyCredentialId = $publicKeyCredentialId;
+ $this->type = $type;
+ $this->transports = $transports;
+ $this->aaguid = $aaguid;
+ $this->credentialPublicKey = $credentialPublicKey;
+ $this->userHandle = $userHandle;
+ $this->counter = $counter;
+ $this->attestationType = $attestationType;
+ $this->trustPath = $trustPath;
+ $this->otherUI = $otherUI;
+ }
+
+ public function getPublicKeyCredentialId(): string
+ {
+ return $this->publicKeyCredentialId;
+ }
+
+ public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor
+ {
+ return new PublicKeyCredentialDescriptor(
+ $this->type,
+ $this->publicKeyCredentialId,
+ $this->transports
+ );
+ }
+
+ public function getAttestationType(): string
+ {
+ return $this->attestationType;
+ }
+
+ public function getTrustPath(): TrustPath
+ {
+ return $this->trustPath;
+ }
+
+ public function getAttestedCredentialData(): AttestedCredentialData
+ {
+ return new AttestedCredentialData(
+ $this->aaguid,
+ $this->publicKeyCredentialId,
+ $this->credentialPublicKey
+ );
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getTransports(): array
+ {
+ return $this->transports;
+ }
+
+ public function getAaguid(): UuidInterface
+ {
+ return $this->aaguid;
+ }
+
+ public function getCredentialPublicKey(): string
+ {
+ return $this->credentialPublicKey;
+ }
+
+ public function getUserHandle(): string
+ {
+ return $this->userHandle;
+ }
+
+ public function getCounter(): int
+ {
+ return $this->counter;
+ }
+
+ public function setCounter(int $counter): void
+ {
+ $this->counter = $counter;
+ }
+
+ public function getOtherUI(): ?array
+ {
+ return $this->otherUI;
+ }
+
+ public function setOtherUI(?array $otherUI): self
+ {
+ $this->otherUI = $otherUI;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed[] $data
+ */
+ public static function createFromArray(array $data): self
+ {
+ $keys = array_keys(get_class_vars(self::class));
+ foreach ($keys as $key) {
+ if ('otherUI' === $key) {
+ continue;
+ }
+ Assertion::keyExists($data, $key, sprintf('The parameter "%s" is missing', $key));
+ }
+ switch (true) {
+ case 36 === mb_strlen($data['aaguid'], '8bit'):
+ $uuid = Uuid::fromString($data['aaguid']);
+ break;
+ default: // Kept for compatibility with old format
+ $decoded = base64_decode($data['aaguid'], true);
+ $uuid = Uuid::fromBytes($decoded);
+ }
+
+ try {
+ return new self(
+ Base64Url::decode($data['publicKeyCredentialId']),
+ $data['type'],
+ $data['transports'],
+ $data['attestationType'],
+ TrustPathLoader::loadTrustPath($data['trustPath']),
+ $uuid,
+ Base64Url::decode($data['credentialPublicKey']),
+ Base64Url::decode($data['userHandle']),
+ $data['counter'],
+ $data['otherUI'] ?? null
+ );
+ } catch (Throwable $throwable) {
+ throw new InvalidArgumentException('Unable to load the data', $throwable->getCode(), $throwable);
+ }
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'publicKeyCredentialId' => Base64Url::encode($this->publicKeyCredentialId),
+ 'type' => $this->type,
+ 'transports' => $this->transports,
+ 'attestationType' => $this->attestationType,
+ 'trustPath' => $this->trustPath->jsonSerialize(),
+ 'aaguid' => $this->aaguid->toString(),
+ 'credentialPublicKey' => Base64Url::encode($this->credentialPublicKey),
+ 'userHandle' => Base64Url::encode($this->userHandle),
+ 'counter' => $this->counter,
+ 'otherUI' => $this->otherUI,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+interface PublicKeyCredentialSourceRepository
+{
+ public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource;
+
+ /**
+ * @return PublicKeyCredentialSource[]
+ */
+ public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array;
+
+ public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use function Safe\base64_decode;
+use function Safe\json_decode;
+
+class PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity
+{
+ /**
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var string
+ */
+ protected $displayName;
+
+ public function __construct(string $name, string $id, string $displayName, ?string $icon = null)
+ {
+ parent::__construct($name, $icon);
+ Assertion::maxLength($id, 64, 'User ID max length is 64 bytes', 'id', '8bit');
+ $this->id = $id;
+ $this->displayName = $displayName;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getDisplayName(): string
+ {
+ return $this->displayName;
+ }
+
+ public static function createFromString(string $data): self
+ {
+ $data = json_decode($data, true);
+ Assertion::isArray($data, 'Invalid data');
+
+ return self::createFromArray($data);
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFromArray(array $json): self
+ {
+ Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.');
+ Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.');
+ Assertion::keyExists($json, 'displayName', 'Invalid input. "displayName" is missing.');
+ $id = base64_decode($json['id'], true);
+
+ return new self(
+ $json['name'],
+ $id,
+ $json['displayName'],
+ $json['icon'] ?? null
+ );
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ $json = parent::jsonSerialize();
+ $json['id'] = base64_encode($this->id);
+ $json['displayName'] = $this->displayName;
+
+ return $json;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use Cose\Algorithm\Algorithm;
+use Cose\Algorithm\ManagerFactory;
+use Cose\Algorithm\Signature\ECDSA;
+use Cose\Algorithm\Signature\EdDSA;
+use Cose\Algorithm\Signature\RSA;
+use Jose\Component\KeyManagement\JWKFactory;
+use Jose\Component\Signature\Algorithm\RS256;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport;
+use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport;
+use Webauthn\AttestationStatement\AttestationObjectLoader;
+use Webauthn\AttestationStatement\AttestationStatementSupportManager;
+use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport;
+use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
+use Webauthn\AttestationStatement\PackedAttestationStatementSupport;
+use Webauthn\AttestationStatement\TPMAttestationStatementSupport;
+use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
+use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
+use Webauthn\Counter\CounterChecker;
+use Webauthn\MetadataService\MetadataStatementRepository;
+use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
+use Webauthn\TokenBinding\TokenBindingHandler;
+
+class Server
+{
+ /**
+ * @var int
+ */
+ public $timeout = 60000;
+
+ /**
+ * @var int
+ */
+ public $challengeSize = 32;
+
+ /**
+ * @var PublicKeyCredentialRpEntity
+ */
+ private $rpEntity;
+
+ /**
+ * @var ManagerFactory
+ */
+ private $coseAlgorithmManagerFactory;
+
+ /**
+ * @var PublicKeyCredentialSourceRepository
+ */
+ private $publicKeyCredentialSourceRepository;
+
+ /**
+ * @var TokenBindingHandler
+ */
+ private $tokenBindingHandler;
+
+ /**
+ * @var ExtensionOutputCheckerHandler
+ */
+ private $extensionOutputCheckerHandler;
+
+ /**
+ * @var string[]
+ */
+ private $selectedAlgorithms;
+
+ /**
+ * @var MetadataStatementRepository|null
+ */
+ private $metadataStatementRepository;
+
+ /**
+ * @var ClientInterface|null
+ */
+ private $httpClient;
+
+ /**
+ * @var string|null
+ */
+ private $googleApiKey;
+
+ /**
+ * @var RequestFactoryInterface|null
+ */
+ private $requestFactory;
+
+ /**
+ * @var CounterChecker|null
+ */
+ private $counterChecker;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var string[]
+ */
+ private $securedRelyingPartyId = [];
+
+ public function __construct(PublicKeyCredentialRpEntity $relyingParty, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?MetadataStatementRepository $metadataStatementRepository = null)
+ {
+ if (null !== $metadataStatementRepository) {
+ @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED);
+ }
+ $this->rpEntity = $relyingParty;
+ $this->logger = new NullLogger();
+
+ $this->coseAlgorithmManagerFactory = new ManagerFactory();
+ $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1());
+ $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256());
+ $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384());
+ $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512());
+ $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256());
+ $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384());
+ $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512());
+ $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256());
+ $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K());
+ $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384());
+ $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512());
+ $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519());
+
+ $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519'];
+ $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository;
+ $this->tokenBindingHandler = new IgnoreTokenBindingHandler();
+ $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler();
+ $this->metadataStatementRepository = $metadataStatementRepository;
+ }
+
+ public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self
+ {
+ $this->metadataStatementRepository = $metadataStatementRepository;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $selectedAlgorithms
+ */
+ public function setSelectedAlgorithms(array $selectedAlgorithms): self
+ {
+ $this->selectedAlgorithms = $selectedAlgorithms;
+
+ return $this;
+ }
+
+ public function setTokenBindingHandler(TokenBindingHandler $tokenBindingHandler): self
+ {
+ $this->tokenBindingHandler = $tokenBindingHandler;
+
+ return $this;
+ }
+
+ public function addAlgorithm(string $alias, Algorithm $algorithm): self
+ {
+ $this->coseAlgorithmManagerFactory->add($alias, $algorithm);
+ $this->selectedAlgorithms[] = $alias;
+ $this->selectedAlgorithms = array_unique($this->selectedAlgorithms);
+
+ return $this;
+ }
+
+ public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): self
+ {
+ $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $securedRelyingPartyId
+ */
+ public function setSecuredRelyingPartyId(array $securedRelyingPartyId): self
+ {
+ Assertion::allString($securedRelyingPartyId, 'Invalid list. Shall be a list of strings');
+ $this->securedRelyingPartyId = $securedRelyingPartyId;
+
+ return $this;
+ }
+
+ /**
+ * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors
+ */
+ public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = null, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions
+ {
+ $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms);
+ $publicKeyCredentialParametersList = [];
+ foreach ($coseAlgorithmManager->all() as $algorithm) {
+ $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters(
+ PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
+ $algorithm::identifier()
+ );
+ }
+ $criteria = $criteria ?? new AuthenticatorSelectionCriteria();
+ $extensions = $extensions ?? new AuthenticationExtensionsClientInputs();
+ $challenge = random_bytes($this->challengeSize);
+
+ return PublicKeyCredentialCreationOptions::create(
+ $this->rpEntity,
+ $userEntity,
+ $challenge,
+ $publicKeyCredentialParametersList
+ )
+ ->excludeCredentials($excludedPublicKeyDescriptors)
+ ->setAuthenticatorSelection($criteria)
+ ->setAttestation($attestationMode ?? PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE)
+ ->setExtensions($extensions)
+ ->setTimeout($this->timeout)
+ ;
+ }
+
+ /**
+ * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors
+ */
+ public function generatePublicKeyCredentialRequestOptions(?string $userVerification = null, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions
+ {
+ return PublicKeyCredentialRequestOptions::create(random_bytes($this->challengeSize))
+ ->setRpId($this->rpEntity->getId())
+ ->setUserVerification($userVerification ?? PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED)
+ ->allowCredentials($allowedPublicKeyDescriptors)
+ ->setTimeout($this->timeout)
+ ->setExtensions($extensions ?? new AuthenticationExtensionsClientInputs())
+ ;
+ }
+
+ public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
+ {
+ $attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
+ $attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager)
+ ->setLogger($this->logger)
+ ;
+ $publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader)
+ ->setLogger($this->logger)
+ ;
+
+ $publicKeyCredential = $publicKeyCredentialLoader->load($data);
+ $authenticatorResponse = $publicKeyCredential->getResponse();
+ Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response');
+
+ $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
+ $attestationStatementSupportManager,
+ $this->publicKeyCredentialSourceRepository,
+ $this->tokenBindingHandler,
+ $this->extensionOutputCheckerHandler,
+ $this->metadataStatementRepository
+ );
+ $authenticatorAttestationResponseValidator->setLogger($this->logger);
+
+ return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest, $this->securedRelyingPartyId);
+ }
+
+ public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
+ {
+ $attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
+ $attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager)
+ ->setLogger($this->logger)
+ ;
+ $publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader)
+ ->setLogger($this->logger)
+ ;
+
+ $publicKeyCredential = $publicKeyCredentialLoader->load($data);
+ $authenticatorResponse = $publicKeyCredential->getResponse();
+ Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response');
+
+ $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
+ $this->publicKeyCredentialSourceRepository,
+ $this->tokenBindingHandler,
+ $this->extensionOutputCheckerHandler,
+ $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms),
+ $this->counterChecker
+ );
+ $authenticatorAssertionResponseValidator->setLogger($this->logger);
+
+ return $authenticatorAssertionResponseValidator->check(
+ $publicKeyCredential->getRawId(),
+ $authenticatorResponse,
+ $publicKeyCredentialRequestOptions,
+ $serverRequest,
+ null !== $userEntity ? $userEntity->getId() : null,
+ $this->securedRelyingPartyId
+ );
+ }
+
+ public function setCounterChecker(CounterChecker $counterChecker): self
+ {
+ $this->counterChecker = $counterChecker;
+
+ return $this;
+ }
+
+ public function setLogger(LoggerInterface $logger): self
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ public function enforceAndroidSafetyNetVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): self
+ {
+ $this->httpClient = $client;
+ $this->googleApiKey = $apiKey;
+ $this->requestFactory = $requestFactory;
+
+ return $this;
+ }
+
+ private function getAttestationStatementSupportManager(): AttestationStatementSupportManager
+ {
+ $attestationStatementSupportManager = new AttestationStatementSupportManager();
+ $attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
+ $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport());
+ if (class_exists(RS256::class) && class_exists(JWKFactory::class)) {
+ $androidSafetyNetAttestationStatementSupport = new AndroidSafetyNetAttestationStatementSupport();
+ if (null !== $this->httpClient && null !== $this->googleApiKey && null !== $this->requestFactory) {
+ $androidSafetyNetAttestationStatementSupport
+ ->enableApiVerification($this->httpClient, $this->googleApiKey, $this->requestFactory)
+ ->setLeeway(2000)
+ ->setMaxAge(60000)
+ ;
+ }
+ $attestationStatementSupportManager->add($androidSafetyNetAttestationStatementSupport);
+ }
+ $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport());
+ $attestationStatementSupportManager->add(new TPMAttestationStatementSupport());
+ $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms);
+ $attestationStatementSupportManager->add(new PackedAttestationStatementSupport($coseAlgorithmManager));
+
+ return $attestationStatementSupportManager;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn;
+
+use Assert\Assertion;
+use CBOR\Stream;
+use function Safe\fclose;
+use function Safe\fopen;
+use function Safe\fread;
+use function Safe\fwrite;
+use function Safe\rewind;
+use function Safe\sprintf;
+
+final class StringStream implements Stream
+{
+ /**
+ * @var resource
+ */
+ private $data;
+
+ /**
+ * @var int
+ */
+ private $length;
+
+ /**
+ * @var int
+ */
+ private $totalRead = 0;
+
+ public function __construct(string $data)
+ {
+ $this->length = mb_strlen($data, '8bit');
+ $resource = fopen('php://memory', 'rb+');
+ fwrite($resource, $data);
+ rewind($resource);
+ $this->data = $resource;
+ }
+
+ public function read(int $length): string
+ {
+ if (0 === $length) {
+ return '';
+ }
+ $read = fread($this->data, $length);
+ $bytesRead = mb_strlen($read, '8bit');
+ Assertion::length($read, $length, sprintf('Out of range. Expected: %d, read: %d.', $length, $bytesRead), null, '8bit');
+ $this->totalRead += $bytesRead;
+
+ return $read;
+ }
+
+ public function close(): void
+ {
+ fclose($this->data);
+ }
+
+ public function isEOF(): bool
+ {
+ return $this->totalRead === $this->length;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TokenBinding;
+
+use Psr\Http\Message\ServerRequestInterface;
+
+final class IgnoreTokenBindingHandler implements TokenBindingHandler
+{
+ public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
+ {
+ //Does nothing
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TokenBinding;
+
+use Assert\Assertion;
+use Psr\Http\Message\ServerRequestInterface;
+
+final class SecTokenBindingHandler implements TokenBindingHandler
+{
+ public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
+ {
+ if (TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus()) {
+ return;
+ }
+
+ Assertion::true($request->hasHeader('Sec-Token-Binding'), 'The header parameter "Sec-Token-Binding" is missing.');
+ $tokenBindingIds = $request->getHeader('Sec-Token-Binding');
+ Assertion::count($tokenBindingIds, 1, 'The header parameter "Sec-Token-Binding" is invalid.');
+ $tokenBindingId = reset($tokenBindingIds);
+ Assertion::eq($tokenBindingId, $tokenBinding->getId(), 'The header parameter "Sec-Token-Binding" is invalid.');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TokenBinding;
+
+use function array_key_exists;
+use Assert\Assertion;
+use Base64Url\Base64Url;
+use function Safe\sprintf;
+
+class TokenBinding
+{
+ public const TOKEN_BINDING_STATUS_PRESENT = 'present';
+ public const TOKEN_BINDING_STATUS_SUPPORTED = 'supported';
+ public const TOKEN_BINDING_STATUS_NOT_SUPPORTED = 'not-supported';
+
+ /**
+ * @var string
+ */
+ private $status;
+
+ /**
+ * @var string|null
+ */
+ private $id;
+
+ public function __construct(string $status, ?string $id)
+ {
+ Assertion::false(self::TOKEN_BINDING_STATUS_PRESENT === $status && null === $id, 'The member "id" is required when status is "present"');
+ $this->status = $status;
+ $this->id = $id;
+ }
+
+ /**
+ * @param mixed[] $json
+ */
+ public static function createFormArray(array $json): self
+ {
+ Assertion::keyExists($json, 'status', 'The member "status" is required');
+ $status = $json['status'];
+ Assertion::inArray(
+ $status,
+ self::getSupportedStatus(),
+ sprintf('The member "status" is invalid. Supported values are: %s', implode(', ', self::getSupportedStatus()))
+ );
+ $id = array_key_exists('id', $json) ? Base64Url::decode($json['id']) : null;
+
+ return new self($status, $id);
+ }
+
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string[]
+ */
+ private static function getSupportedStatus(): array
+ {
+ return [
+ self::TOKEN_BINDING_STATUS_PRESENT,
+ self::TOKEN_BINDING_STATUS_SUPPORTED,
+ self::TOKEN_BINDING_STATUS_NOT_SUPPORTED,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TokenBinding;
+
+use Psr\Http\Message\ServerRequestInterface;
+
+interface TokenBindingHandler
+{
+ public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TokenBinding;
+
+use Assert\Assertion;
+use Psr\Http\Message\ServerRequestInterface;
+
+final class TokenBindingNotSupportedHandler implements TokenBindingHandler
+{
+ public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
+ {
+ Assertion::true(TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus(), 'Token binding not supported.');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TrustPath;
+
+use Assert\Assertion;
+
+final class CertificateTrustPath implements TrustPath
+{
+ /**
+ * @var string[]
+ */
+ private $certificates;
+
+ /**
+ * @param string[] $certificates
+ */
+ public function __construct(array $certificates)
+ {
+ $this->certificates = $certificates;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getCertificates(): array
+ {
+ return $this->certificates;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createFromArray(array $data): TrustPath
+ {
+ Assertion::keyExists($data, 'x5c', 'The trust path type is invalid');
+
+ return new CertificateTrustPath($data['x5c']);
+ }
+
+ /**
+ * @return mixed[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => self::class,
+ 'x5c' => $this->certificates,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TrustPath;
+
+use Assert\Assertion;
+
+final class EcdaaKeyIdTrustPath implements TrustPath
+{
+ /**
+ * @var string
+ */
+ private $ecdaaKeyId;
+
+ public function __construct(string $ecdaaKeyId)
+ {
+ $this->ecdaaKeyId = $ecdaaKeyId;
+ }
+
+ public function getEcdaaKeyId(): string
+ {
+ return $this->ecdaaKeyId;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => self::class,
+ 'ecdaaKeyId' => $this->ecdaaKeyId,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createFromArray(array $data): TrustPath
+ {
+ Assertion::keyExists($data, 'ecdaaKeyId', 'The trust path type is invalid');
+
+ return new EcdaaKeyIdTrustPath($data['ecdaaKeyId']);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TrustPath;
+
+final class EmptyTrustPath implements TrustPath
+{
+ /**
+ * @return string[]
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => self::class,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createFromArray(array $data): TrustPath
+ {
+ return new EmptyTrustPath();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TrustPath;
+
+use JsonSerializable;
+
+interface TrustPath extends JsonSerializable
+{
+ /**
+ * @param mixed[] $data
+ */
+ public static function createFromArray(array $data): self;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\TrustPath;
+
+use function array_key_exists;
+use Assert\Assertion;
+use function in_array;
+use InvalidArgumentException;
+use function Safe\class_implements;
+use function Safe\sprintf;
+
+abstract class TrustPathLoader
+{
+ /**
+ * @param mixed[] $data
+ */
+ public static function loadTrustPath(array $data): TrustPath
+ {
+ Assertion::keyExists($data, 'type', 'The trust path type is missing');
+ $type = $data['type'];
+ $oldTypes = self::oldTrustPathTypes();
+ switch (true) {
+ case array_key_exists($type, $oldTypes):
+ return $oldTypes[$type]::createFromArray($data);
+ case class_exists($type):
+ $implements = class_implements($type);
+ if (in_array(TrustPath::class, $implements, true)) {
+ return $type::createFromArray($data);
+ }
+ // no break
+ default:
+ throw new InvalidArgumentException(sprintf('The trust path type "%s" is not supported', $data['type']));
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ private static function oldTrustPathTypes(): array
+ {
+ return [
+ 'empty' => EmptyTrustPath::class,
+ 'ecdaa_key_id' => EcdaaKeyIdTrustPath::class,
+ 'x5c' => CertificateTrustPath::class,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+namespace Webauthn;
+
+use CBOR\ByteStringObject;
+use CBOR\MapItem;
+use CBOR\MapObject;
+use CBOR\NegativeIntegerObject;
+use CBOR\UnsignedIntegerObject;
+
+class U2FPublicKey
+{
+ public static function isU2FKey($publicKey): bool
+ {
+ return $publicKey[0] === "\x04";
+ }
+
+ public static function createCOSEKey($publicKey): string
+ {
+
+ $mapObject = new MapObject([
+ 1 => MapItem::create(
+ new UnsignedIntegerObject(1, null),
+ new UnsignedIntegerObject(2, null)
+ ),
+ 3 => MapItem::create(
+ new UnsignedIntegerObject(3, null),
+ new NegativeIntegerObject(6, null)
+ ),
+ -1 => MapItem::create(
+ new NegativeIntegerObject(0, null),
+ new UnsignedIntegerObject(1, null)
+ ),
+ -2 => MapItem::create(
+ new NegativeIntegerObject(1, null),
+ new ByteStringObject(substr($publicKey, 1, 32))
+ ),
+ -3 => MapItem::create(
+ new NegativeIntegerObject(2, null),
+ new ByteStringObject(substr($publicKey, 33))
+ ),
+ ]);
+
+ return $mapObject->__toString();
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2021 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace Webauthn\Util;
+
+use Cose\Algorithm\Signature\ECDSA;
+use Cose\Algorithm\Signature\Signature;
+
+/**
+ * This class fixes the signature of the ECDSA based algorithms.
+ *
+ * @internal
+ *
+ * @see https://www.w3.org/TR/webauthn/#signature-attestation-types
+ */
+abstract class CoseSignatureFixer
+{
+ public static function fix(string $signature, Signature $algorithm): string
+ {
+ switch ($algorithm::identifier()) {
+ case ECDSA\ES256K::ID:
+ case ECDSA\ES256::ID:
+ if (64 === mb_strlen($signature, '8bit')) {
+ return $signature;
+ }
+
+ return ECDSA\ECSignature::fromAsn1($signature, 64); //TODO: fix this hardcoded value by adding a dedicated method for the algorithms
+ case ECDSA\ES384::ID:
+ if (96 === mb_strlen($signature, '8bit')) {
+ return $signature;
+ }
+
+ return ECDSA\ECSignature::fromAsn1($signature, 96);
+ case ECDSA\ES512::ID:
+ if (132 === mb_strlen($signature, '8bit')) {
+ return $signature;
+ }
+
+ return ECDSA\ECSignature::fromAsn1($signature, 132);
+ }
+
+ return $signature;
+ }
+}