Failed Conditions
Push — master ( a3e53b...559253 )
by Guilherme
14:58
created

ResultSetMappingBuilder::addAllClassFields()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 48
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 9.0329

Importance

Changes 0
Metric Value
cc 9
eloc 27
nc 8
nop 4
dl 0
loc 48
ccs 25
cts 27
cp 0.9259
crap 9.0329
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\Mapping\FieldMetadata;
10
use Doctrine\ORM\Mapping\InheritanceType;
11
use Doctrine\ORM\Mapping\JoinColumnMetadata;
12
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
13
use InvalidArgumentException;
14
use function in_array;
15
use function sprintf;
16
17
/**
18
 * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields.
19
 */
20
class ResultSetMappingBuilder extends ResultSetMapping
21
{
22
    /**
23
     * Picking this rename mode will register entity columns as is,
24
     * as they are in the database. This can cause clashes when multiple
25
     * entities are fetched that have columns with the same name.
26
     */
27
    public const COLUMN_RENAMING_NONE = 1;
28
29
    /**
30
     * Picking custom renaming allows the user to define the renaming
31
     * of specific columns with a rename array that contains column names as
32
     * keys and result alias as values.
33
     */
34
    public const COLUMN_RENAMING_CUSTOM = 2;
35
36
    /**
37
     * Incremental renaming uses a result set mapping internal counter to add a
38
     * number to each column result, leading to uniqueness. This only works if
39
     * you use {@see generateSelectClause()} to generate the SELECT clause for
40
     * you.
41
     */
42
    public const COLUMN_RENAMING_INCREMENT = 3;
43
44
    /** @var int */
45
    private $sqlCounter = 0;
46
47
    /** @var EntityManagerInterface */
48
    private $em;
49
50
    /**
51
     * Default column renaming mode.
52
     *
53
     * @var int
54
     */
55
    private $defaultRenameMode;
56
57
    /**
58
     * @param int $defaultRenameMode
59
     */
60 52
    public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE)
61
    {
62 52
        $this->em                = $em;
63 52
        $this->defaultRenameMode = $defaultRenameMode;
64 52
    }
65
66
    /**
67
     * Adds a root entity and all of its fields to the result set.
68
     *
69
     * @param string   $class          The class name of the root entity.
70
     * @param string   $alias          The unique alias to use for the root entity.
71
     * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
72
     * @param int|null $renameMode     One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
73
     */
74 43
    public function addRootEntityFromClassMetadata(
75
        string $class,
76
        string $alias,
77
        array $renamedColumns = [],
78
        ?int $renameMode = null
79
    ) {
80 43
        $renameMode = $renameMode ?: (empty($renamedColumns) ? $this->defaultRenameMode : self::COLUMN_RENAMING_CUSTOM);
81
82 43
        $this->addEntityResult($class, $alias);
83 43
        $this->addAllClassFields($class, $alias, $renamedColumns, $renameMode);
84 42
    }
85
86
    /**
87
     * Adds a joined entity and all of its fields to the result set.
88
     *
89
     * @param string   $class          The class name of the joined entity.
90
     * @param string   $alias          The unique alias to use for the joined entity.
91
     * @param string   $parentAlias    The alias of the entity result that is the parent of this joined result.
92
     * @param string   $relation       The association field that connects the parent entity result
93
     *                                 with the joined entity result.
94
     * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
95
     * @param int|null $renameMode     One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
96
     */
97 11
    public function addJoinedEntityFromClassMetadata(
98
        string $class,
99
        string $alias,
100
        string $parentAlias,
101
        string $relation,
102
        array $renamedColumns = [],
103
        ?int $renameMode = null
104
    ) {
105 11
        $renameMode = $renameMode ?: (empty($renamedColumns) ? $this->defaultRenameMode : self::COLUMN_RENAMING_CUSTOM);
106
107 11
        $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
108 11
        $this->addAllClassFields($class, $alias, $renamedColumns, $renameMode);
109 10
    }
110
111
    /**
112
     * Adds all fields of the given class to the result set mapping (columns and meta fields).
113
     *
114
     * @param string[] $customRenameColumns
115
     *
116
     * @throws InvalidArgumentException
117
     */
118 43
    protected function addAllClassFields(string $class, string $alias, array $customRenameColumns, int $renameMode) : void
119
    {
120
        /** @var ClassMetadata $classMetadata */
121 43
        $classMetadata = $this->em->getClassMetadata($class);
122 43
        $platform      = $this->em->getConnection()->getDatabasePlatform();
123
124 43
        if (! $this->isInheritanceSupported($classMetadata)) {
125 1
            throw new InvalidArgumentException(
126 1
                'ResultSetMapping builder does not currently support your inheritance scheme.'
127
            );
128
        }
129
130 42
        foreach ($classMetadata->getPropertiesIterator() as $property) {
131
            switch (true) {
132 42
                case $property instanceof FieldMetadata:
133 42
                    $columnName  = $property->getColumnName();
134 42
                    $columnAlias = $platform->getSQLResultCasing(
135 42
                        $this->getColumnAlias($columnName, $renameMode, $customRenameColumns)
136
                    );
137
138 42
                    if (isset($this->fieldMappings[$columnAlias])) {
139 1
                        throw new InvalidArgumentException(
140 1
                            sprintf("The column '%s' conflicts with another column in the mapper.", $columnName)
141
                        );
142
                    }
143
144 42
                    $this->addFieldResult($alias, $columnAlias, $property->getName());
145 42
                    break;
146
147 32
                case $property instanceof ToOneAssociationMetadata && $property->isOwningSide():
148 26
                    $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
0 ignored issues
show
Unused Code introduced by
The assignment to $targetClass is dead and can be removed.
Loading history...
Bug introduced by
The method getTargetEntity() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\EmbeddedMetadata or Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
                    $targetClass = $this->em->getClassMetadata($property->/** @scrutinizer ignore-call */ getTargetEntity());
Loading history...
149
150 26
                    foreach ($property->getJoinColumns() as $joinColumn) {
0 ignored issues
show
Bug introduced by
The method getJoinColumns() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
                    foreach ($property->/** @scrutinizer ignore-call */ getJoinColumns() as $joinColumn) {
Loading history...
151
                        /** @var JoinColumnMetadata $joinColumn */
152 26
                        $columnName  = $joinColumn->getColumnName();
153 26
                        $columnAlias = $platform->getSQLResultCasing(
154 26
                            $this->getColumnAlias($columnName, $renameMode, $customRenameColumns)
155
                        );
156
157 26
                        if (isset($this->metaMappings[$columnAlias])) {
158
                            throw new InvalidArgumentException(
159
                                sprintf("The column '%s' conflicts with another column in the mapper.", $columnName)
160
                            );
161
                        }
162
163 26
                        $this->addMetaResult($alias, $columnAlias, $columnName, $property->isPrimaryKey(), $joinColumn->getType());
0 ignored issues
show
Bug introduced by
It seems like $joinColumn->getType() can also be of type null; however, parameter $type of Doctrine\ORM\Query\Resul...apping::addMetaResult() does only seem to accept Doctrine\DBAL\Types\Type, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
                        $this->addMetaResult($alias, $columnAlias, $columnName, $property->isPrimaryKey(), /** @scrutinizer ignore-type */ $joinColumn->getType());
Loading history...
164
                    }
165 26
                    break;
166
            }
167
        }
168 42
    }
169
170
    /**
171
     * Checks if inheritance if supported.
172
     *
173
     * @return bool
174
     */
175 43
    private function isInheritanceSupported(ClassMetadata $metadata)
176
    {
177 43
        if ($metadata->inheritanceType === InheritanceType::SINGLE_TABLE
178 43
            && in_array($metadata->getClassName(), $metadata->discriminatorMap, true)) {
179 3
            return true;
180
        }
181
182 41
        return ! in_array($metadata->inheritanceType, [InheritanceType::SINGLE_TABLE, InheritanceType::JOINED], true);
183
    }
184
185
    /**
186
     * Gets column alias for a given column.
187
     *
188
     * @param string   $columnName
189
     * @param int      $mode
190
     * @param string[] $customRenameColumns
191
     *
192
     * @return string
193
     */
194 42
    private function getColumnAlias($columnName, $mode, array $customRenameColumns)
195
    {
196
        switch ($mode) {
197 42
            case self::COLUMN_RENAMING_INCREMENT:
198 3
                return $columnName . $this->sqlCounter++;
199 39
            case self::COLUMN_RENAMING_CUSTOM:
200 10
                return $customRenameColumns[$columnName] ?? $columnName;
201 38
            case self::COLUMN_RENAMING_NONE:
202
            default:
203 38
                return $columnName;
204
        }
205
    }
206
207
    /**
208
     * Generates the Select clause from this ResultSetMappingBuilder.
209
     *
210
     * Works only for all the entity results. The select parts for scalar
211
     * expressions have to be written manually.
212
     *
213
     * @param string[] $tableAliases
214
     *
215
     * @return string
216
     */
217 18
    public function generateSelectClause($tableAliases = [])
218
    {
219 18
        $sql = '';
220
221 18
        foreach ($this->columnOwnerMap as $columnName => $dqlAlias) {
222 18
            $tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias;
223
224 18
            if ($sql) {
225 18
                $sql .= ', ';
226
            }
227
228 18
            $sql .= $tableAlias . '.';
229
230 18
            if (isset($this->fieldMappings[$columnName])) {
231 18
                $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]);
232 18
                $field = $this->fieldMappings[$columnName];
233 18
                $sql  .= $class->getProperty($field)->getColumnName();
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

233
                $sql  .= $class->/** @scrutinizer ignore-call */ getProperty($field)->getColumnName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234 12
            } elseif (isset($this->metaMappings[$columnName])) {
235 11
                $sql .= $this->metaMappings[$columnName];
236 1
            } elseif (isset($this->discriminatorColumns[$dqlAlias])) {
237 1
                $sql .= $this->discriminatorColumns[$dqlAlias];
238
            }
239
240 18
            $sql .= ' AS ' . $columnName;
241
        }
242
243 18
        return $sql;
244
    }
245
246
    /**
247
     * @return string
248
     */
249 1
    public function __toString()
250
    {
251 1
        return $this->generateSelectClause([]);
252
    }
253
}
254