Majkl578 /
doctrine-persistence
| 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
|
|||||||||
| 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
$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
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
$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
Loading history...
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
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
$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
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 |
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.