Completed
Pull Request — master (#340)
by
unknown
02:54
created

ORMPurger::getDeleteFromTableSQL()   A

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
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
    /**
79
     * Set the EntityManagerInterface instance this purger instance should use.
80
     */
81
    public function setEntityManager(EntityManagerInterface $em)
82
    {
83
        $this->em = $em;
84
    }
85
86
    /**
87
     * Retrieve the EntityManagerInterface instance this purger instance is using.
88
     *
89
     * @return EntityManagerInterface
90
     */
91
    public function getObjectManager()
92
    {
93
        return $this->em;
94
    }
95
96
    /** @inheritDoc */
97
    public function purge()
98
    {
99
        $classes = [];
100
101
        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

101
        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...
102
            if ($metadata->isMappedSuperclass || (isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) {
0 ignored issues
show
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...
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...
103
                continue;
104
            }
105
106
            $classes[] = $metadata;
107
        }
108
109
        $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

109
        $commitOrder = $this->getCommitOrder(/** @scrutinizer ignore-type */ $this->em, $classes);
Loading history...
110
111
        // Get platform parameters
112
        $platform = $this->em->getConnection()->getDatabasePlatform();
113
114
        // Drop association tables first
115
        $orderedTables = $this->getAssociationTables($commitOrder, $platform);
116
117
        // Drop tables in reverse commit order
118
        for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
119
            $class = $commitOrder[$i];
120
121
            if ((isset($class->isEmbeddedClass) && $class->isEmbeddedClass) ||
122
                $class->isMappedSuperclass ||
123
                ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName)
124
            ) {
125
                continue;
126
            }
127
128
            $orderedTables[] = $this->getTableName($class, $platform);
129
        }
130
131
        $connection            = $this->em->getConnection();
132
        $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

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