"guzzlehttp/guzzle": "^6.3.0",
"mso/idna-convert": "^1.1.0",
"nikic/php-parser": "^4.0",
+ "phpdocumentor/reflection-docblock": "^4.3",
"psr/container": "^1.0",
"psr/http-message": "~1.0",
"psr/http-server-middleware": "^1.0",
"symfony/finder": "^4.1",
"symfony/polyfill-intl-icu": "^1.6",
"symfony/polyfill-mbstring": "^1.2",
+ "symfony/property-info": "^4.2",
"symfony/routing": "^4.1",
"symfony/yaml": "^4.1",
"typo3/class-alias-loader": "^1.0",
"typo3/cms-cli": "^2.0",
"typo3/cms-composer-installers": "^2.0",
"typo3/phar-stream-wrapper": "^3.0.1",
- "typo3fluid/fluid": "^2.6.0"
+ "typo3fluid/fluid": "^2.6.0",
+ "webmozart/assert": "^1.0"
},
"require-dev": {
"codeception/codeception": "^2.4.5",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "cd80908ee69343d7f094a589ce995519",
+ "content-hash": "665468d5ffefa8e2e72be0a338838765",
"packages": [
{
"name": "cogpowered/finediff",
"time": "2018-09-18T07:03:24+00:00"
},
{
+ "name": "phpdocumentor/reflection-common",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2017-09-11T18:02:19+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
+ "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0",
+ "phpdocumentor/type-resolver": "^0.4.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "doctrine/instantiator": "~1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2017-11-30T07:14:17+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5 || ^7.0",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "time": "2017-07-14T14:27:02+00:00"
+ },
+ {
"name": "psr/cache",
"version": "1.0.1",
"source": {
"time": "2018-07-26T11:24:31+00:00"
},
{
+ "name": "symfony/inflector",
+ "version": "v4.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/inflector.git",
+ "reference": "9f64271222922ef1a10e43f77d88baf72bf22b0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/inflector/zipball/9f64271222922ef1a10e43f77d88baf72bf22b0e",
+ "reference": "9f64271222922ef1a10e43f77d88baf72bf22b0e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Inflector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Inflector Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string",
+ "symfony",
+ "words"
+ ],
+ "time": "2019-01-03T09:07:35+00:00"
+ },
+ {
"name": "symfony/intl",
"version": "v4.1.4",
"source": {
"time": "2018-08-06T14:22:27+00:00"
},
{
+ "name": "symfony/property-info",
+ "version": "v4.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/property-info.git",
+ "reference": "cfb42283015c194d14917f4dc108df7efc63fa4a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/property-info/zipball/cfb42283015c194d14917f4dc108df7efc63fa4a",
+ "reference": "cfb42283015c194d14917f4dc108df7efc63fa4a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/inflector": "~3.4|~4.0"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2",
+ "phpdocumentor/type-resolver": "<0.3.0",
+ "symfony/dependency-injection": "<3.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0",
+ "symfony/cache": "~3.4|~4.0",
+ "symfony/dependency-injection": "~3.4|~4.0",
+ "symfony/serializer": "~3.4|~4.0"
+ },
+ "suggest": {
+ "phpdocumentor/reflection-docblock": "To use the PHPDoc",
+ "psr/cache-implementation": "To cache results",
+ "symfony/doctrine-bridge": "To use Doctrine metadata",
+ "symfony/serializer": "To use Serializer metadata"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\PropertyInfo\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "dunglas@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Property Info Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "doctrine",
+ "phpdoc",
+ "property",
+ "symfony",
+ "type",
+ "validator"
+ ],
+ "time": "2019-01-03T09:07:35+00:00"
+ },
+ {
"name": "symfony/routing",
"version": "v4.1.4",
"source": {
],
"description": "The TYPO3 Fluid template rendering engine",
"time": "2018-12-07T14:46:13+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
+ "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2018-01-29T19:49:41+00:00"
}
],
"packages-dev": [
"time": "2018-02-15T16:58:55+00:00"
},
{
- "name": "phpdocumentor/reflection-common",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "shasum": ""
- },
- "require": {
- "php": ">=5.5"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "time": "2017-09-11T18:02:19+00:00"
- },
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "4.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "phpdocumentor/reflection-common": "^1.0.0",
- "phpdocumentor/type-resolver": "^0.4.0",
- "webmozart/assert": "^1.0"
- },
- "require-dev": {
- "doctrine/instantiator": "~1.0.5",
- "mockery/mockery": "^1.0",
- "phpunit/phpunit": "^6.4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2017-11-30T07:14:17+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "0.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
- "shasum": ""
- },
- "require": {
- "php": "^5.5 || ^7.0",
- "phpdocumentor/reflection-common": "^1.0"
- },
- "require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^5.2||^4.8.24"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "time": "2017-07-14T14:27:02+00:00"
- },
- {
"name": "phpspec/prophecy",
"version": "1.7.6",
"source": {
"typo3"
],
"time": "2019-01-17T14:51:59+00:00"
- },
- {
- "name": "webmozart/assert",
- "version": "1.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/webmozart/assert.git",
- "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
- "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.3 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6",
- "sebastian/version": "^1.0.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Webmozart\\Assert\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@gmail.com"
- }
- ],
- "description": "Assertions to validate method input/output with nice error messages.",
- "keywords": [
- "assert",
- "check",
- "validate"
- ],
- "time": "2018-01-29T19:49:41+00:00"
}
],
"aliases": [],
--- /dev/null
+.. include:: ../../Includes.txt
+
+===========================================================================
+Feature: #87457 - Use symfony/property-info to gather doc block information
+===========================================================================
+
+See :issue:`87457`
+
+Description
+===========
+
+The use of `symfony/property-info` enables us to resolve non fully qualified class names.
+
+This is now possible:
+
+.. code-block:: php
+
+ use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
+ use ExtbaseTeam\BlogExample\Domain\Model\Comment;
+
+ class Post
+ {
+ /*
+ * @var ObjectStorage<Comment>
+ */
+ public $comments;
+ }
+
+Important:
+This only works in extbase models as the reflection
+costs are high and the information is only needed
+in this case.
+
+The non fully qualified class name is now also
+supported for injection properties, although it is
+still recommended to avoid injection properties in
+favor of injection methods or constructor injection.
+
+Example:
+
+.. code-block:: php
+
+ use TYPO3\CMS\Extbase\Annotation as Extbase;
+ use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
+
+ class Service
+ {
+ /*
+ * @Extbase\Inject
+ * @var ConfigurationManager
+ */
+ public $configurationManager;
+ }
+
+
+.. index:: PHP-API, ext:extbase
$propertyValue = null;
if (isset($row[$columnName])) {
switch ($propertyType) {
+ case 'int':
case 'integer':
$propertyValue = (int)$row[$columnName];
break;
case 'float':
$propertyValue = (double)$row[$columnName];
break;
+ case 'bool':
case 'boolean':
$propertyValue = (bool)$row[$columnName];
break;
*/
use Doctrine\Common\Annotations\AnnotationReader;
+use phpDocumentor\Reflection\DocBlock\Tags\Param;
+use phpDocumentor\Reflection\DocBlockFactory;
+use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
+use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+use Symfony\Component\PropertyInfo\Type;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ClassNamingUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchPropertyException;
use TYPO3\CMS\Extbase\Reflection\ClassSchema\Method;
use TYPO3\CMS\Extbase\Reflection\ClassSchema\Property;
+use TYPO3\CMS\Extbase\Reflection\DocBlock\Tags\Null_;
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException;
/**
* @var array
*/
- private $tags;
+ private $injectProperties = [];
/**
* @var array
*/
- private $injectProperties = [];
+ private $injectMethods = [];
/**
- * @var array
+ * @var PropertyInfoExtractor
*/
- private $injectMethods = [];
+ private static $propertyInfoExtractor;
+
+ /**
+ * @var
+ */
+ private static $docBlockFactory;
/**
* Constructs this class schema
$this->modelType = static::MODELTYPE_VALUEOBJECT;
}
- $docCommentParser = new DocCommentParser(true);
- $docCommentParser->parseDocComment($reflectionClass->getDocComment());
- $this->tags = $docCommentParser->getTagsValues();
+ if (self::$propertyInfoExtractor === null) {
+ $docBlockFactory = DocBlockFactory::createInstance();
+ $docBlockFactory->registerTagHandler('var', DocBlock\Tags\Var_::class);
+
+ $phpDocExtractor = new PhpDocExtractor($docBlockFactory);
+ $reflectionExtractor = new ReflectionExtractor();
+
+ self::$propertyInfoExtractor = new PropertyInfoExtractor(
+ [],
+ [$phpDocExtractor, $reflectionExtractor]
+ );
+ }
+
+ if (self::$docBlockFactory === null) {
+ self::$docBlockFactory = DocBlockFactory::createInstance();
+ self::$docBlockFactory->registerTagHandler('author', Null_::class);
+ self::$docBlockFactory->registerTagHandler('covers', Null_::class);
+ self::$docBlockFactory->registerTagHandler('deprecated', Null_::class);
+ self::$docBlockFactory->registerTagHandler('link', Null_::class);
+ self::$docBlockFactory->registerTagHandler('method', Null_::class);
+ self::$docBlockFactory->registerTagHandler('property-read', Null_::class);
+ self::$docBlockFactory->registerTagHandler('property', Null_::class);
+ self::$docBlockFactory->registerTagHandler('property-write', Null_::class);
+ self::$docBlockFactory->registerTagHandler('return', Null_::class);
+ self::$docBlockFactory->registerTagHandler('see', Null_::class);
+ self::$docBlockFactory->registerTagHandler('since', Null_::class);
+ self::$docBlockFactory->registerTagHandler('source', Null_::class);
+ self::$docBlockFactory->registerTagHandler('throw', Null_::class);
+ self::$docBlockFactory->registerTagHandler('throws', Null_::class);
+ self::$docBlockFactory->registerTagHandler('uses', Null_::class);
+ self::$docBlockFactory->registerTagHandler('var', Null_::class);
+ self::$docBlockFactory->registerTagHandler('version', Null_::class);
+ }
$this->reflectProperties($reflectionClass);
$this->reflectMethods($reflectionClass);
'validators' => []
];
- $docCommentParser = new DocCommentParser(true);
- $docCommentParser->parseDocComment($reflectionProperty->getDocComment());
- foreach ($docCommentParser->getTagsValues() as $tag => $values) {
- $this->properties[$propertyName]['tags'][strtolower($tag)] = $values;
- }
-
$this->properties[$propertyName]['annotations']['inject'] = false;
$this->properties[$propertyName]['annotations']['lazy'] = false;
$this->properties[$propertyName]['annotations']['transient'] = false;
$this->properties[$propertyName]['annotations']['transient'] = true;
}
- if ($propertyName !== 'settings'
- && ($annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class) instanceof Inject)
+ $isInjectProperty = $propertyName !== 'settings'
+ && ($annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class) instanceof Inject);
+
+ $isPossibleCollectionProperty = $annotationReader->getPropertyAnnotation($reflectionProperty, Transient::class) === null
+ && $this->isModel();
+
+ $types = [];
+ $typesCount = 0;
+ if ($isInjectProperty || $isPossibleCollectionProperty) {
+ /** @var Type[] $types */
+ $types = (array)self::$propertyInfoExtractor->getTypes($this->className, $propertyName);
+ $typesCount = count($types);
+ }
+
+ if ($typesCount > 0
+ && ($annotation = $annotationReader->getPropertyAnnotation($reflectionProperty, Cascade::class)) instanceof Cascade
) {
- 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) {
- }
+ /** @var Cascade $annotation */
+ $this->properties[$propertyName]['annotations']['cascade'] = $annotation->value;
}
- if ($docCommentParser->isTaggedWith('var') && $this->properties[$propertyName]['annotations']['transient'] === false) {
- if (($annotation = $annotationReader->getPropertyAnnotation($reflectionProperty, Cascade::class)) instanceof Cascade) {
- /** @var Cascade $annotation */
- $this->properties[$propertyName]['annotations']['cascade'] = $annotation->value;
- }
+ if ($isInjectProperty && ($type = $types[0]) instanceof Type) {
+ $this->properties[$propertyName]['annotations']['inject'] = true;
+ $this->properties[$propertyName]['annotations']['type'] = $type->getClassName();
+ $this->properties[$propertyName]['annotations']['dependency'] = $type->getClassName();
- try {
- $type = TypeHandlingUtility::parseType(implode(' ', $docCommentParser->getTagValues('var')));
- } catch (\Exception $e) {
- $type = [
- 'type' => null,
- 'elementType' => null
- ];
- }
+ $this->injectProperties[] = $propertyName;
+ }
- $this->properties[$propertyName]['type'] = $type['type'] ? ltrim($type['type'], '\\') : null;
- $this->properties[$propertyName]['elementType'] = $type['elementType'] ? ltrim($type['elementType'], '\\') : null;
+ if ($isPossibleCollectionProperty) {
+ if ($typesCount === 1) {
+ $this->properties[$propertyName]['type'] = $types[0]->getClassName() ?? $types[0]->getBuiltinType();
+ } elseif ($typesCount === 2) {
+ [$type, $elementType] = $types;
+ $actualType = $type->getClassName() ?? $type->getBuiltinType();
+
+ if (TypeHandlingUtility::isCollectionType($actualType)
+ && $elementType->getBuiltinType() === 'array'
+ && $elementType->getCollectionValueType() instanceof Type
+ && $elementType->getCollectionValueType()->getClassName() !== null
+ ) {
+ $this->properties[$propertyName]['type'] = ltrim($actualType, '\\');
+ $this->properties[$propertyName]['elementType'] = ltrim($elementType->getCollectionValueType()->getClassName(), '\\');
+ }
+ }
}
}
}
$this->methods[$methodName]['annotations'] = [];
$this->methods[$methodName]['isAction'] = StringUtility::endsWith($methodName, 'Action');
- $docCommentParser = new DocCommentParser(true);
- $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
-
$argumentValidators = [];
$annotations = $annotationReader->getMethodAnnotations($reflectionMethod);
}
}
- foreach ($docCommentParser->getTagsValues() as $tag => $values) {
- $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) {
- return ltrim($value, '$');
- }, $values);
- }
-
foreach ($annotations as $annotation) {
if ($annotation instanceof IgnoreValidation) {
$this->methods[$methodName]['tags']['ignorevalidation'][] = $annotation->argumentName;
}
}
- $this->methods[$methodName]['description'] = $docCommentParser->getDescription();
+ $docComment = $reflectionMethod->getDocComment();
+ $docComment = is_string($docComment) ? $docComment : '';
foreach ($reflectionMethod->getParameters() as $parameterPosition => $reflectionParameter) {
/* @var \ReflectionParameter $reflectionParameter */
if (($parameterClass = $reflectionParameter->getClass()) instanceof \ReflectionClass) {
$this->methods[$methodName]['params'][$parameterName]['class'] = $parameterClass->getName();
$this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($parameterClass->getName(), '\\');
- } else {
- $methodTagsAndValues = $this->methods[$methodName]['tags'];
- if (isset($methodTagsAndValues['param'][$parameterPosition])) {
- $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
- if (count($explodedParameters) >= 2) {
- if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
- // ensure that short names of simple types are resolved correctly to the long form
- // this is important for all kinds of type checks later on
- $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
-
- $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($typeInfo['type'], '\\');
- } else {
- $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($explodedParameters[0], '\\');
- }
- }
+ }
+
+ if ($docComment !== '' && $this->methods[$methodName]['params'][$parameterName]['type'] === null) {
+ /*
+ * We create (redundant) instances here in this loop due to the fact that
+ * we do not want to analyse all doc blocks of all available methods. We
+ * use this technique only if we couldn't grasp all necessary data via
+ * reflection.
+ *
+ * Also, if we analyze all method doc blocks, we will trigger numerous errors
+ * due to non PSR-5 compatible tags in the core and in user land code.
+ *
+ * Fetching the data type via doc blocks will also be deprecated and removed
+ * in the near future.
+ */
+ $params = self::$docBlockFactory->create($docComment)
+ ->getTagsByName('param');
+
+ if (isset($params[$parameterPosition])) {
+ /** @var Param $param */
+ $param = $params[$parameterPosition];
+ $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($param->getType(), '\\');
}
}
}
/**
- * @return array
- */
- public function getTags(): array
- {
- return $this->tags;
- }
-
- /**
* @return bool
*/
public function hasInjectProperties(): bool
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Extbase\Reflection\DocBlock\Tags;
+
+use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
+
+/**
+ * Class TYPO3\CMS\Extbase\Reflection\DocBlock\Tags\Null_
+ */
+class Null_ implements StaticMethod
+{
+ public static function create($body)
+ {
+ }
+}
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Extbase\Reflection\DocBlock\Tags;
+
+use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
+use phpDocumentor\Reflection\TypeResolver;
+use phpDocumentor\Reflection\Types\Compound;
+use phpDocumentor\Reflection\Types\Context as TypeContext;
+use Webmozart\Assert\Assert;
+
+/**
+ * Class TYPO3\CMS\Extbase\Reflection\DocBlock\Tags\Var_
+ */
+class Var_ extends \phpDocumentor\Reflection\DocBlock\Tags\Var_
+{
+ /**
+ * @param $body
+ * @param TypeResolver|null $typeResolver
+ * @param DescriptionFactory|null $descriptionFactory
+ * @param TypeContext|null $context
+ * @return \phpDocumentor\Reflection\DocBlock\Tags\Var_|Var_
+ */
+ public static function create(
+ $body,
+ TypeResolver $typeResolver = null,
+ DescriptionFactory $descriptionFactory = null,
+ TypeContext $context = null
+ ) {
+ /*
+ * This class is needed to detect collections like
+ * @var Collection<CollectionType>
+ *
+ * While writing this comment, TYPO3 has a dependency to
+ * phpdocumentor/type-resolver:^0.4.0 via
+ * phpdocumentor/reflection-docblock:4.3.0.
+ *
+ * phpdocumentor/type-resolver can detect collections from
+ * version 0.5.0 on, but as there is no newer version of
+ * phpdocumentor/reflection-docblock, this feature is
+ * unavailable at the moment.
+ *
+ * Once phpdocumentor/reflection-docblock:5.0.0 has been
+ * released, TYPO3 should use that version along with an
+ * updated version of phpdocumentor/type-resolver and
+ * this class should be removed then.
+ */
+ Assert::stringNotEmpty($body);
+ Assert::allNotNull([$typeResolver, $descriptionFactory]);
+
+ $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
+ $type = null;
+ $variableName = '';
+
+ // if the first item that is encountered is not a variable; it is a type
+ if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
+ $currentPart = array_shift($parts);
+
+ $matches = [];
+ $pattern = '/(?P<type>[^\s<>]+)<(?P<elementType>[^\s<>]+)>/';
+ if (preg_match($pattern, $currentPart, $matches)) {
+ $type = new Compound([
+ $typeResolver->resolve($matches['type'], $context),
+ $typeResolver->resolve($matches['elementType'] . '[]', $context),
+ ]);
+ } else {
+ $type = $typeResolver->resolve($currentPart, $context);
+ }
+
+ array_shift($parts);
+ }
+
+ // if the next item starts with a $ or ...$ it must be the variable name
+ if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
+ $variableName = array_shift($parts);
+ array_shift($parts);
+
+ if (substr($variableName, 0, 1) === '$') {
+ $variableName = substr($variableName, 1);
+ }
+ }
+
+ $description = $descriptionFactory->create(implode('', $parts), $context);
+
+ return new static($variableName, $type, $description);
+ }
+}
+++ /dev/null
-<?php
-namespace TYPO3\CMS\Extbase\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!
- */
-
-/**
- * A little parser which creates tag objects from doc comments
- * @internal only to be used within Extbase, not part of TYPO3 Core API.
- */
-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 = '';
-
- /**
- * @var array An array of tag names and their values (multiple values are possible)
- */
- 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.
- *
- * @param string $docComment A doc comment as returned by the reflection getDocComment() method
- */
- public function parseDocComment($docComment)
- {
- $this->description = '';
- $this->tags = [];
- $lines = explode(LF, $docComment);
- foreach ($lines as $line) {
- if ($line !== '' && strpos($line, '@') !== false) {
- $this->parseTag(substr($line, strpos($line, '@')));
- } elseif (empty($this->tags)) {
- $this->description .= preg_replace('#\\s*/?[*/]*(.*)$#', '$1', $line) . LF;
- }
- }
- $this->description = trim($this->description);
- }
-
- /**
- * Returns the tags which have been previously parsed
- *
- * @return array Array of tag names and their (multiple) values
- */
- public function getTagsValues()
- {
- return $this->tags;
- }
-
- /**
- * Returns the values of the specified tag. The doc comment
- * must be parsed with parseDocComment() before tags are
- * available.
- *
- * @param string $tagName The tag name to retrieve the values for
- * @throws \RuntimeException
- * @return array The tag's values
- */
- public function getTagValues($tagName)
- {
- if (!$this->isTaggedWith($tagName)) {
- throw new \RuntimeException('Tag "' . $tagName . '" does not exist.', 1169128255);
- }
- return $this->tags[$tagName];
- }
-
- /**
- * Checks if a tag with the given name exists
- *
- * @param string $tagName The tag name to check for
- * @return bool TRUE the tag exists, otherwise FALSE
- */
- public function isTaggedWith($tagName)
- {
- return isset($this->tags[$tagName]);
- }
-
- /**
- * Returns the description which has been previously parsed
- *
- * @return string The description which has been parsed
- */
- public function getDescription()
- {
- return $this->description;
- }
-
- /**
- * Parses a line of a doc comment for a tag and its value.
- * The result is stored in the interal tags array.
- *
- * @param string $line A line of a doc comment which starts with an @-sign
- */
- protected function parseTag($line)
- {
- $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 {
- $this->tags[$tag] = [];
- }
- }
-}
/**
* @test
*/
+ public function classSchemaDetectsTypeAndElementTypeWithoutFQCN(): void
+ {
+ $property = (new ClassSchema(DummyClassWithAllTypesOfProperties::class))
+ ->getProperty('propertyWithObjectStorageAnnotationWithoutFQCN');
+
+ static::assertSame(ObjectStorage::class, $property->getType());
+ static::assertSame(DummyClassWithAllTypesOfProperties::class, $property->getElementType());
+ }
+
+ /**
+ * @test
+ */
public function classSchemaDetectsValidateAnnotationsModelProperties(): void
{
$this->resetSingletonInstances = true;
'propertyWithTransientAnnotation',
'propertyWithCascadeAnnotation',
'propertyWithCascadeAnnotationWithoutVarAnnotation',
- 'propertyWithObjectStorageAnnotation'
+ 'propertyWithObjectStorageAnnotation',
+ 'propertyWithObjectStorageAnnotationWithoutFQCN',
],
array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->getProperties())
);
'methodWithMandatoryParam',
'methodWithNullableParam',
'methodWithDefaultValueParam',
- 'methodWithTypeHintedParam'
+ 'methodWithTypeHintedParam',
+ 'methodWithDocBlockTypeHintOnly',
],
array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class))->getMethods())
);
static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']);
static::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
- static::assertSame('DummyClassWithInjectDoctrineAnnotation', $injectProperties['propertyWithRelativeClassName']);
+ static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithRelativeClassName']);
static::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
- static::assertSame('ClassSchemaTest', $injectProperties['propertyWithImportedClassName']);
+ static::assertSame(self::class, $injectProperties['propertyWithImportedClassName']);
static::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
- static::assertSame('AliasedClassSchemaTest', $injectProperties['propertyWithImportedAndAliasedClassName']);
+ static::assertSame(self::class, $injectProperties['propertyWithImportedAndAliasedClassName']);
}
/**
/**
* @test
*/
- public function testClassSchemaGetTags()
- {
- $tags = (new ClassSchema(Fixture\DummyClassWithTags::class))->getTags();
- static::assertArrayHasKey('see', $tags);
-
- // test ignored tags
- static::assertArrayNotHasKey('package', $tags);
- static::assertArrayNotHasKey('subpackage', $tags);
- static::assertArrayNotHasKey('license', $tags);
- static::assertArrayNotHasKey('copyright', $tags);
- static::assertArrayNotHasKey('author', $tags);
- static::assertArrayNotHasKey('version', $tags);
- }
-
- /**
- * @test
- */
public function classSchemaGenerationThrowsExceptionWithValidateDoctrineAnnotationsForParamWithoutTypeHint()
{
$this->resetSingletonInstances = true;
new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParam::class);
}
+
+ /**
+ * @test
+ */
+ public function classSchemaDetectsMethodParameterTypeViaReflection(): void
+ {
+ $class = new class {
+ public function foo(string $foo)
+ {
+ }
+
+ public function bar(ClassSchema $foo)
+ {
+ }
+ };
+
+ $classSchema = new ClassSchema(get_class($class));
+ static::assertSame('string', $classSchema->getMethod('foo')->getParameter('foo')->getType());
+ static::assertSame(ClassSchema::class, $classSchema->getMethod('bar')->getParameter('foo')->getType());
+ }
+
+ /**
+ * @test
+ */
+ public function classSchemaPrefersMethodParameterTypeDetectionViaReflection(): void
+ {
+ $class = new class {
+ /**
+ * @param ClassSchema $foo
+ */
+ public function foo(string $foo)
+ {
+ }
+ };
+
+ $classSchema = new ClassSchema(get_class($class));
+ static::assertSame('string', $classSchema->getMethod('foo')->getParameter('foo')->getType());
+ }
+
+ /**
+ * @test
+ */
+ public function classSchemaDetectsMethodParameterTypeDetectionViaDocBlocksIfNoTypeHintIsGiven(): void
+ {
+ $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
+ static::assertSame(Fixture\DummyClassWithAllTypesOfMethods::class, $classSchema->getMethod('methodWithDocBlockTypeHintOnly')->getParameter('param')->getType());
+ }
}
+++ /dev/null
-<?php
-namespace TYPO3\CMS\Extbase\Tests\Unit\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\DocCommentParser;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Test case
- */
-class DocCommentParserTest extends UnitTestCase
-{
- /**
- * @test
- */
- public function stripsSlashFromMethodComment()
- {
- $commentParser = new DocCommentParser();
- $comment = '/**
- * Here is some text, but neither an argument nor a return type.
- */
-';
- $commentParser->parseDocComment($comment);
- $this->assertSame(
- 'Here is some text, but neither an argument nor a return type.',
- $commentParser->getDescription()
- );
- }
-}
public static function methodWithTypeHintedParam(string $param)
{
}
+
+ /**
+ * @param \TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithAllTypesOfMethods $param
+ */
+ public function methodWithDocBlockTypeHintOnly($param)
+ {
+ }
}
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Annotation\ORM\Transient;
+use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* Fixture class with getters and setters
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithAllTypesOfProperties>
*/
public $propertyWithObjectStorageAnnotation;
+
+ /**
+ * @var ObjectStorage<DummyClassWithAllTypesOfProperties>
+ */
+ public $propertyWithObjectStorageAnnotationWithoutFQCN;
}
"sort-packages": true
},
"require": {
- "typo3/cms-core": "10.0.*@dev"
+ "phpdocumentor/reflection-docblock": "^4.3",
+ "symfony/property-info": "^4.2",
+ "typo3/cms-core": "10.0.*@dev",
+ "webmozart/assert": "^1.0"
},
"suggest": {
"typo3/cms-scheduler": "Additional scheduler tasks"