Completed
Pull Request — master (#1219)
by Maciej
09:16
created

generateClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 0
cts 6
cp 0
rs 9.4286
cc 1
eloc 5
nc 1
nop 2
crap 2
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\ODM\MongoDB\Configuration;
23
24
final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator
25
{
26
    /**
27
     * The namespace that contains all persistent collection classes.
28
     *
29
     * @var string
30
     */
31
    private $collectionNamespace;
32
33
    /**
34
     * The directory that contains all persistent collection classes.
35
     *
36
     * @var string
37
     */
38
    private $collectionDir;
39
40
    /**
41
     * @param string $collectionDir
42
     * @param string $collectionNs
43
     */
44 5
    public function __construct($collectionDir, $collectionNs)
45
    {
46 5
        $this->collectionDir = $collectionDir;
47 5
        $this->collectionNamespace = $collectionNs;
48 5
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function generateClass($class, $dir)
54
    {
55
        $collClassName = str_replace('\\', '', $class) . 'Persistent';
56
        $className = $this->collectionNamespace . '\\' . $collClassName;
57
        $fileName = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php';
58
        $this->generateCollectionClass($class, $className, $fileName);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 4
    public function loadClass($collectionClass, $autoGenerate)
65
    {
66
        // These checks are not in __construct() because of BC and should be moved for 2.0
67 4
        if ( ! $this->collectionDir) {
68
            throw PersistentCollectionException::directoryRequired();
69
        }
70 4
        if ( ! $this->collectionNamespace) {
71
            throw PersistentCollectionException::namespaceRequired();
72
        }
73
74 4
        $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent';
75 4
        $className = $this->collectionNamespace . '\\' . $collClassName;
76 4 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...
77 2
            $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php';
78
            switch ($autoGenerate) {
79 2
                case Configuration::AUTOGENERATE_NEVER:
80
                    require $fileName;
81
                    break;
82
83 2
                case Configuration::AUTOGENERATE_ALWAYS:
84 2
                    $this->generateCollectionClass($collectionClass, $className, $fileName);
85 2
                    require $fileName;
86 2
                    break;
87
88
                case Configuration::AUTOGENERATE_FILE_NOT_EXISTS:
89
                    if ( ! file_exists($fileName)) {
90
                        $this->generateCollectionClass($collectionClass, $className, $fileName);
91
                    }
92
                    require $fileName;
93
                    break;
94
95
                case Configuration::AUTOGENERATE_EVAL:
96
                    $this->generateCollectionClass($collectionClass, $className, false);
97
                    break;
98
            }
99 2
        }
100
101 4
        return $className;
102
    }
103
104 2
    private function generateCollectionClass($for, $targetFqcn, $fileName)
105
    {
106 2
        $exploded = explode('\\', $targetFqcn);
107 2
        $class = array_pop($exploded);
108 2
        $namespace = join('\\', $exploded);
109
        $code = <<<CODE
110
<?php
111
112
namespace $namespace;
113
114
use Doctrine\Common\Collections\Collection as BaseCollection;
115
use Doctrine\ODM\MongoDB\DocumentManager;
116
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
117
use Doctrine\ODM\MongoDB\MongoDBException;
118
use Doctrine\ODM\MongoDB\UnitOfWork;
119
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
120
121
/**
122
 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR
123
 */
124 2
class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface
125
{
126
    use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait;
127
128
    /**
129
     * @param BaseCollection \$coll
130
     * @param DocumentManager \$dm
131
     * @param UnitOfWork \$uow
132
     */
133
    public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow)
134
    {
135
        \$this->coll = \$coll;
136
        \$this->dm = \$dm;
137
        \$this->uow = \$uow;
138
    }
139
140 2
CODE;
141 2
        $rc = new \ReflectionClass($for);
142 2
        $rt = new \ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait');
143 2
        foreach ($rc->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
144
            if (
145 2
                $rt->hasMethod($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...
146 2
                $method->isConstructor() ||
147 2
                $method->isFinal() ||
148 2
                $method->isStatic()
149 2
            ) {
150 2
                continue;
151
            }
152 2
            $code .= $this->generateMethod($method);
153 2
        }
154 2
        $code .= "}\n";
155
156 2
        if ($fileName === false) {
157
            if ( ! class_exists($targetFqcn)) {
158
                eval(substr($code, 5));
159
            }
160 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...
161 2
            $parentDirectory = dirname($fileName);
162
163 2
            if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
164
                throw PersistentCollectionException::directoryNotWritable();
165
            }
166
167 2
            if ( ! is_writable($parentDirectory)) {
168
                throw PersistentCollectionException::directoryNotWritable();
169
            }
170
171 2
            $tmpFileName = $fileName . '.' . uniqid('', true);
172 2
            file_put_contents($tmpFileName, $code);
173 2
            rename($tmpFileName, $fileName);
174
        }
175 2
    }
176
177 2
    private function generateMethod(\ReflectionMethod $method)
178
    {
179 2
        $parametersString = $this->buildParametersString($method);
180 2
        $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters()));
181
182
        $method = <<<CODE
183
184
    /**
185
     * {@inheritDoc}
186
     */
187 2
    public function {$method->getName()}($parametersString)
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
188
    {
189
        \$this->initialize();
190 2
        return \$this->coll->{$method->getName()}($callParamsString);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
191
    }
192
193 2
CODE;
194 2
        return $method;
195
    }
196
197
    /**
198
     * @param \ReflectionMethod $method
199
     *
200
     * @return string
201
     */
202 2
    private function buildParametersString(\ReflectionMethod $method)
203
    {
204 2
        $parameters = $method->getParameters();
205 2
        $parameterDefinitions = array();
206
207
        /* @var $param \ReflectionParameter */
208 2
        foreach ($parameters as $param) {
209 2
            $parameterDefinition = '';
210
211 2
            if ($parameterType = $this->getParameterType($param)) {
0 ignored issues
show
Bug introduced by
It seems like $param defined by $param on line 208 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...
212 2
                $parameterDefinition .= $parameterType . ' ';
213 2
            }
214
215 2
            if ($param->isPassedByReference()) {
216
                $parameterDefinition .= '&';
217
            }
218
219 2
            if (method_exists($param, 'isVariadic')) {
220
                if ($param->isVariadic()) {
221
                    $parameterDefinition .= '...';
222
                }
223
            }
224
225 2
            $parameters[]     = '$' . $param->getName();
226 2
            $parameterDefinition .= '$' . $param->getName();
227
228 2
            if ($param->isDefaultValueAvailable()) {
229
                $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
230
            }
231
232 2
            $parameterDefinitions[] = $parameterDefinition;
233 2
        }
234
235 2
        return implode(', ', $parameterDefinitions);
236
    }
237
238
    /**
239
     * @param \ReflectionParameter $parameter
240
     *
241
     * @return string|null
242
     */
243 2
    private function getParameterType(\ReflectionParameter $parameter)
244
    {
245
        // We need to pick the type hint class too
246 2
        if ($parameter->isArray()) {
247
            return 'array';
248
        }
249
250 2
        if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) {
251
            return 'callable';
252
        }
253
254
        try {
255 2
            $parameterClass = $parameter->getClass();
256
257 2
            if ($parameterClass) {
258 2
                return '\\' . $parameterClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $parameterClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
259
            }
260 1
        } catch (\ReflectionException $previous) {
261
            // @todo ProxyGenerator throws specialized exceptions
262
            throw $previous;
263
        }
264
265 1
        return null;
266
    }
267
268
    /**
269
     * @param \ReflectionParameter[] $parameters
270
     *
271
     * @return string[]
272
     */
273 2
    private function getParameterNamesForDecoratedCall(array $parameters)
274
    {
275 2
        return array_map(
276 2
            function (\ReflectionParameter $parameter) {
277 2
                $name = '';
278
279 2
                if (method_exists($parameter, 'isVariadic')) {
280
                    if ($parameter->isVariadic()) {
281
                        $name .= '...';
282
                    }
283
                }
284
285 2
                $name .= '$' . $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...
286
287 2
                return $name;
288 2
            },
289
            $parameters
290 2
        );
291
    }
292
}
293