Passed
Pull Request — master (#7448)
by Ilya
14:31
created

OneToManyPersister::deleteJoinedEntityCollection()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 68
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 3.0001

Importance

Changes 0
Metric Value
cc 3
eloc 40
nc 4
nop 1
dl 0
loc 68
ccs 40
cts 41
cp 0.9756
crap 3.0001
rs 9.28
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Persisters\Collection;
6
7
use BadMethodCallException;
8
use Doctrine\Common\Collections\Criteria;
9
use Doctrine\DBAL\DBALException;
10
use Doctrine\ORM\Mapping\InheritanceType;
11
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
12
use Doctrine\ORM\PersistentCollection;
13
use function array_keys;
14
use function array_map;
15
use function array_merge;
16
use function array_reverse;
17
use function array_values;
18
use function implode;
19
use function sprintf;
20
21
/**
22
 * Persister for one-to-many collections.
23
 */
24
class OneToManyPersister extends AbstractCollectionPersister
25
{
26
    /**
27
     * {@inheritdoc}
28
     */
29 2
    public function delete(PersistentCollection $collection)
30
    {
31
        // The only valid case here is when you have weak entities. In this
32
        // scenario, you have @OneToMany with orphanRemoval=true, and replacing
33
        // the entire collection with a new would trigger this operation.
34 2
        $association = $collection->getMapping();
35
36 2
        if (! $association->isOrphanRemoval()) {
37
            // Handling non-orphan removal should never happen, as @OneToMany
38
            // can only be inverse side. For owning side one to many, it is
39
            // required to have a join table, which would classify as a ManyToManyPersister.
40
            return;
41
        }
42
43 2
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
44
45 2
        return $targetClass->inheritanceType === InheritanceType::JOINED
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Bug Best Practice introduced by
The expression return $targetClass->inh...Collection($collection) returns the type integer which is incompatible with the return type mandated by Doctrine\ORM\Persisters\...tionPersister::delete() of void.

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...
46 1
            ? $this->deleteJoinedEntityCollection($collection)
47 2
            : $this->deleteEntityCollection($collection);
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 361
    public function update(PersistentCollection $collection)
54
    {
55
        // This can never happen. One to many can only be inverse side.
56
        // For owning side one to many, it is required to have a join table,
57
        // then classifying it as a ManyToManyPersister.
58 361
        return;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 2
    public function get(PersistentCollection $collection, $index)
65
    {
66 2
        $association = $collection->getMapping();
67
68 2
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
69
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
70
        }
71
72 2
        $persister = $this->uow->getEntityPersister($association->getTargetEntity());
73
        $criteria  = [
74 2
            $association->getMappedBy()  => $collection->getOwner(),
75 2
            $association->getIndexedBy() => $index,
76
        ];
77
78 2
        return $persister->load($criteria, null, $association, [], null, 1);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 14
    public function count(PersistentCollection $collection)
85
    {
86 14
        $association = $collection->getMapping();
87 14
        $persister   = $this->uow->getEntityPersister($association->getTargetEntity());
88
89
        // only works with single id identifier entities. Will throw an
90
        // exception in Entity Persisters if that is not the case for the
91
        // 'mappedBy' field.
92
        $criteria = [
93 14
            $association->getMappedBy()  => $collection->getOwner(),
94
        ];
95
96 14
        return $persister->count($criteria);
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 5
    public function slice(PersistentCollection $collection, $offset, $length = null)
103
    {
104 5
        $association = $collection->getMapping();
105 5
        $persister   = $this->uow->getEntityPersister($association->getTargetEntity());
106
107 5
        return $persister->getOneToManyCollection($association, $collection->getOwner(), $offset, $length);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 4
    public function containsKey(PersistentCollection $collection, $key)
114
    {
115 4
        $association = $collection->getMapping();
116
117 4
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
118
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
119
        }
120
121 4
        $persister = $this->uow->getEntityPersister($association->getTargetEntity());
122
123
        // only works with single id identifier entities. Will throw an
124
        // exception in Entity Persisters if that is not the case for the
125
        // 'mappedBy' field.
126
        $criteria = [
127 4
            $association->getMappedBy()  => $collection->getOwner(),
128 4
            $association->getIndexedBy() => $key,
129
        ];
130
131 4
        return (bool) $persister->count($criteria);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 10
    public function contains(PersistentCollection $collection, $element)
138
    {
139 10
        if (! $this->isValidEntityState($element)) {
140 3
            return false;
141
        }
142
143 8
        $association = $collection->getMapping();
144 8
        $persister   = $this->uow->getEntityPersister($association->getTargetEntity());
145
146
        // only works with single id identifier entities. Will throw an
147
        // exception in Entity Persisters if that is not the case for the
148
        // 'mappedBy' field.
149 8
        $criteria = new Criteria(
150 8
            Criteria::expr()->eq($association->getMappedBy(), $collection->getOwner())
151
        );
152
153 8
        return $persister->exists($element, $criteria);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 11
    public function removeElement(PersistentCollection $collection, $element)
160
    {
161 11
        $association = $collection->getMapping();
162
163 11
        if (! $association->isOrphanRemoval()) {
164
            // no-op: this is not the owning side, therefore no operations should be applied
165 8
            return false;
166
        }
167
168 3
        if (! $this->isValidEntityState($element)) {
169 1
            return false;
170
        }
171
172 2
        $persister = $this->uow->getEntityPersister($association->getTargetEntity());
173
174 2
        return $persister->delete($element);
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
181
    {
182
        throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.');
183
    }
184
185
    /**
186
     * @return int
187
     *
188
     * @throws DBALException
189
     */
190 1
    private function deleteEntityCollection(PersistentCollection $collection)
191
    {
192 1
        $association  = $collection->getMapping();
193 1
        $identifier   = $this->uow->getEntityIdentifier($collection->getOwner());
194 1
        $sourceClass  = $this->em->getClassMetadata($association->getSourceEntity());
195 1
        $targetClass  = $this->em->getClassMetadata($association->getTargetEntity());
196 1
        $inverseAssoc = $targetClass->getProperty($association->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() 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

196
        /** @scrutinizer ignore-call */ 
197
        $inverseAssoc = $targetClass->getProperty($association->getMappedBy());

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...
197 1
        $columns      = [];
198 1
        $parameters   = [];
199
200 1
        foreach ($inverseAssoc->getJoinColumns() as $joinColumn) {
201 1
            $columns[]    = $this->platform->quoteIdentifier($joinColumn->getColumnName());
202 1
            $parameters[] = $identifier[$sourceClass->fieldNames[$joinColumn->getReferencedColumnName()]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
203
        }
204
205 1
        $tableName = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
206 1
        $statement = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
207
208 1
        return $this->conn->executeUpdate($statement, $parameters);
209
    }
210
211
    /**
212
     * Delete Class Table Inheritance entities.
213
     * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables.
214
     *
215
     * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =)
216
     *
217
     * @return int
218
     *
219
     * @throws DBALException
220
     */
221 1
    private function deleteJoinedEntityCollection(PersistentCollection $collection)
222
    {
223 1
        $association     = $collection->getMapping();
224 1
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
225 1
        $rootClass       = $this->em->getClassMetadata($targetClass->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

225
        $rootClass       = $this->em->getClassMetadata($targetClass->/** @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...
226 1
        $sourcePersister = $this->uow->getEntityPersister($association->getSourceEntity());
227
228
        // 1) Build temporary table DDL
229 1
        $tempTable         = $this->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

229
        $tempTable         = $this->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...
230 1
        $idColumns         = $rootClass->getIdentifierColumns($this->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

230
        /** @scrutinizer ignore-call */ 
231
        $idColumns         = $rootClass->getIdentifierColumns($this->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...
231 1
        $idColumnNameList  = implode(', ', array_keys($idColumns));
232 1
        $columnDefinitions = [];
233
234 1
        foreach ($idColumns as $columnName => $column) {
235 1
            $type = $column->getType();
236
237 1
            $columnDefinitions[$columnName] = [
238 1
                'notnull' => true,
239 1
                'type'    => $type,
240
            ];
241
        }
242
243 1
        $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable
244 1
            . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
245
246 1
        $this->conn->executeUpdate($statement);
247
248
        // 2) Build insert table records into temporary table
249 1
        $dql   = ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames())
250 1
               . ' FROM ' . $targetClass->getClassName() . ' t0 WHERE t0.' . $association->getMappedBy() . ' = :owner';
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

250
               . ' FROM ' . $targetClass->/** @scrutinizer ignore-call */ getClassName() . ' t0 WHERE t0.' . $association->getMappedBy() . ' = :owner';

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...
251 1
        $query = $this->em->createQuery($dql)->setParameter('owner', $collection->getOwner());
252
253 1
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ') ' . $query->getSQL();
0 ignored issues
show
Bug introduced by
Are you sure $query->getSQL() of type string[] can be used in concatenation? ( Ignorable by Annotation )

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

253
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ') ' . /** @scrutinizer ignore-type */ $query->getSQL();
Loading history...
254 1
        $parameters = array_values($sourcePersister->getIdentifier($collection->getOwner()));
255 1
        $numDeleted = $this->conn->executeUpdate($statement, $parameters);
256
257
        // 3) Create statement used in DELETE ... WHERE ... IN (subselect)
258 1
        $deleteSQLTemplate = sprintf(
259 1
            'DELETE FROM %%s WHERE (%s) IN (SELECT %s FROM %s)',
260 1
            $idColumnNameList,
261 1
            $idColumnNameList,
262 1
            $tempTable
263
        );
264
265
        // 4) Delete records on each table in the hierarchy
266 1
        $hierarchyClasses = array_merge(
267 1
            array_map(
268
                function ($className) {
269
                    return $this->em->getClassMetadata($className);
270 1
                },
271 1
                array_reverse($targetClass->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

271
                array_reverse($targetClass->/** @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...
272
            ),
273 1
            [$targetClass],
274 1
            $targetClass->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

274
            $targetClass->/** @scrutinizer ignore-call */ 
275
                          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...
275
        );
276
277 1
        foreach ($hierarchyClasses as $class) {
278 1
            $statement = sprintf($deleteSQLTemplate, $class->table->getQuotedQualifiedName($this->platform));
279
280 1
            $this->conn->executeUpdate($statement);
281
        }
282
283
        // 5) Drop temporary table
284 1
        $statement = $this->platform->getDropTemporaryTableSQL($tempTable);
285
286 1
        $this->conn->executeUpdate($statement);
287
288 1
        return $numDeleted;
289
    }
290
}
291