Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

MultiTableUpdateExecutor::__construct()   D

Complexity

Conditions 9
Paths 36

Size

Total Lines 98
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 56
nc 36
nop 2
dl 0
loc 98
rs 4.8872
c 0
b 0
f 0
ccs 0
cts 71
cp 0
crap 90

How to fix   Long Method   

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
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query\Exec;
6
7
use Doctrine\DBAL\Connection;
8
use Doctrine\ORM\Query\AST;
9
use Doctrine\ORM\Query\ParameterTypeInferer;
10
use Throwable;
11
12
/**
13
 * Executes the SQL statements for bulk DQL UPDATE statements on classes in
14
 * Class Table Inheritance (JOINED).
15
 */
16
class MultiTableUpdateExecutor extends AbstractSqlExecutor
17
{
18
    /**
19
     * @var string
20
     */
21
    private $createTempTableSql;
22
23
    /**
24
     * @var string
25
     */
26
    private $dropTempTableSql;
27
28
    /**
29
     * @var string
30
     */
31
    private $insertSql;
32
33
    /**
34
     * @var mixed[]
35
     */
36
    private $sqlParameters = [];
37
38
    /**
39
     * @var int
40
     */
41
    private $numParametersInUpdateClause = 0;
42
43
    /**
44
     * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
45
     *
46
     * Internal note: Any SQL construction and preparation takes place in the constructor for
47
     *                best performance. With a query cache the executor will be cached.
48
     *
49
     * @param \Doctrine\ORM\Query\AST\Node  $AST       The root AST node of the DQL query.
50
     * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST.
51
     */
52
    public function __construct(AST\UpdateStatement $AST, $sqlWalker)
53
    {
54
        $em       = $sqlWalker->getEntityManager();
55
        $conn     = $em->getConnection();
56
        $platform = $conn->getDatabasePlatform();
57
58
        $updateClause = $AST->updateClause;
59
        $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
60
        $rootClass    = $em->getClassMetadata($primaryClass->getRootClassName());
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not exist on Doctrine\Common\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

60
        $rootClass    = $em->getClassMetadata($primaryClass->/** @scrutinizer ignore-call */ getRootClassName());

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...
61
62
        $updateItems = $updateClause->updateItems;
63
64
        $tempTable        = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
0 ignored issues
show
Bug introduced by
The method getTemporaryIdTableName() does not exist on Doctrine\Common\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

64
        $tempTable        = $platform->getTemporaryTableName($rootClass->/** @scrutinizer ignore-call */ getTemporaryIdTableName());

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...
65
        $idColumns        = $rootClass->getIdentifierColumns($em);
0 ignored issues
show
Bug introduced by
The method getIdentifierColumns() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifier()? ( Ignorable by Annotation )

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

65
        /** @scrutinizer ignore-call */ 
66
        $idColumns        = $rootClass->getIdentifierColumns($em);

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...
66
        $idColumnNameList = implode(', ', array_keys($idColumns));
67
68
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
69
        $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 'i0', $updateClause->aliasIdentificationVariable);
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\Common\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

69
        $sqlWalker->setSQLTableAlias($primaryClass->/** @scrutinizer ignore-call */ getTableName(), 'i0', $updateClause->aliasIdentificationVariable);

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...
70
71
        $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')'
72
                . ' SELECT i0.' . implode(', i0.', array_keys($idColumns));
73
74
        $rangeDecl  = new AST\RangeVariableDeclaration($primaryClass->getClassName(), $updateClause->aliasIdentificationVariable);
0 ignored issues
show
Bug introduced by
The method getClassName() does not exist on Doctrine\Common\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

74
        $rangeDecl  = new AST\RangeVariableDeclaration($primaryClass->/** @scrutinizer ignore-call */ getClassName(), $updateClause->aliasIdentificationVariable);

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...
75
        $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
76
77
        $this->insertSql .= $sqlWalker->walkFromClause($fromClause);
78
79
        // 2. Create statement used in UPDATE ... WHERE ... IN (subselect)
80
        $updateSQLTemplate = sprintf(
81
            'UPDATE %%s SET %%s WHERE (%s) IN (SELECT %s FROM %s)',
82
            $idColumnNameList,
83
            $idColumnNameList,
84
            $tempTable
85
        );
86
87
        // 3. Create and store UPDATE statements
88
        $hierarchyClasses = array_merge(
89
            array_map(
90
                function ($className) use ($em) {
91
                    return $em->getClassMetadata($className);
92
                },
93
                array_reverse($primaryClass->getSubClasses())
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not exist on Doctrine\Common\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

93
                array_reverse($primaryClass->/** @scrutinizer ignore-call */ getSubClasses())

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...
94
            ),
95
            [$primaryClass],
96
            $primaryClass->getAncestorsIterator()->getArrayCopy()
0 ignored issues
show
Bug introduced by
The method getAncestorsIterator() does not exist on Doctrine\Common\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

96
            $primaryClass->/** @scrutinizer ignore-call */ 
97
                           getAncestorsIterator()->getArrayCopy()

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...
97
        );
98
99
        $i = 0;
100
101
        foreach ($hierarchyClasses as $class) {
102
            $updateSQLParts = [];
103
104
            foreach ($updateItems as $updateItem) {
105
                $field    = $updateItem->pathExpression->field;
106
                $property = $class->getProperty($field);
107
108
                if ($property && ! $class->isInheritedProperty($field)) {
109
                    $updateSQLParts[] = $sqlWalker->walkUpdateItem($updateItem);
110
                    $newValue         = $updateItem->newValue;
111
112
                    if ($newValue instanceof AST\InputParameter) {
113
                        $this->sqlParameters[$i][] = $newValue->name;
114
115
                        ++$this->numParametersInUpdateClause;
116
                    }
117
                }
118
            }
119
120
            if ($updateSQLParts) {
121
                $this->sqlStatements[$i] = sprintf(
122
                    $updateSQLTemplate,
123
                    $class->table->getQuotedQualifiedName($platform),
124
                    implode(', ', $updateSQLParts)
125
                );
126
127
                $i++;
128
            }
129
        }
130
131
        // Append WHERE clause to insertSql, if there is one.
132
        if ($AST->whereClause) {
133
            $this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
134
        }
135
136
        // 4. Store DDL for temporary identifier table.
137
        $columnDefinitions = [];
138
139
        foreach ($idColumns as $columnName => $column) {
140
            $columnDefinitions[$columnName] = [
141
                'notnull' => true,
142
                'type'    => $column->getType(),
143
            ];
144
        }
145
146
        $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
147
                . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
148
149
        $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
150
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155
    public function execute(Connection $conn, array $params, array $types)
156
    {
157
        // Create temporary id table
158
        $conn->executeUpdate($this->createTempTableSql);
159
160
        try {
161
            // Insert identifiers. Parameters from the update clause are cut off.
162
            $numUpdated = $conn->executeUpdate(
163
                $this->insertSql,
164
                array_slice($params, $this->numParametersInUpdateClause),
165
                array_slice($types, $this->numParametersInUpdateClause)
166
            );
167
168
            // Execute UPDATE statements
169
            foreach ($this->sqlStatements as $key => $statement) {
170
                $paramValues = [];
171
                $paramTypes  = [];
172
173
                if (isset($this->sqlParameters[$key])) {
174
                    foreach ($this->sqlParameters[$key] as $parameterKey => $parameterName) {
175
                        $paramValues[] = $params[$parameterKey];
176
                        $paramTypes[]  = $types[$parameterKey] ?? ParameterTypeInferer::inferType($params[$parameterKey]);
177
                    }
178
                }
179
180
                $conn->executeUpdate($statement, $paramValues, $paramTypes);
181
            }
182
        } catch (Throwable $exception) {
183
            // FAILURE! Drop temporary table to avoid possible collisions
184
            $conn->executeUpdate($this->dropTempTableSql);
185
186
            // Re-throw exception
187
            throw $exception;
188
        }
189
190
        // Drop temporary table
191
        $conn->executeUpdate($this->dropTempTableSql);
192
193
        return $numUpdated;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $numUpdated returns the type integer which is incompatible with the return type mandated by Doctrine\ORM\Query\Exec\...tSqlExecutor::execute() of Doctrine\DBAL\Driver\Statement.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
194
    }
195
}
196