From 5de78f2c6be68a5d7cd799abb129fcfa20a3e29a Mon Sep 17 00:00:00 2001 From: Tomas Vondracek Date: Thu, 23 Jul 2026 21:18:08 +0200 Subject: [PATCH 1/2] Fix generic object type in Field factory and discriminator new $classname() produced a bare object, so PHPStan could not verify the Field method calls in FieldRepository::factory() and FieldDiscriminatorListener::extractFieldType(). Narrow the instances to Field / FieldInterface via inline @var, removing 7 baseline entries (6 method.notFound, 1 return.type). --- phpstan-baseline.php | 42 ------------------- .../Listener/FieldDiscriminatorListener.php | 4 +- src/Repository/FieldRepository.php | 1 + 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index e1e807a2a..29baee00c 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -2587,12 +2587,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Event/Listener/ContentFillListener.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:getType\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Event/Listener/FieldDiscriminatorListener.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#', 'identifier' => 'argument.type', @@ -3229,36 +3223,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Repository/ContentRepository.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:setDefaultLocale\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:setDefinition\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:setLabel\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:setLocale\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Call to an undefined method object\\:\\:setName\\(\\)\\.$#', - 'identifier' => 'method.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getMetadataFactory\\(\\) on Doctrine\\\\ORM\\\\EntityManagerInterface\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -3277,12 +3241,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Repository/FieldRepository.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Bolt\\\\Repository\\\\FieldRepository\\:\\:factory\\(\\) should return Bolt\\\\Entity\\\\Field but returns object\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Repository/FieldRepository.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Bolt\\\\Repository\\\\FieldRepository\\:\\:findAllByParent\\(\\) return type has no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', diff --git a/src/Event/Listener/FieldDiscriminatorListener.php b/src/Event/Listener/FieldDiscriminatorListener.php index 6d8b5d359..b6a2ed660 100644 --- a/src/Event/Listener/FieldDiscriminatorListener.php +++ b/src/Event/Listener/FieldDiscriminatorListener.php @@ -66,7 +66,9 @@ private function isField(string $class): bool private function extractFieldType(string $class): string { - $fieldType = (new $class())->getType(); + /** @var FieldInterface $field */ + $field = new $class(); + $fieldType = $field->getType(); if (in_array($fieldType, $this->tempMap, true) === true) { throw new LogicException("Found duplicate discriminator map entry '" . $fieldType . "' in " . $class); } diff --git a/src/Repository/FieldRepository.php b/src/Repository/FieldRepository.php index 6771541ff..71174f93b 100644 --- a/src/Repository/FieldRepository.php +++ b/src/Repository/FieldRepository.php @@ -96,6 +96,7 @@ public static function factory(Collection $definition, string $name = '', string $classname = self::getFieldClassname($type); if ($classname && class_exists($classname)) { + /** @var Field $field */ $field = new $classname(); } else { $field = new Field(); From 46db83145c7641ea21323410e31b8cbe35656964 Mon Sep 17 00:00:00 2001 From: Tomas Vondracek Date: Thu, 23 Jul 2026 21:24:29 +0200 Subject: [PATCH 2/2] Fix invalid array key types (offsetAccess.invalidOffset) Several array keys could be null or array, which PHPStan level 8 rejects as possibly-invalid offsets: - Content::getContentType() (?string) in ContentValidator: skip null keys. - Field/Content::getId() (?int) in TranslationsManager and FieldExtension: cast to int (entities are always persisted in these paths). - key() (int|string|null) in SelectQuery::getCheckboxFieldExpression: cast to string. - Filter::getKey() (string|array) in SelectQuery join maps: narrow the key to string via inline @var, matching how it is used elsewhere (sprintf '%s'). Removes the baselined array|string offsetAccess.invalidOffset entry and clears the 6 non-baselined failures reported on newer PHP. --- phpstan-baseline.php | 6 ------ src/Storage/SelectQuery.php | 16 +++++++++++----- src/Twig/FieldExtension.php | 2 +- src/Utils/TranslationsManager.php | 2 +- src/Validator/ContentValidator.php | 3 +++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 29baee00c..031a5190e 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -3943,12 +3943,6 @@ 'count' => 4, 'path' => __DIR__ . '/src/Storage/SelectQuery.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Possibly invalid array key type array\\|string\\.$#', - 'identifier' => 'offsetAccess.invalidOffset', - 'count' => 3, - 'path' => __DIR__ . '/src/Storage/SelectQuery.php', -]; $ignoreErrors[] = [ 'message' => '#^Property Bolt\\\\Storage\\\\SelectQuery\\:\\:\\$coreDateFields type has no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', diff --git a/src/Storage/SelectQuery.php b/src/Storage/SelectQuery.php index e15512491..7b46652e6 100644 --- a/src/Storage/SelectQuery.php +++ b/src/Storage/SelectQuery.php @@ -498,8 +498,10 @@ private function getCoreFieldExpression(Filter $filter): string private function getReferenceFieldExpression(Filter $filter): string { - if ($filter->getKey() !== $this->anything) { - $this->referenceJoins[$filter->getKey()] = $filter; + /** @var string $key */ + $key = $filter->getKey(); + if ($key !== $this->anything) { + $this->referenceJoins[$key] = $filter; return $filter->getExpression(); } @@ -518,7 +520,9 @@ private function getReferenceFieldExpression(Filter $filter): string private function getTaxonomyFieldExpression(Filter $filter): string { - $this->taxonomyJoins[$filter->getKey()] = $filter; + /** @var string $key */ + $key = $filter->getKey(); + $this->taxonomyJoins[$key] = $filter; $originalExpression = $filter->getExpression(); $originalLeftExpression = '/content\.([^\s])*/'; @@ -542,14 +546,16 @@ private function getCheckboxFieldExpression(Filter $filter): string $value = $isSqlite ? true : 'true'; } - $filter->setParameters([key($filter->getParameters()) => $value]); + $filter->setParameters([(string) key($filter->getParameters()) => $value]); return $this->getRegularFieldExpression($filter); } private function getRegularFieldExpression(Filter $filter): string { - $this->fieldJoins[$filter->getKey()] = $filter; + /** @var string $key */ + $key = $filter->getKey(); + $this->fieldJoins[$key] = $filter; $expr = $this->qb->expr()->andX(); // where clause for the value of the field diff --git a/src/Twig/FieldExtension.php b/src/Twig/FieldExtension.php index 94cf1181f..b18f2c59f 100644 --- a/src/Twig/FieldExtension.php +++ b/src/Twig/FieldExtension.php @@ -120,7 +120,7 @@ public function getSelected(SelectField $field, $returnsingle = false, $returnar // Sort the results in the order of the $ids. $order = array_flip($ids); - $records = $records->sortBy(fn (Content $record) => $order[$record->getId()])->values()->toArray(); + $records = $records->sortBy(fn (Content $record) => $order[(int) $record->getId()])->values()->toArray(); if ($returnsingle || (! $returnarray && $definition->get('multiple') === false)) { return current($records); diff --git a/src/Utils/TranslationsManager.php b/src/Utils/TranslationsManager.php index a192f68bd..826dd7e64 100644 --- a/src/Utils/TranslationsManager.php +++ b/src/Utils/TranslationsManager.php @@ -44,7 +44,7 @@ private function getFieldChildrenTranslations(array &$translations, FieldParentI $this->getFieldChildrenTranslations($translations, $child); } - $translations[$child->getId()] = $child->getTranslations(); + $translations[(int) $child->getId()] = $child->getTranslations(); } } diff --git a/src/Validator/ContentValidator.php b/src/Validator/ContentValidator.php index e816f8cc1..268ebb052 100644 --- a/src/Validator/ContentValidator.php +++ b/src/Validator/ContentValidator.php @@ -159,6 +159,9 @@ private function relationsToMap(Collection $relations): array foreach ($relations as $relation) { $to = $relation->getToContent(); $typeName = $to->getContentType(); + if ($typeName === null) { + continue; + } if (! isset($result[$typeName])) { $result[$typeName] = []; }