diff --git a/generated/array.php b/generated/array.php index 4a6b844f..0842d4ff 100644 --- a/generated/array.php +++ b/generated/array.php @@ -28,6 +28,36 @@ function array_combine(array $keys, array $values): array } +/** + * 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 and NULL on failure. + * @throws ArrayException + * + */ +function array_flip(array $array): array +{ + error_clear_last(); + $result = \array_flip($array); + if ($result === null) { + throw ArrayException::createFromPhpError(); + } + return $result; +} + + /** * array_multisort can be used to sort several * arrays at once, or a multi-dimensional array by one or more diff --git a/generated/functionsList.php b/generated/functionsList.php index 10ccc565..00a1d286 100644 --- a/generated/functionsList.php +++ b/generated/functionsList.php @@ -26,6 +26,7 @@ 'apcu_inc', 'apcu_sma_info', 'array_combine', + 'array_flip', 'array_multisort', 'array_walk_recursive', 'arsort', @@ -475,6 +476,7 @@ 'define', 'highlight_file', 'highlight_string', + 'sapi_windows_cp_conv', 'sapi_windows_cp_set', 'sapi_windows_vt100_support', 'sleep', @@ -900,6 +902,8 @@ 'simplexml_load_file', 'simplexml_load_string', 'socket_accept', + 'socket_addrinfo_bind', + 'socket_addrinfo_connect', 'socket_bind', 'socket_connect', 'socket_create_listen', @@ -909,6 +913,7 @@ 'socket_get_option', 'socket_getpeername', 'socket_getsockname', + 'socket_import_stream', 'socket_listen', 'socket_read', 'socket_send', diff --git a/generated/misc.php b/generated/misc.php index 41da6ae6..9d50256c 100644 --- a/generated/misc.php +++ b/generated/misc.php @@ -92,6 +92,30 @@ function highlight_string(string $str, bool $return = false) } +/** + * 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, or NULL on failure. + * @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. * diff --git a/generated/sockets.php b/generated/sockets.php index d9e0b524..36e2d4e8 100644 --- a/generated/sockets.php +++ b/generated/sockets.php @@ -45,6 +45,46 @@ function socket_accept($socket) } +/** + * 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|null Returns a Socket resource on success or NULL on failure. + * @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|null Returns a Socket resource on success or NULL on failure. + * @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 @@ -336,6 +376,25 @@ function socket_getsockname($socket, ?string &$addr, ?int &$port = null): void } +/** + * Imports a stream that encapsulates a socket into a socket extension resource. + * + * @param resource $stream The stream resource to import. + * @return resource Returns FALSE or NULL on failure. + * @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 diff --git a/generator/src/DocPage.php b/generator/src/DocPage.php index 0c34f28f..f9568209 100644 --- a/generator/src/DocPage.php +++ b/generator/src/DocPage.php @@ -21,32 +21,39 @@ public function __construct(string $_path) $this->path = $_path; } - /* - * Detect function which didn't return FALSE on error. - * - * @return bool - */ - public function detectFalsyFunction(): bool + // Ignore function if it was removed before PHP 7.1 + private function getIsDeprecated(string $file): bool { - $file = file_get_contents($this->path); - if (preg_match('/&warn\.deprecated\.function-(\d+-\d+-\d+)\.removed-(\d+-\d+-\d+)/', $file, $matches)) { $removedVersion = $matches[2]; [$major, $minor] = explode('-', $removedVersion); if ($major < 7 || ($major == 7 && $minor == 0)) { - // Ignore function if it was removed before PHP 7.1 - return false; + return true; } } + if (preg_match('/&warn\.removed\.function-(\d+-\d+-\d+)/', $file, $matches) && isset($matches[2])) { $removedVersion = $matches[2]; [$major, $minor] = explode('-', $removedVersion); if ($major < 7 || ($major == 7 && $minor == 0)) { - // Ignore function if it was removed before PHP 7.1 - return false; + return true; } } + return false; + } + + /* + * Detect function which return FALSE on error. + */ + public function detectFalsyFunction(): bool + { + $file = file_get_contents($this->path); + + if ($this->getIsDeprecated($file)) { + return false; + } + if (preg_match('/&false;\s+on\s+error/m', $file)) { return true; } @@ -90,6 +97,24 @@ public function detectFalsyFunction(): bool return false; } + /* + * Detect function which return NULL on error. + */ + public function detectNullsyFunction(): bool + { + $file = \file_get_contents($this->path); + + if ($this->getIsDeprecated($file)) { + return false; + } + + if (preg_match('/&null;\s+on\s+failure/', $file)) { + return true; + } + + return false; + } + /** * @return \SimpleXMLElement[] diff --git a/generator/src/Method.php b/generator/src/Method.php index 0ff014db..687d4474 100644 --- a/generator/src/Method.php +++ b/generator/src/Method.php @@ -7,6 +7,8 @@ class Method { + const FALSY_TYPE = 1; + const NULLSY_TYPE = 2; /** * @var \SimpleXMLElement */ @@ -27,13 +29,18 @@ class Method * @var PhpStanFunctionMapReader */ private $phpStanFunctionMapReader; + /** + * @var int + */ + private $errorType; - public function __construct(\SimpleXMLElement $_functionObject, \SimpleXMLElement $rootEntity, string $moduleName, PhpStanFunctionMapReader $phpStanFunctionMapReader) + public function __construct(\SimpleXMLElement $_functionObject, \SimpleXMLElement $rootEntity, string $moduleName, PhpStanFunctionMapReader $phpStanFunctionMapReader, int $errorType) { $this->functionObject = $_functionObject; $this->rootEntity = $rootEntity; $this->moduleName = $moduleName; $this->phpStanFunctionMapReader = $phpStanFunctionMapReader; + $this->errorType = $errorType; } public function getFunctionName(): string @@ -41,6 +48,11 @@ public function getFunctionName(): string return $this->functionObject->methodname->__toString(); } + public function getErrorType(): int + { + return $this->errorType; + } + public function getReturnType(): string { // If the function returns a boolean, since false is for error, true is for success. diff --git a/generator/src/Parameter.php b/generator/src/Parameter.php index 24ff990d..a32eb13b 100644 --- a/generator/src/Parameter.php +++ b/generator/src/Parameter.php @@ -157,4 +157,9 @@ private function getInnerXml(\SimpleXMLElement $SimpleXMLElement): string $inner_xml = trim($inner_xml); return $inner_xml; } + + public function isTypeable(): bool + { + return $this->getType() !== 'mixed' && $this->getType() !== 'resource' && \count(\explode("|", $this->getType())) < 2; + } } diff --git a/generator/src/Scanner.php b/generator/src/Scanner.php index e53cfffb..f48ae58e 100644 --- a/generator/src/Scanner.php +++ b/generator/src/Scanner.php @@ -94,7 +94,11 @@ public function getMethods(array $paths): array } $docPage = new DocPage($path); - if ($docPage->detectFalsyFunction()) { + $isFalsy = $docPage->detectFalsyFunction(); + $isNullsy = $docPage->detectNullsyFunction(); + if ($isFalsy || $isNullsy) { + $errorType = $isFalsy ? Method::FALSY_TYPE : Method::NULLSY_TYPE; + $functionObjects = $docPage->getMethodSynopsis(); if (count($functionObjects) > 1) { $overloadedFunctions = array_merge($overloadedFunctions, \array_map(function ($functionObject) { @@ -107,7 +111,7 @@ public function getMethods(array $paths): array } $rootEntity = $docPage->loadAndResolveFile(); foreach ($functionObjects as $functionObject) { - $function = new Method($functionObject, $rootEntity, $docPage->getModule(), $phpStanFunctionMapReader); + $function = new Method($functionObject, $rootEntity, $docPage->getModule(), $phpStanFunctionMapReader, $errorType); if (isset($ignoredFunctions[$function->getFunctionName()])) { continue; } diff --git a/generator/src/WritePhpFunction.php b/generator/src/WritePhpFunction.php index 7d0dd8db..47acd58f 100644 --- a/generator/src/WritePhpFunction.php +++ b/generator/src/WritePhpFunction.php @@ -99,12 +99,24 @@ private function writePhpFunction(): string private function generateExceptionCode(string $moduleName, Method $method) : string { + $errorValue = null; + switch ($method->getErrorType()) { + case Method::FALSY_TYPE: + $errorValue = 'false'; + break; + case Method::NULLSY_TYPE: + $errorValue = 'null'; + break; + default: + throw new \LogicException("Method doesn't have an error type"); + } + // Special case for CURL: we need the first argument of the method if this is a resource. if ($moduleName === 'Curl') { $params = $method->getParams(); if (\count($params) > 0 && $params[0]->getParameter() === 'ch') { return " - if (\$result === false) { + if (\$result === $errorValue) { throw CurlException::createFromCurlResource(\$ch); } "; @@ -113,7 +125,7 @@ private function generateExceptionCode(string $moduleName, Method $method) : str $exceptionName = FileCreator::toExceptionName($moduleName); return " - if (\$result === false) { + if (\$result === $errorValue) { throw {$exceptionName}::createFromPhpError(); } "; @@ -130,7 +142,7 @@ private function displayParamsWithType(array $params): string foreach ($params as $param) { $paramAsString = ''; - if ($param->getType() !== 'mixed' && $param->getType() !== 'resource') { + if ($param->isTypeable()) { if ($param->isNullable()) { $paramAsString .= '?'; } diff --git a/generator/tests/DocPageTest.php b/generator/tests/DocPageTest.php index 2e2b81ab..571a7e91 100644 --- a/generator/tests/DocPageTest.php +++ b/generator/tests/DocPageTest.php @@ -26,4 +26,13 @@ public function testDetectFalsyFunction() $this->assertTrue($mcryptDecrypt->detectFalsyFunction()); $this->assertTrue($fsockopen->detectFalsyFunction()); } + + public function testDetectNullsyFunction() + { + $pregMatch = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/array/functions/array-flip.xml'); + $implode = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/strings/functions/implode.xml'); + + $this->assertTrue($pregMatch->detectNullsyFunction()); + $this->assertFalse($implode->detectNullsyFunction()); + } } diff --git a/generator/tests/MethodTest.php b/generator/tests/MethodTest.php index 92e07182..6b00500e 100644 --- a/generator/tests/MethodTest.php +++ b/generator/tests/MethodTest.php @@ -11,7 +11,7 @@ public function testGetFunctionName() { $docPage = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/pcre/functions/preg-match.xml'); $xmlObject = $docPage->getMethodSynopsis(); - $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader()); + $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader(), Method::FALSY_TYPE); $name = $method->getFunctionName(); $this->assertEquals('preg_match', $name); } @@ -20,7 +20,7 @@ public function testGetFunctionType() { $docPage = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/pcre/functions/preg-match.xml'); $xmlObject = $docPage->getMethodSynopsis(); - $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader()); + $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader(), Method::FALSY_TYPE); $type = $method->getReturnType(); $this->assertEquals('int', $type); } @@ -29,7 +29,7 @@ public function testGetFunctionParam() { $docPage = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/pcre/functions/preg-match.xml'); $xmlObject = $docPage->getMethodSynopsis(); - $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader()); + $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader(), Method::FALSY_TYPE); $params = $method->getParams(); $this->assertEquals('string', $params[0]->getType()); $this->assertEquals('pattern', $params[0]->getParameter()); @@ -39,7 +39,7 @@ public function testGetInitializer() { $docPage = new DocPage(__DIR__ . '/../doc/doc-en/en/reference/apc/functions/apc-cache-info.xml'); $xmlObject = $docPage->getMethodSynopsis(); - $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader()); + $method = new Method($xmlObject[0], $docPage->loadAndResolveFile(), $docPage->getModule(), new PhpStanFunctionMapReader(), Method::FALSY_TYPE); $params = $method->getParams(); $this->assertEquals('', $params[0]->getDefaultValue()); diff --git a/rector-migrate.yml b/rector-migrate.yml index b883ec01..702cf12c 100644 --- a/rector-migrate.yml +++ b/rector-migrate.yml @@ -29,6 +29,7 @@ services: apcu_inc: 'Safe\apcu_inc' apcu_sma_info: 'Safe\apcu_sma_info' array_combine: 'Safe\array_combine' + array_flip: 'Safe\array_flip' array_multisort: 'Safe\array_multisort' array_walk_recursive: 'Safe\array_walk_recursive' arsort: 'Safe\arsort' @@ -478,6 +479,7 @@ services: define: 'Safe\define' highlight_file: 'Safe\highlight_file' highlight_string: 'Safe\highlight_string' + sapi_windows_cp_conv: 'Safe\sapi_windows_cp_conv' sapi_windows_cp_set: 'Safe\sapi_windows_cp_set' sapi_windows_vt100_support: 'Safe\sapi_windows_vt100_support' sleep: 'Safe\sleep' @@ -903,6 +905,8 @@ services: simplexml_load_file: 'Safe\simplexml_load_file' simplexml_load_string: 'Safe\simplexml_load_string' 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_listen: 'Safe\socket_create_listen' @@ -912,6 +916,7 @@ services: socket_get_option: 'Safe\socket_get_option' socket_getpeername: 'Safe\socket_getpeername' socket_getsockname: 'Safe\socket_getsockname' + socket_import_stream: 'Safe\socket_import_stream' socket_listen: 'Safe\socket_listen' socket_read: 'Safe\socket_read' socket_send: 'Safe\socket_send'