ResultSetMappingBuilder::generateSelectClause()   C
last analyzed

Complexity

Conditions 12
Paths 29

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
rs 6.9666
c 0
b 0
f 0
cc 12
nc 29
nop 1

How to fix   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
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Doctrine\Mapping;
12
13
use Doctrine;
14
use Doctrine\ORM\EntityManager;
15
use Kdyby;
16
use Nette;
17
18
19
20
/**
21
 * @author Filip Procházka <[email protected]>
22
 */
23
class ResultSetMappingBuilder extends Doctrine\ORM\Query\ResultSetMappingBuilder
24
{
25
26
	/**
27
	 * @var \Doctrine\ORM\EntityManager
28
	 */
29
	private $em;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
30
31
	/**
32
	 * @var int
33
	 */
34
	private $sqlCounter = 0;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
35
36
	/**
37
	 * @var \Doctrine\DBAL\Platforms\AbstractPlatform
38
	 */
39
	private $platform;
40
41
	/**
42
	 * @var int
43
	 */
44
	private $defaultRenameMode;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
45
46
47
48
	public function __construct(EntityManager $em, $defaultRenameMode = Doctrine\ORM\Query\ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
49
	{
50
		parent::__construct($em, $defaultRenameMode);
51
		$this->em = $em;
52
		$this->platform = $this->em->getConnection()->getDatabasePlatform();
53
		$this->defaultRenameMode = $defaultRenameMode;
54
	}
55
56
57
58
	protected function addAllClassFields($class, $alias, $columnAliasMap = [])
59
	{
60
		$classMetadata = $this->em->getClassMetadata($class);
61
62
		foreach ($classMetadata->parentClasses as $parentClass) {
63
			$parentClass = $this->em->getClassMetadata($parentClass);
64
			$parentAliasMap = $this->getColumnAliasMap($parentClass->getName());
65
			$this->addFieldsFromClass($parentClass, $alias, $parentAliasMap);
66
			$this->addAssociationsFromClass($parentClass, $alias, $parentAliasMap);
67
		}
68
69
		$this->addFieldsFromClass($classMetadata, $alias, $columnAliasMap);
70
		$this->addAssociationsFromClass($classMetadata, $alias, $columnAliasMap);
71
72
		if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
73
			$discrColumn = $classMetadata->discriminatorColumn['name'];
74
			$resultColumnName = $this->getColumnAlias($discrColumn);
75
76
			$this->setDiscriminatorColumn($alias, $resultColumnName);
77
			$this->addMetaResult($alias, $resultColumnName, $discrColumn);
78
79
			foreach ($classMetadata->subClasses as $subClass) {
80
				$subClass = $this->em->getClassMetadata($subClass);
81
				$subAliasMap = $this->getColumnAliasMap($subClass->getName());
82
				$this->addFieldsFromClass($subClass, $alias, $subAliasMap);
83
				$this->addAssociationsFromClass($subClass, $alias, $subAliasMap);
84
			}
85
		}
86
	}
87
88
89
90
	protected function addFieldsFromClass(Doctrine\ORM\Mapping\ClassMetadata $class, $alias, $columnAliasMap)
91
	{
92
		foreach ($class->getColumnNames() as $columnName) {
93
			$propertyName = $class->getFieldName($columnName);
94
			if ($class->isInheritedField($propertyName)) {
95
				continue;
96
			}
97
98
			$columnAlias = $this->platform->getSQLResultCasing($columnAliasMap[$columnName]);
99
100
			if (isset($this->fieldMappings[$columnAlias])) {
101
				throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
102
			}
103
104
			$this->addFieldResult($alias, $columnAlias, $propertyName, $class->getName());
105
		}
106
	}
107
108
109
110
	protected function addAssociationsFromClass(Doctrine\ORM\Mapping\ClassMetadata $class, $alias, $columnAliasMap)
111
	{
112
		foreach ($class->associationMappings as $fieldName => $associationMapping) {
113
			if ($class->isInheritedAssociation($fieldName)) {
114
				continue;
115
			}
116
117
			if (!$associationMapping['isOwningSide'] || !$associationMapping['type'] & ClassMetadata::TO_ONE) {
118
				continue;
119
			}
120
121
			if (empty($associationMapping['joinColumns'])) { // todo: joinTableColumns
122
				continue;
123
			}
124
125
			foreach ($associationMapping['joinColumns'] as $joinColumn) {
126
				$columnName = $joinColumn['name'];
127
				$columnAlias = $this->platform->getSQLResultCasing($columnAliasMap[$columnName]);
128
129
				if (isset($this->metaMappings[$columnAlias])) {
130
					throw new \InvalidArgumentException("The column '$columnAlias' conflicts with another column in the mapper.");
131
				}
132
133
				$this->addMetaResult(
134
					$alias,
135
					$columnAlias,
136
					$columnName,
137
					(isset($associationMapping['id']) && $associationMapping['id'] === true)
138
				);
139
			}
140
		}
141
	}
142
143
144
145
	/**
146
	 * Gets column alias for a given column.
147
	 *
148
	 * @param string $columnName
149
	 * @param int $mode
150
	 * @param array $customRenameColumns
151
	 *
152
	 * @return string
153
	 */
154
	private function getColumnAlias($columnName, $mode = NULL, array $customRenameColumns = [])
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
155
	{
156
		$mode = $mode ?: $this->defaultRenameMode;
157
		switch ($mode) {
158
			case self::COLUMN_RENAMING_INCREMENT:
159
				return $columnName . '_' . $this->sqlCounter++;
160
161
			case self::COLUMN_RENAMING_CUSTOM:
162
				return isset($customRenameColumns[$columnName]) ? $customRenameColumns[$columnName] : $columnName;
163
164
			case self::COLUMN_RENAMING_NONE:
165
			default:
166
				return $columnName;
167
		}
168
	}
169
170
171
172
	/**
173
	 * Retrieves a class columns and join columns aliases that are used in the SELECT clause.
174
	 *
175
	 * This depends on the renaming mode selected by the user.
176
	 *
177
	 * @param string $className
178
	 * @param int $mode
179
	 * @param array $customRenameColumns
180
	 * @throws \Doctrine\ORM\Mapping\MappingException
181
	 * @return array
182
	 */
183
	private function getColumnAliasMap($className, $mode = NULL, array $customRenameColumns = [])
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
184
	{
185
		$mode = $mode ? : $this->defaultRenameMode;
186
187
		if ($customRenameColumns) { // for BC with 2.2-2.3 API
0 ignored issues
show
Bug Best Practice introduced by
The expression $customRenameColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
188
			$mode = self::COLUMN_RENAMING_CUSTOM;
189
		}
190
191
		$columnAlias = [];
192
		$class = $this->em->getClassMetadata($className);
193
194
		foreach ($class->getColumnNames() as $columnName) {
195
			if ($class->isInheritedField($class->getFieldForColumn($columnName))) {
196
				continue;
197
			}
198
199
			$columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
200
		}
201
202
		foreach ($class->associationMappings as $fieldName => $associationMapping) {
203
			if ($class->isInheritedAssociation($fieldName)) {
204
				continue;
205
			}
206
207
			if (!$associationMapping['isOwningSide'] || !$associationMapping['type'] & ClassMetadata::TO_ONE) {
208
				continue;
209
			}
210
211
			if (empty($associationMapping['joinColumns'])) {
212
				continue;
213
			}
214
215
			foreach ($associationMapping['joinColumns'] as $joinColumn) { // todo: joinTableColumns
216
				$columnName = $joinColumn['name'];
217
				$columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
218
			}
219
		}
220
221
		return $columnAlias;
222
	}
223
224
225
226
	/**
227
	 * Generates the Select clause from this ResultSetMappingBuilder.
228
	 *
229
	 * Works only for all the entity results. The select parts for scalar
230
	 * expressions have to be written manually.
231
	 *
232
	 * @param array $tableAliases
233
	 *
234
	 * @return string
235
	 */
236
	public function generateSelectClause($tableAliases = [])
237
	{
238
		$sql = "";
239
240
		foreach ($this->columnOwnerMap as $columnName => $dqlAlias) {
241
			$tableAlias = isset($tableAliases[$dqlAlias]) ? $tableAliases[$dqlAlias] : $dqlAlias;
242
243
			if ($sql) {
244
				$sql .= ", ";
245
			}
246
247
			$sql .= $tableAlias . ".";
248
249
			if (isset($this->fieldMappings[$columnName])) {
250
				$class = $this->em->getClassMetadata($this->declaringClasses[$columnName]);
251
				$fieldName = $this->fieldMappings[$columnName];
252
253
				if (!$class->hasField($fieldName)) {
254
					if (!$class->isInheritanceTypeSingleTable() && !$class->isInheritanceTypeJoined()) {
255
						throw new Kdyby\Doctrine\UnexpectedValueException("Entity " . $class->getName() . " has no field '$fieldName' for column '$columnName'.");
256
					}
257
258
					foreach ($class->subClasses as $subClass) {
259
						$subClass = $this->em->getClassMetadata($subClass);
260
						if (!$subClass->hasField($fieldName)) {
261
							continue;
262
						}
263
264
						$sql .= $subClass->fieldMappings[$fieldName]['columnName'];
265
						break;
266
					}
267
268
				} else {
269
					$sql .= $class->fieldMappings[$fieldName]['columnName'];
270
				}
271
272
			} elseif (isset($this->metaMappings[$columnName])) {
273
				$sql .= $this->metaMappings[$columnName];
274
275
			} elseif (isset($this->discriminatorColumns[$columnName])) {
276
				$sql .= $this->discriminatorColumns[$columnName];
277
			}
278
279
			$sql .= " AS " . $columnName;
280
		}
281
282
		return $sql;
283
	}
284
285
}
286