From 6eae6ee7dfc1b83736fb9e930a07510614134c4e Mon Sep 17 00:00:00 2001
From: Larry Garfield <larry@garfieldtech.com>
Date: Mon, 9 May 2022 10:42:43 -0500
Subject: [PATCH] [BUGFIX] Resolve access to undefined property PHPStan
 warnings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch addresses most "undefined property" PHPStan messages.

Used command:

> Build/Scripts/runTests.sh -s phpstanGenerateBaseline

Resolves: #97596
Releases: main, 11.5
Change-Id: I8c599166fa1747cc34573a035598eaaadbf680cc
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/74583
Tested-by: core-ci <typo3@b13.com>
Tested-by: Nikita Hovratov <nikita.h@live.de>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Nikita Hovratov <nikita.h@live.de>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
---
 Build/phpstan/phpstan-baseline.neon           | 40 -------------------
 .../ViewHelpers/ArrayBrowserViewHelper.php    |  1 -
 .../Classes/Database/Schema/Parser/Parser.php |  2 +-
 .../core/Classes/Utility/GeneralUtility.php   | 13 +++++-
 .../Parser/PageTsConfigParserTest.php         |  1 +
 .../Utility/DoubleMetaPhoneUtility.php        |  4 ++
 .../Matcher/ConstructorArgumentMatcher.php    | 20 +++++-----
 7 files changed, 27 insertions(+), 54 deletions(-)

diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon
index f0152cd56694..35e664e9248b 100644
--- a/Build/phpstan/phpstan-baseline.neon
+++ b/Build/phpstan/phpstan-baseline.neon
@@ -190,11 +190,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/backend/Classes/View/PageLayoutView.php
 
-		-
-			message: "#^Access to an undefined property TYPO3\\\\CMS\\\\Backend\\\\View\\\\ArrayBrowser\\:\\:\\$dontLinkVar\\.$#"
-			count: 1
-			path: ../../typo3/sysext/backend/Classes/ViewHelpers/ArrayBrowserViewHelper.php
-
 		-
 			message: "#^Call to an undefined method TYPO3Fluid\\\\Fluid\\\\Core\\\\Rendering\\\\RenderingContextInterface\\:\\:getRequest\\(\\)\\.$#"
 			count: 1
@@ -615,11 +610,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/core/Classes/Database/Schema/DefaultTcaSchema.php
 
-		-
-			message: "#^Access to an undefined property TYPO3\\\\CMS\\\\Core\\\\Database\\\\Schema\\\\Parser\\\\AST\\\\CreateColumnDefinitionItem\\:\\:\\$null\\.$#"
-			count: 1
-			path: ../../typo3/sysext/core/Classes/Database/Schema/Parser/Parser.php
-
 		-
 			message: "#^Unreachable statement \\- code above always terminates\\.$#"
 			count: 3
@@ -1335,16 +1325,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
 
-		-
-			message: "#^Access to an undefined property object\\:\\:\\$info\\.$#"
-			count: 1
-			path: ../../typo3/sysext/core/Classes/Utility/GeneralUtility.php
-
-		-
-			message: "#^Call to an undefined method object\\:\\:getLastErrorArray\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/core/Classes/Utility/GeneralUtility.php
-
 		-
 			message: "#^Method TYPO3\\\\CMS\\\\Core\\\\Utility\\\\GeneralUtility\\:\\:getMaxUploadFileSize\\(\\) should return int but returns float\\.$#"
 			count: 1
@@ -1590,11 +1570,6 @@ parameters:
 			count: 2
 			path: ../../typo3/sysext/core/Tests/Unit/Configuration/FlexForm/FlexFormToolsTest.php
 
-		-
-			message: "#^Access to an undefined property TYPO3\\\\CMS\\\\Core\\\\Tests\\\\Unit\\\\Configuration\\\\Parser\\\\PageTsConfigParserTest\\:\\:\\$setup\\.$#"
-			count: 1
-			path: ../../typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
-
 		-
 			message: "#^Parameter \\#2 \\$coreCache of class TYPO3\\\\CMS\\\\Core\\\\ExpressionLanguage\\\\ProviderConfigurationLoader constructor expects TYPO3\\\\CMS\\\\Core\\\\Cache\\\\Frontend\\\\PhpFrontend, TYPO3\\\\CMS\\\\Core\\\\Cache\\\\Frontend\\\\FrontendInterface given\\.$#"
 			count: 1
@@ -3710,11 +3685,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php
 
-		-
-			message: "#^Access to an undefined property TYPO3\\\\CMS\\\\IndexedSearch\\\\Utility\\\\DoubleMetaPhoneUtility\\:\\:\\$pObj\\.$#"
-			count: 1
-			path: ../../typo3/sysext/indexed_search/Classes/Indexer.php
-
 		-
 			message: "#^Offset int\\<0, max\\> does not exist on array\\{\\}\\.$#"
 			count: 1
@@ -3790,16 +3760,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstantMatcher.php
 
-		-
-			message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$args\\.$#"
-			count: 5
-			path: ../../typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php
-
-		-
-			message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$class\\.$#"
-			count: 5
-			path: ../../typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php
-
 		-
 			message: "#^Property PhpParser\\\\Node\\\\Expr\\\\New_\\:\\:\\$class \\(PhpParser\\\\Node\\\\Expr\\|PhpParser\\\\Node\\\\Name\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\) in isset\\(\\) is not nullable\\.$#"
 			count: 1
diff --git a/typo3/sysext/backend/Classes/ViewHelpers/ArrayBrowserViewHelper.php b/typo3/sysext/backend/Classes/ViewHelpers/ArrayBrowserViewHelper.php
index 7af8d1bae8c7..0136e0d6d1b6 100644
--- a/typo3/sysext/backend/Classes/ViewHelpers/ArrayBrowserViewHelper.php
+++ b/typo3/sysext/backend/Classes/ViewHelpers/ArrayBrowserViewHelper.php
@@ -50,7 +50,6 @@ final class ArrayBrowserViewHelper extends AbstractViewHelper
     public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
     {
         $arrayBrowser = GeneralUtility::makeInstance(ArrayBrowser::class);
-        $arrayBrowser->dontLinkVar = true;
         $arrayBrowser->expAll = true;
         return $arrayBrowser->tree($arguments['data'], '');
     }
diff --git a/typo3/sysext/core/Classes/Database/Schema/Parser/Parser.php b/typo3/sysext/core/Classes/Database/Schema/Parser/Parser.php
index ab4819a98f8a..c81c1f5c0c3a 100644
--- a/typo3/sysext/core/Classes/Database/Schema/Parser/Parser.php
+++ b/typo3/sysext/core/Classes/Database/Schema/Parser/Parser.php
@@ -724,7 +724,7 @@ class Parser
                     $this->match(Lexer::T_NULL);
                     break;
                 case Lexer::T_NULL:
-                    $columnDefinitionItem->null = true;
+                    $columnDefinitionItem->allowNull = true;
                     $this->match(Lexer::T_NULL);
                     break;
                 case Lexer::T_DEFAULT:
diff --git a/typo3/sysext/core/Classes/Utility/GeneralUtility.php b/typo3/sysext/core/Classes/Utility/GeneralUtility.php
index e9dae31bdbed..1a72be3d86f1 100644
--- a/typo3/sysext/core/Classes/Utility/GeneralUtility.php
+++ b/typo3/sysext/core/Classes/Utility/GeneralUtility.php
@@ -24,6 +24,7 @@ use Psr\Container\ContainerInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
+use TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Core\ClassLoadingInformation;
 use TYPO3\CMS\Core\Core\Environment;
@@ -3202,11 +3203,15 @@ class GeneralUtility
      * Find the best service and check if it works.
      * Returns object of the service class.
      *
+     * This method is used for the legacy ExtensionManager:addService() mechanism,
+     * not with Dependency-Injected services. In practice, all remaining core uses of
+     * this mechanism are authentication services, which all have an info property.
+     *
      * @param string $serviceType Type of service (service key).
      * @param string $serviceSubType Sub type like file extensions or similar. Defined by the service.
      * @param array $excludeServiceKeys List of service keys which should be excluded in the search for a service
      * @throws \RuntimeException
-     * @return object|string[] The service object or an array with error infos.
+     * @return object|string[]|false The service object or an array with error infos, or false if no service was found.
      */
     public static function makeInstanceService($serviceType, $serviceSubType = '', array $excludeServiceKeys = [])
     {
@@ -3219,7 +3224,11 @@ class GeneralUtility
         while ($info = ExtensionManagementUtility::findService($serviceType, $serviceSubType, $excludeServiceKeys)) {
             // provide information about requested service to service object
             $info = array_merge($info, $requestInfo);
-            $obj = self::makeInstance($info['className']);
+
+            /** @var class-string<AbstractAuthenticationService> $className */
+            $className = $info['className'];
+            /** @var AbstractAuthenticationService $obj */
+            $obj = self::makeInstance($className);
             if (is_object($obj)) {
                 if (!is_callable([$obj, 'init'])) {
                     self::getLogger()->error('Requested service {class} has no init() method.', [
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php b/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
index 7d49348c425d..f41b23bbd56b 100644
--- a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
+++ b/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
@@ -42,6 +42,7 @@ class PageTsConfigParserTest extends UnitTestCase
         $matcherProphecy = $this->prophesize(ConditionMatcherInterface::class);
         $typoScriptParserProphecy = $this->prophesize(TypoScriptParser::class);
         $typoScriptParserProphecy->parse($input, $matcherProphecy)->shouldBeCalled()->will(function () use ($expectedParsedTsConfig) {
+            /** @var TypoScriptParser $this */
             $this->setup = $expectedParsedTsConfig;
         });
         $cache = new NullFrontend('runtime');
diff --git a/typo3/sysext/indexed_search/Classes/Utility/DoubleMetaPhoneUtility.php b/typo3/sysext/indexed_search/Classes/Utility/DoubleMetaPhoneUtility.php
index 5d756a6183d2..1ff951e7dec6 100644
--- a/typo3/sysext/indexed_search/Classes/Utility/DoubleMetaPhoneUtility.php
+++ b/typo3/sysext/indexed_search/Classes/Utility/DoubleMetaPhoneUtility.php
@@ -15,6 +15,8 @@
 
 namespace TYPO3\CMS\IndexedSearch\Utility;
 
+use TYPO3\CMS\IndexedSearch\Indexer;
+
 /**
  * TYPO3: Had to change name to "\TYPO3\CMS\IndexedSearch\Utility\DoubleMetaPhoneUtility" from just "DoubleMetaPhone" because TYPO3 requires a user class to be prefixed so:
  * TYPO3: If you want to use this metaphone method instead of the default in the indexer you can enable it in the extension configuration
@@ -53,6 +55,8 @@ class DoubleMetaPhoneUtility
      */
     public $current = 0;
 
+    public Indexer $pObj;
+
     //  methods
     // TYPO3 specific API to this class. BEGIN
     /**
diff --git a/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php b/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php
index 1cb91544a7a3..da459f543aff 100644
--- a/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php
+++ b/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php
@@ -71,7 +71,7 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
         $resolvedNode = $node->getAttribute(self::NODE_RESOLVED_AS, null) ?? $node;
         if (!$resolvedNode instanceof New_
             || !isset($resolvedNode->class)
-            || is_object($node->class) && !method_exists($node->class, '__toString')
+            || (isset($node->class) && is_object($node->class) && !method_exists($node->class, '__toString'))
             || !array_key_exists((string)$resolvedNode->class, $this->matcherDefinitions)
         ) {
             return null;
@@ -98,10 +98,10 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
      */
     protected function handleRequiredArguments(Node $node, Node $resolvedNode): bool
     {
-        $className = (string)$resolvedNode->class;
+        $className = (string)($resolvedNode->class ?? '');
         $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_REQUIRED] ?? null;
         $mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
-        $numberOfArguments = count($resolvedNode->args);
+        $numberOfArguments = count($resolvedNode->args ?? []);
 
         if ($candidate === null || $numberOfArguments >= $mandatoryArguments) {
             return false;
@@ -128,10 +128,10 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
      */
     protected function handleDroppedArguments(Node $node, Node $resolvedNode): bool
     {
-        $className = (string)$resolvedNode->class;
+        $className = (string)($resolvedNode->class ?? '');
         $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_DROPPED] ?? null;
         $maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
-        $numberOfArguments = count($resolvedNode->args);
+        $numberOfArguments = count($resolvedNode->args ?? []);
 
         if ($candidate === null || $numberOfArguments <= $maximumArguments) {
             return false;
@@ -158,12 +158,12 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
      */
     protected function handleCalledArguments(Node $node, Node $resolvedNode): bool
     {
-        $className = (string)$resolvedNode->class;
+        $className = (string)($resolvedNode->class ?? '');
         $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_CALLED] ?? null;
-        $isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($resolvedNode->args);
+        $isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($resolvedNode->args ?? []);
         $mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
         $maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
-        $numberOfArguments = count($resolvedNode->args);
+        $numberOfArguments = count($resolvedNode->args ?? []);
 
         if ($candidate === null
             || !$isArgumentUnpackingUsed
@@ -191,7 +191,7 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
      */
     protected function handleUnusedArguments(Node $node, Node $resolvedNode): bool
     {
-        $className = (string)$resolvedNode->class;
+        $className = (string)($resolvedNode->class ?? '');
         $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_UNUSED] ?? null;
         // values in array (if any) are actual position counts
         // e.g. `[2, 4]` refers to internal argument indexes `[1, 3]`
@@ -201,7 +201,7 @@ class ConstructorArgumentMatcher extends AbstractCoreMatcher
             return false;
         }
 
-        $arguments = $resolvedNode->args;
+        $arguments = $resolvedNode->args ?? [];
         // keeping positions having argument values that are not null
         $unusedArgumentPositions = array_filter(
             $unusedArgumentPositions,
-- 
GitLab