Failed Conditions
Pull Request — master (#6691)
by Dariusz
23:59 queued 15:29
created

JoinedSubclassPersister   F

Complexity

Total Complexity 96

Size/Duplication

Total Lines 574
Duplicated Lines 18.12 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 96.39%

Importance

Changes 0
Metric Value
wmc 96
lcom 1
cbo 13
dl 104
loc 574
ccs 267
cts 277
cp 0.9639
rs 3.4814
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getDiscriminatorColumnTableName() 0 8 2
A getVersionedClassMetadata() 0 10 2
B getOwningTable() 6 28 4
F executeInserts() 12 100 17
C update() 0 36 7
B delete() 0 30 3
D getSelectSQL() 16 61 12
B getCountSQL() 0 25 5
A getLockTablesSql() 12 22 3
F getSelectColumnsSQL() 33 94 18
D getInsertColumnList() 0 37 17
A assignDefaultVersionValue() 0 5 1
B getJoinSql() 25 36 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like JoinedSubclassPersister often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JoinedSubclassPersister, and based on these observations, apply Extract Interface, too.

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\Persisters\Entity;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
24
use Doctrine\DBAL\LockMode;
25
use Doctrine\DBAL\Types\Type;
26
27
use Doctrine\Common\Collections\Criteria;
28
use Doctrine\ORM\Utility\PersisterHelper;
29
30
/**
31
 * The joined subclass persister maps a single entity instance to several tables in the
32
 * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
33
 *
34
 * @author Roman Borschel <[email protected]>
35
 * @author Benjamin Eberlei <[email protected]>
36
 * @author Alexander <[email protected]>
37
 * @since 2.0
38
 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
39
 */
40
class JoinedSubclassPersister extends AbstractEntityInheritancePersister
41
{
42
    /**
43
     * Map that maps column names to the table names that own them.
44
     * This is mainly a temporary cache, used during a single request.
45
     *
46
     * @var array
47
     */
48
    private $owningTableMap = [];
49
50
    /**
51
     * Map of table to quoted table names.
52
     *
53
     * @var array
54
     */
55
    private $quotedTableMap = [];
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 284
    protected function getDiscriminatorColumnTableName()
61
    {
62 284
        $class = ($this->class->name !== $this->class->rootEntityName)
63 278
            ? $this->em->getClassMetadata($this->class->rootEntityName)
64 284
            : $this->class;
65
66 284
        return $class->getTableName();
67
    }
68
69
    /**
70
     * This function finds the ClassMetadata instance in an inheritance hierarchy
71
     * that is responsible for enabling versioning.
72
     *
73
     * @return \Doctrine\ORM\Mapping\ClassMetadata
74
     */
75 36
    private function getVersionedClassMetadata()
76
    {
77 36
        if (isset($this->class->fieldMappings[$this->class->versionField]['inherited'])) {
78 6
            $definingClassName = $this->class->fieldMappings[$this->class->versionField]['inherited'];
79
80 6
            return $this->em->getClassMetadata($definingClassName);
81
        }
82
83 30
        return $this->class;
84
    }
85
86
    /**
87
     * Gets the name of the table that owns the column the given field is mapped to.
88
     *
89
     * @param string $fieldName
90
     *
91
     * @return string
92
     *
93
     * @override
94
     */
95 281
    public function getOwningTable($fieldName)
96
    {
97 281
        if (isset($this->owningTableMap[$fieldName])) {
98 149
            return $this->owningTableMap[$fieldName];
99
        }
100
101
        switch (true) {
102 281 View Code Duplication
            case isset($this->class->associationMappings[$fieldName]['inherited']):
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...
103 249
                $cm = $this->em->getClassMetadata($this->class->associationMappings[$fieldName]['inherited']);
104 249
                break;
105
106 230 View Code Duplication
            case isset($this->class->fieldMappings[$fieldName]['inherited']):
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...
107 200
                $cm = $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]['inherited']);
108 200
                break;
109
110
            default:
111 226
                $cm = $this->class;
112 226
                break;
113
        }
114
115 281
        $tableName          = $cm->getTableName();
116 281
        $quotedTableName    = $this->quoteStrategy->getTableName($cm, $this->platform);
0 ignored issues
show
Compatibility introduced by
$cm of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
117
118 281
        $this->owningTableMap[$fieldName] = $tableName;
119 281
        $this->quotedTableMap[$tableName] = $quotedTableName;
120
121 281
        return $tableName;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 294
    public function executeInserts()
128
    {
129 294
        if ( ! $this->queuedInserts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->queuedInserts 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...
130 207
            return [];
131
        }
132
133 284
        $postInsertIds  = [];
134 284
        $idGenerator    = $this->class->idGenerator;
135 284
        $isPostInsertId = $idGenerator->isPostInsertGenerator();
136 284
        $rootClass      = ($this->class->name !== $this->class->rootEntityName)
137 278
            ? $this->em->getClassMetadata($this->class->rootEntityName)
138 284
            : $this->class;
139
140
        // Prepare statement for the root table
141 284
        $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
142 284
        $rootTableName = $rootClass->getTableName();
143 284
        $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL());
144
145
        // Prepare statements for sub tables.
146 284
        $subTableStmts = [];
147
148 284
        if ($rootClass !== $this->class) {
149 278
            $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL());
150
        }
151
152 284
        foreach ($this->class->parentClasses as $parentClassName) {
153 278
            $parentClass = $this->em->getClassMetadata($parentClassName);
154 278
            $parentTableName = $parentClass->getTableName();
155
156 278
            if ($parentClass !== $rootClass) {
157 157
                $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName);
158 278
                $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL());
159
            }
160
        }
161
162
        // Execute all inserts. For each entity:
163
        // 1) Insert on root table
164
        // 2) Insert on sub tables
165 284
        foreach ($this->queuedInserts as $entity) {
166 284
            $insertData = $this->prepareInsertData($entity);
167
168
            // Execute insert on root table
169 284
            $paramIndex = 1;
170
171 284
            foreach ($insertData[$rootTableName] as $columnName => $value) {
172 284
                $rootTableStmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]);
173
            }
174
175 284
            $rootTableStmt->execute();
176
177 284 View Code Duplication
            if ($isPostInsertId) {
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...
178 280
                $generatedId = $idGenerator->generate($this->em, $entity);
0 ignored issues
show
Compatibility introduced by
$this->em of type object<Doctrine\ORM\EntityManagerInterface> is not a sub-type of object<Doctrine\ORM\EntityManager>. It seems like you assume a concrete implementation of the interface Doctrine\ORM\EntityManagerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
179
                $id = [
180 280
                    $this->class->identifier[0] => $generatedId
181
                ];
182 280
                $postInsertIds[] = [
183 280
                    'generatedId' => $generatedId,
184 280
                    'entity' => $entity,
185
                ];
186
            } else {
187 4
                $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
188
            }
189
190 284
            if ($this->class->isVersioned) {
191 9
                $this->assignDefaultVersionValue($entity, $id);
192
            }
193
194
            // Execute inserts on subtables.
195
            // The order doesn't matter because all child tables link to the root table via FK.
196 284
            foreach ($subTableStmts as $tableName => $stmt) {
197
                /** @var \Doctrine\DBAL\Statement $stmt */
198 278
                $paramIndex = 1;
199 278
                $data       = $insertData[$tableName] ?? [];
200
201 278
                foreach ((array) $id as $idName => $idVal) {
202 278
                    $type = isset($this->columnTypes[$idName]) ? $this->columnTypes[$idName] : Type::STRING;
203
204 278
                    $stmt->bindValue($paramIndex++, $idVal, $type);
205
                }
206
207 278
                foreach ($data as $columnName => $value) {
208 213
                    if (!is_array($id) || !isset($id[$columnName])) {
209 213
                        $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]);
210
                    }
211
                }
212
213 284
                $stmt->execute();
214
            }
215
        }
216
217 284
        $rootTableStmt->closeCursor();
218
219 284
        foreach ($subTableStmts as $stmt) {
220 278
            $stmt->closeCursor();
221
        }
222
223 284
        $this->queuedInserts = [];
224
225 284
        return $postInsertIds;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 31
    public function update($entity)
232
    {
233 31
        $updateData = $this->prepareUpdateData($entity);
234
235 31
        if ( ! $updateData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $updateData 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...
236
            return;
237
        }
238
239 31
        if (($isVersioned = $this->class->isVersioned) === false) {
240
            return;
241
        }
242
243 31
        $versionedClass  = $this->getVersionedClassMetadata();
244 31
        $versionedTable  = $versionedClass->getTableName();
245
246 31
        foreach ($updateData as $tableName => $data) {
247 31
            $tableName = $this->quotedTableMap[$tableName];
248 31
            $versioned = $isVersioned && $versionedTable === $tableName;
249
250 31
            $this->updateTable($entity, $tableName, $data, $versioned);
251
        }
252
253
        // Make sure the table with the version column is updated even if no columns on that
254
        // table were affected.
255 30
        if ($isVersioned) {
256 5
            if ( ! isset($updateData[$versionedTable])) {
257 2
                $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
258
259 2
                $this->updateTable($entity, $tableName, [], true);
260
            }
261
262 4
            $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
263
264 4
            $this->assignDefaultVersionValue($entity, $identifiers);
265
        }
266 29
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 5
    public function delete($entity)
272
    {
273 5
        $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
274 5
        $id         = array_combine($this->class->getIdentifierColumnNames(), $identifier);
275
276 5
        $this->deleteJoinTableRecords($identifier);
277
278
        // If the database platform supports FKs, just
279
        // delete the row from the root table. Cascades do the rest.
280 5
        if ($this->platform->supportsForeignKeyConstraints()) {
281
            $rootClass  = $this->em->getClassMetadata($this->class->rootEntityName);
282
            $rootTable  = $this->quoteStrategy->getTableName($rootClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$rootClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
283
284
            return (bool) $this->conn->delete($rootTable, $id);
285
        }
286
287
        // Delete from all tables individually, starting from this class' table up to the root table.
288 5
        $rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
289
290 5
        $affectedRows = $this->conn->delete($rootTable, $id);
291
292 5
        foreach ($this->class->parentClasses as $parentClass) {
293 4
            $parentMetadata = $this->em->getClassMetadata($parentClass);
294 4
            $parentTable    = $this->quoteStrategy->getTableName($parentMetadata, $this->platform);
0 ignored issues
show
Compatibility introduced by
$parentMetadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
295
296 4
            $this->conn->delete($parentTable, $id);
297
        }
298
299 5
        return (bool) $affectedRows;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305 69
    public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, array $orderBy = null)
306
    {
307 69
        $this->switchPersisterContext($offset, $limit);
308
309 69
        $baseTableAlias = $this->getSQLTableAlias($this->class->name);
310 69
        $joinSql        = $this->getJoinSql($baseTableAlias);
311
312 69 View Code Duplication
        if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
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...
313 2
            $joinSql .= $this->getSelectManyToManyJoinSQL($assoc);
314
        }
315
316 69
        $conditionSql = ($criteria instanceof Criteria)
317
            ? $this->getSelectConditionCriteriaSQL($criteria)
318 69
            : $this->getSelectConditionSQL($criteria, $assoc);
319
320
        // If the current class in the root entity, add the filters
321 69
        if ($filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName))) {
0 ignored issues
show
Compatibility introduced by
$this->em->getClassMetad...>class->rootEntityName) of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
322 4
            $conditionSql .= $conditionSql
323 2
                ? ' AND ' . $filterSql
324 4
                : $filterSql;
325
        }
326
327 69
        $orderBySql = '';
328
329 69
        if ($assoc !== null && isset($assoc['orderBy'])) {
330 1
            $orderBy = $assoc['orderBy'];
331
        }
332
333 69
        if ($orderBy) {
334 1
            $orderBySql = $this->getOrderBySQL($orderBy, $baseTableAlias);
335
        }
336
337 69
        $lockSql = '';
338
339 View Code Duplication
        switch ($lockMode) {
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...
340 69
            case LockMode::PESSIMISTIC_READ:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
341
342
                $lockSql = ' ' . $this->platform->getReadLockSQL();
343
344
                break;
345
346 69
            case LockMode::PESSIMISTIC_WRITE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
347
348
                $lockSql = ' ' . $this->platform->getWriteLockSQL();
349
350
                break;
351
        }
352
353 69
        $tableName  = $this->quoteStrategy->getTableName($this->class, $this->platform);
354 69
        $from       = ' FROM ' . $tableName . ' ' . $baseTableAlias;
355 69
        $where      = $conditionSql != '' ? ' WHERE ' . $conditionSql : '';
356 69
        $lock       = $this->platform->appendLockHint($from, $lockMode);
357 69
        $columnList = $this->getSelectColumnsSQL();
358 69
        $query      = 'SELECT '  . $columnList
359 69
                    . $lock
360 69
                    . $joinSql
361 69
                    . $where
362 69
                    . $orderBySql;
363
364 69
        return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql;
365
    }
366
367
    /**
368
     * {@inheritDoc}
369
     */
370 6
    public function getCountSQL($criteria = [])
371
    {
372 6
        $tableName      = $this->quoteStrategy->getTableName($this->class, $this->platform);
373 6
        $baseTableAlias = $this->getSQLTableAlias($this->class->name);
374 6
        $joinSql        = $this->getJoinSql($baseTableAlias);
375
376 6
        $conditionSql = ($criteria instanceof Criteria)
377 6
            ? $this->getSelectConditionCriteriaSQL($criteria)
378 6
            : $this->getSelectConditionSQL($criteria);
379
380 6
        $filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName));
0 ignored issues
show
Compatibility introduced by
$this->em->getClassMetad...>class->rootEntityName) of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
381
382 6
        if ('' !== $filterSql) {
383 1
            $conditionSql = $conditionSql
384 1
                ? $conditionSql . ' AND ' . $filterSql
385 1
                : $filterSql;
386
        }
387
388
        $sql = 'SELECT COUNT(*) '
389 6
            . 'FROM ' . $tableName . ' ' . $baseTableAlias
390 6
            . $joinSql
391 6
            . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql);
392
393 6
        return $sql;
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399 6
    protected function getLockTablesSql($lockMode)
400
    {
401 6
        $joinSql            = '';
402 6
        $identifierColumns  = $this->class->getIdentifierColumnNames();
403 6
        $baseTableAlias     = $this->getSQLTableAlias($this->class->name);
404
405
        // INNER JOIN parent tables
406 6 View Code Duplication
        foreach ($this->class->parentClasses as $parentClassName) {
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...
407 5
            $conditions     = [];
408 5
            $tableAlias     = $this->getSQLTableAlias($parentClassName);
409 5
            $parentClass    = $this->em->getClassMetadata($parentClassName);
410 5
            $joinSql       .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
0 ignored issues
show
Compatibility introduced by
$parentClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
411
412 5
            foreach ($identifierColumns as $idColumn) {
413 5
                $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
414
            }
415
416 5
            $joinSql .= implode(' AND ', $conditions);
417
        }
418
419 6
        return parent::getLockTablesSql($lockMode) . $joinSql;
420
    }
421
422
    /**
423
     * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly.
424
     *
425
     * @return string
426
     */
427 69
    protected function getSelectColumnsSQL()
428
    {
429
        // Create the column list fragment only once
430 69
        if ($this->currentPersisterContext->selectColumnListSql !== null) {
431 16
            return $this->currentPersisterContext->selectColumnListSql;
432
        }
433
434 69
        $columnList         = [];
435 69
        $discrColumn        = $this->class->discriminatorColumn['name'];
436 69
        $discrColumnType    = $this->class->discriminatorColumn['type'];
437 69
        $baseTableAlias     = $this->getSQLTableAlias($this->class->name);
438 69
        $resultColumnName   = $this->platform->getSQLResultCasing($discrColumn);
439
440 69
        $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r');
441 69
        $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName);
442 69
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumn, false, $discrColumnType);
443
444
        // Add regular columns
445 69
        foreach ($this->class->fieldMappings as $fieldName => $mapping) {
446 69
            $class = isset($mapping['inherited'])
447 47
                ? $this->em->getClassMetadata($mapping['inherited'])
448 69
                : $this->class;
449
450 69
            $columnList[] = $this->getSelectColumnSQL($fieldName, $class);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
451
        }
452
453
        // Add foreign key columns
454 69
        foreach ($this->class->associationMappings as $mapping) {
455 57
            if ( ! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE)) {
456 44
                continue;
457
            }
458
459 56
            $tableAlias = isset($mapping['inherited'])
460 39
                ? $this->getSQLTableAlias($mapping['inherited'])
461 56
                : $baseTableAlias;
462
463 56
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
464
465 56
            foreach ($mapping['joinColumns'] as $joinColumn) {
466 56
                $columnList[] = $this->getSelectJoinColumnSQL(
467 56
                    $tableAlias,
468 56
                    $joinColumn['name'],
469 56
                    $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform),
470 56
                    PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em)
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
471
                );
472
            }
473
        }
474
475
        // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult).
476 69
        $tableAlias = ($this->class->rootEntityName == $this->class->name)
477 26
            ? $baseTableAlias
478 69
            : $this->getSQLTableAlias($this->class->rootEntityName);
479
480 69
        $columnList[] = $tableAlias . '.' . $discrColumn;
481
482
        // sub tables
483 69 View Code Duplication
        foreach ($this->class->subClasses as $subClassName) {
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...
484 50
            $subClass   = $this->em->getClassMetadata($subClassName);
485 50
            $tableAlias = $this->getSQLTableAlias($subClassName);
486
487
            // Add subclass columns
488 50
            foreach ($subClass->fieldMappings as $fieldName => $mapping) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
489 50
                if (isset($mapping['inherited'])) {
490 50
                    continue;
491
                }
492
493 45
                $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass);
0 ignored issues
show
Compatibility introduced by
$subClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
494
            }
495
496
            // Add join columns (foreign keys)
497 50
            foreach ($subClass->associationMappings as $mapping) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
498 44
                if ( ! $mapping['isOwningSide']
499 44
                        || ! ($mapping['type'] & ClassMetadata::TO_ONE)
500 44
                        || isset($mapping['inherited'])) {
501 42
                    continue;
502
                }
503
504 33
                $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
505
506 33
                foreach ($mapping['joinColumns'] as $joinColumn) {
507 33
                    $columnList[] = $this->getSelectJoinColumnSQL(
508 33
                        $tableAlias,
509 33
                        $joinColumn['name'],
510 33
                        $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform),
0 ignored issues
show
Compatibility introduced by
$subClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
511 50
                        PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em)
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
512
                    );
513
                }
514
            }
515
        }
516
517 69
        $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
518
519 69
        return $this->currentPersisterContext->selectColumnListSql;
520
    }
521
522
    /**
523
     * {@inheritdoc}
524
     */
525 284
    protected function getInsertColumnList()
526
    {
527
        // Identifier columns must always come first in the column list of subclasses.
528 284
        $columns = $this->class->parentClasses
529 278
            ? $this->class->getIdentifierColumnNames()
530 284
            : [];
531
532 284
        foreach ($this->class->reflFields as $name => $field) {
533 284
            if (isset($this->class->fieldMappings[$name]['inherited'])
534 278
                    && ! isset($this->class->fieldMappings[$name]['id'])
535 284
                    || isset($this->class->associationMappings[$name]['inherited'])
536 284
                    || ($this->class->isVersioned && $this->class->versionField == $name)
537 284
                    || isset($this->class->embeddedClasses[$name])) {
538 264
                continue;
539
            }
540
541 284
            if (isset($this->class->associationMappings[$name])) {
542 256
                $assoc = $this->class->associationMappings[$name];
543 256
                if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) {
544 255
                    foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
545 256
                        $columns[] = $sourceCol;
546
                    }
547
                }
548 284
            } else if ($this->class->name != $this->class->rootEntityName ||
549 284
                    ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] != $name) {
550 284
                $columns[]                  = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform);
551 284
                $this->columnTypes[$name]   = $this->class->fieldMappings[$name]['type'];
552
            }
553
        }
554
555
        // Add discriminator column if it is the topmost class.
556 284
        if ($this->class->name == $this->class->rootEntityName) {
557 284
            $columns[] = $this->class->discriminatorColumn['name'];
558
        }
559
560 284
        return $columns;
561
    }
562
563
    /**
564
     * {@inheritdoc}
565
     */
566 9
    protected function assignDefaultVersionValue($entity, array $id)
567
    {
568 9
        $value = $this->fetchVersionValue($this->getVersionedClassMetadata(), $id);
569 9
        $this->class->setFieldValue($entity, $this->class->versionField, $value);
570 9
    }
571
572
    /**
573
     * @param string $baseTableAlias
574
     *
575
     * @return string
576
     */
577 74
    private function getJoinSql($baseTableAlias)
578
    {
579 74
        $joinSql          = '';
580 74
        $identifierColumn = $this->class->getIdentifierColumnNames();
581
582
        // INNER JOIN parent tables
583 74 View Code Duplication
        foreach ($this->class->parentClasses as $parentClassName) {
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...
584 51
            $conditions   = [];
585 51
            $parentClass  = $this->em->getClassMetadata($parentClassName);
586 51
            $tableAlias   = $this->getSQLTableAlias($parentClassName);
587 51
            $joinSql     .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
0 ignored issues
show
Compatibility introduced by
$parentClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
588
589
590 51
            foreach ($identifierColumn as $idColumn) {
591 51
                $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
592
            }
593
594 51
            $joinSql .= implode(' AND ', $conditions);
595
        }
596
597
        // OUTER JOIN sub tables
598 74 View Code Duplication
        foreach ($this->class->subClasses as $subClassName) {
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...
599 52
            $conditions  = [];
600 52
            $subClass    = $this->em->getClassMetadata($subClassName);
601 52
            $tableAlias  = $this->getSQLTableAlias($subClassName);
602 52
            $joinSql    .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
0 ignored issues
show
Compatibility introduced by
$subClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
603
604 52
            foreach ($identifierColumn as $idColumn) {
605 52
                $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
606
            }
607
608 52
            $joinSql .= implode(' AND ', $conditions);
609
        }
610
611 74
        return $joinSql;
612
    }
613
}
614