• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitExpert / phpstan-magento / 14927363969

09 May 2025 10:54AM UTC coverage: 88.0%. Remained the same
14927363969

Pull #340

github

web-flow
Merge 21f87d541 into 942283892
Pull Request #340: fix: error where scopeConfig proxy would get $scopeType = default

0 of 1 new or added line in 1 file covered. (0.0%)

638 of 725 relevant lines covered (88.0%)

1.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.57
/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php
1
<?php
2

3
/*
4
 * This file is part of the phpstan-magento package.
5
 *
6
 * (c) bitExpert AG
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
declare(strict_types=1);
12

13
namespace bitExpert\PHPStan\Magento\Autoload;
14

15
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
16
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
17
use Laminas\Code\Generator\ClassGenerator;
18
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
19
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
20
use Laminas\Code\Generator\DocBlockGenerator;
21
use Laminas\Code\Generator\MethodGenerator;
22
use Laminas\Code\Generator\ParameterGenerator;
23
use PHPStan\Cache\Cache;
24
use ReflectionClass;
25

26
class ExtensionAutoloader implements Autoloader
27
{
28
    /**
29
     * @var Cache
30
     */
31
    private $cache;
32
    /**
33
     * @var ClassLoaderProvider
34
     */
35
    private $classLoaderProvider;
36
    /**
37
     * @var ExtensionAttributeDataProvider
38
     */
39
    private $attributeDataProvider;
40

41
    /**
42
     * ExtensionAutoloader constructor.
43
     *
44
     * @param Cache $cache
45
     * @param ClassLoaderProvider $classLoaderProvider
46
     * @param ExtensionAttributeDataProvider $attributeDataProvider
47
     */
48
    public function __construct(
49
        Cache $cache,
50
        ClassLoaderProvider $classLoaderProvider,
51
        ExtensionAttributeDataProvider $attributeDataProvider
52
    ) {
53
        $this->cache = $cache;
4✔
54
        $this->classLoaderProvider = $classLoaderProvider;
4✔
55
        $this->attributeDataProvider = $attributeDataProvider;
4✔
56
    }
57

58
    public function autoload(string $class): void
59
    {
60
        if (preg_match('#Extension$#', $class) !== 1) {
4✔
61
            return;
1✔
62
        }
63

64
        // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
65
        // local classes in your project. We need to check first if classes exists locally before generating them!
66
        $pathToLocalClass = $this->classLoaderProvider->findFile($class);
3✔
67
        if ($pathToLocalClass === false) {
3✔
68
            $pathToLocalClass = $this->cache->load($class, '');
2✔
69
            if ($pathToLocalClass === null) {
2✔
70
                $this->cache->save($class, '', $this->getFileContents($class));
1✔
71
                $pathToLocalClass = $this->cache->load($class, '');
1✔
72
            }
73
        }
74

75
        require_once($pathToLocalClass);
3✔
76
    }
77

78
    /**
79
     * Given an extension attributes interface name, generate that interface (if possible)
80
     *
81
     * @throws \ReflectionException
82
     */
83
    public function getFileContents(string $className): string
84
    {
85
        /** @var class-string $sourceInterface */
86
        $sourceInterface = rtrim(substr($className, 0, -1 * strlen('Extension')), '\\') . 'ExtensionInterface';
1✔
87
        $sourceInterfaceReflection = new ReflectionClass($sourceInterface);
1✔
88
        /** @var class-string $attrInterface */
89
        $attrInterface = rtrim(substr($sourceInterface, 0, -1 * strlen('ExtensionInterface')), '\\') . 'Interface';
1✔
90

91
        $generator = new ClassGenerator();
1✔
92
        $generator
1✔
93
            ->setName($className)
1✔
94
            ->setExtendedClass('\Magento\Framework\Api\AbstractSimpleObject')
1✔
95
            ->setImplementedInterfaces([$sourceInterface]);
1✔
96

97
        $attrs = $this->attributeDataProvider->getAttributesForInterface($attrInterface);
1✔
98
        foreach ($attrs as $propertyName => $type) {
1✔
99
            /**
100
             * Generate getters and setters for each extension attribute
101
             *
102
             * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods
103
             */
104

105
            // check return type of method in interface and reuse it in the generated class
106
            $returnType = null;
1✔
107
            try {
108
                $reflectionMethod = $sourceInterfaceReflection->getMethod('get' . ucfirst($propertyName));
1✔
109
                $returnType = $reflectionMethod->getReturnType();
×
110
            } catch (\Exception $e) {
1✔
111
            }
112

113
            $generator->addMethodFromGenerator(
1✔
114
                MethodGenerator::fromArray([
1✔
115
                    'name' => 'get' . ucfirst($propertyName),
1✔
116
                    'returntype' => $returnType,
1✔
117
                    'docblock' => DocBlockGenerator::fromArray([
1✔
118
                        'tags' => [
1✔
119
                            new ReturnTag([$type, 'null']),
1✔
120
                        ],
1✔
121
                    ]),
1✔
122
                ])
1✔
123
            );
1✔
124

125
            // check param type of method in interface and reuse it in the generated class
126
            $paramType = null;
1✔
127
            try {
128
                $reflectionMethod = $sourceInterfaceReflection->getMethod('set' . ucfirst($propertyName));
1✔
129
                $reflectionParams = $reflectionMethod->getParameters();
×
130
                if (isset($reflectionParams[0])) {
×
131
                    $paramType = $reflectionParams[0]->getType();
×
132
                    if (($paramType !== null) && $reflectionParams[0]->isOptional()) {
×
133
                        $paramType = '?'.$paramType;
×
134
                    }
135
                }
136

137
                if ($paramType !== null) {
×
138
                    $paramType = (string) $paramType;
×
139
                }
140
            } catch (\Exception $e) {
1✔
141
            }
142

143
            // check return type of method in interface and reuse it in the generated class
144
            $returnType = null;
1✔
145
            try {
146
                $reflectionMethod = $sourceInterfaceReflection->getMethod('set' . ucfirst($propertyName));
1✔
147
                $returnType = $reflectionMethod->getReturnType();
×
148
            } catch (\Exception $e) {
1✔
149
            }
150

151
            $generator->addMethodFromGenerator(
1✔
152
                MethodGenerator::fromArray([
1✔
153
                    'name' => 'set' . ucfirst($propertyName),
1✔
154
                    'parameters' => [new ParameterGenerator($propertyName, $paramType)],
1✔
155
                    'returntype' => $returnType,
1✔
156
                    'docblock' => DocBlockGenerator::fromArray([
1✔
157
                        'tags' => [
1✔
158
                            new ParamTag($propertyName, [$type]),
1✔
159
                            new ReturnTag('$this')
1✔
160
                        ]
1✔
161
                    ])
1✔
162
                ])
1✔
163
            );
1✔
164
        }
165

166
        return "<?php\n\n" . $generator->generate();
1✔
167
    }
168

169
    public function register(): void
170
    {
171
        \spl_autoload_register([$this, 'autoload'], true, false);
1✔
172
    }
173

174
    public function unregister(): void
175
    {
176
        \spl_autoload_unregister([$this, 'autoload']);
1✔
177
    }
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc