Passed
Pull Request — 2.7 (#8110)
by Antonio J.
08:01
created

OneToManyPersister::deleteJoinedEntityCollection()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 52
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 3

Importance

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

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 2
    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 2
        $mapping = $collection->getMapping();
46
47 2
        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 2
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
55
56 2
        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 1
            ? $this->deleteJoinedEntityCollection($collection)
58 2
            : $this->deleteEntityCollection($collection);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 368
    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 368
        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 11
    public function count(PersistentCollection $collection)
102
    {
103 11
        $mapping   = $collection->getMapping();
104 11
        $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 11
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
110
111 11
        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 5
    public function containsKey(PersistentCollection $collection, $key)
129
    {
130 5
        $mapping = $collection->getMapping();
131
132 5
        if ( ! isset($mapping['indexBy'])) {
133
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
134
        }
135
136 5
        $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 5
        $criteria = new Criteria();
142
143 5
        $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
144
145 5
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
146 5
        $indexByExpr = $targetClass->hasField($mapping['indexBy']) ? Criteria::expr()->eq($mapping['indexBy'], $key) : Criteria::expr()->in($targetClass->getFieldForColumn($mapping['indexBy']), [$key]);
147 5
        $criteria->andWhere($indexByExpr);
148
149 5
        return (bool) $persister->count($criteria);
150
    }
151
152
     /**
153
     * {@inheritdoc}
154
     */
155 9
    public function contains(PersistentCollection $collection, $element)
156
    {
157 9
        if ( ! $this->isValidEntityState($element)) {
158 3
            return false;
159
        }
160
161 7
        $mapping   = $collection->getMapping();
162 7
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
163
164
        // only works with single id identifier entities. Will throw an
165
        // exception in Entity Persisters if that is not the case for the
166
        // 'mappedBy' field.
167 7
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
168
169 7
        return $persister->exists($element, $criteria);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
176
    {
177
        throw new \BadMethodCallException("Filtering a collection by Criteria is not supported by this CollectionPersister.");
178
    }
179
180
    /**
181
     * @param PersistentCollection $collection
182
     *
183
     * @return int
184
     *
185
     * @throws \Doctrine\DBAL\DBALException
186
     */
187 1
    private function deleteEntityCollection(PersistentCollection $collection)
188
    {
189 1
        $mapping     = $collection->getMapping();
190 1
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
191 1
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
192 1
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
193 1
        $columns     = [];
194 1
        $parameters  = [];
195
196 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...
197 1
            $columns[]    = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
198 1
            $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
199
        }
200
201 1
        $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
202 1
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
203
204 1
        return $this->conn->executeUpdate($statement, $parameters);
205
    }
206
207
    /**
208
     * Delete Class Table Inheritance entities.
209
     * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables.
210
     *
211
     * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =)
212
     *
213
     * @param PersistentCollection $collection
214
     *
215
     * @return int
216
     *
217
     * @throws \Doctrine\DBAL\DBALException
218
     */
219 1
    private function deleteJoinedEntityCollection(PersistentCollection $collection)
220
    {
221 1
        $mapping     = $collection->getMapping();
222 1
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
223 1
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
224 1
        $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...
225
226
        // 1) Build temporary table DDL
227 1
        $tempTable         = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
228 1
        $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

228
        /** @scrutinizer ignore-call */ 
229
        $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...
229 1
        $idColumnList      = implode(', ', $idColumnNames);
230 1
        $columnDefinitions = [];
231
232 1
        foreach ($idColumnNames as $idColumnName) {
233 1
            $columnDefinitions[$idColumnName] = [
234 1
                'notnull' => true,
235 1
                'type'    => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)),
236
            ];
237
        }
238
239 1
        $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable
240 1
            . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
241
242 1
        $this->conn->executeUpdate($statement);
243
244
        // 2) Build insert table records into temporary table
245 1
        $query = $this->em->createQuery(
246 1
            ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames())
247 1
            . ' 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...
248 1
        )->setParameter('owner', $collection->getOwner());
249
250 1
        $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

250
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . /** @scrutinizer ignore-type */ $query->getSQL();
Loading history...
251 1
        $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner()));
252 1
        $numDeleted = $this->conn->executeUpdate($statement, $parameters);
253
254
        // 3) Delete records on each table in the hierarchy
255 1
        $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...
256
257 1
        foreach (array_reverse($classNames) as $className) {
258 1
            $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform);
259 1
            $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')'
260 1
                . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')';
261
262 1
            $this->conn->executeUpdate($statement);
263
        }
264
265
        // 4) Drop temporary table
266 1
        $statement = $this->platform->getDropTemporaryTableSQL($tempTable);
267
268 1
        $this->conn->executeUpdate($statement);
269
270 1
        return $numDeleted;
271
    }
272
}
273