[FEATURE] Replace @inject with @TYPO3\CMS\Extbase\Annotation\Inject 90/54590/10
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Wed, 8 Nov 2017 13:57:33 +0000 (14:57 +0100)
committerStefan Neufeind <typo3.neufeind@speedpartner.de>
Thu, 16 Nov 2017 15:46:18 +0000 (16:46 +0100)
This patch introduces the usage of doctrine annotations.
Therefore the AnnotationRegistry is configured during the
bootstrap and the annotation "TYPO3\CMS\Extbase\Annotation\Inject"
is evaluated when building a ClassSchema for a class.

On top of that this patch puts the static variable
$ignoredTags into the DocCommentParser class, which
saved quite some bytes of RAM when reconstituting
the ClassSchema objects from the cache.

Resolves: #82869
Releases: master
Change-Id: I99d17706395ebc0c308c7f611c59f207fe0b6233
Reviewed-on: https://review.typo3.org/54590
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
18 files changed:
composer.json
composer.lock
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Annotation/Inject.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
typo3/sysext/extbase/Classes/Reflection/DocCommentParser.php
typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyClassWithAllTypesOfProperties.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyClassWithInjectDoctrineAnnotation.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyClassWithInjectProperty.php [new file with mode: 0644]
typo3/sysext/install/Classes/Controller/UpgradeController.php
typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcher.php [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php [new file with mode: 0644]
typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/PropertyAnnotationMatcherFixture.php [new file with mode: 0644]
typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcherTest.php [new file with mode: 0644]

index 88e3527..ff0c382 100644 (file)
@@ -39,6 +39,7 @@
                "symfony/yaml": "^2.7 || ^3.0",
                "symfony/polyfill-mbstring": "^1.2",
                "doctrine/instantiator": "~1.0.4",
+               "doctrine/annotations": "^1.3",
                "typo3/cms-cli": "^1.0",
                "typo3/class-alias-loader": "^1.0",
                "typo3/cms-composer-installers": "^1.4",
index 12ab5b8..f19f51a 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "f8ebffa676af5146bdaa2b87785bc3d8",
+    "content-hash": "96561ed50946b9022ef86e512c2906fd",
     "packages": [
         {
             "name": "cogpowered/finediff",
index 6d1f401..c1484e0 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -219,6 +221,27 @@ class Bootstrap
         if (defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE) {
             self::$usesComposerClassLoading = true;
         }
+
+        /** @see initializeAnnotationRegistry */
+        AnnotationRegistry::registerLoader([$classLoader, 'loadClass']);
+
+        /*
+         * All annotations defined by and for Extbase need to be
+         * ignored during their deprecation. Later their usage may and
+         * should throw an Exception
+         */
+        AnnotationReader::addGlobalIgnoredName('inject');
+        AnnotationReader::addGlobalIgnoredName('transient');
+        AnnotationReader::addGlobalIgnoredName('lazy');
+        AnnotationReader::addGlobalIgnoredName('validate');
+        AnnotationReader::addGlobalIgnoredName('cascade');
+        AnnotationReader::addGlobalIgnoredName('ignorevalidation');
+        AnnotationReader::addGlobalIgnoredName('firsttest');
+        AnnotationReader::addGlobalIgnoredName('anothertest');
+        AnnotationReader::addGlobalIgnoredName('test');
+        AnnotationReader::addGlobalIgnoredName('const');
+        // ...
+
         return $this;
     }
 
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst
new file mode 100644 (file)
index 0000000..769c789
--- /dev/null
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+===============================================================================
+Deprecation: #82869 - Replace @inject with @TYPO3\CMS\Extbase\Annotation\Inject
+===============================================================================
+
+See :issue:`82869`
+
+Description
+===========
+
+The `@inject` annotation has been deprecated and must be replaced with the doctrine annotation `@TYPO3\CMS\Extbase\Annotation\Inject`.
+
+
+Impact
+======
+
+From version 9.0 on, `@inject` is deprecated and will be removed in version 10.
+
+
+Affected Installations
+======================
+
+All extensions that use `@inject` for dependency injection
+
+
+Migration
+=========
+
+Use `@TYPO3\CMS\Extbase\Annotation\Inject` instead.
+
+.. index:: PHP-API, ext:extbase, FullyScanned
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst
new file mode 100644 (file)
index 0000000..f1715c7
--- /dev/null
@@ -0,0 +1,62 @@
+.. include:: ../../Includes.txt
+
+===========================================================================
+Feature: #82869 - Replace @inject with @TYPO3\CMS\Extbase\Annotation\Inject
+===========================================================================
+
+See :issue:`82869`
+
+Description
+===========
+
+As a successor to the `@inject` annotation, the doctrine annotation `@TYPO3\CMS\Extbase\Annotation\Inject` has been introduced.
+
+Example:
+
+.. code-block:: php
+
+       /**
+        * @TYPO3\CMS\Extbase\Annotation\Inject
+        * @var Foo
+        */
+       public $property;
+
+Doctrine annotations are actual defined classes, therefore you can also use the annotation with a use statement.
+
+Example:
+
+.. code-block:: php
+
+       use TYPO3\CMS\Extbase\Annotation\Inject;
+
+.. code-block:: php
+
+       /**
+        * @Inject
+        * @var Foo
+        */
+       public $property;
+
+Used annotations can also be aliased which the core will most likely be using a lot in the future.
+
+Example:
+
+.. code-block:: php
+
+       use TYPO3\CMS\Extbase\Annotation as Extbase;
+
+.. code-block:: php
+
+       /**
+        * @Extbase\Inject
+        * @var Foo
+        */
+       public $property;
+
+
+Impact
+======
+
+In 9.x there is no actual impact. Both the simple `@inject` and `@TYPO3\CMS\Extbase\Annotation\Inject` can be used side by side. However, `@inject` is deprecated in 9.x and will be removed in version 10.
+
+.. index:: PHP-API, ext:extbase, FullyScanned
diff --git a/typo3/sysext/extbase/Classes/Annotation/Inject.php b/typo3/sysext/extbase/Classes/Annotation/Inject.php
new file mode 100644 (file)
index 0000000..829ac65
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Extbase\Annotation;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY"})
+ */
+class Inject
+{
+}
index fc36ebd..6bd6995 100644 (file)
@@ -14,8 +14,10 @@ namespace TYPO3\CMS\Extbase\Reflection;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\Common\Annotations\AnnotationReader;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\ClassNamingUtility;
+use TYPO3\CMS\Extbase\Annotation\Inject;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
 use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
@@ -91,11 +93,6 @@ class ClassSchema
     /**
      * @var array
      */
-    protected static $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
-
-    /**
-     * @var array
-     */
     private $tags = [];
 
     /**
@@ -136,15 +133,9 @@ class ClassSchema
             $this->modelType = static::MODELTYPE_VALUEOBJECT;
         }
 
-        $docCommentParser = new DocCommentParser();
+        $docCommentParser = new DocCommentParser(true);
         $docCommentParser->parseDocComment($reflectionClass->getDocComment());
-        foreach ($docCommentParser->getTagsValues() as $tag => $values) {
-            if (in_array($tag, static::$ignoredTags, true)) {
-                continue;
-            }
-
-            $this->tags[$tag] = $values;
-        }
+        $this->tags = $docCommentParser->getTagsValues();
 
         $this->reflectProperties($reflectionClass);
         $this->reflectMethods($reflectionClass);
@@ -155,6 +146,8 @@ class ClassSchema
      */
     protected function reflectProperties(\ReflectionClass $reflectionClass)
     {
+        $annotationReader = new AnnotationReader();
+
         foreach ($reflectionClass->getProperties() as $reflectionProperty) {
             $propertyName = $reflectionProperty->getName();
 
@@ -170,14 +163,10 @@ class ClassSchema
                 'tags'        => []
             ];
 
-            $docCommentParser = new DocCommentParser();
+            $docCommentParser = new DocCommentParser(true);
             $docCommentParser->parseDocComment($reflectionProperty->getDocComment());
             foreach ($docCommentParser->getTagsValues() as $tag => $values) {
-                if (in_array($tag, static::$ignoredTags, true)) {
-                    continue;
-                }
-
-                $this->properties[$propertyName]['tags'][$tag] = $values;
+                $this->properties[$propertyName]['tags'][strtolower($tag)] = $values;
             }
 
             $this->properties[$propertyName]['annotations']['inject'] = false;
@@ -187,7 +176,25 @@ class ClassSchema
             $this->properties[$propertyName]['annotations']['cascade'] = null;
             $this->properties[$propertyName]['annotations']['dependency'] = null;
 
+            if ($propertyName !== 'settings'
+                && ($annotation = $annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class)) instanceof Inject
+            ) {
+                try {
+                    $varValue = ltrim($docCommentParser->getTagValues('var')[0], '\\');
+                    $this->properties[$propertyName]['annotations']['inject'] = true;
+                    $this->properties[$propertyName]['annotations']['type'] = $varValue;
+                    $this->properties[$propertyName]['annotations']['dependency'] = $varValue;
+
+                    $this->injectProperties[] = $propertyName;
+                } catch (\Exception $e) {
+                }
+            }
+
             if ($propertyName !== 'settings' && $docCommentParser->isTaggedWith('inject')) {
+                trigger_error(
+                    'Tagging properties with @inject is deprecated and will be removed in TYPO3 v10.0.',
+                    E_USER_DEPRECATED
+                );
                 try {
                     $varValues = $docCommentParser->getTagValues('var');
                     $this->properties[$propertyName]['annotations']['inject'] = true;
@@ -253,13 +260,9 @@ class ClassSchema
             $this->methods[$methodName]['params']       = [];
             $this->methods[$methodName]['tags']         = [];
 
-            $docCommentParser = new DocCommentParser();
+            $docCommentParser = new DocCommentParser(true);
             $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
             foreach ($docCommentParser->getTagsValues() as $tag => $values) {
-                if (in_array($tag, static::$ignoredTags, true)) {
-                    continue;
-                }
-
                 $this->methods[$methodName]['tags'][$tag] = $values;
             }
 
index bd15cee..65ee503 100644 (file)
@@ -20,6 +20,11 @@ namespace TYPO3\CMS\Extbase\Reflection;
 class DocCommentParser
 {
     /**
+     * @var array
+     */
+    protected static $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
+
+    /**
      * @var string The description as found in the doc comment
      */
     protected $description = '';
@@ -30,6 +35,19 @@ class DocCommentParser
     protected $tags = [];
 
     /**
+     * @var bool
+     */
+    private $useIgnoredTags;
+
+    /**
+     * @param bool $useIgnoredTags
+     */
+    public function __construct($useIgnoredTags = false)
+    {
+        $this->useIgnoredTags = $useIgnoredTags;
+    }
+
+    /**
      * Parses the given doc comment and saves the result (description and
      * tags) in the parser's object. They can be retrieved by the
      * getTags() getTagValues() and getDescription() methods.
@@ -109,6 +127,11 @@ class DocCommentParser
     {
         $tagAndValue = preg_split('/\\s/', $line, 2);
         $tag = substr($tagAndValue[0], 1);
+
+        if ($this->useIgnoredTags && in_array($tag, static::$ignoredTags, true)) {
+            return;
+        }
+
         if (count($tagAndValue) > 1) {
             $this->tags[$tag][] = trim($tagAndValue[1]);
         } else {
index 5c7ce12..11d8b44 100644 (file)
@@ -140,14 +140,21 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
 
     public function testClassSchemaDetectsInjectProperties()
     {
-        $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
+        $classSchema = new ClassSchema(Fixture\DummyClassWithInjectDoctrineAnnotation::class);
         static::assertTrue($classSchema->hasInjectProperties());
 
-        $propertyDefinition = $classSchema->getProperty('propertyWithInjectAnnotation');
-        static::assertTrue($propertyDefinition['annotations']['inject']);
-
         $injectProperties = $classSchema->getInjectProperties();
-        static::assertArrayHasKey('propertyWithInjectAnnotation', $injectProperties);
+        static::assertArrayHasKey('propertyWithFullQualifiedClassName', $injectProperties);
+        static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']);
+
+        static::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
+        static::assertSame('DummyClassWithInjectDoctrineAnnotation', $injectProperties['propertyWithRelativeClassName']);
+
+        static::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
+        static::assertSame('ClassSchemaTest', $injectProperties['propertyWithImportedClassName']);
+
+        static::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
+        static::assertSame('AliasedClassSchemaTest', $injectProperties['propertyWithImportedAndAliasedClassName']);
     }
 
     public function testClassSchemaDetectsInjectMethods()
index 624a415..ec2fa4d 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation\Inject;
+
 /**
  * Fixture class with getters and setters
  *
@@ -43,7 +45,7 @@ class DummyClassWithAllTypesOfProperties
     public $propertyWithIgnoredTags;
 
     /**
-     * @inject
+     * @Inject
      * @var DummyClassWithAllTypesOfProperties
      */
     public $propertyWithInjectAnnotation;
diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyClassWithInjectDoctrineAnnotation.php b/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyClassWithInjectDoctrineAnnotation.php
new file mode 100644 (file)
index 0000000..69b8ca3
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\Annotation\Inject;
+use TYPO3\CMS\Extbase\Tests\Unit\Reflection\ClassSchemaTest;
+use TYPO3\CMS\Extbase\Tests\Unit\Reflection\ClassSchemaTest as AliasedClassSchemaTest;
+
+/**
+ * Fixture class with absolute inject annotation
+ */
+class DummyClassWithInjectDoctrineAnnotation
+{
+    /**
+     * @Inject
+     * @var \TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithInjectDoctrineAnnotation
+     */
+    public $propertyWithFullQualifiedClassName;
+
+    /**
+     * @Inject
+     * @var DummyClassWithInjectDoctrineAnnotation
+     */
+    public $propertyWithRelativeClassName;
+
+    /**
+     * @Inject
+     * @var ClassSchemaTest
+     */
+    public $propertyWithImportedClassName;
+
+    /**
+     * @Inject
+     * @var AliasedClassSchemaTest
+     */
+    public $propertyWithImportedAndAliasedClassName;
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php
new file mode 100644 (file)
index 0000000..756822e
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\Reflection\ClassSchema;
+
+/**
+ * Test case
+ */
+class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
+{
+    public function testClassSchemaDetectsInjectProperties()
+    {
+        $classSchema = new ClassSchema(Fixture\DummyClassWithInjectProperty::class);
+        static::assertTrue($classSchema->hasInjectProperties());
+
+        $propertyDefinition = $classSchema->getProperty('propertyWithInjectAnnotation');
+        static::assertTrue($propertyDefinition['annotations']['inject']);
+
+        $injectProperties = $classSchema->getInjectProperties();
+        static::assertArrayHasKey('propertyWithInjectAnnotation', $injectProperties);
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyClassWithInjectProperty.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyClassWithInjectProperty.php
new file mode 100644 (file)
index 0000000..bf59fea
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture class with getters and setters
+ *
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
+ */
+class DummyClassWithInjectProperty
+{
+    /**
+     * @inject
+     * @var DummyClassWithInjectProperty
+     */
+    public $propertyWithInjectAnnotation;
+}
index 6031f2d..9e84755 100644 (file)
@@ -48,6 +48,7 @@ use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodArgumentRequiredMatcher
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodArgumentUnusedMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodCallMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodCallStaticMatcher;
+use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\PropertyAnnotationMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\PropertyProtectedMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\PropertyPublicMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\MatcherFactory;
@@ -100,6 +101,10 @@ class UpgradeController extends AbstractController
             'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/ConstantMatcher.php',
         ],
         [
+            'class' => PropertyAnnotationMatcher::class,
+            'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php',
+        ],
+        [
             'class' => FunctionCallMatcher::class,
             'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/FunctionCallMatcher.php',
         ],
diff --git a/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcher.php b/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcher.php
new file mode 100644 (file)
index 0000000..0890f44
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use PhpParser\Comment\Doc;
+use PhpParser\Node;
+use PhpParser\Node\Stmt\Property;
+use PhpParser\Node\Stmt\PropertyProperty;
+
+/**
+ * Find usages of property annotations
+ */
+class PropertyAnnotationMatcher extends AbstractCoreMatcher
+{
+    /**
+     * Prepare $this->flatMatcherDefinitions once and validate config
+     *
+     * @param array $matcherDefinitions Incoming main configuration
+     */
+    public function __construct(array $matcherDefinitions)
+    {
+        $this->matcherDefinitions = $matcherDefinitions;
+        $this->validateMatcherDefinitions();
+    }
+
+    /**
+     * Called by PhpParser.
+     * Test for property annotations (strong match)
+     *
+     * @param Node $node
+     */
+    public function enterNode(Node $node)
+    {
+        if ($node instanceof Property
+            && ($property = reset($node->props)) instanceof PropertyProperty
+            && ($docComment = $node->getDocComment()) instanceof Doc
+            && !$this->isFileIgnored($node)
+            && !$this->isLineIgnored($node)
+        ) {
+            /** @var PropertyProperty $property */
+            $isPossibleMatch = false;
+            $match = [
+                'restFiles' => [],
+                'line' => $property->getAttribute('startLine'),
+                'indicator' => 'strong',
+            ];
+
+            $matches = [];
+            preg_match_all(
+                '/\s*\s@(?<annotations>[^\s.]*).*\n/',
+                $docComment->getText(),
+                $matches
+            );
+
+            foreach ($matches['annotations'] as $annotation) {
+                $annotation = '@' . $annotation;
+
+                if (!isset($this->matcherDefinitions[$annotation])) {
+                    continue;
+                }
+
+                $isPossibleMatch = true;
+                $match['message'] = 'Property "' . $property->name . '" uses an ' . $annotation . ' annotation.';
+                $match['restFiles'] = array_unique(array_merge(
+                    $match['restFiles'],
+                    $this->matcherDefinitions[$annotation]['restFiles']
+                ));
+            }
+
+            if ($isPossibleMatch) {
+                $this->matches[] = $match;
+            }
+        }
+    }
+}
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php
new file mode 100644 (file)
index 0000000..3ececbc
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+return [
+    '@inject' => [
+        'restFiles' => [
+            'Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+            'Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+        ],
+    ],
+];
diff --git a/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/PropertyAnnotationMatcherFixture.php b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/PropertyAnnotationMatcherFixture.php
new file mode 100644 (file)
index 0000000..1e4d996
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Tests\Unit\ExtensionScanner\Php\Matcher\Fixtures;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture file
+ */
+class PropertyAnnotationMatcherFixture
+{
+    /**
+     * @inject
+     */
+    public $property;
+}
diff --git a/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcherTest.php b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcherTest.php
new file mode 100644 (file)
index 0000000..ab970a6
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Tests\Unit\ExtensionScanner\Php\Matcher;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\NameResolver;
+use PhpParser\ParserFactory;
+use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\PropertyAnnotationMatcher;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class PropertyAnnotationMatcherTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function hitsFromFixtureAreFound()
+    {
+        $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
+        $fixtureFile = __DIR__ . '/Fixtures/PropertyAnnotationMatcherFixture.php';
+        $statements = $parser->parse(file_get_contents($fixtureFile));
+
+        $traverser = new NodeTraverser();
+        $traverser->addVisitor(new NameResolver());
+
+        $configuration = [
+            '@inject' => [
+                'restFiles' => [
+                    'Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+                    'Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+                ],
+            ],
+        ];
+        $subject = new PropertyAnnotationMatcher($configuration);
+        $traverser->addVisitor($subject);
+        $traverser->traverse($statements);
+        $expectedHitLineNumbers = [
+            26,
+        ];
+        $actualHitLineNumbers = [];
+        foreach ($subject->getMatches() as $hit) {
+            $actualHitLineNumbers[] = $hit['line'];
+        }
+        $this->assertEquals($expectedHitLineNumbers, $actualHitLineNumbers);
+    }
+}