diff --git a/config.xsd b/config.xsd
index 167bd38dcb6..91f007d2b5d 100644
--- a/config.xsd
+++ b/config.xsd
@@ -21,6 +21,7 @@
+
@@ -127,6 +128,12 @@
+
+
+
+
+
+
diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php
index a2cbc88a701..9a34cbb07df 100644
--- a/src/Psalm/Config.php
+++ b/src/Psalm/Config.php
@@ -80,6 +80,7 @@
use const LIBXML_NONET;
use function is_a;
use const SCANDIR_SORT_NONE;
+use function array_map;
/**
* @psalm-suppress PropertyNotSetInConstructor
@@ -124,6 +125,15 @@ class Config
'MixedReturnTypeCoercion',
];
+ /**
+ * These are special object classes that allow any and all properties to be get/set on them
+ * @var array
+ */
+ protected $universal_object_crates = [
+ \stdClass::class,
+ SimpleXMLElement::class,
+ ];
+
/**
* @var static|null
*/
@@ -958,6 +968,15 @@ private static function fromXmlAndPaths(string $base_dir, string $file_contents,
}
}
+ if (isset($config_xml->universalObjectCrates) && isset($config_xml->universalObjectCrates->class)) {
+ /** @var \SimpleXMLElement $universal_object_crate */
+ foreach ($config_xml->universalObjectCrates->class as $universal_object_crate) {
+ /** @var class-string $classString */
+ $classString = $universal_object_crate['name'];
+ $config->addUniversalObjectCrate($classString);
+ }
+ }
+
if (isset($config_xml->ignoreExceptions)) {
if (isset($config_xml->ignoreExceptions->class)) {
/** @var \SimpleXMLElement $exception_class */
@@ -2109,4 +2128,20 @@ private function getPHPVersionFromComposerJson(): ?string
}
return null;
}
+
+ /**
+ * @param class-string $class
+ */
+ public function addUniversalObjectCrate(string $class): void
+ {
+ $this->universal_object_crates[] = $class;
+ }
+
+ /**
+ * @return array
+ */
+ public function getUniversalObjectCrates(): array
+ {
+ return array_map('strtolower', $this->universal_object_crates);
+ }
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php
index 971466fcec0..afc98392a01 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php
@@ -4,6 +4,7 @@
use PhpParser;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\PropertyProperty;
+use Psalm\Config;
use Psalm\Internal\Analyzer\ClassAnalyzer;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
@@ -268,7 +269,11 @@ public static function analyze(
(
in_array(
strtolower($lhs_type_part->value),
- ['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode'],
+ Config::getInstance()->getUniversalObjectCrates() + [
+ 'dateinterval',
+ 'domdocument',
+ 'domnode'
+ ],
true
)
)
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php
index ba5021908ee..a752092b223 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php
@@ -2,6 +2,7 @@
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
use PhpParser;
+use Psalm\Config;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
@@ -397,7 +398,7 @@ public static function analyze(
// but we don't want to throw an error
// Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164
if ($lhs_type_part instanceof TObject
- || in_array(strtolower($lhs_type_part->value), ['stdclass', 'simplexmlelement'], true)
+ || in_array(strtolower($lhs_type_part->value), Config::getInstance()->getUniversalObjectCrates(), true)
) {
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
diff --git a/tests/PropertyTypeTest.php b/tests/PropertyTypeTest.php
index 4b457e13127..94d2203bfac 100644
--- a/tests/PropertyTypeTest.php
+++ b/tests/PropertyTypeTest.php
@@ -127,6 +127,32 @@ public function getX(bool $b): int {
$this->analyzeFile('somefile.php', new Context());
}
+ /**
+ * @return void
+ */
+ public function testUniversalObjectCrates(): void
+ {
+ /** @var class-string $classString */
+ $classString = 'Foo';
+ Config::getInstance()->addUniversalObjectCrate($classString);
+
+ $this->addFile(
+ 'somefile.php',
+ 'bar;
+
+ // sets are fine
+ $f->buzz = false;
+ '
+ );
+
+ $this->analyzeFile('somefile.php', new Context());
+ }
+
/**
* @return void
*/