Completed
Pull Request — master (#6015)
by Javier
09:24
created

EntityRepositoryGenerator::generateClassName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
c 0
b 0
f 0
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
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\ORM\Tools;
21
22
use Doctrine\ORM\Mapping\ClassMetadataInfo;
23
use Doctrine\Common\Util\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
26
/**
27
 * Class to generate entity repository classes
28
 *
29
 *
30
 * @link    www.doctrine-project.org
31
 * @since   2.0
32
 * @author  Benjamin Eberlei <[email protected]>
33
 * @author  Guilherme Blanco <[email protected]>
34
 * @author  Jonathan Wage <[email protected]>
35
 * @author  Roman Borschel <[email protected]>
36
 */
37
class EntityRepositoryGenerator
38
{
39
    /**
40
     * @var string
41
     */
42
    private $repositoryName;
43
44
    /**
45
     * Hash-map for handle types.
46
     *
47
     * @var array
48
     */
49
    protected $typeAlias = [
50
        Type::DATETIMETZ    => '\DateTime',
51
        Type::DATETIME      => '\DateTime',
52
        Type::DATE          => '\DateTime',
53
        Type::TIME          => '\DateTime',
54
        Type::OBJECT        => '\stdClass',
55
        Type::INTEGER       => 'int',
56
        Type::BIGINT        => 'int',
57
        Type::SMALLINT      => 'int',
58
        Type::TEXT          => 'string',
59
        Type::BLOB          => 'string',
60
        Type::DECIMAL       => 'string',
61
        Type::JSON_ARRAY    => 'array',
62
        Type::SIMPLE_ARRAY  => 'array',
63
        Type::BOOLEAN       => 'bool',
64
    ];
65
66
    /**
67
     * @var string
68
     */
69
    protected static $_template =
70
'<?php
71
72
<namespace>
73
74
/**
75
 * <className>
76
 *
77
 * This class was generated by the Doctrine ORM. Add your own custom
78
 * repository methods below.
79
 */
80
class <className> extends <repositoryName>
81
{
82
}
83
';
84
85
    /**
86
     * @var string
87
     */
88
    protected static $_templateWithMagicMethodDocblocks = <<<'EOT'
89
<?php
90
91
<namespace>
92
93
/**
94
 * <className>
95
 *
96
<magicMethodDocblocks>
97
 *
98
 * This class was generated by the Doctrine ORM. Add your own custom
99
 * repository methods below.
100
 */
101
class <className> extends <repositoryName>
102
{
103
}
104
EOT;
105
106
    /**
107
     * @var string
108
     */
109
    protected static $findByDocblockTemplate = <<<'EOT'
110
 * @method <entity>[] findBy<fieldName>(<arg1TypeHint>$<arg1Name>) Finds occurrences of <entity> matching the <arg1Name> property.
111
EOT;
112
113
    /**
114
     * @var string
115
     */
116
    protected static $findOneByDocblockTemplate = <<<'EOT'
117
 * @method <entity> findOneBy<fieldName>(<arg1TypeHint>$<arg1Name>) Finds a single occurrence of <entity> matching the <arg1Name> property, if any.
118
EOT;
119
120
    /**
121
     * @var string
122
     */
123
    protected static $countByDocblockTemplate = <<<'EOT'
124
 * @method int countBy<fieldName>(<arg1TypeHint>$<arg1Name>) Counts the number of occurrences of <entity> matching the <arg1Name> property.
125
EOT;
126
127
    /**
128
     * @param string $fullClassName
129
     *
130
     * @return string
131
     */
132 4
    public function generateEntityRepositoryClass($fullClassName)
133
    {
134
        $variables = [
135 4
            '<namespace>'       => $this->generateEntityRepositoryNamespace($fullClassName),
136 4
            '<repositoryName>'  => $this->generateEntityRepositoryName($fullClassName),
137 4
            '<className>'       => $this->generateClassName($fullClassName),
138
        ];
139
140 4
        return str_replace(array_keys($variables), array_values($variables), self::$_template);
141
    }
142
143
    /**
144
     * @param ClassMetadataInfo $metadata
145
     *
146
     * @return string
147
     */
148
    public function generateEntityRepositoryClassWithMagicMethodDocblocks(ClassMetadataInfo $metadata)
149
    {
150
        $variables = [
151
            '<namespace>'               => $this->generateEntityRepositoryNamespace($metadata->customRepositoryClassName),
152
            '<repositoryName>'          => $this->generateEntityRepositoryName($metadata->customRepositoryClassName),
153
            '<className>'               => $this->generateClassName($metadata->customRepositoryClassName),
154
            '<magicMethodDocblocks>'    => $this->generateDocblocksForMagicMethods($metadata),
155
        ];
156
157
        return str_replace(array_keys($variables), array_values($variables), self::$_templateWithMagicMethodDocblocks);
158
    }
159
160
    /**
161
     * Generates the namespace, if class do not have namespace, return empty string instead.
162
     *
163
     * @param string $fullClassName
164
     *
165
     * @return string $namespace
166
     */
167 4
    private function getClassNamespace($fullClassName)
168
    {
169 4
        $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\'));
170
171 4
        return $namespace;
172
    }
173
174
    /**
175
     * Generates the class name
176
     *
177
     * @param string $fullClassName
178
     *
179
     * @return string
180
     */
181 4
    private function generateClassName($fullClassName)
182
    {
183 4
        $namespace = $this->getClassNamespace($fullClassName);
184
185 4
        $className = $fullClassName;
186
187 4
        if ($namespace) {
188 4
            $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName));
189
        }
190
191 4
        return $className;
192
    }
193
194
    /**
195
     * Generates the namespace statement, if class do not have namespace, return empty string instead.
196
     *
197
     * @param string $fullClassName The full repository class name.
198
     *
199
     * @return string $namespace
200
     */
201 4
    private function generateEntityRepositoryNamespace($fullClassName)
202
    {
203 4
        $namespace = $this->getClassNamespace($fullClassName);
204
205 4
        return $namespace ? 'namespace ' . $namespace . ';' : '';
206
    }
207
208
    /**
209
     * @param string $fullClassName
210
     *
211
     * @return string $repositoryName
212
     */
213 4
    private function generateEntityRepositoryName($fullClassName)
214
    {
215 4
        $namespace = $this->getClassNamespace($fullClassName);
216
217 4
        $repositoryName = $this->repositoryName ?: 'Doctrine\ORM\EntityRepository';
218
219 4
        if ($namespace && $repositoryName[0] !== '\\') {
220 4
            $repositoryName = '\\' . $repositoryName;
221
        }
222
223 4
        return $repositoryName;
224
    }
225
226
    /**
227
     * @param string $fullClassName
228
     * @param string $outputDirectory
229
     *
230
     * @return void
231
     */
232 4
    public function writeEntityRepositoryClass($fullClassName, $outputDirectory)
233
    {
234 4
        $code = $this->generateEntityRepositoryClass($fullClassName);
235
236 4
        $path = $outputDirectory . \DIRECTORY_SEPARATOR
237 4
              . str_replace('\\', \DIRECTORY_SEPARATOR, $fullClassName) . '.php';
238 4
        $dir = dirname($path);
239
240 4
        if ( ! is_dir($dir)) {
241 2
            mkdir($dir, 0775, true);
242
        }
243
244 4
        if ( ! file_exists($path)) {
245 4
            file_put_contents($path, $code);
246 4
            chmod($path, 0664);
247
        }
248 4
    }
249
250
    /**
251
     * @param string $repositoryName
252
     *
253
     * @return \Doctrine\ORM\Tools\EntityRepositoryGenerator
254
     */
255 4
    public function setDefaultRepositoryName($repositoryName)
256
    {
257 4
        $this->repositoryName = $repositoryName;
258
259 4
        return $this;
260
    }
261
262
    public function writeEntityRepository(ClassMetadataInfo $metadata, $outputDirectory)
263
    {
264
        $code = $this->generateEntityRepositoryClassWithMagicMethodDocblocks($metadata);
265
266
        $path = $outputDirectory . \DIRECTORY_SEPARATOR
267
              . str_replace('\\', \DIRECTORY_SEPARATOR, $metadata->customRepositoryClassName) . '.php';
268
        $dir = dirname($path);
269
270
        if ( ! is_dir($dir)) {
271
            mkdir($dir, 0775, true);
272
        }
273
274
        if ( ! file_exists($path)) {
275
            file_put_contents($path, $code);
276
            chmod($path, 0664);
277
        }
278
    }
279
280
    public function generate(array $metadatas, $outputDirectory)
281
    {
282
        foreach ($metadatas as $metadata) {
283
            $this->writeEntityRepository($metadata, $outputDirectory);
284
        }
285
    }
286
287
    public function generateDocblocksForMagicMethods(ClassMetadataInfo $metadata)
288
    {
289
        $docblocks = [];
290
291
        foreach ($metadata->fieldMappings as $fieldMapping) {
292
            if (isset($fieldMapping['declaredField']) &&
293
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
294
            ) {
295
                continue;
296
            }
297
298
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findBy', $fieldMapping['fieldName'], $fieldMapping['type'])) {
299
                $docblocks[] = $code;
300
            }
301
302
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findOneBy', $fieldMapping['fieldName'], $fieldMapping['type'])) {
303
                $docblocks[] = $code;
304
            }
305
306
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'countBy', $fieldMapping['fieldName'], $fieldMapping['type'])) {
307
                $docblocks[] = $code;
308
            }
309
        }
310
311
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
312
            if (isset($embeddedClass['declaredField'])) {
313
                continue;
314
            }
315
316
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findBy', $fieldName, $embeddedClass['class'])) {
317
                $docblocks[] = $code;
318
            }
319
320
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findOneBy', $fieldName, $embeddedClass['class'])) {
321
                $docblocks[] = $code;
322
            }
323
324
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'countBy', $fieldName, $embeddedClass['class'])) {
325
                $docblocks[] = $code;
326
            }
327
        }
328
329
        foreach ($metadata->associationMappings as $associationMapping) {
330
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findBy', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
331
                $docblocks[] = $code;
332
            }
333
334
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'findOneBy', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
335
                $docblocks[] = $code;
336
            }
337
338
            if ($code = $this->generateDocblocksForMagicMethod($metadata, 'countBy', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
339
                $docblocks[] = $code;
340
            }
341
        }
342
343
        return implode("\n", $docblocks);
344
    }
345
346
    protected function generateDocblocksForMagicMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null)
347
    {
348
        $variableName = Inflector::camelize($fieldName);
349
        $var = sprintf('%sDocblockTemplate', $type);
350
        $template = static::$$var;
351
352
        $methodTypeHint = null;
0 ignored issues
show
Unused Code introduced by
$methodTypeHint is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
353
        $types          = Type::getTypesMap();
354
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
355
356
        if ($typeHint && ! isset($types[$typeHint])) {
357
            $variableType   =  '\\' . ltrim($variableType, '\\');
358
            $methodTypeHint =  '\\' . $typeHint . ' ';
0 ignored issues
show
Unused Code introduced by
$methodTypeHint is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
359
        }
360
361
        $replacements = [
362
            '<entity>'          => $this->getClassName($metadata),
363
            '<arg1TypeHint>'        => $variableType ? ($variableType . ' ') : '',
364
            '<arg1Name>'            => $variableName,
365
            '<fieldName>'           => ucfirst($fieldName),
366
        ];
367
368
        $docblock = str_replace(
369
            array_keys($replacements),
370
            array_values($replacements),
371
            $template
372
        );
373
374
        return $docblock;
375
    }
376
377
    /**
378
     * @param string $type
379
     *
380
     * @return string
381
     */
382
    protected function getType($type)
383
    {
384
        if (isset($this->typeAlias[$type])) {
385
            return $this->typeAlias[$type];
386
        }
387
388
        return $type;
389
    }
390
391
    /**
392
     * @param ClassMetadataInfo $metadata
393
     *
394
     * @return string
395
     */
396
    protected function getClassName(ClassMetadataInfo $metadata)
397
    {
398
        return ($pos = strrpos($metadata->name, '\\'))
399
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
400
    }
401
402
    /**
403
     * @param array $associationMapping
404
     *
405
     * @return bool
406
     */
407
    protected function isAssociationIsNullable($associationMapping)
408
    {
409
        if (isset($associationMapping['id']) && $associationMapping['id']) {
410
            return false;
411
        }
412
413
        if (isset($associationMapping['joinColumns'])) {
414
            $joinColumns = $associationMapping['joinColumns'];
415
        } else {
416
            //@todo there is no way to retrieve targetEntity metadata
417
            $joinColumns = [];
418
        }
419
420
        foreach ($joinColumns as $joinColumn) {
421
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
422
                return false;
423
            }
424
        }
425
426
        return true;
427
    }
428
}
429