├── .editorconfig
├── .github
└── workflows
│ └── continious-integration.yaml
├── .gitignore
├── LICENSE
├── README.md
├── build
├── .gitignore
└── phpstan-with-extension.neon
├── composer.json
├── composer.lock
├── ecs.yaml
├── extension.neon
├── phpstan.bootstrap.php
├── phpstan.neon
├── phpunit.xml
├── src
└── Type
│ ├── ContextGetAspectDynamicReturnTypeExtension.php
│ ├── GeneralUtilityDynamicReturnTypeExtension.php
│ ├── ObjectManagerGetDynamicReturnTypeExtension.php
│ ├── ObjectManagerInterfaceGetDynamicReturnTypeExtension.php
│ ├── QueryExecuteReturnTypeExtension.php
│ └── QueryInterfaceExecuteReturnTypeExtension.php
└── tests
├── StaticCodeAnalysis
├── Constants.php
├── GeneralUtility
│ └── MakeInstance
│ │ ├── CallWithClassConstant.php
│ │ ├── CallWithClassString.php
│ │ ├── CallWithSelf.php
│ │ └── CallWithStatic.php
└── ObjectManager
│ └── Get
│ ├── CallWithClassConstant.php
│ ├── CallWithClassString.php
│ ├── CallWithSelf.php
│ └── CallWithStatic.php
├── Unit
└── Type
│ ├── ContextGetAspectDynamicReturnTypeExtensionTest.php
│ └── GeneralUtilityDynamicReturnTypeExtensionTest.php
└── bootstrap.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | charset = utf-8
9 | end_of_line = lf
10 | indent_style = space
11 | indent_size = 4
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 |
15 | [*.neon]
16 | indent_size = 2
17 |
18 | [*.{yaml,yml}]
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.github/workflows/continious-integration.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | - "push"
3 |
4 | name: "Continuous Integration"
5 |
6 | jobs:
7 | coding-standards:
8 | name: "Coding Standards"
9 |
10 | runs-on: "ubuntu-latest"
11 |
12 | strategy:
13 | matrix:
14 | php-version:
15 | - "7.2"
16 |
17 | dependencies:
18 | - "locked"
19 |
20 | steps:
21 | - name: "Checkout"
22 | uses: "actions/checkout@v2"
23 |
24 | - name: "Install PHP with extensions"
25 | uses: "shivammathur/setup-php@v2"
26 | with:
27 | coverage: "none"
28 | php-version: "${{ matrix.php-version }}"
29 |
30 | - name: "Validate composer.json and composer.lock"
31 | run: "composer validate --strict"
32 |
33 | - name: "Determine composer cache directory"
34 | id: "determine-composer-cache-directory"
35 | run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
36 |
37 | - name: "Cache dependencies installed with composer"
38 | uses: "actions/cache@v1"
39 | with:
40 | path: "${{ steps.determine-composer-cache-directory.outputs.directory }}"
41 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}"
42 | restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
43 |
44 | - name: "Install locked dependencies with composer"
45 | run: "composer install --no-interaction --no-progress --no-suggest --no-plugins"
46 |
47 | - name: "Run ergebnis/composer-normalize"
48 | run: "composer normalize --dry-run"
49 |
50 | - name: "Run ecs"
51 | run: "vendor/bin/ecs check"
52 |
53 | static-code-analysis:
54 | name: "Static Code Analysis"
55 |
56 | runs-on: "ubuntu-latest"
57 |
58 | strategy:
59 | matrix:
60 | php-version:
61 | - "7.2"
62 |
63 | dependencies:
64 | - "lowest"
65 | - "locked"
66 | - "highest"
67 |
68 | steps:
69 | - name: "Checkout"
70 | uses: "actions/checkout@v2"
71 |
72 | - name: "Install PHP with extensions"
73 | uses: "shivammathur/setup-php@v2"
74 | with:
75 | coverage: "none"
76 | php-version: "${{ matrix.php-version }}"
77 |
78 | - name: "Determine composer cache directory"
79 | id: "determine-composer-cache-directory"
80 | run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
81 |
82 | - name: "Cache dependencies installed with composer"
83 | uses: "actions/cache@v1"
84 | with:
85 | path: "${{ steps.determine-composer-cache-directory.outputs.directory }}"
86 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}"
87 | restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
88 |
89 | - name: "Install lowest dependencies with composer"
90 | if: "matrix.dependencies == 'lowest'"
91 | run: "composer update --no-interaction --no-progress --no-suggest --no-plugins --prefer-lowest"
92 |
93 | - name: "Install locked dependencies with composer"
94 | if: "matrix.dependencies == 'locked'"
95 | run: "composer install --no-interaction --no-progress --no-suggest --no-plugins"
96 |
97 | - name: "Install highest dependencies with composer"
98 | if: "matrix.dependencies == 'highest'"
99 | run: "composer update --no-interaction --no-progress --no-suggest --no-plugins"
100 |
101 | - name: "Show phpstan/phpstan version"
102 | run: "vendor/bin/phpstan --version"
103 |
104 | - name: "Run phpstan/phpstan"
105 | run: "vendor/bin/phpstan analyse --configuration=build/phpstan-with-extension.neon"
106 |
107 | tests:
108 | name: "Tests"
109 |
110 | runs-on: "ubuntu-latest"
111 |
112 | strategy:
113 | matrix:
114 | php-version:
115 | - "7.2"
116 |
117 | dependencies:
118 | - "locked"
119 |
120 | steps:
121 | - name: "Checkout"
122 | uses: "actions/checkout@v2"
123 |
124 | - name: "Install PHP with extensions"
125 | uses: "shivammathur/setup-php@v2"
126 | with:
127 | coverage: "none"
128 | php-version: "${{ matrix.php-version }}"
129 |
130 | - name: "Determine composer cache directory"
131 | id: "determine-composer-cache-directory"
132 | run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
133 |
134 | - name: "Cache dependencies installed with composer"
135 | uses: "actions/cache@v1"
136 | with:
137 | path: "${{ steps.determine-composer-cache-directory.outputs.directory }}"
138 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}"
139 | restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
140 |
141 | - name: "Install lowest dependencies with composer"
142 | if: "matrix.dependencies == 'lowest'"
143 | run: "composer update --no-interaction --no-progress --no-suggest --no-plugins --prefer-lowest"
144 |
145 | - name: "Install locked dependencies with composer"
146 | if: "matrix.dependencies == 'locked'"
147 | run: "composer install --no-interaction --no-progress --no-suggest --no-plugins"
148 |
149 | - name: "Install highest dependencies with composer"
150 | if: "matrix.dependencies == 'highest'"
151 | run: "composer update --no-interaction --no-progress --no-suggest --no-plugins"
152 |
153 | - name: "Run tests with phpunit/phpunit"
154 | run: "vendor/bin/phpunit --configuration=phpunit.xml"
155 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /public
2 | /vendor
3 | /.phpunit.result.cache
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 FriendsOfTYPO3 Team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TYPO3 extension for PHPStan
2 |
3 | > [!NOTE]
4 | > `friendsoftypo3/phpstan-typo3` is obsolete and has been retired.
5 | > The package dependency should be removed from TYPO3 project or extension development: `composer rem --dev friendsoftypo3/phpstan-typo3`.
6 | > See below for transition options.
7 |
8 | ## Migrating away from friendsoftypo3/phpstan-typo3 since TYPO3 core v12
9 |
10 | ### Remove package and rely on core annotations
11 |
12 | * The list of global TYPO3 specific constants shrunk over time and phpstan finds more of the remaining ones by default.
13 | phpstan may mumble about constants `LF` and `CR` not being defined. They can be made known to phpstan like this in
14 | a `phpstan.neon` config file:
15 | ```
16 | parameters:
17 | bootstrapFiles:
18 | - phpstan.bootstrap.php
19 | ```
20 | File `phpstan.bootstrap.php` then contains:
21 | ```
22 | getAspect()`, `Query->execute()` and `QueryInterface->execute()`
27 | have proper method annotations since TYPO3 v12, the phpstan extension classes are not needed anymore and
28 | phpstan "understands" return values of these methods out of the box.
29 | * `ObjectManagerInterface->get()` and `ObjectManager->get()` extensions have been removed from TYPO3 since v12
30 | and are thus obsolete.
31 |
32 | ## TYPO3 CMS class reflection extension for PHPStan & framework-specific rules
33 |
34 | This extension provides the following features:
35 |
36 | * Provides correct return type for `\TYPO3\CMS\Core\Context\Context->getAspect()`.
37 | * Provides correct return type for `\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance()`.
38 | * Provides correct return type for `\TYPO3\CMS\Extbase\Object\ObjectManagerInterface->get()`.
39 | * Provides correct return type for `\TYPO3\CMS\Extbase\Object\ObjectManager->get()`.
40 | * Provides correct return type for `\TYPO3\CMS\Extbase\Persistence\Generic\Query->execute()`.
41 | * Provides correct return type for `\TYPO3\CMS\Extbase\Persistence\QueryInterface->execute()`.
42 |
43 |
44 | Details on GeneralUtility::makeInstance()
45 |
46 | Dynamic return types are returned for:
47 |
48 | * `GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class)`
49 | * `GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler')`
50 | * `GeneralUtility::makeInstance(self::class)`
51 | * `GeneralUtility::makeInstance(static::class)`
52 |
53 |
54 |
55 | Details on ObjectManagerInterface::get() and ObjectManager::get()
56 |
57 | Dynamic return types are returned for:
58 |
59 | * `ObjectManager->get(\TYPO3\CMS\Core\DataHandling\DataHandler::class)`
60 | * `ObjectManager->get('TYPO3\\CMS\\Core\\DataHandling\\DataHandler')`
61 | * `ObjectManager->get(self::class)`
62 | * `ObjectManager->get(static::class)`
63 |
64 |
65 | ## Installation & Configuration
66 |
67 | To use this extension, require it in [Composer](https://getcomposer.org/):
68 |
69 | ```
70 | composer require friendsoftypo3/phpstan-typo3 --dev
71 | ```
72 |
73 | Once installed, put this into your `phpstan.neon` config:
74 |
75 | ```
76 | includes:
77 | - vendor/friendsoftypo3/phpstan-typo3/extension.neon
78 | ```
79 |
80 | ## FAQ
81 |
82 | > I found this extension and the one from Sascha (`saschaegerer/phpstan-typo3`). Why are there two extensions and which should I use?
83 |
84 | Well, this package has one specific purpose. It's made to help making the TYPO3 core phpstan max level compatible. To achieve this, the core team needs to be able to have its own extension which can be quickly adjusted as soon as the core itself changes. If for example, a new core version is released, the core team can quickly raise the dependency constraints for `typo3/cms-core` and `typo3/cms-extbase` which cannot be done when working with Sascha's package.
85 |
86 | Also, Sascha's package contains dynamic return type providers that are not needed (yet) to make the core more compatible with phpstan.
87 |
88 | To sum it all up: There is no competition between both extensions and this extension should not be used by users but only by the TYPO3 core.
89 |
--------------------------------------------------------------------------------
/build/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 |
--------------------------------------------------------------------------------
/build/phpstan-with-extension.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - %currentWorkingDirectory%/extension.neon
3 |
4 | parameters:
5 | level: max
6 |
7 | paths:
8 | - %currentWorkingDirectory%/tests/StaticCodeAnalysis/
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "friendsoftypo3/phpstan-typo3",
3 | "type": "phpstan-extension",
4 | "description": "TYPO3 rules for PHPStan",
5 | "license": "GPL-2.0-or-later",
6 | "require": {
7 | "php": "^7.2 || ^8.0 || ^8.1",
8 | "phpstan/phpstan": "^0.12.99 || ^1.2.0"
9 | },
10 | "require-dev": {
11 | "ergebnis/composer-normalize": "^2.3",
12 | "jangregor/phpstan-prophecy": "^0.6.2",
13 | "phpunit/phpunit": "^8.5",
14 | "symplify/easy-coding-standard": "^7.2",
15 | "typo3/cms-core": "^10.4 || 11.*.*@dev",
16 | "typo3/cms-extbase": "^10.4 || 11.*.*@dev"
17 | },
18 | "config": {
19 | "allow-plugins": {
20 | "composer/package-versions-deprecated": true,
21 | "typo3/class-alias-loader": true,
22 | "typo3/cms-composer-installers": true,
23 | "ergebnis/composer-normalize": true
24 | },
25 | "platform": {
26 | "php": "7.2"
27 | },
28 | "sort-packages": true
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "FriendsOfTYPO3\\PHPStan\\TYPO3\\": "src/"
33 | }
34 | },
35 | "autoload-dev": {
36 | "psr-4": {
37 | "FriendsOfTYPO3\\PHPStan\\TYPO3\\Tests\\": "tests/"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ecs.yaml:
--------------------------------------------------------------------------------
1 | parameters:
2 | sets:
3 | - 'psr12'
4 | paths:
5 | - 'src'
6 | - 'tests'
7 |
--------------------------------------------------------------------------------
/extension.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | bootstrapFiles:
3 | - phpstan.bootstrap.php
4 |
5 | services:
6 | -
7 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\GeneralUtilityDynamicReturnTypeExtension
8 | tags:
9 | - phpstan.broker.dynamicStaticMethodReturnTypeExtension
10 | -
11 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ContextGetAspectDynamicReturnTypeExtension
12 | tags:
13 | - phpstan.broker.dynamicMethodReturnTypeExtension
14 | -
15 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ObjectManagerGetDynamicReturnTypeExtension
16 | tags:
17 | - phpstan.broker.dynamicMethodReturnTypeExtension
18 | -
19 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ObjectManagerInterfaceGetDynamicReturnTypeExtension
20 | tags:
21 | - phpstan.broker.dynamicMethodReturnTypeExtension
22 | -
23 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\QueryExecuteReturnTypeExtension
24 | tags:
25 | - phpstan.broker.dynamicMethodReturnTypeExtension
26 | -
27 | class: FriendsOfTYPO3\PHPStan\TYPO3\Type\QueryInterfaceExecuteReturnTypeExtension
28 | tags:
29 | - phpstan.broker.dynamicMethodReturnTypeExtension
--------------------------------------------------------------------------------
/phpstan.bootstrap.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | src/
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Type/ContextGetAspectDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'getAspect';
34 | }
35 |
36 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
37 | {
38 | $defaultObjectType = new ObjectType(AspectInterface::class);
39 |
40 | if (!($argument = $methodCall->args[0] ?? null) instanceof Arg) {
41 | return $defaultObjectType;
42 | }
43 | /** @var Arg $argument */
44 |
45 | if (!($string = $argument->value ?? null) instanceof String_) {
46 | return $defaultObjectType;
47 | }
48 | /** @var String_ $string */
49 |
50 | switch ($string->value) {
51 | case 'date':
52 | $type = new ObjectType(DateTimeAspect::class);
53 | break;
54 | case 'visibility':
55 | $type = new ObjectType(VisibilityAspect::class);
56 | break;
57 | case 'frontend.user':
58 | case 'backend.user':
59 | $type = new ObjectType(UserAspect::class);
60 | break;
61 | case 'workspace':
62 | $type = new ObjectType(WorkspaceAspect::class);
63 | break;
64 | case 'language':
65 | $type = new ObjectType(LanguageAspect::class);
66 | break;
67 | case 'typoscript':
68 | $type = new ObjectType(TypoScriptAspect::class);
69 | break;
70 | default:
71 | $type = $defaultObjectType;
72 | break;
73 | }
74 |
75 | return $type;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Type/GeneralUtilityDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'makeInstance';
32 | }
33 |
34 | public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): TypeInterface
35 | {
36 | try {
37 | $classNameArgument = $this->fetchClassNameArgument($methodCall);
38 | $classNameArgumentValueExpression = $classNameArgument->value;
39 |
40 | switch (true) {
41 | case $classNameArgumentValueExpression instanceof StringNode:
42 | /*
43 | * Examples:
44 | *
45 | * - GeneralUtility::makeInstance('foo')
46 | * - GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler')
47 | */
48 | return $this->createObjectTypeFromString($classNameArgumentValueExpression);
49 | case $classNameArgumentValueExpression instanceof ClassConstFetch:
50 | /*
51 | * Examples:
52 | *
53 | * - GeneralUtility::makeInstance(TYPO3\CMS\Core\DataHandling\DataHandler::class)
54 | * - GeneralUtility::makeInstance(self::class)
55 | * - GeneralUtility::makeInstance(static::class)
56 | */
57 | return $this->createObjectTypeFromClassConstFetch($classNameArgumentValueExpression, $scope->getClassReflection());
58 | default:
59 | throw new \InvalidArgumentException(
60 | 'Argument $className is neither a string nor a class constant',
61 | 1584879239
62 | );
63 | }
64 | } catch (\Throwable $exception) {
65 | return new ObjectWithoutClassType();
66 | }
67 | }
68 |
69 | private function fetchClassNameArgument(StaticCall $methodCall): ArgumentNode
70 | {
71 | if (empty($methodCall->args)) {
72 | /*
73 | * This usually does not happen as calling GeneralUtility::makeInstance() without the mandatory argument
74 | * $className results in a syntax error.
75 | */
76 | throw new \LogicException('Method makeInstance is called without arguments.', 1584878263);
77 | }
78 |
79 | return $methodCall->args[0];
80 | }
81 |
82 | private function createObjectTypeFromString(StringNode $string): TypeInterface
83 | {
84 | $className = $string->value;
85 |
86 | if (!class_exists($className)) {
87 | throw new \LogicException('makeInstance has been called with non class name string', 1584879581);
88 | }
89 |
90 | return new ObjectType($className);
91 | }
92 |
93 | private function createObjectTypeFromClassConstFetch(ClassConstFetch $expression, ?ClassReflection $classReflection): TypeInterface
94 | {
95 | $class = $expression->class;
96 | if (!$class instanceof Name) {
97 | throw new \LogicException('', 1584878823);
98 | }
99 | /** @var Name $class */
100 |
101 | $className = $class->toString();
102 |
103 | if ($className === 'self' && $classReflection !== null) {
104 | return new ObjectType($classReflection->getName());
105 | }
106 |
107 | if ($className === 'static' && $classReflection !== null) {
108 | $callingClass = $classReflection->getName();
109 | return new StaticType($callingClass);
110 | }
111 |
112 | return new ObjectType($className);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Type/ObjectManagerGetDynamicReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'get';
32 | }
33 |
34 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): TypeInterface
35 | {
36 | try {
37 | $classNameArgument = $this->fetchClassNameArgument($methodCall);
38 | $classNameArgumentValueExpression = $classNameArgument->value;
39 |
40 | switch (true) {
41 | case $classNameArgumentValueExpression instanceof StringNode:
42 | /*
43 | * Examples:
44 | *
45 | * - GeneralUtility::makeInstance('foo')
46 | * - GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler')
47 | */
48 | return $this->createObjectTypeFromString($classNameArgumentValueExpression);
49 | case $classNameArgumentValueExpression instanceof ClassConstFetch:
50 | /*
51 | * Examples:
52 | *
53 | * - GeneralUtility::makeInstance(TYPO3\CMS\Core\DataHandling\DataHandler::class)
54 | * - GeneralUtility::makeInstance(self::class)
55 | * - GeneralUtility::makeInstance(static::class)
56 | */
57 | return $this->createObjectTypeFromClassConstFetch($classNameArgumentValueExpression, $scope->getClassReflection());
58 | default:
59 | throw new \InvalidArgumentException(
60 | 'Argument $className is neither a string nor a class constant',
61 | 1584879239
62 | );
63 | }
64 | } catch (\Throwable $exception) {
65 | return new ObjectWithoutClassType();
66 | }
67 | }
68 |
69 | private function fetchClassNameArgument(MethodCall $methodCall): ArgumentNode
70 | {
71 | if (empty($methodCall->args)) {
72 | /*
73 | * This usually does not happen as calling GeneralUtility::makeInstance() without the mandatory argument
74 | * $className results in a syntax error.
75 | */
76 | throw new \LogicException('Method makeInstance is called without arguments.', 1584881724);
77 | }
78 |
79 | return $methodCall->args[0];
80 | }
81 |
82 | private function createObjectTypeFromString(StringNode $string): TypeInterface
83 | {
84 | $className = $string->value;
85 |
86 | if (!class_exists($className)) {
87 | throw new \LogicException('makeInstance has been called with non class name string', 1584881731);
88 | }
89 |
90 | return new ObjectType($className);
91 | }
92 |
93 | private function createObjectTypeFromClassConstFetch(ClassConstFetch $expression, ?ClassReflection $classReflection): TypeInterface
94 | {
95 | $class = $expression->class;
96 | if (!$class instanceof Name) {
97 | throw new \LogicException('', 1584881734);
98 | }
99 | /** @var Name $class */
100 |
101 | $className = $class->toString();
102 |
103 | if ($className === 'self' && $classReflection !== null) {
104 | return new ObjectType($classReflection->getName());
105 | }
106 |
107 | if ($className === 'static' && $classReflection !== null) {
108 | $callingClass = $classReflection->getName();
109 | return new StaticType($callingClass);
110 | }
111 |
112 | return new ObjectType($className);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Type/QueryExecuteReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'execute';
31 | }
32 |
33 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type
34 | {
35 | if (empty($methodCall->args)) {
36 | return new ObjectType(QueryResultInterface::class);
37 | }
38 |
39 | $firstArgument = $scope->getType($methodCall->args[0]->value);
40 | if (!$firstArgument instanceof ConstantBooleanType) {
41 | throw new \InvalidArgumentException(
42 | 'Argument $returnRawQueryResult is not a boolean.',
43 | 1584879250
44 | );
45 | }
46 |
47 | $returnRawQueryResult = $firstArgument->getValue();
48 | if ($returnRawQueryResult) {
49 | return new ArrayType(
50 | new StringType(),
51 | new MixedType()
52 | );
53 | }
54 |
55 | return new ObjectType(QueryResultInterface::class);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Type/QueryInterfaceExecuteReturnTypeExtension.php:
--------------------------------------------------------------------------------
1 | getName() === 'execute';
32 | }
33 |
34 | public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type
35 | {
36 | if (empty($methodCall->args)) {
37 | return new ObjectType(QueryResultInterface::class);
38 | }
39 |
40 | $firstArgument = $scope->getType($methodCall->args[0]->value);
41 | if (!$firstArgument instanceof ConstantBooleanType) {
42 | throw new \InvalidArgumentException(
43 | 'Argument $returnRawQueryResult is not a boolean.',
44 | 1584879250
45 | );
46 | }
47 |
48 | $returnRawQueryResult = $firstArgument->getValue();
49 | if ($returnRawQueryResult) {
50 | return new ArrayType(
51 | new StringType(),
52 | new MixedType()
53 | );
54 | }
55 |
56 | return new ObjectType(QueryResultInterface::class);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/Constants.php:
--------------------------------------------------------------------------------
1 | LF,
7 | 'CR' => CR,
8 | 'CRLF' => CRLF,
9 |
10 | 'FILE_DENY_PATTERN_DEFAULT' => FILE_DENY_PATTERN_DEFAULT,
11 |
12 | 'TYPO3_REQUESTTYPE' => TYPO3_REQUESTTYPE,
13 | 'TYPO3_REQUESTTYPE_FE' => TYPO3_REQUESTTYPE_FE,
14 | 'TYPO3_REQUESTTYPE_BE' => TYPO3_REQUESTTYPE_BE,
15 | 'TYPO3_REQUESTTYPE_CLI' => TYPO3_REQUESTTYPE_CLI,
16 | 'TYPO3_REQUESTTYPE_AJAX' => TYPO3_REQUESTTYPE_AJAX,
17 | 'TYPO3_REQUESTTYPE_INSTALL' => TYPO3_REQUESTTYPE_INSTALL,
18 |
19 | 'TYPO3_MODE' => TYPO3_MODE,
20 | 'TYPO3_mainDir' => TYPO3_mainDir,
21 | 'TYPO3_version' => TYPO3_version,
22 | 'TYPO3_branch' => TYPO3_branch,
23 | ];
24 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/GeneralUtility/MakeInstance/CallWithClassConstant.php:
--------------------------------------------------------------------------------
1 | storeLogMessages = true;
12 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/GeneralUtility/MakeInstance/CallWithClassString.php:
--------------------------------------------------------------------------------
1 | storeLogMessages = true;
11 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/GeneralUtility/MakeInstance/CallWithSelf.php:
--------------------------------------------------------------------------------
1 | get(DataHandler::class);
13 | $object->storeLogMessages = true;
14 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/ObjectManager/Get/CallWithClassString.php:
--------------------------------------------------------------------------------
1 | get('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
12 | $object->storeLogMessages = true;
13 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/ObjectManager/Get/CallWithSelf.php:
--------------------------------------------------------------------------------
1 | get(self::class);
15 | }
16 |
17 | public function bar(ObjectManager $objectManager): self
18 | {
19 | return $objectManager->get(self::class);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/StaticCodeAnalysis/ObjectManager/Get/CallWithStatic.php:
--------------------------------------------------------------------------------
1 | get(static::class);
15 | }
16 |
17 | public function bar(ObjectManager $objectManager): self
18 | {
19 | return $objectManager->get(static::class);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Unit/Type/ContextGetAspectDynamicReturnTypeExtensionTest.php:
--------------------------------------------------------------------------------
1 | extension = new ContextGetAspectDynamicReturnTypeExtension();
34 | }
35 |
36 | public function testGetClass(): void
37 | {
38 | static::assertSame(Context::class, $this->extension->getClass());
39 | }
40 |
41 | /**
42 | * @return \Generator>
43 | */
44 | public function dataIsMethodSupported(): \Generator
45 | {
46 | yield ['getAspect', true];
47 | yield ['foo', false];
48 | }
49 |
50 | /**
51 | * @dataProvider dataIsMethodSupported
52 | * @param string $method
53 | * @param bool $expectedResult
54 | */
55 | public function testIsMethodSupported(string $method, bool $expectedResult): void
56 | {
57 | $methodReflection = $this->prophesize(MethodReflection::class);
58 | $methodReflection->getName()->willReturn($method);
59 |
60 | self::assertSame($expectedResult, $this->extension->isMethodSupported($methodReflection->reveal()));
61 | }
62 |
63 | public function testGetTypeFromMethodCallReturnsAspectInterfaceAsDefault(): void
64 | {
65 | $scope = $this->prophesize(Scope::class)->reveal();
66 | $methodCall = $this->prophesize(Expr\MethodCall::class)->reveal();
67 | $methodReflection = $this->prophesize(MethodReflection::class)->reveal();
68 |
69 | /** @var ObjectType $type */
70 | $type = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
71 |
72 | static::assertInstanceOf(ObjectType::class, $type);
73 | static::assertSame(AspectInterface::class, $type->getClassName());
74 | }
75 |
76 | /**
77 | * @return \Generator>
78 | */
79 | public function getTypeFromMethodCallDataProvider(): \Generator
80 | {
81 | yield ['date', DateTimeAspect::class];
82 | yield ['visibility', VisibilityAspect::class];
83 | yield ['frontend.user', UserAspect::class];
84 | yield ['backend.user', UserAspect::class];
85 | yield ['workspace', WorkspaceAspect::class];
86 | yield ['language', LanguageAspect::class];
87 | yield ['typoscript', TypoScriptAspect::class];
88 | yield ['unknown', AspectInterface::class];
89 | }
90 |
91 | /**
92 | * @dataProvider getTypeFromMethodCallDataProvider
93 | * @param string $argumentValue
94 | * @param string $expectedClassName
95 | */
96 | public function testGetTypeFromMethodCall(string $argumentValue, string $expectedClassName): void
97 | {
98 | $string = $this->prophesize(String_::class)->reveal();
99 | $string->value = $argumentValue;
100 |
101 | $arg = $this->prophesize(Arg::class)->reveal();
102 | $arg->value = $string;
103 |
104 | $methodCall = $this->prophesize(Expr\MethodCall::class)->reveal();
105 | $methodCall->args = [$arg];
106 |
107 | /** @var ObjectType $type */
108 | $type = $this->extension->getTypeFromMethodCall(
109 | $this->prophesize(MethodReflection::class)->reveal(),
110 | $methodCall,
111 | $this->prophesize(Scope::class)->reveal()
112 | );
113 |
114 | static::assertInstanceOf(ObjectType::class, $type);
115 | static::assertSame($expectedClassName, $type->getClassName());
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/Unit/Type/GeneralUtilityDynamicReturnTypeExtensionTest.php:
--------------------------------------------------------------------------------
1 | extension = new GeneralUtilityDynamicReturnTypeExtension();
31 | }
32 |
33 | public function testGetClass(): void
34 | {
35 | static::assertSame(GeneralUtility::class, $this->extension->getClass());
36 | }
37 |
38 | /**
39 | * @return \Generator>
40 | */
41 | public function dataIsMethodSupported(): \Generator
42 | {
43 | yield ['makeInstance', true];
44 | yield ['foo', false];
45 | }
46 |
47 | /**
48 | * @dataProvider dataIsMethodSupported
49 | * @param string $method
50 | * @param bool $expectedResult
51 | */
52 | public function testIsMethodSupported(string $method, bool $expectedResult): void
53 | {
54 | $methodReflection = $this->prophesize(MethodReflection::class);
55 | $methodReflection->getName()->willReturn($method);
56 |
57 | self::assertSame($expectedResult, $this->extension->isStaticMethodSupported($methodReflection->reveal()));
58 | }
59 |
60 | public function testGetTypeFromMethodCall(): void
61 | {
62 | $lexer = new Emulative(['usedAttributes' => []]);
63 | $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, $lexer);
64 |
65 | $code = <<parse($code);
72 | static::assertIsArray($expressions);
73 |
74 | /** @var array $expressions */
75 | reset($expressions);
76 |
77 | /** @var Expression $expression */
78 | $expression = current($expressions);
79 |
80 | /** @var Assign $assignment */
81 | $assignment = $expression->expr;
82 |
83 | /** @var StaticCall $staticCall */
84 | $staticCall = $assignment->expr;
85 |
86 | /** @var ObjectType $type */
87 | $type = $this->extension->getTypeFromStaticMethodCall(
88 | $this->prophesize(MethodReflection::class)->reveal(),
89 | $staticCall,
90 | $this->prophesize(Scope::class)->reveal()
91 | );
92 |
93 | static::assertInstanceOf(ObjectType::class, $type);
94 | static::assertSame(DataHandler::class, $type->getClassName());
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |