Completed
Pull Request — 1.3.x (#71)
by Grégoire
06:05
created

PersistentObject::set()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 13
ccs 10
cts 10
cp 1
rs 8.8333
cc 7
nc 4
nop 2
crap 7
1
<?php
2
3
namespace Doctrine\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
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\Persistence\Per...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
$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...
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...
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\Persistence\Per...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