diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6b50564944..ea9ac8fdc10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: ini-values: zend.assertions=1, assert.exception=1 tools: composer:v2 coverage: none - extensions: decimal + extensions: none, curl, dom, filter, json, libxml, mbstring, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter - uses: actions/checkout@v2 diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 1f9c4e83ae4..3f8842ad797 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -52,6 +52,7 @@ jobs: ini-values: zend.assertions=1, assert.exception=1 tools: composer:v2 coverage: none + extensions: none, curl, dom, filter, json, libxml, mbstring, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter - uses: actions/checkout@v2 diff --git a/UPGRADING.md b/UPGRADING.md index 8dafd844e3b..d25d8c8936d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -165,6 +165,7 @@ - [BC] Property `Psalm\Config::$allow_phpstorm_generics` was removed - [BC] Property `Psalm\Config::$exit_functions` was removed - [BC] Property `Psalm\Config::$forbid_echo` was removed + - [BC] Property `Psalm\Config::$load_xdebug_stub` was removed - [BC] Method `Psalm\Type::getEmpty()` was removed - [BC] Legacy hook interfaces have been removed: - `Psalm\Plugin\Hook\MethodReturnTypeProviderInterface` diff --git a/config.xsd b/config.xsd index 1a29b16b27c..a4f4b5042f5 100644 --- a/config.xsd +++ b/config.xsd @@ -21,6 +21,8 @@ + + @@ -47,18 +49,6 @@ - - - - Default is runtime-specific: if not present, Psalm will only load the Xdebug stub if psalm has unloaded the extension. - - - - - Deprecated. In Psalm 5 extensions will be loaded based on composer.json and overridden with enableExtensions/disableExtensions. - - - @@ -682,4 +672,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dictionaries/InternalTaintSinkMap.php b/dictionaries/InternalTaintSinkMap.php index df5f7faf248..cb8c6077d35 100644 --- a/dictionaries/InternalTaintSinkMap.php +++ b/dictionaries/InternalTaintSinkMap.php @@ -43,9 +43,6 @@ 'mysqli_stmt::prepare' => [['sql']], 'passthru' => [['shell']], 'pcntl_exec' => [['shell']], -'PDO::prepare' => [['sql']], -'PDO::query' => [['sql']], -'PDO::exec' => [['sql']], 'pg_exec' => [[], ['sql']], 'pg_prepare' => [[], [], ['sql']], 'pg_put_line' => [[], ['sql']], diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index 26861e4ada4..d50b3633e32 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -246,16 +246,6 @@ When `true`, Psalm will attempt to find all unused code (including unused variab ``` When `true`, Psalm will report all `@psalm-suppress` annotations that aren't used, the equivalent of running with `--find-unused-psalm-suppress`. Defaults to `false`. -#### loadXdebugStub -```xml - -``` -If not present, Psalm will only load the Xdebug stub if Psalm has unloaded the extension. -When `true`, Psalm will load the Xdebug extension stub (as the extension is unloaded when Psalm runs). -Setting to `false` prevents the stub from loading. - #### ensureArrayStringOffsetsExist ```xml ``` @@ -432,6 +422,23 @@ Optional. Same format as ``. Directories Psalm should load but not #### <fileExtensions> Optional. A list of extensions to search over. See [Checking non-PHP files](checking_non_php_files.md) to understand how to extend this. +#### <enableExtensions> +Optional. A list of extensions to enable. By default, only extensions required by your composer.json will be enabled. +```xml + + + + +``` + +#### <disableExtensions> +Optional. A list of extensions to disable. By default, only extensions required by your composer.json will be enabled. +```xml + + + +``` + #### <plugins> Optional. A list of `` entries. See the [Plugins](plugins/using_plugins.md) section for more information. @@ -483,7 +490,7 @@ The following configuration declares custom types for super-globals (`$GLOBALS` ```xml - + ``` diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 614c782fb9e..1f7e035e144 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -24,9 +24,6 @@ getAdditionalFileTypeAnalyzers getAdditionalFileTypeScanners - - $this->load_xdebug_stub - @@ -203,12 +200,6 @@ $stmt->expr->getArgs()[0] - - - $config->load_xdebug_stub - $config->load_xdebug_stub - - $callables[0] @@ -286,8 +277,7 @@ $storage->template_extended_count - - $imported_type_data[3] + $l[4] $r[4] $var_line_parts[0] diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index fe2208d0d80..56982c5fc52 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -46,6 +46,7 @@ use XdgBaseDir\Xdg; use stdClass; +use function array_key_exists; use function array_map; use function array_merge; use function array_pad; @@ -58,7 +59,6 @@ use function count; use function dirname; use function explode; -use function extension_loaded; use function file_exists; use function file_get_contents; use function filetype; @@ -196,15 +196,6 @@ class Config */ public $throw_exception = false; - /** - * Whether or not to load Xdebug stub - * - * @deprecated going to be removed in Psalm 5 - * - * @var bool|null - */ - public $load_xdebug_stub; - /** * The directory to store PHP Parser (and other) caches * @@ -574,6 +565,34 @@ class Config /** @var ?int */ public $threads; + /** + * @psalm-readonly-allow-private-mutation + * @var array{ + * decimal: bool, + * dom: bool, + * ds: bool, + * geos: bool, + * gmp: bool, + * mongodb: bool, + * mysqli: bool, + * pdo: bool, + * soap: bool, + * xdebug: bool, + * } + */ + public $php_extensions = [ + "decimal" => false, + "dom" => false, + "ds" => false, + "geos" => false, + "gmp" => false, + "mongodb" => false, + "mysqli" => false, + "pdo" => false, + "soap" => false, + "xdebug" => false, + ]; + protected function __construct() { self::$instance = $this; @@ -931,7 +950,6 @@ private static function fromXmlAndPaths( 'ignoreInternalFunctionFalseReturn' => 'ignore_internal_falsable_issues', 'ignoreInternalFunctionNullReturn' => 'ignore_internal_nullable_issues', 'includePhpVersionsInErrorBaseline' => 'include_php_versions_in_error_baseline', - 'loadXdebugStub' => 'load_xdebug_stub', 'ensureArrayStringOffsetsExist' => 'ensure_array_string_offsets_exist', 'ensureArrayIntOffsetsExist' => 'ensure_array_int_offsets_exist', 'reportMixedIssues' => 'show_mixed_issues', @@ -966,6 +984,36 @@ private static function fromXmlAndPaths( $base_dir = $current_dir; } + $composer_json_path = Composer::getJsonFilePath($config->base_dir); + + $composer_json = null; + if (file_exists($composer_json_path)) { + if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) { + throw new UnexpectedValueException('Invalid composer.json at ' . $composer_json_path); + } + } + foreach ($config->php_extensions as $ext => $_) { + $config->php_extensions[$ext] = isset($composer_json["require"]["ext-$ext"]); + } + + if (isset($config_xml->enableExtensions) && isset($config_xml->enableExtensions->extension)) { + foreach ($config_xml->enableExtensions->extension as $extension) { + assert(isset($extension["name"])); + $extensionName = (string) $extension["name"]; + assert(array_key_exists($extensionName, $config->php_extensions)); + $config->php_extensions[$extensionName] = true; + } + } + + if (isset($config_xml->disableExtensions) && isset($config_xml->disableExtensions->extension)) { + foreach ($config_xml->disableExtensions->extension as $extension) { + assert(isset($extension["name"])); + $extensionName = (string) $extension["name"]; + assert(array_key_exists($extensionName, $config->php_extensions)); + $config->php_extensions[$extensionName] = false; + } + } + if (isset($config_xml['phpVersion'])) { $config->configured_php_version = (string) $config_xml['phpVersion']; } @@ -1969,7 +2017,6 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreGenericClasses.phpstub', $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreGenericIterators.phpstub', $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreImmutableClasses.phpstub', - $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'DOM.phpstub', $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Reflection.phpstub', $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'SPL.phpstub', ]; @@ -1984,39 +2031,11 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): $this->internal_stubs[] = $stringable_path; } - if (extension_loaded('PDO')) { - $ext_pdo_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'pdo.phpstub'; - $this->internal_stubs[] = $ext_pdo_path; - } - - if (extension_loaded('soap')) { - $ext_soap_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'soap.phpstub'; - $this->internal_stubs[] = $ext_soap_path; - } - - if (extension_loaded('ds')) { - $ext_ds_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'ext-ds.phpstub'; - $this->internal_stubs[] = $ext_ds_path; - } - - if (extension_loaded('mongodb')) { - $ext_mongodb_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'mongodb.phpstub'; - $this->internal_stubs[] = $ext_mongodb_path; - } - - if ($this->load_xdebug_stub) { - $xdebug_stub_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Xdebug.phpstub'; - $this->internal_stubs[] = $xdebug_stub_path; - } - - if (extension_loaded('mysqli')) { - $ext_mysqli_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'mysqli.phpstub'; - $this->internal_stubs[] = $ext_mysqli_path; - } - - if (extension_loaded('decimal')) { - $ext_decimal_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'decimal.phpstub'; - $this->internal_stubs[] = $ext_decimal_path; + foreach ($this->php_extensions as $ext => $enabled) { + if ($enabled) { + $this->internal_stubs[] = $dir_lvl_2 . DIRECTORY_SEPARATOR . "stubs" + . DIRECTORY_SEPARATOR . "extensions" . DIRECTORY_SEPARATOR . "$ext.phpstub"; + } } foreach ($this->internal_stubs as $stub_path) { diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 70893dddc4a..2063355c711 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -253,7 +253,7 @@ public static function run(array $argv): void self::emitMacPcreWarning($options, $threads); - self::restart($options, $config, $threads); + self::restart($options, $threads); if (isset($options['debug-emitted-issues'])) { $config->debug_emitted_issues = true; @@ -882,7 +882,7 @@ private static function emitMacPcreWarning(array $options, int $threads): void } } - private static function restart(array $options, Config $config, int $threads): void + private static function restart(array $options, int $threads): void { $ini_handler = new PsalmRestarter('PSALM'); @@ -907,10 +907,6 @@ private static function restart(array $options, Config $config, int $threads): v // If Xdebug is enabled, restart without it $ini_handler->check(); - - if ($config->load_xdebug_stub === null && PsalmRestarter::getSkippedVersion() !== '') { - $config->load_xdebug_stub = true; - } } private static function detectThreads(array $options, Config $config, bool $in_ci): int diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php index 1d2521ea003..6ef85193afc 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php @@ -2,7 +2,7 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; -use PDO; +use Psalm\Config; use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; use Psalm\Type; @@ -15,8 +15,6 @@ use Psalm\Type\Atomic\TScalar; use Psalm\Type\Union; -use function class_exists; - /** * @internal */ @@ -29,11 +27,12 @@ public static function getClassLikeNames(): array public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union { + $config = Config::getInstance(); $source = $event->getSource(); $call_args = $event->getCallArgs(); $method_name_lowercase = $event->getMethodNameLowercase(); if ($method_name_lowercase === 'fetch' - && class_exists('PDO') + && $config->php_extensions["pdo"] && isset($call_args[0]) && ($first_arg_type = $source->getNodeTypeProvider()->getType($call_args[0]->value)) && $first_arg_type->isSingleIntLiteral() @@ -41,7 +40,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) $fetch_mode = $first_arg_type->getSingleIntLiteral()->value; switch ($fetch_mode) { - case PDO::FETCH_ASSOC: // array|false + case 2: // PDO::FETCH_ASSOC - array|false return new Union([ new TArray([ Type::getString(), @@ -53,7 +52,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) new TFalse(), ]); - case PDO::FETCH_BOTH: // array|false + case 4: // PDO::FETCH_BOTH - array|false return new Union([ new TArray([ Type::getArrayKey(), @@ -65,16 +64,16 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) new TFalse(), ]); - case PDO::FETCH_BOUND: // bool + case 6: // PDO::FETCH_BOUND - bool return Type::getBool(); - case PDO::FETCH_CLASS: // object|false + case 8: // PDO::FETCH_CLASS - object|false return new Union([ new TObject(), new TFalse(), ]); - case PDO::FETCH_LAZY: // object|false + case 1: // PDO::FETCH_LAZY - object|false // This actually returns a PDORow object, but that class is // undocumented, and its attributes are all dynamic anyway return new Union([ @@ -82,7 +81,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) new TFalse(), ]); - case PDO::FETCH_NAMED: // array>|false + case 11: // PDO::FETCH_NAMED - array>|false return new Union([ new TArray([ Type::getString(), @@ -94,7 +93,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) new TFalse(), ]); - case PDO::FETCH_NUM: // list|false + case 3: // PDO::FETCH_NUM - list|false return new Union([ new TList( new Union([ @@ -105,7 +104,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event) new TFalse(), ]); - case PDO::FETCH_OBJ: // stdClass|false + case 5: // PDO::FETCH_OBJ - stdClass|false return new Union([ new TNamedObject('stdClass'), new TFalse(), diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php index cd88b142770..c8eba436197 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php @@ -2,7 +2,6 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; -use PDO; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\MethodParamsProviderEvent; @@ -62,7 +61,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array $value = $first_call_arg_type->getSingleIntLiteral()->value; switch ($value) { - case PDO::FETCH_COLUMN: + case 7: // PDO::FETCH_COLUMN $params[] = new FunctionLikeParameter( 'colno', false, @@ -73,7 +72,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array ); break; - case PDO::FETCH_CLASS: + case 8: // PDO::FETCH_CLASS $params[] = new FunctionLikeParameter( 'classname', false, @@ -93,7 +92,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array ); break; - case PDO::FETCH_INTO: + case 9: // PDO::FETCH_INTO $params[] = new FunctionLikeParameter( 'object', false, diff --git a/stubs/decimal.phpstub b/stubs/extensions/decimal.phpstub similarity index 100% rename from stubs/decimal.phpstub rename to stubs/extensions/decimal.phpstub diff --git a/stubs/DOM.phpstub b/stubs/extensions/dom.phpstub similarity index 100% rename from stubs/DOM.phpstub rename to stubs/extensions/dom.phpstub diff --git a/stubs/ext-ds.phpstub b/stubs/extensions/ds.phpstub similarity index 100% rename from stubs/ext-ds.phpstub rename to stubs/extensions/ds.phpstub diff --git a/stubs/ext-geos.phpstub b/stubs/extensions/geos.phpstub similarity index 100% rename from stubs/ext-geos.phpstub rename to stubs/extensions/geos.phpstub diff --git a/stubs/extensions/gmp.phpstub b/stubs/extensions/gmp.phpstub new file mode 100644 index 00000000000..6a621bc0d66 --- /dev/null +++ b/stubs/extensions/gmp.phpstub @@ -0,0 +1,11 @@ + + */ +class PDOStatement implements Traversable +{ + /** + * @psalm-taint-sink callable $class + * + * @template T of object + * @param class-string $class + * @param array $ctorArgs + * @return false|T + */ + public function fetchObject($class = \stdclass::class, array $ctorArgs = array()) {} +} + + class PDOException extends RuntimeException { + protected string $code; + public ?array $errorInfo = null; +} diff --git a/stubs/soap.phpstub b/stubs/extensions/soap.phpstub similarity index 92% rename from stubs/soap.phpstub rename to stubs/extensions/soap.phpstub index 0eb107823ef..8a3fafa4dcd 100644 --- a/stubs/soap.phpstub +++ b/stubs/extensions/soap.phpstub @@ -282,3 +282,30 @@ class SoapClient { public function __setSoapHeaders ($soapheaders = null) {} } + +class SoapFault extends Exception { + /** + * @param array|string|null $code + */ + public function __construct( + $code, + string $string, + ?string $actor = null, + mixed $details = null, + ?string $name = null, + mixed $headerFault = null + ) {} +} + +class SoapHeader { + public function __construct( + string $namespace, + string $name, + // Actually doesn't have a default, not specifying results in no SoapHeader::$data property. Specifying null + // results in a SoapHeader::$data property with null as the value. This probably makes no difference. + mixed $data = null, + bool $mustUnderstand = false, + // Same as $data, no default. The documentation specifies this as a `string` but it accepts null. + ?string $actor = null + ) {} +} diff --git a/stubs/Xdebug.phpstub b/stubs/extensions/xdebug.phpstub similarity index 100% rename from stubs/Xdebug.phpstub rename to stubs/extensions/xdebug.phpstub diff --git a/stubs/pdo.phpstub b/stubs/pdo.phpstub deleted file mode 100644 index 0abf5053353..00000000000 --- a/stubs/pdo.phpstub +++ /dev/null @@ -1,19 +0,0 @@ - - */ -class PDOStatement implements Traversable -{ - /** - * @psalm-taint-sink callable $class - * - * @template T of object - * @param class-string $class - * @param array $ctorArgs - * @return false|T - */ - public function fetchObject($class = \stdclass::class, array $ctorArgs = array()) {} -} diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 2f2e37f197d..9b268caf1e6 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -8,8 +8,6 @@ use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; -use function class_exists; - use const DIRECTORY_SEPARATOR; class BinaryOperationTest extends TestCase @@ -19,10 +17,6 @@ class BinaryOperationTest extends TestCase public function testGMPOperations(): void { - if (class_exists('GMP') === false) { - $this->markTestSkipped('Cannot run test, base class "GMP" does not exist!'); - } - $this->addFile( 'somefile.php', 'markTestSkipped('Cannot run test, base class "Decimal\\Decimal" does not exist!'); - } - $this->addFile( 'somefile.php', 'markTestSkipped('Cannot run test, base class "mysqli" does not exist!'); - } - $this->addFile( 'somefile.php', 'file_path, trim(' + + + + + + + + ')); + + $config_file = new ConfigFile((string)getcwd(), $this->file_path); + $config = $config_file->getConfig(); + + $this->assertTrue($config->php_extensions["mysqli"]); + $this->assertTrue($config->php_extensions["pdo"]); + } + + public function testDisableExtensions(): void + { + file_put_contents($this->file_path, trim(' + + + + + + + + + + + + ')); + + $config_file = new ConfigFile((string)getcwd(), $this->file_path); + $config = $config_file->getConfig(); + + $this->assertFalse($config->php_extensions["mysqli"]); + $this->assertFalse($config->php_extensions["pdo"]); + } + + public function testInvalidExtension(): void + { + $this->expectException(ConfigException::class); + + file_put_contents($this->file_path, trim(' + + + + + + + ')); + + (new ConfigFile((string)getcwd(), $this->file_path))->getConfig(); + } + /** * @param string $expected_template * @param string $contents diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 9d3eeb449ec..0102eaf050a 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -6,8 +6,6 @@ use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; -use function class_exists; - use const DIRECTORY_SEPARATOR; class MethodCallTest extends TestCase @@ -17,10 +15,6 @@ class MethodCallTest extends TestCase public function testExtendDocblockParamType(): void { - if (class_exists('SoapClient') === false) { - $this->markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } - $this->addFile( 'somefile.php', 'markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } - $this->addFile( 'somefile.php', 'markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } - $this->addFile( 'somefile.php', 'markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } - $this->addFile( 'somefile.php', 'expectExceptionMessage('ImplementedParamTypeMismatch'); $this->expectException(CodeException::class); - if (class_exists('SoapClient') === false) { - $this->markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } $this->addFile( 'somefile.php', @@ -286,10 +269,6 @@ public function testExtendDocblockParamTypeWithWrongParam(): void $this->expectException(CodeException::class); $this->expectExceptionMessage('MethodSignatureMismatch'); - if (class_exists('SoapClient') === false) { - $this->markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); - } - $this->addFile( 'somefile.php', 'php_extensions as $ext => $_enabled) { + $this->php_extensions[$ext] = true; + } + $this->throw_exception = true; $this->use_docblock_types = true; $this->level = 1;