Passed
Pull Request — 2.6 (#8015)
by
unknown
08:26
created

OneToManyPersister::deleteJoinedEntityCollection()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 52
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 31
c 0
b 0
f 0
dl 0
loc 52
ccs 0
cts 32
cp 0
rs 9.424
cc 3
nc 4
nop 1
crap 12

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Persisters\Collection;
21
22
use Doctrine\Common\Collections\Criteria;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\PersistentCollection;
25
use Doctrine\ORM\Utility\PersisterHelper;
26
27
/**
28
 * Persister for one-to-many collections.
29
 *
30
 * @author  Roman Borschel <[email protected]>
31
 * @author  Guilherme Blanco <[email protected]>
32
 * @author  Alexander <[email protected]>
33
 * @since   2.0
34
 */
35
class OneToManyPersister extends AbstractCollectionPersister
36
{
37
    /**
38
     * {@inheritdoc}
39
     */
40 1
    public function delete(PersistentCollection $collection)
41
    {
42
        // The only valid case here is when you have weak entities. In this
43
        // scenario, you have @OneToMany with orphanRemoval=true, and replacing
44
        // the entire collection with a new would trigger this operation.
45 1
        $mapping = $collection->getMapping();
46
47 1
        if ( ! $mapping['orphanRemoval']) {
48
            // Handling non-orphan removal should never happen, as @OneToMany
49
            // can only be inverse side. For owning side one to many, it is
50
            // required to have a join table, which would classify as a ManyToManyPersister.
51
            return;
52
        }
53
54 1
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
55
56 1
        return $targetClass->isInheritanceTypeJoined()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $targetClass->isI...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...
57
            ? $this->deleteJoinedEntityCollection($collection)
58 1
            : $this->deleteEntityCollection($collection);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 372
    public function update(PersistentCollection $collection)
65
    {
66
        // This can never happen. One to many can only be inverse side.
67
        // For owning side one to many, it is required to have a join table,
68
        // then classifying it as a ManyToManyPersister.
69 372
        return;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 2
    public function get(PersistentCollection $collection, $index)
76
    {
77 2
        $mapping = $collection->getMapping();
78
79 2
        if ( ! isset($mapping['indexBy'])) {
80
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
81
        }
82
83 2
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
84
85 2
        return $persister->load(
86
            [
87 2
                $mapping['mappedBy'] => $collection->getOwner(),
88 2
                $mapping['indexBy']  => $index
89
            ],
90 2
            null,
91 2
            $mapping,
92 2
            [],
93 2
            null,
94 2
            1
95
        );
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101 14
    public function count(PersistentCollection $collection)
102
    {
103 14
        $mapping   = $collection->getMapping();
104 14
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
105
106
        // only works with single id identifier entities. Will throw an
107
        // exception in Entity Persisters if that is not the case for the
108
        // 'mappedBy' field.
109 14
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
110
111 14
        return $persister->count($criteria);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 5
    public function slice(PersistentCollection $collection, $offset, $length = null)
118
    {
119 5
        $mapping   = $collection->getMapping();
120 5
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
121
122 5
        return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length);
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 4
    public function containsKey(PersistentCollection $collection, $key)
129
    {
130 4
        $mapping = $collection->getMapping();
131
132 4
        if ( ! isset($mapping['indexBy'])) {
133
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
134
        }
135
136 4
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
137
138
        // only works with single id identifier entities. Will throw an
139
        // exception in Entity Persisters if that is not the case for the
140
        // 'mappedBy' field.
141 4
        $criteria = new Criteria();
142
143 4
        $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
144 4
        $criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key));
145
146 4
        return (bool) $persister->count($criteria);
147
    }
148
149
     /**
150
     * {@inheritdoc}
151
     */
152 10
    public function contains(PersistentCollection $collection, $element)
153
    {
154 10
        if ( ! $this->isValidEntityState($element)) {
155 3
            return false;
156
        }
157
158 8
        $mapping   = $collection->getMapping();
159 8
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
160
161
        // only works with single id identifier entities. Will throw an
162
        // exception in Entity Persisters if that is not the case for the
163
        // 'mappedBy' field.
164 8
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
165
166 8
        return $persister->exists($element, $criteria);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 11
    public function removeElement(PersistentCollection $collection, $element)
173
    {
174 11
        $mapping = $collection->getMapping();
175
176 11
        if ( ! $mapping['orphanRemoval']) {
177
            // no-op: this is not the owning side, therefore no operations should be applied
178 8
            return false;
179
        }
180
181 3
        if ( ! $this->isValidEntityState($element)) {
182 1
            return false;
183
        }
184
185
        return $this
186 2
            ->uow
187 2
            ->getEntityPersister($mapping['targetEntity'])
188 2
            ->delete($element);
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
195
    {
196
        throw new \BadMethodCallException("Filtering a collection by Criteria is not supported by this CollectionPersister.");
197
    }
198
199
    /**
200
     * @param PersistentCollection $collection
201
     *
202
     * @return int
203
     *
204
     * @throws \Doctrine\DBAL\DBALException
205
     */
206 1
    private function deleteEntityCollection(PersistentCollection $collection)
207
    {
208 1
        $mapping     = $collection->getMapping();
209 1
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
210 1
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
211 1
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
212 1
        $columns     = [];
213 1
        $parameters  = [];
214
215 1
        foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] as $joinColumn) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
216 1
            $columns[]    = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
217 1
            $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
218
        }
219
220 1
        $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
221 1
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
222
223 1
        return $this->conn->executeUpdate($statement, $parameters);
224
    }
225
226
    /**
227
     * Delete Class Table Inheritance entities.
228
     * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables.
229
     *
230
     * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =)
231
     *
232
     * @param PersistentCollection $collection
233
     *
234
     * @return int
235
     *
236
     * @throws \Doctrine\DBAL\DBALException
237
     */
238
    private function deleteJoinedEntityCollection(PersistentCollection $collection)
239
    {
240
        $mapping     = $collection->getMapping();
241
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
242
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
243
        $rootClass   = $this->em->getClassMetadata($targetClass->rootEntityName);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
244
245
        // 1) Build temporary table DDL
246
        $tempTable         = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
247
        $idColumnNames     = $rootClass->getIdentifierColumnNames();
0 ignored issues
show
Bug introduced by
The method getIdentifierColumnNames() 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

247
        /** @scrutinizer ignore-call */ 
248
        $idColumnNames     = $rootClass->getIdentifierColumnNames();

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...
248
        $idColumnList      = implode(', ', $idColumnNames);
249
        $columnDefinitions = [];
250
251
        foreach ($idColumnNames as $idColumnName) {
252
            $columnDefinitions[$idColumnName] = [
253
                'notnull' => true,
254
                'type'    => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)),
255
            ];
256
        }
257
258
        $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable
259
            . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
260
261
        $this->conn->executeUpdate($statement);
262
263
        // 2) Build insert table records into temporary table
264
        $query = $this->em->createQuery(
265
            ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames())
266
            . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping['mappedBy'] . ' = :owner'
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
267
        )->setParameter('owner', $collection->getOwner());
268
269
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $query->getSQL();
0 ignored issues
show
Bug introduced by
Are you sure $query->getSQL() of type array 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

269
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . /** @scrutinizer ignore-type */ $query->getSQL();
Loading history...
270
        $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner()));
271
        $numDeleted = $this->conn->executeUpdate($statement, $parameters);
272
273
        // 3) Delete records on each table in the hierarchy
274
        $classNames = array_merge($targetClass->parentClasses, [$targetClass->name], $targetClass->subClasses);
0 ignored issues
show
Bug introduced by
Accessing subClasses 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 introduced by
Accessing parentClasses on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
275
276
        foreach (array_reverse($classNames) as $className) {
277
            $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform);
278
            $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')'
279
                . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')';
280
281
            $this->conn->executeUpdate($statement);
282
        }
283
284
        // 4) Drop temporary table
285
        $statement = $this->platform->getDropTemporaryTableSQL($tempTable);
286
287
        $this->conn->executeUpdate($statement);
288
289
        return $numDeleted;
290
    }
291
}
292