Passed
Push — add_typed_no_default_reflectio... ( 26533b...1005a7 )
by Benjamin
04:11
created

PersistentObject::completeOwningSide()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
rs 10
ccs 7
cts 7
cp 1
cc 3
nc 3
nop 3
crap 3
1
<?php
2
3
namespace Doctrine\Common\Persistence;
4
5
use BadMethodCallException;
6
use Doctrine\Common\Collections\ArrayCollection;
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\Persistence\Mapping\ClassMetadata;
9
use InvalidArgumentException;
10
use RuntimeException;
11
use function class_exists;
12
use function lcfirst;
13
use function substr;
14
15
/**
16
 * PersistentObject base class that implements getter/setter methods for all mapped fields and associations
17
 * by overriding __call.
18
 *
19
 * This class is a forward compatible implementation of the PersistentObject trait.
20
 *
21
 * Limitations:
22
 *
23
 * 1. All persistent objects have to be associated with a single ObjectManager, multiple
24
 *    ObjectManagers are not supported. You can set the ObjectManager with `PersistentObject#setObjectManager()`.
25
 * 2. Setters and getters only work if a ClassMetadata instance was injected into the PersistentObject.
26
 *    This is either done on `postLoad` of an object or by accessing the global object manager.
27
 * 3. There are no hooks for setters/getters. Just implement the method yourself instead of relying on __call().
28
 * 4. Slower than handcoded implementations: An average of 7 method calls per access to a field and 11 for an association.
29
 * 5. Only the inverse side associations get autoset on the owning side as well. Setting objects on the owning side
30
 *    will not set the inverse side associations.
31
 *
32
 * @deprecated Deprecated `PersistentObject` class in 1.2. Please implement this functionality
33
 *             directly in your application if you want ActiveRecord style functionality.
34
 *
35
 * @example
36
 *
37
 *  PersistentObject::setObjectManager($em);
38
 *
39
 *  class Foo extends PersistentObject
40
 *  {
41
 *      private $id;
42
 *  }
43
 *
44
 *  $foo = new Foo();
45
 *  $foo->getId(); // method exists through __call
46
 */
47
abstract class PersistentObject implements ObjectManagerAware
0 ignored issues
show
Deprecated Code introduced by
The interface Doctrine\Common\Persistence\ObjectManagerAware has been deprecated: 1.3 Use Doctrine\Persistence\ObjectManagerAware ( Ignorable by Annotation )

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

47
abstract class PersistentObject implements /** @scrutinizer ignore-deprecated */ ObjectManagerAware

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
48
{
49
    /** @var ObjectManager|null */
50
    private static $objectManager = null;
51
52
    /** @var ClassMetadata|null */
53
    private $cm = null;
54
55
    /**
56
     * Sets the object manager responsible for all persistent object base classes.
57
     *
58
     * @return void
59
     */
60 18
    public static function setObjectManager(?ObjectManager $objectManager = null)
61
    {
62 18
        self::$objectManager = $objectManager;
63 18
    }
64
65
    /**
66
     * @return ObjectManager|null
67
     */
68 1
    public static function getObjectManager()
69
    {
70 1
        return self::$objectManager;
71
    }
72
73
    /**
74
     * Injects the Doctrine Object Manager.
75
     *
76
     * @return void
77
     *
78
     * @throws RuntimeException
79
     */
80 18
    public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata)
81
    {
82 18
        if ($objectManager !== self::$objectManager) {
83 1
            throw new RuntimeException('Trying to use PersistentObject with different ObjectManager instances. ' .
84 1
                'Was PersistentObject::setObjectManager() called?');
85
        }
86
87 18
        $this->cm = $classMetadata;
88 18
    }
89
90
    /**
91
     * Sets a persistent fields value.
92
     *
93
     * @param string  $field
94
     * @param mixed[] $args
95
     *
96
     * @return void
97
     *
98
     * @throws BadMethodCallException   When no persistent field exists by that name.
99
     * @throws InvalidArgumentException When the wrong target object type is passed to an association.
100
     */
101 7
    private function set($field, $args)
102
    {
103 7
        if ($this->cm->hasField($field) && ! $this->cm->isIdentifier($field)) {
0 ignored issues
show
Bug introduced by
The method hasField() 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

103
        if ($this->cm->/** @scrutinizer ignore-call */ hasField($field) && ! $this->cm->isIdentifier($field)) {

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...
104 1
            $this->$field = $args[0];
105 6
        } elseif ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) {
106 4
            $targetClass = $this->cm->getAssociationTargetClass($field);
107 4
            if (! ($args[0] instanceof $targetClass) && $args[0] !== null) {
108 1
                throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'");
109
            }
110 3
            $this->$field = $args[0];
111 3
            $this->completeOwningSide($field, $targetClass, $args[0]);
0 ignored issues
show
Bug introduced by
$targetClass of type string is incompatible with the type Doctrine\Persistence\Mapping\ClassMetadata expected by parameter $targetClass of Doctrine\Common\Persiste...t::completeOwningSide(). ( Ignorable by Annotation )

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

111
            $this->completeOwningSide($field, /** @scrutinizer ignore-type */ $targetClass, $args[0]);
Loading history...
112
        } else {
113 2
            throw new BadMethodCallException("no field with name '" . $field . "' exists on '" . $this->cm->getName() . "'");
114
        }
115 4
    }
116
117
    /**
118
     * Gets a persistent field value.
119
     *
120
     * @param string $field
121
     *
122
     * @return mixed
123
     *
124
     * @throws BadMethodCallException When no persistent field exists by that name.
125
     */
126 8
    private function get($field)
127
    {
128 8
        if ($this->cm->hasField($field) || $this->cm->hasAssociation($field)) {
129 7
            return $this->$field;
130
        }
131
132 1
        throw new BadMethodCallException("no field with name '" . $field . "' exists on '" . $this->cm->getName() . "'");
133
    }
134
135
    /**
136
     * If this is an inverse side association, completes the owning side.
137
     *
138
     * @param string        $field
139
     * @param ClassMetadata $targetClass
140
     * @param object        $targetObject
141
     *
142
     * @return void
143
     */
144 3
    private function completeOwningSide($field, $targetClass, $targetObject)
145
    {
146
        // add this object on the owning side as well, for obvious infinite recursion
147
        // reasons this is only done when called on the inverse side.
148 3
        if (! $this->cm->isAssociationInverseSide($field)) {
149 3
            return;
150
        }
151
152 1
        $mappedByField  = $this->cm->getAssociationMappedByTargetField($field);
153 1
        $targetMetadata = self::$objectManager->getClassMetadata($targetClass);
0 ignored issues
show
Bug introduced by
The method getClassMetadata() 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

153
        /** @scrutinizer ignore-call */ 
154
        $targetMetadata = self::$objectManager->getClassMetadata($targetClass);

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...
Bug introduced by
$targetClass of type Doctrine\Persistence\Mapping\ClassMetadata is incompatible with the type string expected by parameter $className of Doctrine\Persistence\Obj...ger::getClassMetadata(). ( Ignorable by Annotation )

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

153
        $targetMetadata = self::$objectManager->getClassMetadata(/** @scrutinizer ignore-type */ $targetClass);
Loading history...
154
155 1
        $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? 'add' : 'set') . $mappedByField;
156 1
        $targetObject->$setter($this);
157 1
    }
158
159
    /**
160
     * Adds an object to a collection.
161
     *
162
     * @param string  $field
163
     * @param mixed[] $args
164
     *
165
     * @return void
166
     *
167
     * @throws BadMethodCallException
168
     * @throws InvalidArgumentException
169
     */
170 3
    private function add($field, $args)
171
    {
172 3
        if (! $this->cm->hasAssociation($field) || ! $this->cm->isCollectionValuedAssociation($field)) {
173 1
            throw new BadMethodCallException('There is no method add' . $field . '() on ' . $this->cm->getName());
174
        }
175
176 2
        $targetClass = $this->cm->getAssociationTargetClass($field);
177 2
        if (! ($args[0] instanceof $targetClass)) {
178 1
            throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'");
179
        }
180 1
        if (! ($this->$field instanceof Collection)) {
181 1
            $this->$field = new ArrayCollection($this->$field ?: []);
182
        }
183 1
        $this->$field->add($args[0]);
184 1
        $this->completeOwningSide($field, $targetClass, $args[0]);
0 ignored issues
show
Bug introduced by
$targetClass of type string is incompatible with the type Doctrine\Persistence\Mapping\ClassMetadata expected by parameter $targetClass of Doctrine\Common\Persiste...t::completeOwningSide(). ( Ignorable by Annotation )

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

184
        $this->completeOwningSide($field, /** @scrutinizer ignore-type */ $targetClass, $args[0]);
Loading history...
185 1
    }
186
187
    /**
188
     * Initializes Doctrine Metadata for this class.
189
     *
190
     * @return void
191
     *
192
     * @throws RuntimeException
193
     */
194 16
    private function initializeDoctrine()
195
    {
196 16
        if ($this->cm !== null) {
197 14
            return;
198
        }
199
200 3
        if (! self::$objectManager) {
201 1
            throw new RuntimeException('No runtime object manager set. Call PersistentObject#setObjectManager().');
202
        }
203
204 2
        $this->cm = self::$objectManager->getClassMetadata(static::class);
205 2
    }
206
207
    /**
208
     * Magic methods.
209
     *
210
     * @param string  $method
211
     * @param mixed[] $args
212
     *
213
     * @return mixed
214
     *
215
     * @throws BadMethodCallException
216
     */
217 16
    public function __call($method, $args)
218
    {
219 16
        $this->initializeDoctrine();
220
221 15
        $command = substr($method, 0, 3);
222 15
        $field   = lcfirst(substr($method, 3));
223 15
        if ($command === 'set') {
224 7
            $this->set($field, $args);
225 12
        } elseif ($command === 'get') {
226 8
            return $this->get($field);
227 5
        } elseif ($command === 'add') {
228 3
            $this->add($field, $args);
229
        } else {
230 2
            throw new BadMethodCallException('There is no method ' . $method . ' on ' . $this->cm->getName());
231
        }
232 4
    }
233
}
234
235
class_exists(\Doctrine\Common\Persistence\PersistentObject::class);
236