Completed
Push — master ( a0071b...e33605 )
by Michael
12s
created

ORM/Query/Exec/MultiTableDeleteExecutor.php (1 issue)

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 2
    public function __construct(AST\Node $AST, $sqlWalker)
42
    {
43 2
        $em       = $sqlWalker->getEntityManager();
44 2
        $conn     = $em->getConnection();
45 2
        $platform = $conn->getDatabasePlatform();
46
47 2
        $primaryClass    = $em->getClassMetadata($AST->deleteClause->abstractSchemaName);
48 2
        $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable;
49 2
        $rootClass       = $em->getClassMetadata($primaryClass->getRootClassName());
50
51 2
        $tempTable        = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
52 2
        $idColumns        = $rootClass->getIdentifierColumns($em);
53 2
        $idColumnNameList = implode(', ', array_keys($idColumns));
54
55
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
56 2
        $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 'i0', $primaryDqlAlias);
57
58 2
        $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')'
59 2
                . ' SELECT i0.' . implode(', i0.', array_keys($idColumns));
60
61 2
        $rangeDecl        = new AST\RangeVariableDeclaration($primaryClass->getClassName(), $primaryDqlAlias);
62 2
        $fromClause       = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
63 2
        $this->insertSql .= $sqlWalker->walkFromClause($fromClause);
64
65
        // Append WHERE clause, if there is one.
66 2
        if ($AST->whereClause) {
67
            $this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
68
        }
69
70
        // 2. Create statement used in DELETE ... WHERE ... IN (subselect)
71 2
        $deleteSQLTemplate = sprintf(
72 2
            'DELETE FROM %%s WHERE (%s) IN (SELECT %s FROM %s)',
73 2
            $idColumnNameList,
74 2
            $idColumnNameList,
75 2
            $tempTable
76
        );
77
78
        // 3. Create and store DELETE statements
79 2
        $hierarchyClasses = array_merge(
80 2
            array_map(
81 2
                function ($className) use ($em) {
82 2
                    return $em->getClassMetadata($className);
83 2
                },
84 2
                array_reverse($primaryClass->getSubClasses())
85
            ),
86 2
            [$primaryClass],
87 2
            $primaryClass->getAncestorsIterator()->getArrayCopy()
88
        );
89
90 2
        foreach ($hierarchyClasses as $class) {
91 2
            $this->sqlStatements[] = sprintf($deleteSQLTemplate, $class->table->getQuotedQualifiedName($platform));
92
        }
93
94
        // 4. Store DDL for temporary identifier table.
95 2
        $columnDefinitions = [];
96
97 2
        foreach ($idColumns as $columnName => $column) {
98 2
            $columnDefinitions[$columnName] = [
99 2
                'notnull' => true,
100 2
                'type'    => $column->getType(),
101
            ];
102
        }
103
104 2
        $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
105 2
                . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
106
107 2
        $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
108 2
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 2
    public function execute(Connection $conn, array $params, array $types)
114
    {
115
        // Create temporary id table
116 2
        $conn->executeUpdate($this->createTempTableSql);
117
118
        try {
119
            // Insert identifiers
120 2
            $numDeleted = $conn->executeUpdate($this->insertSql, $params, $types);
121
122
            // Execute DELETE statements
123 2
            foreach ($this->sqlStatements as $sql) {
124 2
                $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 2
        $conn->executeUpdate($this->dropTempTableSql);
136
137 2
        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