Failed Conditions
Push — master ( 901929...f6cc12 )
by Jonathan
11:18
created

MultiTableUpdateExecutor::__construct()   D

Complexity

Conditions 9
Paths 36

Size

Total Lines 98
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 9

Importance

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

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

59
        $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...
60
61 4
        $updateItems = $updateClause->updateItems;
62
63 4
        $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

63
        $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...
64 4
        $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

64
        /** @scrutinizer ignore-call */ 
65
        $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...
65 4
        $idColumnNameList = implode(', ', array_keys($idColumns));
66
67
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
68 4
        $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

68
        $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...
69
70 4
        $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')'
71 4
                . ' SELECT i0.' . implode(', i0.', array_keys($idColumns));
72
73 4
        $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

73
        $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...
74 4
        $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
75
76 4
        $this->insertSql .= $sqlWalker->walkFromClause($fromClause);
77
78
        // 2. Create statement used in UPDATE ... WHERE ... IN (subselect)
79 4
        $updateSQLTemplate = sprintf(
80 4
            'UPDATE %%s SET %%s WHERE (%s) IN (SELECT %s FROM %s)',
81 4
            $idColumnNameList,
82 4
            $idColumnNameList,
83 4
            $tempTable
84
        );
85
86
        // 3. Create and store UPDATE statements
87 4
        $hierarchyClasses = array_merge(
88 4
            array_map(
89
                function ($className) use ($em) {
90 4
                    return $em->getClassMetadata($className);
91 4
                },
92 4
                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

92
                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...
93
            ),
94 4
            [$primaryClass],
95 4
            $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

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