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

MultiTableDeleteExecutor::execute()   B

Complexity

Conditions 3
Paths 5

Size

Total Lines 25
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 5
nop 3
dl 0
loc 25
ccs 0
cts 14
cp 0
crap 12
rs 8.8571
c 0
b 0
f 0
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 Throwable;
10
11
/**
12
 * Executes the SQL statements for bulk DQL DELETE statements on classes in
13
 * Class Table Inheritance (JOINED).
14
 */
15
class MultiTableDeleteExecutor extends AbstractSqlExecutor
16
{
17
    /**
18
     * @var string
19
     */
20
    private $createTempTableSql;
21
22
    /**
23
     * @var string
24
     */
25
    private $dropTempTableSql;
26
27
    /**
28
     * @var string
29
     */
30
    private $insertSql;
31
32
    /**
33
     * Initializes a new <tt>MultiTableDeleteExecutor</tt>.
34
     *
35
     * Internal note: Any SQL construction and preparation takes place in the constructor for
36
     *                best performance. With a query cache the executor will be cached.
37
     *
38
     * @param \Doctrine\ORM\Query\AST\Node  $AST       The root AST node of the DQL query.
39
     * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST.
40
     */
41
    public function __construct(AST\DeleteStatement $AST, $sqlWalker)
42
    {
43
        $em       = $sqlWalker->getEntityManager();
44
        $conn     = $em->getConnection();
45
        $platform = $conn->getDatabasePlatform();
46
47
        $primaryClass    = $em->getClassMetadata($AST->deleteClause->abstractSchemaName);
48
        $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable;
49
        $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

49
        $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...
50
51
        $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

51
        $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...
52
        $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

52
        /** @scrutinizer ignore-call */ 
53
        $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...
53
        $idColumnNameList = implode(', ', array_keys($idColumns));
54
55
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
56
        $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 'i0', $primaryDqlAlias);
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

56
        $sqlWalker->setSQLTableAlias($primaryClass->/** @scrutinizer ignore-call */ getTableName(), 'i0', $primaryDqlAlias);

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...
57
58
        $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')'
59
                . ' SELECT i0.' . implode(', i0.', array_keys($idColumns));
60
61
        $rangeDecl        = new AST\RangeVariableDeclaration($primaryClass->getClassName(), $primaryDqlAlias);
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

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

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...
62
        $fromClause       = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
63
        $this->insertSql .= $sqlWalker->walkFromClause($fromClause);
64
65
        // Append WHERE clause, if there is one.
66
        if ($AST->whereClause) {
67
            $this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
68
        }
69
70
        // 2. Create statement used in DELETE ... WHERE ... IN (subselect)
71
        $deleteSQLTemplate = sprintf(
72
            'DELETE FROM %%s WHERE (%s) IN (SELECT %s FROM %s)',
73
            $idColumnNameList,
74
            $idColumnNameList,
75
            $tempTable
76
        );
77
78
        // 3. Create and store DELETE statements
79
        $hierarchyClasses = array_merge(
80
            array_map(
81
                function ($className) use ($em) {
82
                    return $em->getClassMetadata($className);
83
                },
84
                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

84
                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...
85
            ),
86
            [$primaryClass],
87
            $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

87
            $primaryClass->/** @scrutinizer ignore-call */ 
88
                           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...
88
        );
89
90
        foreach ($hierarchyClasses as $class) {
91
            $this->sqlStatements[] = sprintf($deleteSQLTemplate, $class->table->getQuotedQualifiedName($platform));
92
        }
93
94
        // 4. Store DDL for temporary identifier table.
95
        $columnDefinitions = [];
96
97
        foreach ($idColumns as $columnName => $column) {
98
            $columnDefinitions[$columnName] = [
99
                'notnull' => true,
100
                'type'    => $column->getType(),
101
            ];
102
        }
103
104
        $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
105
                . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
106
107
        $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113
    public function execute(Connection $conn, array $params, array $types)
114
    {
115
        // Create temporary id table
116
        $conn->executeUpdate($this->createTempTableSql);
117
118
        try {
119
            // Insert identifiers
120
            $numDeleted = $conn->executeUpdate($this->insertSql, $params, $types);
121
122
            // Execute DELETE statements
123
            foreach ($this->sqlStatements as $sql) {
124
                $conn->executeUpdate($sql);
125
            }
126
        } catch (Throwable $exception) {
127
            // FAILURE! Drop temporary table to avoid possible collisions
128
            $conn->executeUpdate($this->dropTempTableSql);
129
130
            // Re-throw exception
131
            throw $exception;
132
        }
133
134
        // Drop temporary table
135
        $conn->executeUpdate($this->dropTempTableSql);
136
137
        return $numDeleted;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $numDeleted 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...
138
    }
139
}
140