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

MultiTableDeleteExecutor::execute()   B

Complexity

Conditions 3
Paths 5

Size

Total Lines 25
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.243

Importance

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

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

53
        $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...
54 2
        $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

54
        /** @scrutinizer ignore-call */ 
55
        $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...
55 2
        $idColumnNameList = implode(', ', array_keys($idColumns));
56
57
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
58 2
        $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

58
        $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...
59
60 2
        $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')'
61 2
                . ' SELECT i0.' . implode(', i0.', array_keys($idColumns));
62
63 2
        $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

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

86
                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...
87
            ),
88 2
            [$primaryClass],
89 2
            $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

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