Completed
Pull Request — master (#1860)
by Andreas
14:44
created

generateCollectionClass()   C

Complexity

Conditions 11
Paths 15

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11.044

Importance

Changes 0
Metric Value
dl 0
loc 71
ccs 26
cts 28
cp 0.9286
rs 6.486
c 0
b 0
f 0
cc 11
nc 15
nop 3
crap 11.044

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 */
38
final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator
39
{
40
    /**
41
     * The namespace that contains all persistent collection classes.
42
     *
43
     * @var string
44
     */
45
    private $collectionNamespace;
46
47
    /**
48
     * The directory that contains all persistent collection classes.
49
     *
50
     * @var string
51
     */
52
    private $collectionDir;
53
54 11
    public function __construct(string $collectionDir, string $collectionNs)
55
    {
56 11
        $this->collectionDir = $collectionDir;
57 11
        $this->collectionNamespace = $collectionNs;
58 11
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function generateClass(string $class, string $dir) : void
64
    {
65
        $collClassName = str_replace('\\', '', $class) . 'Persistent';
66
        $className = $this->collectionNamespace . '\\' . $collClassName;
67
        $fileName = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php';
68
        $this->generateCollectionClass($class, $className, $fileName);
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 10
    public function loadClass(string $collectionClass, int $autoGenerate) : string
75
    {
76
        // These checks are not in __construct() because of BC and should be moved for 2.0
77 10
        if (! $this->collectionDir) {
78
            throw PersistentCollectionException::directoryRequired();
79
        }
80 10
        if (! $this->collectionNamespace) {
81
            throw PersistentCollectionException::namespaceRequired();
82
        }
83
84 10
        $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent';
85 10
        $className = $this->collectionNamespace . '\\' . $collClassName;
86 10
        if (! class_exists($className, false)) {
87 6
            $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php';
88 6
            switch ($autoGenerate) {
89
                case Configuration::AUTOGENERATE_NEVER:
90
                    require $fileName;
91
                    break;
92
93
                case Configuration::AUTOGENERATE_ALWAYS:
94 3
                    $this->generateCollectionClass($collectionClass, $className, $fileName);
95 3
                    require $fileName;
96 3
                    break;
97
98
                case Configuration::AUTOGENERATE_FILE_NOT_EXISTS:
99
                    if (! file_exists($fileName)) {
100
                        $this->generateCollectionClass($collectionClass, $className, $fileName);
101
                    }
102
                    require $fileName;
103
                    break;
104
105
                case Configuration::AUTOGENERATE_EVAL:
106 3
                    $this->generateCollectionClass($collectionClass, $className, false);
107 3
                    break;
108
            }
109
        }
110
111 10
        return $className;
112
    }
113
114
    /**
115
     * @param string|false $fileName Filename to write collection class code or false to eval it.
116
     */
117 6
    private function generateCollectionClass(string $for, string $targetFqcn, $fileName)
118
    {
119 6
        $exploded = explode('\\', $targetFqcn);
120 6
        $class = array_pop($exploded);
121 6
        $namespace = implode('\\', $exploded);
122
        $code = <<<CODE
123
<?php
124
125 6
namespace $namespace;
126
127
use Doctrine\Common\Collections\Collection as BaseCollection;
128
use Doctrine\ODM\MongoDB\DocumentManager;
129
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
130
use Doctrine\ODM\MongoDB\MongoDBException;
131
use Doctrine\ODM\MongoDB\UnitOfWork;
132
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
133
134
/**
135
 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR
136
 */
137 6
class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface
138
{
139
    use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait;
140
141
    /**
142
     * @param BaseCollection \$coll
143
     * @param DocumentManager \$dm
144
     * @param UnitOfWork \$uow
145
     */
146
    public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow)
147
    {
148
        \$this->coll = \$coll;
149
        \$this->dm = \$dm;
150
        \$this->uow = \$uow;
151
    }
152
153
CODE;
154 6
        $rc = new ReflectionClass($for);
155 6
        $rt = new ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait');
156 6
        foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
157 6
            if ($rt->hasMethod($method->name) ||
158 6
                $method->isConstructor() ||
159 6
                $method->isFinal() ||
160 6
                $method->isStatic()
161
            ) {
162 6
                continue;
163
            }
164 6
            $code .= $this->generateMethod($method);
165
        }
166 6
        $code .= "}\n";
167
168 6
        if ($fileName === false) {
169 3
            if (! class_exists($targetFqcn)) {
170 3
                eval(substr($code, 5));
171
            }
172
        } else {
173 3
            $parentDirectory = dirname($fileName);
174
175 3
            if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) {
176
                throw PersistentCollectionException::directoryNotWritable();
177
            }
178
179 3
            if (! is_writable($parentDirectory)) {
180
                throw PersistentCollectionException::directoryNotWritable();
181
            }
182
183 3
            $tmpFileName = $fileName . '.' . uniqid('', true);
184 3
            file_put_contents($tmpFileName, $code);
185 3
            rename($tmpFileName, $fileName);
186
        }
187 6
    }
188
189 6
    private function generateMethod(ReflectionMethod $method) : string
190
    {
191 6
        $parametersString = $this->buildParametersString($method);
192 6
        $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters()));
193
194
        return <<<CODE
195
196
    /**
197
     * {@inheritDoc}
198
     */
199 6
    public function {$method->name}($parametersString){$this->getMethodReturnType($method)}
200
    {
201
        \$this->initialize();
202
        if (\$this->needsSchedulingForDirtyCheck()) {
203
            \$this->changed();
204
        }
205 6
        return \$this->coll->{$method->name}($callParamsString);
206
    }
207
208
CODE;
209
    }
210
211 6
    private function buildParametersString(ReflectionMethod $method) : string
212
    {
213 6
        $parameters = $method->getParameters();
214 6
        $parameterDefinitions = [];
215
216
        /** @var ReflectionParameter $param */
217 6
        foreach ($parameters as $param) {
218 6
            $parameterDefinition = '';
219 6
            $parameterType = $this->getParameterType($param);
0 ignored issues
show
Bug introduced by
It seems like $param defined by $param on line 217 can also be of type string; however, Doctrine\ODM\MongoDB\Per...tor::getParameterType() does only seem to accept object<ReflectionParameter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
220
221 6
            if ($parameterType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameterType of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
222 6
                $parameterDefinition .= $parameterType . ' ';
223
            }
224
225 6
            if ($param->isPassedByReference()) {
226
                $parameterDefinition .= '&';
227
            }
228
229 6
            if (method_exists($param, 'isVariadic')) {
230 6
                if ($param->isVariadic()) {
231
                    $parameterDefinition .= '...';
232
                }
233
            }
234
235 6
            $parameters[]     = '$' . $param->name;
236 6
            $parameterDefinition .= '$' . $param->name;
237
238 6
            if ($param->isDefaultValueAvailable()) {
239
                $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
240
            }
241
242 6
            $parameterDefinitions[] = $parameterDefinition;
243
        }
244
245 6
        return implode(', ', $parameterDefinitions);
246
    }
247
248 6
    private function getParameterType(ReflectionParameter $parameter) : ?string
249
    {
250
        // We need to pick the type hint class too
251 6
        if ($parameter->isArray()) {
252
            return 'array';
253
        }
254
255 6
        if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) {
256
            return 'callable';
257
        }
258
259
        try {
260 6
            $parameterClass = $parameter->getClass();
261
262 6
            if ($parameterClass) {
263 6
                return '\\' . $parameterClass->name;
264
            }
265
        } catch (ReflectionException $previous) {
266
            // @todo ProxyGenerator throws specialized exceptions
267
            throw $previous;
268
        }
269
270 2
        return null;
271
    }
272
273
    /**
274
     * @param ReflectionParameter[] $parameters
275
     *
276
     * @return string[]
277
     */
278 6
    private function getParameterNamesForDecoratedCall(array $parameters) : array
279
    {
280 6
        return array_map(
281
            static function (ReflectionParameter $parameter) {
282 6
                $name = '';
283
284 6
                if (method_exists($parameter, 'isVariadic')) {
285 6
                    if ($parameter->isVariadic()) {
286
                        $name .= '...';
287
                    }
288
                }
289
290 6
                return $name . '$' . $parameter->name;
291 6
            },
292 6
            $parameters
293
        );
294
    }
295
296
    /**
297
     * @see \Doctrine\Common\Proxy\ProxyGenerator::getMethodReturnType()
298
     */
299 6
    private function getMethodReturnType(ReflectionMethod $method) : string
300
    {
301 6
        if (! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
302 6
            return '';
303
        }
304 2
        return ': ' . $this->formatType($method->getReturnType(), $method);
305
    }
306
307
    /**
308
     * @see \Doctrine\Common\Proxy\ProxyGenerator::formatType()
309
     */
310 2
    private function formatType(
311
        ReflectionType $type,
312
        ReflectionMethod $method,
313
        ?ReflectionParameter $parameter = null
314
    ) : string {
315 2
        $name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
316 2
        $nameLower = strtolower($name);
317 2
        if ($nameLower === 'self') {
318
            $name = $method->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
319
        }
320 2
        if ($nameLower === 'parent') {
321
            $name = $method->getDeclaringClass()->getParentClass()->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(),
0 ignored issues
show
introduced by
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
327
                    $method->getName(),
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
328
                    $parameter->getName()
329
                );
330
            }
331
            throw PersistentCollectionException::invalidReturnTypeHint(
332
                $method->getDeclaringClass()->getName(),
0 ignored issues
show
introduced by
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
333
                $method->getName()
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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