ORMPurger::getDeleteFromTableSQL()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Common\DataFixtures\Purger;
6
7
use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter;
8
use Doctrine\DBAL\Platforms\AbstractPlatform;
9
use Doctrine\DBAL\Schema\Identifier;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use function array_reverse;
13
use function array_search;
14
use function count;
15
use function is_callable;
16
use function method_exists;
17
use function preg_match;
18
19
/**
20
 * Class responsible for purging databases of data before reloading data fixtures.
21
 */
22
class ORMPurger implements PurgerInterface, ORMPurgerInterface
23
{
24
    public const PURGE_MODE_DELETE   = 1;
25
    public const PURGE_MODE_TRUNCATE = 2;
26
27
    /** @var EntityManagerInterface|null */
28
    private $em;
29
30
    /**
31
     * If the purge should be done through DELETE or TRUNCATE statements
32
     *
33
     * @var int
34
     */
35
    private $purgeMode = self::PURGE_MODE_DELETE;
36
37
    /**
38
     * Table/view names to be excleded from purge
39
     *
40
     * @var string[]
41
     */
42
    private $excluded;
43
44
    /**
45
     * Construct new purger instance.
46
     *
47
     * @param EntityManagerInterface $em       EntityManagerInterface instance used for persistence.
48
     * @param string[]               $excluded array of table/view names to be excleded from purge
49
     */
50
    public function __construct(?EntityManagerInterface $em = null, array $excluded = [])
51
    {
52
        $this->em       = $em;
53
        $this->excluded = $excluded;
54
    }
55
56
    /**
57
     * Set the purge mode
58
     *
59
     * @param int $mode
60
     *
61
     * @return void
62
     */
63
    public function setPurgeMode($mode)
64
    {
65
        $this->purgeMode = $mode;
66
    }
67
68
    /**
69
     * Get the purge mode
70
     *
71
     * @return int
72
     */
73
    public function getPurgeMode()
74
    {
75
        return $this->purgeMode;
76
    }
77
78
    /** @inheritDoc */
79
    public function setEntityManager(EntityManagerInterface $em)
80
    {
81
        $this->em = $em;
82
    }
83
84
    /**
85
     * Retrieve the EntityManagerInterface instance this purger instance is using.
86
     *
87
     * @return EntityManagerInterface
88
     */
89
    public function getObjectManager()
90
    {
91
        return $this->em;
92
    }
93
94
    /** @inheritDoc */
95
    public function purge()
96
    {
97
        $classes = [];
98
99
        foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) {
0 ignored issues
show
Bug introduced by
The method getMetadataFactory() does not exist on null. ( Ignorable by Annotation )

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

99
        foreach ($this->em->/** @scrutinizer ignore-call */ getMetadataFactory()->getAllMetadata() as $metadata) {

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...
100
            if ($metadata->isMappedSuperclass || (isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Bug introduced by
Accessing isEmbeddedClass on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
101
                continue;
102
            }
103
104
            $classes[] = $metadata;
105
        }
106
107
        $commitOrder = $this->getCommitOrder($this->em, $classes);
0 ignored issues
show
Bug introduced by
It seems like $this->em can also be of type null; however, parameter $em of Doctrine\Common\DataFixt...urger::getCommitOrder() does only seem to accept Doctrine\ORM\EntityManagerInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

107
        $commitOrder = $this->getCommitOrder(/** @scrutinizer ignore-type */ $this->em, $classes);
Loading history...
108
109
        // Get platform parameters
110
        $platform = $this->em->getConnection()->getDatabasePlatform();
111
112
        // Drop association tables first
113
        $orderedTables = $this->getAssociationTables($commitOrder, $platform);
114
115
        // Drop tables in reverse commit order
116
        for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
117
            $class = $commitOrder[$i];
118
119
            if ((isset($class->isEmbeddedClass) && $class->isEmbeddedClass) ||
120
                $class->isMappedSuperclass ||
121
                ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName)
122
            ) {
123
                continue;
124
            }
125
126
            $orderedTables[] = $this->getTableName($class, $platform);
127
        }
128
129
        $connection            = $this->em->getConnection();
130
        $filterExpr            = $connection->getConfiguration()->getFilterSchemaAssetsExpression();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Configurat...chemaAssetsExpression() has been deprecated: Use Configuration::getSchemaAssetsFilter() instead ( Ignorable by Annotation )

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

130
        $filterExpr            = /** @scrutinizer ignore-deprecated */ $connection->getConfiguration()->getFilterSchemaAssetsExpression();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
131
        $emptyFilterExpression = empty($filterExpr);
132
133
        $schemaAssetsFilter = method_exists($connection->getConfiguration(), 'getSchemaAssetsFilter') ? $connection->getConfiguration()->getSchemaAssetsFilter() : null;
134
135
        foreach ($orderedTables as $tbl) {
136
            // If we have a filter expression, check it and skip if necessary
137
            if (! $emptyFilterExpression && ! preg_match($filterExpr, $tbl)) {
138
                continue;
139
            }
140
141
            // If the table is excluded, skip it as well
142
            if (array_search($tbl, $this->excluded) !== false) {
143
                continue;
144
            }
145
146
            // Support schema asset filters as presented in
147
            if (is_callable($schemaAssetsFilter) && ! $schemaAssetsFilter($tbl)) {
148
                continue;
149
            }
150
151
            if ($this->purgeMode === self::PURGE_MODE_DELETE) {
152
                $connection->executeUpdate($this->getDeleteFromTableSQL($tbl, $platform));
153
            } else {
154
                $connection->executeUpdate($platform->getTruncateTableSQL($tbl, true));
155
            }
156
        }
157
    }
158
159
    /**
160
     * @param ClassMetadata[] $classes
161
     *
162
     * @return ClassMetadata[]
163
     */
164
    private function getCommitOrder(EntityManagerInterface $em, array $classes)
165
    {
166
        $sorter = new TopologicalSorter();
167
168
        foreach ($classes as $class) {
169
            if (! $sorter->hasNode($class->name)) {
170
                $sorter->addNode($class->name, $class);
171
            }
172
173
            // $class before its parents
174
            foreach ($class->parentClasses as $parentClass) {
175
                $parentClass     = $em->getClassMetadata($parentClass);
176
                $parentClassName = $parentClass->getName();
177
178
                if (! $sorter->hasNode($parentClassName)) {
179
                    $sorter->addNode($parentClassName, $parentClass);
180
                }
181
182
                $sorter->addDependency($class->name, $parentClassName);
183
            }
184
185
            foreach ($class->associationMappings as $assoc) {
186
                if (! $assoc['isOwningSide']) {
187
                    continue;
188
                }
189
190
                /** @var ClassMetadata $targetClass */
191
                $targetClass     = $em->getClassMetadata($assoc['targetEntity']);
192
                $targetClassName = $targetClass->getName();
193
194
                if (! $sorter->hasNode($targetClassName)) {
195
                    $sorter->addNode($targetClassName, $targetClass);
196
                }
197
198
                // add dependency ($targetClass before $class)
199
                $sorter->addDependency($targetClassName, $class->name);
200
201
                // parents of $targetClass before $class, too
202
                foreach ($targetClass->parentClasses as $parentClass) {
203
                    $parentClass     = $em->getClassMetadata($parentClass);
204
                    $parentClassName = $parentClass->getName();
205
206
                    if (! $sorter->hasNode($parentClassName)) {
207
                        $sorter->addNode($parentClassName, $parentClass);
208
                    }
209
210
                    $sorter->addDependency($parentClassName, $class->name);
211
                }
212
            }
213
        }
214
215
        return array_reverse($sorter->sort());
216
    }
217
218
    /**
219
     * @param array $classes
220
     *
221
     * @return array
222
     */
223
    private function getAssociationTables(array $classes, AbstractPlatform $platform)
224
    {
225
        $associationTables = [];
226
227
        foreach ($classes as $class) {
228
            foreach ($class->associationMappings as $assoc) {
229
                if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) {
230
                    continue;
231
                }
232
233
                $associationTables[] = $this->getJoinTableName($assoc, $class, $platform);
234
            }
235
        }
236
237
        return $associationTables;
238
    }
239
240
    private function getTableName(ClassMetadata $class, AbstractPlatform $platform) : string
241
    {
242
        if (isset($class->table['schema']) && ! method_exists($class, 'getSchemaName')) {
243
            return $class->table['schema'] . '.' . $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
244
        }
245
246
        return $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
247
    }
248
249
    /**
250
     * @param array            $association
251
     * @param ClassMetadata    $class
252
     * @param AbstractPlatform $platform
253
     *
254
     * @return string
255
     */
256
    private function getJoinTableName($assoc, $class, $platform)
257
    {
258
        if (isset($assoc['joinTable']['schema']) && ! method_exists($class, 'getSchemaName')) {
259
            return $assoc['joinTable']['schema'] . '.' . $this->em->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
260
        }
261
262
        return $this->em->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
263
    }
264
265
    private function getDeleteFromTableSQL(string $tableName, AbstractPlatform $platform) : string
266
    {
267
        $tableIdentifier = new Identifier($tableName);
268
269
        return 'DELETE FROM ' . $tableIdentifier->getQuotedName($platform);
270
    }
271
}
272