Completed
Push — master ( 26ecbc...8c0c5d )
by Maciej
14s
created

DefaultPersistentCollectionGenerator.php (1 issue)

reflection_usage.declaring_class_name

Unknown

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\PersistentCollection;
6
7
use Doctrine\ODM\MongoDB\Configuration;
8
use ReflectionClass;
9
use ReflectionException;
10
use ReflectionMethod;
11
use ReflectionParameter;
12
use ReflectionType;
13
use const DIRECTORY_SEPARATOR;
14
use function array_map;
15
use function array_pop;
16
use function class_exists;
17
use function dirname;
18
use function explode;
19
use function file_exists;
20
use function file_put_contents;
21
use function implode;
22
use function interface_exists;
23
use function is_dir;
24
use function is_writable;
25
use function method_exists;
26
use function mkdir;
27
use function rename;
28
use function str_replace;
29
use function strtolower;
30
use function substr;
31
use function uniqid;
32
use function var_export;
33
34
/**
35
 * Default generator for custom PersistentCollection classes.
36
 */
37
final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator
38
{
39
    /**
40
     * The namespace that contains all persistent collection classes.
41
     *
42
     * @var string
43
     */
44
    private $collectionNamespace;
45
46
    /**
47
     * The directory that contains all persistent collection classes.
48
     *
49
     * @var string
50
     */
51
    private $collectionDir;
52
53 11
    public function __construct(string $collectionDir, string $collectionNs)
54
    {
55 11
        $this->collectionDir       = $collectionDir;
56 11
        $this->collectionNamespace = $collectionNs;
57 11
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function generateClass(string $class, string $dir) : void
63
    {
64
        $collClassName = str_replace('\\', '', $class) . 'Persistent';
65
        $className     = $this->collectionNamespace . '\\' . $collClassName;
66
        $fileName      = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php';
67
        $this->generateCollectionClass($class, $className, $fileName);
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 10
    public function loadClass(string $collectionClass, int $autoGenerate) : string
74
    {
75
        // These checks are not in __construct() because of BC and should be moved for 2.0
76 10
        if (! $this->collectionDir) {
77
            throw PersistentCollectionException::directoryRequired();
78
        }
79 10
        if (! $this->collectionNamespace) {
80
            throw PersistentCollectionException::namespaceRequired();
81
        }
82
83 10
        $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent';
84 10
        $className     = $this->collectionNamespace . '\\' . $collClassName;
85 10
        if (! class_exists($className, false)) {
86 6
            $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php';
87 6
            switch ($autoGenerate) {
88
                case Configuration::AUTOGENERATE_NEVER:
89
                    require $fileName;
90
                    break;
91
92
                case Configuration::AUTOGENERATE_ALWAYS:
93 3
                    $this->generateCollectionClass($collectionClass, $className, $fileName);
94 3
                    require $fileName;
95 3
                    break;
96
97
                case Configuration::AUTOGENERATE_FILE_NOT_EXISTS:
98
                    if (! file_exists($fileName)) {
99
                        $this->generateCollectionClass($collectionClass, $className, $fileName);
100
                    }
101
                    require $fileName;
102
                    break;
103
104
                case Configuration::AUTOGENERATE_EVAL:
105 3
                    $this->generateCollectionClass($collectionClass, $className, false);
106 3
                    break;
107
            }
108
        }
109
110 10
        return $className;
111
    }
112
113
    /**
114
     * @param string|false $fileName Filename to write collection class code or false to eval it.
115
     */
116 6
    private function generateCollectionClass(string $for, string $targetFqcn, $fileName)
117
    {
118 6
        $exploded  = explode('\\', $targetFqcn);
119 6
        $class     = array_pop($exploded);
120 6
        $namespace = implode('\\', $exploded);
121
        $code      = <<<CODE
122
<?php
123
124 6
namespace $namespace;
125
126
use Doctrine\Common\Collections\Collection as BaseCollection;
127
use Doctrine\ODM\MongoDB\DocumentManager;
128
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
129
use Doctrine\ODM\MongoDB\MongoDBException;
130
use Doctrine\ODM\MongoDB\UnitOfWork;
131
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
132
133
/**
134
 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR
135
 */
136 6
class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface
137
{
138
    use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait;
139
140
    /**
141
     * @param BaseCollection \$coll
142
     * @param DocumentManager \$dm
143
     * @param UnitOfWork \$uow
144
     */
145
    public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow)
146
    {
147
        \$this->coll = \$coll;
148
        \$this->dm = \$dm;
149
        \$this->uow = \$uow;
150
    }
151
152
CODE;
153 6
        $rc        = new ReflectionClass($for);
154 6
        $rt        = new ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait');
155 6
        foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
156 6
            if ($rt->hasMethod($method->name) ||
157 6
                $method->isConstructor() ||
158 6
                $method->isFinal() ||
159 6
                $method->isStatic()
160
            ) {
161 6
                continue;
162
            }
163 6
            $code .= $this->generateMethod($method);
164
        }
165 6
        $code .= "}\n";
166
167 6
        if ($fileName === false) {
168 3
            if (! class_exists($targetFqcn)) {
169 3
                eval(substr($code, 5));
170
            }
171
        } else {
172 3
            $parentDirectory = dirname($fileName);
173
174 3
            if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) {
175
                throw PersistentCollectionException::directoryNotWritable();
176
            }
177
178 3
            if (! is_writable($parentDirectory)) {
179
                throw PersistentCollectionException::directoryNotWritable();
180
            }
181
182 3
            $tmpFileName = $fileName . '.' . uniqid('', true);
183 3
            file_put_contents($tmpFileName, $code);
184 3
            rename($tmpFileName, $fileName);
185
        }
186 6
    }
187
188 6
    private function generateMethod(ReflectionMethod $method) : string
189
    {
190 6
        $parametersString = $this->buildParametersString($method);
191 6
        $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters()));
192
193
        return <<<CODE
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 6
    public function {$method->name}($parametersString){$this->getMethodReturnType($method)}
199
    {
200
        \$this->initialize();
201
        if (\$this->needsSchedulingForDirtyCheck()) {
202
            \$this->changed();
203
        }
204 6
        return \$this->coll->{$method->name}($callParamsString);
205
    }
206
207
CODE;
208
    }
209
210 6
    private function buildParametersString(ReflectionMethod $method) : string
211
    {
212 6
        $parameters           = $method->getParameters();
213 6
        $parameterDefinitions = [];
214
215
        /** @var ReflectionParameter $param */
216 6
        foreach ($parameters as $param) {
217 6
            $parameterDefinition = '';
218 6
            $parameterType       = $this->getParameterType($param);
219
220 6
            if ($parameterType) {
221 6
                $parameterDefinition .= $parameterType . ' ';
222
            }
223
224 6
            if ($param->isPassedByReference()) {
225
                $parameterDefinition .= '&';
226
            }
227
228 6
            if (method_exists($param, 'isVariadic')) {
229 6
                if ($param->isVariadic()) {
230
                    $parameterDefinition .= '...';
231
                }
232
            }
233
234 6
            $parameters[]         = '$' . $param->name;
235 6
            $parameterDefinition .= '$' . $param->name;
236
237 6
            if ($param->isDefaultValueAvailable()) {
238
                $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
239
            }
240
241 6
            $parameterDefinitions[] = $parameterDefinition;
242
        }
243
244 6
        return implode(', ', $parameterDefinitions);
245
    }
246
247 6
    private function getParameterType(ReflectionParameter $parameter) : ?string
248
    {
249
        // We need to pick the type hint class too
250 6
        if ($parameter->isArray()) {
251
            return 'array';
252
        }
253
254 6
        if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) {
255
            return 'callable';
256
        }
257
258
        try {
259 6
            $parameterClass = $parameter->getClass();
260
261 6
            if ($parameterClass) {
262 6
                return '\\' . $parameterClass->name;
263
            }
264
        } catch (ReflectionException $previous) {
265
            // @todo ProxyGenerator throws specialized exceptions
266
            throw $previous;
267
        }
268
269 2
        return null;
270
    }
271
272
    /**
273
     * @param ReflectionParameter[] $parameters
274
     *
275
     * @return string[]
276
     */
277 6
    private function getParameterNamesForDecoratedCall(array $parameters) : array
278
    {
279 6
        return array_map(
280
            static function (ReflectionParameter $parameter) {
281 6
                $name = '';
282
283 6
                if (method_exists($parameter, 'isVariadic')) {
284 6
                    if ($parameter->isVariadic()) {
285
                        $name .= '...';
286
                    }
287
                }
288
289 6
                return $name . '$' . $parameter->name;
290 6
            },
291 6
            $parameters
292
        );
293
    }
294
295 6
    private function getMethodReturnType(ReflectionMethod $method) : string
296
    {
297 6
        $returnType = $method->getReturnType();
298 6
        if ($returnType === null) {
299 6
            return '';
300
        }
301
302 2
        return ': ' . $this->formatType($returnType, $method);
303
    }
304
305 2
    private function formatType(
306
        ReflectionType $type,
307
        ReflectionMethod $method,
308
        ?ReflectionParameter $parameter = null
309
    ) : string {
310 2
        $name      = method_exists($type, 'getName') ? $type->getName() : (string) $type;
311 2
        $nameLower = strtolower($name);
312 2
        if ($nameLower === 'self') {
313
            $name = $method->getDeclaringClass()->getName();
314
        }
315 2
        if ($nameLower === 'parent') {
316
            $parentClass = $method->getDeclaringClass()->getParentClass();
317
            if (! $parentClass) {
318
                throw PersistentCollectionException::parentClassRequired($method->getDeclaringClass()->getName(), $method->getName());
0 ignored issues
show
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
319
            }
320
321
            $name = $parentClass->getName();
322
        }
323 2
        if (! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
324
            if ($parameter !== null) {
325
                throw PersistentCollectionException::invalidParameterTypeHint(
326
                    $method->getDeclaringClass()->getName(),
327
                    $method->getName(),
328
                    $parameter->getName()
329
                );
330
            }
331
            throw PersistentCollectionException::invalidReturnTypeHint(
332
                $method->getDeclaringClass()->getName(),
333
                $method->getName()
334
            );
335
        }
336 2
        if (! $type->isBuiltin()) {
337 2
            $name = '\\' . $name;
338
        }
339 2
        if ($type->allowsNull()
340 2
            && ($parameter === null || ! $parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== null)
341
        ) {
342 1
            $name = '?' . $name;
343
        }
344 2
        return $name;
345
    }
346
}
347