Completed
Pull Request — master (#6055)
by Martin
09:59
created

OneToManyPersister::slice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 3
crap 2
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
    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
        $mapping = $collection->getMapping();
46
47
        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
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
55
56
        return $targetClass->isInheritanceTypeJoined()
57
            ? $this->deleteJoinedEntityCollection($collection)
58
            : $this->deleteEntityCollection($collection);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    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
        return;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function get(PersistentCollection $collection, $index)
76
    {
77
        $mapping = $collection->getMapping();
78
79
        if ( ! isset($mapping['indexBy'])) {
80
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
81
        }
82
83
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
84
85
        return $persister->load(
86
            array(
87
                $mapping['mappedBy'] => $collection->getOwner(),
88
                $mapping['indexBy']  => $index
89
            ),
90
            null,
91
            $mapping,
92
            array(),
93
            null,
94
            1
95
        );
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function count(PersistentCollection $collection)
102
    {
103
        $mapping   = $collection->getMapping();
104
        $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
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
110
111
        return $persister->count($criteria);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function slice(PersistentCollection $collection, $offset, $length = null)
118
    {
119
        $mapping   = $collection->getMapping();
120
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
121
122
        return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length);
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function containsKey(PersistentCollection $collection, $key)
129
    {
130
        $mapping = $collection->getMapping();
131
132
        if ( ! isset($mapping['indexBy'])) {
133
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
134
        }
135
136
        $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
        $criteria = new Criteria();
142
143
        $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
144
        $criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key));
145
146
        return (bool) $persister->count($criteria);
147
    }
148
149
     /**
150
     * {@inheritdoc}
151
     */
152
    public function contains(PersistentCollection $collection, $element)
153
    {
154
        if ( ! $this->isValidEntityState($element)) {
155
            return false;
156
        }
157
158
        $mapping   = $collection->getMapping();
159
        $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
        $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
165
166
        return $persister->exists($element, $criteria);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function removeElement(PersistentCollection $collection, $element)
173
    {
174
        $mapping = $collection->getMapping();
175
176
        if ( ! $mapping['orphanRemoval']) {
177
            // no-op: this is not the owning side, therefore no operations should be applied
178
            return false;
179
        }
180
181
        if ( ! $this->isValidEntityState($element)) {
182
            return false;
183
        }
184
185
        return $this
186
            ->uow
187
            ->getEntityPersister($mapping['targetEntity'])
188
            ->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
    private function deleteEntityCollection(PersistentCollection $collection)
207
    {
208
        $mapping     = $collection->getMapping();
209
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
210
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
211
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
212
        $columns     = [];
213
        $parameters  = [];
214
215
        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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
216
            $columns[]    = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
217
            $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
218
        }
219
220
        $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
221
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
222
223
        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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
248
        $idColumnList      = implode(', ', $idColumnNames);
249
        $columnDefinitions = [];
250
251
        foreach ($idColumnNames as $idColumnName) {
252
            $columnDefinitions[$idColumnName] = array(
253
                'notnull' => true,
254
                'type'    => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)),
0 ignored issues
show
Compatibility introduced by
$rootClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
267
        )->setParameter('owner', $collection->getOwner());
268
269
        $statement  = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $query->getSQL();
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, array($targetClass->name), $targetClass->subClasses);
0 ignored issues
show
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
275
276
        foreach (array_reverse($classNames) as $className) {
277
            $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform);
0 ignored issues
show
Compatibility introduced by
$this->em->getClassMetadata($className) of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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