Completed
Pull Request — master (#1532)
by Maks
10:57
created

__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\PersistentCollection;
21
22
use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
23
use Doctrine\ODM\MongoDB\Configuration;
24
25
/**
26
 * Default generator for custom PersistentCollection classes.
27
 *
28
 * @since 1.1
29
 */
30
final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator
31
{
32
    /**
33
     * The namespace that contains all persistent collection classes.
34
     *
35
     * @var string
36
     */
37
    private $collectionNamespace;
38
39
    /**
40
     * The directory that contains all persistent collection classes.
41
     *
42
     * @var string
43
     */
44
    private $collectionDir;
45
46
    /**
47
     * @param string $collectionDir
48
     * @param string $collectionNs
49
     */
50 8
    public function __construct($collectionDir, $collectionNs)
51
    {
52 8
        $this->collectionDir = $collectionDir;
53 8
        $this->collectionNamespace = $collectionNs;
54 8
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function generateClass($class, $dir)
60
    {
61
        $collClassName = str_replace('\\', '', $class) . 'Persistent';
62
        $className = $this->collectionNamespace . '\\' . $collClassName;
63
        $fileName = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php';
64
        $this->generateCollectionClass($class, $className, $fileName);
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 7
    public function loadClass($collectionClass, $autoGenerate)
71
    {
72
        // These checks are not in __construct() because of BC and should be moved for 2.0
73 7
        if ( ! $this->collectionDir) {
74
            throw PersistentCollectionException::directoryRequired();
75
        }
76 7
        if ( ! $this->collectionNamespace) {
77
            throw PersistentCollectionException::namespaceRequired();
78
        }
79
80 7
        $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent';
81 7
        $className = $this->collectionNamespace . '\\' . $collClassName;
82 7 View Code Duplication
        if ( ! class_exists($className, false)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
83 3
            $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php';
84
            switch ($autoGenerate) {
85 3
                case Configuration::AUTOGENERATE_NEVER:
86
                    require $fileName;
87
                    break;
88
89 3
                case Configuration::AUTOGENERATE_ALWAYS:
90 3
                    $this->generateCollectionClass($collectionClass, $className, $fileName);
91 3
                    require $fileName;
92 3
                    break;
93
94
                case Configuration::AUTOGENERATE_FILE_NOT_EXISTS:
95
                    if ( ! file_exists($fileName)) {
96
                        $this->generateCollectionClass($collectionClass, $className, $fileName);
97
                    }
98
                    require $fileName;
99
                    break;
100
101
                case Configuration::AUTOGENERATE_EVAL:
102
                    $this->generateCollectionClass($collectionClass, $className, false);
103
                    break;
104
            }
105
        }
106
107 7
        return $className;
108
    }
109
110 3
    private function generateCollectionClass($for, $targetFqcn, $fileName)
111
    {
112 3
        $exploded = explode('\\', $targetFqcn);
113 3
        $class = array_pop($exploded);
114 3
        $namespace = join('\\', $exploded);
115
        $code = <<<CODE
116
<?php
117
118 3
namespace $namespace;
119
120
use Doctrine\Common\Collections\Collection as BaseCollection;
121
use Doctrine\ODM\MongoDB\DocumentManager;
122
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
123
use Doctrine\ODM\MongoDB\MongoDBException;
124
use Doctrine\ODM\MongoDB\UnitOfWork;
125
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
126
127
/**
128
 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR
129
 */
130 3
class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface
131
{
132
    use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait;
133
134
    /**
135
     * @param BaseCollection \$coll
136
     * @param DocumentManager \$dm
137
     * @param UnitOfWork \$uow
138
     */
139
    public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow)
140
    {
141
        \$this->coll = \$coll;
142
        \$this->dm = \$dm;
143
        \$this->uow = \$uow;
144
    }
145
146
CODE;
147 3
        $rc = new \ReflectionClass($for);
148 3
        $rt = new \ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait');
149 3
        foreach ($rc->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
150
            if (
151 3
                $rt->hasMethod($method->name) ||
152 3
                $method->isConstructor() ||
153 3
                $method->isFinal() ||
154 3
                $method->isStatic()
155
            ) {
156 3
                continue;
157
            }
158 3
            $code .= $this->generateMethod($method);
159
        }
160 3
        $code .= "}\n";
161
162 3
        if ($fileName === false) {
163
            if ( ! class_exists($targetFqcn)) {
164
                eval(substr($code, 5));
165
            }
166 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167 3
            $parentDirectory = dirname($fileName);
168
169 3
            if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
170
                throw PersistentCollectionException::directoryNotWritable();
171
            }
172
173 3
            if ( ! is_writable($parentDirectory)) {
174
                throw PersistentCollectionException::directoryNotWritable();
175
            }
176
177 3
            $tmpFileName = $fileName . '.' . uniqid('', true);
178 3
            file_put_contents($tmpFileName, $code);
179 3
            rename($tmpFileName, $fileName);
180
        }
181 3
    }
182
183 3
    private function generateMethod(\ReflectionMethod $method)
184
    {
185 3
        $parametersString = $this->buildParametersString($method);
186 3
        $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters()));
187
188
        $method = <<<CODE
189
190
    /**
191
     * {@inheritDoc}
192
     */
193 3
    public function {$method->name}($parametersString){$this->getMethodReturnType($method)}
194
    {
195
        \$this->initialize();
196
        if (\$this->needsSchedulingForDirtyCheck()) {
197
            \$this->changed();
198
        }
199 3
        return \$this->coll->{$method->name}($callParamsString);
200
    }
201
202
CODE;
203 3
        return $method;
204
    }
205
206
    /**
207
     * @param \ReflectionMethod $method
208
     *
209
     * @return string
210
     */
211 3
    private function buildParametersString(\ReflectionMethod $method)
212
    {
213 3
        $parameters = $method->getParameters();
214 3
        $parameterDefinitions = array();
215
216
        /* @var $param \ReflectionParameter */
217 3
        foreach ($parameters as $param) {
218 3
            $parameterDefinition = '';
219
220 3
            if ($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...
221 3
                $parameterDefinition .= $parameterType . ' ';
222
            }
223
224 3
            if ($param->isPassedByReference()) {
225
                $parameterDefinition .= '&';
226
            }
227
228 3
            if (method_exists($param, 'isVariadic')) {
229 3
                if ($param->isVariadic()) {
230
                    $parameterDefinition .= '...';
231
                }
232
            }
233
234 3
            $parameters[]     = '$' . $param->name;
235 3
            $parameterDefinition .= '$' . $param->name;
236
237 3
            if ($param->isDefaultValueAvailable()) {
238
                $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
239
            }
240
241 3
            $parameterDefinitions[] = $parameterDefinition;
242
        }
243
244 3
        return implode(', ', $parameterDefinitions);
245
    }
246
247
    /**
248
     * @param \ReflectionParameter $parameter
249
     *
250
     * @return string|null
251
     */
252 3
    private function getParameterType(\ReflectionParameter $parameter)
253
    {
254
        // We need to pick the type hint class too
255 3
        if ($parameter->isArray()) {
256
            return 'array';
257
        }
258
259 3
        if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) {
260
            return 'callable';
261
        }
262
263
        try {
264 3
            $parameterClass = $parameter->getClass();
265
266 3
            if ($parameterClass) {
267 3
                return '\\' . $parameterClass->name;
268
            }
269
        } catch (\ReflectionException $previous) {
270
            // @todo ProxyGenerator throws specialized exceptions
271
            throw $previous;
272
        }
273
274 2
        return null;
275
    }
276
277
    /**
278
     * @param \ReflectionParameter[] $parameters
279
     *
280
     * @return string[]
281
     */
282 3
    private function getParameterNamesForDecoratedCall(array $parameters)
283
    {
284 3
        return array_map(
285 3
            function (\ReflectionParameter $parameter) {
286 3
                $name = '';
287
288 3
                if (method_exists($parameter, 'isVariadic')) {
289 3
                    if ($parameter->isVariadic()) {
290
                        $name .= '...';
291
                    }
292
                }
293
294 3
                $name .= '$' . $parameter->name;
295
296 3
                return $name;
297 3
            },
298
            $parameters
299
        );
300
    }
301
302
    /**
303
     * @Param \ReflectionMethod $method
304
     *
305
     * @return string
306
     *
307
     * @see \Doctrine\Common\Proxy\ProxyGenerator::getMethodReturnType()
308
     */
309 3
    private function getMethodReturnType(\ReflectionMethod $method)
310
    {
311 3
        if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
312 3
            return '';
313
        }
314
        return ': ' . $this->formatType($method->getReturnType(), $method);
315
    }
316
317
    /**
318
     * @param \ReflectionType $type
319
     * @param \ReflectionMethod $method
320
     * @param \ReflectionParameter|null $parameter
321
     *
322
     * @return string
323
     *
324
     * @see \Doctrine\Common\Proxy\ProxyGenerator::formatType()
325
     */
326
    private function formatType(
327
        \ReflectionType $type,
328
        \ReflectionMethod $method,
329
        \ReflectionParameter $parameter = null
330
    ) {
331
        $name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
332
        $nameLower = strtolower($name);
333
        if ('self' === $nameLower) {
334
            $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...
335
        }
336
        if ('parent' === $nameLower) {
337
            $name = $method->getDeclaringClass()->getParentClass()->getName();
338
        }
339
        if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
340
            if (null !== $parameter) {
341
                throw UnexpectedValueException::invalidParameterTypeHint(
342
                    $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...
343
                    $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...
344
                    $parameter->getName()
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
345
                );
346
            }
347
            throw UnexpectedValueException::invalidReturnTypeHint(
348
                $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...
349
                $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...
350
            );
351
        }
352
        if ( ! $type->isBuiltin()) {
353
            $name = '\\' . $name;
354
        }
355
        if ($type->allowsNull()
356
            && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
357
        ) {
358
            $name = '?' . $name;
359
        }
360
        return $name;
361
    }
362
}
363