Failed Conditions
Push — master ( 3a6c46...2fbe69 )
by Luís
36s queued 11s
created

PersistentObject::getObjectManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 1
nc 1
nop 0
crap 1
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 lcfirst;
12
use function substr;
13
14
/**
15
 * PersistentObject base class that implements getter/setter methods for all mapped fields and associations
16
 * by overriding __call.
17
 *
18
 * This class is a forward compatible implementation of the PersistentObject trait.
19
 *
20
 * Limitations:
21
 *
22
 * 1. All persistent objects have to be associated with a single ObjectManager, multiple
23
 *    ObjectManagers are not supported. You can set the ObjectManager with `PersistentObject#setObjectManager()`.
24
 * 2. Setters and getters only work if a ClassMetadata instance was injected into the PersistentObject.
25
 *    This is either done on `postLoad` of an object or by accessing the global object manager.
26
 * 3. There are no hooks for setters/getters. Just implement the method yourself instead of relying on __call().
27
 * 4. Slower than handcoded implementations: An average of 7 method calls per access to a field and 11 for an association.
28
 * 5. Only the inverse side associations get autoset on the owning side as well. Setting objects on the owning side
29
 *    will not set the inverse side associations.
30
 *
31
 * @example
32
 *
33
 *  PersistentObject::setObjectManager($em);
34
 *
35
 *  class Foo extends PersistentObject
36
 *  {
37
 *      private $id;
38
 *  }
39
 *
40
 *  $foo = new Foo();
41
 *  $foo->getId(); // method exists through __call
42
 */
43
abstract class PersistentObject implements ObjectManagerAware
44
{
45
    /** @var ObjectManager|null */
46
    private static $objectManager = null;
47
48
    /** @var ClassMetadata|null */
49
    private $cm = null;
50
51
    /**
52
     * Sets the object manager responsible for all persistent object base classes.
53
     *
54
     * @return void
55
     */
56 18
    public static function setObjectManager(?ObjectManager $objectManager = null)
57
    {
58 18
        self::$objectManager = $objectManager;
59 18
    }
60
61
    /**
62
     * @return ObjectManager|null
63
     */
64 1
    public static function getObjectManager()
65
    {
66 1
        return self::$objectManager;
67
    }
68
69
    /**
70
     * Injects the Doctrine Object Manager.
71
     *
72
     * @return void
73
     *
74
     * @throws RuntimeException
75
     */
76 18
    public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata)
77
    {
78 18
        if ($objectManager !== self::$objectManager) {
79 1
            throw new RuntimeException('Trying to use PersistentObject with different ObjectManager instances. ' .
80 1
                'Was PersistentObject::setObjectManager() called?');
81
        }
82
83 18
        $this->cm = $classMetadata;
84 18
    }
85
86
    /**
87
     * Sets a persistent fields value.
88
     *
89
     * @param string  $field
90
     * @param mixed[] $args
91
     *
92
     * @return void
93
     *
94
     * @throws BadMethodCallException   When no persistent field exists by that name.
95
     * @throws InvalidArgumentException When the wrong target object type is passed to an association.
96
     */
97 7
    private function set($field, $args)
98
    {
99 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

99
        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...
100 1
            $this->$field = $args[0];
101 6
        } elseif ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) {
102 4
            $targetClass = $this->cm->getAssociationTargetClass($field);
103 4
            if (! ($args[0] instanceof $targetClass) && $args[0] !== null) {
104 1
                throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'");
105
            }
106 3
            $this->$field = $args[0];
107 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

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

149
        $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

149
        /** @scrutinizer ignore-call */ 
150
        $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...
150
151 1
        $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? 'add' : 'set') . $mappedByField;
152 1
        $targetObject->$setter($this);
153 1
    }
154
155
    /**
156
     * Adds an object to a collection.
157
     *
158
     * @param string  $field
159
     * @param mixed[] $args
160
     *
161
     * @return void
162
     *
163
     * @throws BadMethodCallException
164
     * @throws InvalidArgumentException
165
     */
166 3
    private function add($field, $args)
167
    {
168 3
        if (! $this->cm->hasAssociation($field) || ! $this->cm->isCollectionValuedAssociation($field)) {
169 1
            throw new BadMethodCallException('There is no method add' . $field . '() on ' . $this->cm->getName());
170
        }
171
172 2
        $targetClass = $this->cm->getAssociationTargetClass($field);
173 2
        if (! ($args[0] instanceof $targetClass)) {
174 1
            throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'");
175
        }
176 1
        if (! ($this->$field instanceof Collection)) {
177 1
            $this->$field = new ArrayCollection($this->$field ?: []);
178
        }
179 1
        $this->$field->add($args[0]);
180 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

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