bankiru /
doctrine-api-client
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Bankiru\Api\Doctrine; |
||
| 4 | |||
| 5 | use Bankiru\Api\Doctrine\Cache\ApiEntityCache; |
||
| 6 | use Bankiru\Api\Doctrine\Cache\EntityCacheAwareInterface; |
||
| 7 | use Bankiru\Api\Doctrine\Cache\LoggingCache; |
||
| 8 | use Bankiru\Api\Doctrine\Cache\VoidEntityCache; |
||
| 9 | use Bankiru\Api\Doctrine\Exception\MappingException; |
||
| 10 | use Bankiru\Api\Doctrine\Hydration\EntityHydrator; |
||
| 11 | use Bankiru\Api\Doctrine\Mapping\ApiMetadata; |
||
| 12 | use Bankiru\Api\Doctrine\Mapping\EntityMetadata; |
||
| 13 | use Bankiru\Api\Doctrine\Persister\ApiPersister; |
||
| 14 | use Bankiru\Api\Doctrine\Persister\CollectionPersister; |
||
| 15 | use Bankiru\Api\Doctrine\Persister\EntityPersister; |
||
| 16 | use Bankiru\Api\Doctrine\Proxy\ApiCollection; |
||
| 17 | use Bankiru\Api\Doctrine\Rpc\CrudsApiInterface; |
||
| 18 | use Bankiru\Api\Doctrine\Utility\IdentifierFlattener; |
||
| 19 | use Doctrine\Common\Collections\ArrayCollection; |
||
| 20 | use Doctrine\Common\Collections\Collection; |
||
| 21 | use Doctrine\Common\NotifyPropertyChanged; |
||
| 22 | use Doctrine\Common\Persistence\ObjectManagerAware; |
||
| 23 | use Doctrine\Common\PropertyChangedListener; |
||
| 24 | use Doctrine\Common\Proxy\Proxy; |
||
| 25 | |||
| 26 | class UnitOfWork implements PropertyChangedListener |
||
| 27 | { |
||
| 28 | /** |
||
| 29 | * An entity is in MANAGED state when its persistence is managed by an EntityManager. |
||
| 30 | */ |
||
| 31 | const STATE_MANAGED = 1; |
||
| 32 | /** |
||
| 33 | * An entity is new if it has just been instantiated (i.e. using the "new" operator) |
||
| 34 | * and is not (yet) managed by an EntityManager. |
||
| 35 | */ |
||
| 36 | const STATE_NEW = 2; |
||
| 37 | /** |
||
| 38 | * A detached entity is an instance with persistent state and identity that is not |
||
| 39 | * (or no longer) associated with an EntityManager (and a UnitOfWork). |
||
| 40 | */ |
||
| 41 | const STATE_DETACHED = 3; |
||
| 42 | /** |
||
| 43 | * A removed entity instance is an instance with a persistent identity, |
||
| 44 | * associated with an EntityManager, whose persistent state will be deleted |
||
| 45 | * on commit. |
||
| 46 | */ |
||
| 47 | const STATE_REMOVED = 4; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * The (cached) states of any known entities. |
||
| 51 | * Keys are object ids (spl_object_hash). |
||
| 52 | * |
||
| 53 | * @var array |
||
| 54 | */ |
||
| 55 | private $entityStates = []; |
||
| 56 | |||
| 57 | /** @var EntityManager */ |
||
| 58 | private $manager; |
||
| 59 | /** @var EntityPersister[] */ |
||
| 60 | private $persisters = []; |
||
| 61 | /** @var CollectionPersister[] */ |
||
| 62 | private $collectionPersisters = []; |
||
| 63 | /** @var array */ |
||
| 64 | private $entityIdentifiers = []; |
||
| 65 | /** @var object[][] */ |
||
| 66 | private $identityMap = []; |
||
| 67 | /** @var IdentifierFlattener */ |
||
| 68 | private $identifierFlattener; |
||
| 69 | /** @var array */ |
||
| 70 | private $originalEntityData = []; |
||
| 71 | /** @var array */ |
||
| 72 | private $entityDeletions = []; |
||
| 73 | /** @var array */ |
||
| 74 | private $entityChangeSets = []; |
||
| 75 | /** @var array */ |
||
| 76 | private $entityInsertions = []; |
||
| 77 | /** @var array */ |
||
| 78 | private $entityUpdates = []; |
||
| 79 | /** @var array */ |
||
| 80 | private $readOnlyObjects = []; |
||
| 81 | /** @var array */ |
||
| 82 | private $scheduledForSynchronization = []; |
||
| 83 | /** @var array */ |
||
| 84 | private $orphanRemovals = []; |
||
| 85 | /** @var ApiCollection[] */ |
||
| 86 | private $collectionDeletions = []; |
||
| 87 | /** @var array */ |
||
| 88 | private $extraUpdates = []; |
||
| 89 | /** @var ApiCollection[] */ |
||
| 90 | private $collectionUpdates = []; |
||
| 91 | /** @var ApiCollection[] */ |
||
| 92 | private $visitedCollections = []; |
||
| 93 | |||
| 94 | /** |
||
| 95 | * UnitOfWork constructor. |
||
| 96 | * |
||
| 97 | * @param EntityManager $manager |
||
| 98 | */ |
||
| 99 | public function __construct(EntityManager $manager) |
||
| 100 | { |
||
| 101 | $this->manager = $manager; |
||
| 102 | $this->identifierFlattener = new IdentifierFlattener($this->manager); |
||
| 103 | } |
||
| 104 | |||
| 105 | /** |
||
| 106 | * @param $className |
||
| 107 | * |
||
| 108 | * @return EntityPersister |
||
| 109 | */ |
||
| 110 | public function getEntityPersister($className) |
||
| 111 | { |
||
| 112 | if (!array_key_exists($className, $this->persisters)) { |
||
| 113 | /** @var ApiMetadata $classMetadata */ |
||
| 114 | $classMetadata = $this->manager->getClassMetadata($className); |
||
| 115 | |||
| 116 | $api = $this->createApi($classMetadata); |
||
| 117 | |||
| 118 | if ($api instanceof EntityCacheAwareInterface) { |
||
| 119 | $api->setEntityCache($this->createEntityCache($classMetadata)); |
||
| 120 | } |
||
| 121 | |||
| 122 | $this->persisters[$className] = new ApiPersister($this->manager, $api); |
||
| 123 | } |
||
| 124 | |||
| 125 | return $this->persisters[$className]; |
||
| 126 | } |
||
| 127 | |||
| 128 | /** |
||
| 129 | * Checks whether an entity is registered in the identity map of this UnitOfWork. |
||
| 130 | * |
||
| 131 | * @param object $entity |
||
| 132 | * |
||
| 133 | * @return boolean |
||
| 134 | */ |
||
| 135 | public function isInIdentityMap($entity) |
||
| 136 | { |
||
| 137 | $oid = spl_object_hash($entity); |
||
| 138 | |||
| 139 | if (!isset($this->entityIdentifiers[$oid])) { |
||
| 140 | return false; |
||
| 141 | } |
||
| 142 | |||
| 143 | /** @var EntityMetadata $classMetadata */ |
||
| 144 | $classMetadata = $this->manager->getClassMetadata(get_class($entity)); |
||
| 145 | $idHash = implode(' ', $this->entityIdentifiers[$oid]); |
||
| 146 | |||
| 147 | if ($idHash === '') { |
||
| 148 | return false; |
||
| 149 | } |
||
| 150 | |||
| 151 | return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); |
||
| 152 | } |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Gets the identifier of an entity. |
||
| 156 | * The returned value is always an array of identifier values. If the entity |
||
| 157 | * has a composite identifier then the identifier values are in the same |
||
| 158 | * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). |
||
| 159 | * |
||
| 160 | * @param object $entity |
||
| 161 | * |
||
| 162 | * @return array The identifier values. |
||
| 163 | */ |
||
| 164 | public function getEntityIdentifier($entity) |
||
| 165 | { |
||
| 166 | return $this->entityIdentifiers[spl_object_hash($entity)]; |
||
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * @param $className |
||
| 171 | * @param \stdClass $data |
||
| 172 | * |
||
| 173 | * @return ObjectManagerAware|object |
||
| 174 | * @throws MappingException |
||
| 175 | */ |
||
| 176 | public function getOrCreateEntity($className, \stdClass $data) |
||
| 177 | { |
||
| 178 | /** @var EntityMetadata $class */ |
||
| 179 | $class = $this->manager->getClassMetadata($className); |
||
| 180 | $hydrator = new EntityHydrator($this->manager, $class); |
||
| 181 | |||
| 182 | $tmpEntity = $hydrator->hydarate($data); |
||
| 183 | |||
| 184 | $id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($tmpEntity)); |
||
| 185 | $idHash = implode(' ', $id); |
||
| 186 | |||
| 187 | $overrideLocalValues = false; |
||
| 188 | if (isset($this->identityMap[$class->rootEntityName][$idHash])) { |
||
| 189 | $entity = $this->identityMap[$class->rootEntityName][$idHash]; |
||
| 190 | $oid = spl_object_hash($entity); |
||
| 191 | |||
| 192 | if ($entity instanceof Proxy && !$entity->__isInitialized()) { |
||
| 193 | $entity->__setInitialized(true); |
||
| 194 | |||
| 195 | $overrideLocalValues = true; |
||
| 196 | $this->originalEntityData[$oid] = $data; |
||
| 197 | |||
| 198 | if ($entity instanceof NotifyPropertyChanged) { |
||
| 199 | $entity->addPropertyChangedListener($this); |
||
| 200 | } |
||
| 201 | } |
||
| 202 | } else { |
||
| 203 | $entity = $this->newInstance($class); |
||
| 204 | $oid = spl_object_hash($entity); |
||
| 205 | $this->entityIdentifiers[$oid] = $id; |
||
| 206 | $this->entityStates[$oid] = self::STATE_MANAGED; |
||
| 207 | $this->originalEntityData[$oid] = $data; |
||
| 208 | $this->identityMap[$class->rootEntityName][$idHash] = $entity; |
||
| 209 | if ($entity instanceof NotifyPropertyChanged) { |
||
| 210 | $entity->addPropertyChangedListener($this); |
||
| 211 | } |
||
| 212 | $overrideLocalValues = true; |
||
| 213 | } |
||
| 214 | |||
| 215 | if (!$overrideLocalValues) { |
||
| 216 | return $entity; |
||
| 217 | } |
||
| 218 | |||
| 219 | $entity = $hydrator->hydarate($data, $entity); |
||
| 220 | |||
| 221 | return $entity; |
||
| 222 | } |
||
| 223 | |||
| 224 | /** |
||
| 225 | * INTERNAL: |
||
| 226 | * Registers an entity as managed. |
||
| 227 | * |
||
| 228 | * @param object $entity The entity. |
||
| 229 | * @param array $id The identifier values. |
||
| 230 | * @param \stdClass|null $data The original entity data. |
||
| 231 | * |
||
| 232 | * @return void |
||
| 233 | */ |
||
| 234 | public function registerManaged($entity, array $id, \stdClass $data = null) |
||
| 235 | { |
||
| 236 | $oid = spl_object_hash($entity); |
||
| 237 | |||
| 238 | $this->entityIdentifiers[$oid] = $id; |
||
| 239 | $this->entityStates[$oid] = self::STATE_MANAGED; |
||
| 240 | $this->originalEntityData[$oid] = $data; |
||
| 241 | |||
| 242 | $this->addToIdentityMap($entity); |
||
| 243 | |||
| 244 | if ($entity instanceof NotifyPropertyChanged && (!$entity instanceof Proxy || $entity->__isInitialized())) { |
||
| 245 | $entity->addPropertyChangedListener($this); |
||
| 246 | } |
||
| 247 | } |
||
| 248 | |||
| 249 | /** |
||
| 250 | * INTERNAL: |
||
| 251 | * Registers an entity in the identity map. |
||
| 252 | * Note that entities in a hierarchy are registered with the class name of |
||
| 253 | * the root entity. |
||
| 254 | * |
||
| 255 | * @ignore |
||
| 256 | * |
||
| 257 | * @param object $entity The entity to register. |
||
| 258 | * |
||
| 259 | * @return boolean TRUE if the registration was successful, FALSE if the identity of |
||
| 260 | * the entity in question is already managed. |
||
| 261 | * |
||
| 262 | */ |
||
| 263 | public function addToIdentityMap($entity) |
||
| 264 | { |
||
| 265 | /** @var EntityMetadata $classMetadata */ |
||
| 266 | $classMetadata = $this->manager->getClassMetadata(get_class($entity)); |
||
| 267 | $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); |
||
| 268 | |||
| 269 | if ($idHash === '') { |
||
| 270 | throw new \InvalidArgumentException('Entitty does not have valid identifiers to be stored at identity map'); |
||
| 271 | } |
||
| 272 | |||
| 273 | $className = $classMetadata->rootEntityName; |
||
| 274 | |||
| 275 | if (isset($this->identityMap[$className][$idHash])) { |
||
| 276 | return false; |
||
| 277 | } |
||
| 278 | |||
| 279 | $this->identityMap[$className][$idHash] = $entity; |
||
| 280 | |||
| 281 | return true; |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * Gets the identity map of the UnitOfWork. |
||
| 286 | * |
||
| 287 | * @return array |
||
| 288 | */ |
||
| 289 | public function getIdentityMap() |
||
| 290 | { |
||
| 291 | return $this->identityMap; |
||
| 292 | } |
||
| 293 | |||
| 294 | /** |
||
| 295 | * Gets the original data of an entity. The original data is the data that was |
||
| 296 | * present at the time the entity was reconstituted from the database. |
||
| 297 | * |
||
| 298 | * @param object $entity |
||
| 299 | * |
||
| 300 | * @return array |
||
| 301 | */ |
||
| 302 | public function getOriginalEntityData($entity) |
||
| 303 | { |
||
| 304 | $oid = spl_object_hash($entity); |
||
| 305 | |||
| 306 | if (isset($this->originalEntityData[$oid])) { |
||
| 307 | return $this->originalEntityData[$oid]; |
||
| 308 | } |
||
| 309 | |||
| 310 | return []; |
||
| 311 | } |
||
| 312 | |||
| 313 | /** |
||
| 314 | * INTERNAL: |
||
| 315 | * Checks whether an identifier hash exists in the identity map. |
||
| 316 | * |
||
| 317 | * @ignore |
||
| 318 | * |
||
| 319 | * @param string $idHash |
||
| 320 | * @param string $rootClassName |
||
| 321 | * |
||
| 322 | * @return boolean |
||
| 323 | */ |
||
| 324 | public function containsIdHash($idHash, $rootClassName) |
||
| 325 | { |
||
| 326 | return isset($this->identityMap[$rootClassName][$idHash]); |
||
| 327 | } |
||
| 328 | |||
| 329 | /** |
||
| 330 | * INTERNAL: |
||
| 331 | * Gets an entity in the identity map by its identifier hash. |
||
| 332 | * |
||
| 333 | * @ignore |
||
| 334 | * |
||
| 335 | * @param string $idHash |
||
| 336 | * @param string $rootClassName |
||
| 337 | * |
||
| 338 | * @return object |
||
| 339 | */ |
||
| 340 | public function getByIdHash($idHash, $rootClassName) |
||
| 341 | { |
||
| 342 | return $this->identityMap[$rootClassName][$idHash]; |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * INTERNAL: |
||
| 347 | * Tries to get an entity by its identifier hash. If no entity is found for |
||
| 348 | * the given hash, FALSE is returned. |
||
| 349 | * |
||
| 350 | * @ignore |
||
| 351 | * |
||
| 352 | * @param mixed $idHash (must be possible to cast it to string) |
||
| 353 | * @param string $rootClassName |
||
| 354 | * |
||
| 355 | * @return object|bool The found entity or FALSE. |
||
| 356 | */ |
||
| 357 | public function tryGetByIdHash($idHash, $rootClassName) |
||
| 358 | { |
||
| 359 | $stringIdHash = (string)$idHash; |
||
| 360 | |||
| 361 | if (isset($this->identityMap[$rootClassName][$stringIdHash])) { |
||
| 362 | return $this->identityMap[$rootClassName][$stringIdHash]; |
||
| 363 | } |
||
| 364 | |||
| 365 | return false; |
||
| 366 | } |
||
| 367 | |||
| 368 | /** |
||
| 369 | * Gets the state of an entity with regard to the current unit of work. |
||
| 370 | * |
||
| 371 | * @param object $entity |
||
| 372 | * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). |
||
| 373 | * This parameter can be set to improve performance of entity state detection |
||
| 374 | * by potentially avoiding a database lookup if the distinction between NEW and DETACHED |
||
| 375 | * is either known or does not matter for the caller of the method. |
||
| 376 | * |
||
| 377 | * @return int The entity state. |
||
| 378 | */ |
||
| 379 | public function getEntityState($entity, $assume = null) |
||
| 380 | { |
||
| 381 | $oid = spl_object_hash($entity); |
||
| 382 | if (isset($this->entityStates[$oid])) { |
||
| 383 | return $this->entityStates[$oid]; |
||
| 384 | } |
||
| 385 | if ($assume !== null) { |
||
| 386 | return $assume; |
||
| 387 | } |
||
| 388 | // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. |
||
| 389 | // Note that you can not remember the NEW or DETACHED state in _entityStates since |
||
| 390 | // the UoW does not hold references to such objects and the object hash can be reused. |
||
| 391 | // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. |
||
| 392 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 393 | $id = $class->getIdentifierValues($entity); |
||
| 394 | if (!$id) { |
||
| 395 | return self::STATE_NEW; |
||
| 396 | } |
||
| 397 | |||
| 398 | return self::STATE_DETACHED; |
||
| 399 | } |
||
| 400 | |||
| 401 | /** |
||
| 402 | * Tries to find an entity with the given identifier in the identity map of |
||
| 403 | * this UnitOfWork. |
||
| 404 | * |
||
| 405 | * @param mixed $id The entity identifier to look for. |
||
| 406 | * @param string $rootClassName The name of the root class of the mapped entity hierarchy. |
||
| 407 | * |
||
| 408 | * @return object|bool Returns the entity with the specified identifier if it exists in |
||
| 409 | * this UnitOfWork, FALSE otherwise. |
||
| 410 | */ |
||
| 411 | public function tryGetById($id, $rootClassName) |
||
| 412 | { |
||
| 413 | /** @var EntityMetadata $metadata */ |
||
| 414 | $metadata = $this->manager->getClassMetadata($rootClassName); |
||
| 415 | $idHash = implode(' ', (array)$this->identifierFlattener->flattenIdentifier($metadata, $id)); |
||
| 416 | |||
| 417 | if (isset($this->identityMap[$rootClassName][$idHash])) { |
||
| 418 | return $this->identityMap[$rootClassName][$idHash]; |
||
| 419 | } |
||
| 420 | |||
| 421 | return false; |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Notifies this UnitOfWork of a property change in an entity. |
||
| 426 | * |
||
| 427 | * @param object $entity The entity that owns the property. |
||
| 428 | * @param string $propertyName The name of the property that changed. |
||
| 429 | * @param mixed $oldValue The old value of the property. |
||
| 430 | * @param mixed $newValue The new value of the property. |
||
| 431 | * |
||
| 432 | * @return void |
||
| 433 | */ |
||
| 434 | public function propertyChanged($entity, $propertyName, $oldValue, $newValue) |
||
| 435 | { |
||
| 436 | $oid = spl_object_hash($entity); |
||
| 437 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 438 | $isAssocField = $class->hasAssociation($propertyName); |
||
| 439 | if (!$isAssocField && !$class->hasField($propertyName)) { |
||
| 440 | return; // ignore non-persistent fields |
||
| 441 | } |
||
| 442 | // Update changeset and mark entity for synchronization |
||
| 443 | $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue]; |
||
| 444 | if (!isset($this->scheduledForSynchronization[$class->getRootEntityName()][$oid])) { |
||
| 445 | $this->scheduleForDirtyCheck($entity); |
||
| 446 | } |
||
| 447 | } |
||
| 448 | |||
| 449 | /** |
||
| 450 | * Persists an entity as part of the current unit of work. |
||
| 451 | * |
||
| 452 | * @param object $entity The entity to persist. |
||
| 453 | * |
||
| 454 | * @return void |
||
| 455 | */ |
||
| 456 | public function persist($entity) |
||
| 457 | { |
||
| 458 | $visited = []; |
||
| 459 | $this->doPersist($entity, $visited); |
||
| 460 | } |
||
| 461 | |||
| 462 | /** |
||
| 463 | * @param ApiMetadata $class |
||
| 464 | * @param $entity |
||
| 465 | * |
||
| 466 | * @throws \InvalidArgumentException |
||
| 467 | * @throws \RuntimeException |
||
| 468 | */ |
||
| 469 | public function recomputeSingleEntityChangeSet(ApiMetadata $class, $entity) |
||
| 470 | { |
||
| 471 | $oid = spl_object_hash($entity); |
||
| 472 | if (!isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) { |
||
| 473 | throw new \InvalidArgumentException('Entity is not managed'); |
||
| 474 | } |
||
| 475 | |||
| 476 | $actualData = []; |
||
| 477 | foreach ($class->getReflectionProperties() as $name => $refProp) { |
||
| 478 | if (!$class->isIdentifier($name) && !$class->isCollectionValuedAssociation($name)) { |
||
| 479 | $actualData[$name] = $refProp->getValue($entity); |
||
| 480 | } |
||
| 481 | } |
||
| 482 | if (!isset($this->originalEntityData[$oid])) { |
||
| 483 | throw new \RuntimeException( |
||
| 484 | 'Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.' |
||
| 485 | ); |
||
| 486 | } |
||
| 487 | $originalData = $this->originalEntityData[$oid]; |
||
| 488 | $changeSet = []; |
||
| 489 | foreach ($actualData as $propName => $actualValue) { |
||
| 490 | $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; |
||
| 491 | if ($orgValue !== $actualValue) { |
||
| 492 | $changeSet[$propName] = [$orgValue, $actualValue]; |
||
| 493 | } |
||
| 494 | } |
||
| 495 | if ($changeSet) { |
||
|
0 ignored issues
–
show
|
|||
| 496 | if (isset($this->entityChangeSets[$oid])) { |
||
| 497 | $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); |
||
| 498 | } else { |
||
| 499 | if (!isset($this->entityInsertions[$oid])) { |
||
| 500 | $this->entityChangeSets[$oid] = $changeSet; |
||
| 501 | $this->entityUpdates[$oid] = $entity; |
||
| 502 | } |
||
| 503 | } |
||
| 504 | $this->originalEntityData[$oid] = $actualData; |
||
| 505 | } |
||
| 506 | } |
||
| 507 | |||
| 508 | /** |
||
| 509 | * Schedules an entity for insertion into the database. |
||
| 510 | * If the entity already has an identifier, it will be added to the identity map. |
||
| 511 | * |
||
| 512 | * @param object $entity The entity to schedule for insertion. |
||
| 513 | * |
||
| 514 | * @return void |
||
| 515 | * |
||
| 516 | * @throws \InvalidArgumentException |
||
| 517 | */ |
||
| 518 | public function scheduleForInsert($entity) |
||
| 519 | { |
||
| 520 | $oid = spl_object_hash($entity); |
||
| 521 | if (isset($this->entityUpdates[$oid])) { |
||
| 522 | throw new \InvalidArgumentException('Dirty entity cannot be scheduled for insertion'); |
||
| 523 | } |
||
| 524 | if (isset($this->entityDeletions[$oid])) { |
||
| 525 | throw new \InvalidArgumentException('Removed entity scheduled for insertion'); |
||
| 526 | } |
||
| 527 | if (isset($this->originalEntityData[$oid]) && !isset($this->entityInsertions[$oid])) { |
||
| 528 | throw new \InvalidArgumentException('Managed entity scheduled for insertion'); |
||
| 529 | } |
||
| 530 | if (isset($this->entityInsertions[$oid])) { |
||
| 531 | throw new \InvalidArgumentException('Entity scheduled for insertion twice'); |
||
| 532 | } |
||
| 533 | $this->entityInsertions[$oid] = $entity; |
||
| 534 | if (isset($this->entityIdentifiers[$oid])) { |
||
| 535 | $this->addToIdentityMap($entity); |
||
| 536 | } |
||
| 537 | if ($entity instanceof NotifyPropertyChanged) { |
||
| 538 | $entity->addPropertyChangedListener($this); |
||
| 539 | } |
||
| 540 | } |
||
| 541 | |||
| 542 | /** |
||
| 543 | * Checks whether an entity is scheduled for insertion. |
||
| 544 | * |
||
| 545 | * @param object $entity |
||
| 546 | * |
||
| 547 | * @return boolean |
||
| 548 | */ |
||
| 549 | public function isScheduledForInsert($entity) |
||
| 550 | { |
||
| 551 | return isset($this->entityInsertions[spl_object_hash($entity)]); |
||
| 552 | } |
||
| 553 | |||
| 554 | /** |
||
| 555 | * Schedules an entity for being updated. |
||
| 556 | * |
||
| 557 | * @param object $entity The entity to schedule for being updated. |
||
| 558 | * |
||
| 559 | * @return void |
||
| 560 | * |
||
| 561 | * @throws \InvalidArgumentException |
||
| 562 | */ |
||
| 563 | public function scheduleForUpdate($entity) |
||
| 564 | { |
||
| 565 | $oid = spl_object_hash($entity); |
||
| 566 | if (!isset($this->entityIdentifiers[$oid])) { |
||
| 567 | throw new \InvalidArgumentException('Entity has no identity'); |
||
| 568 | } |
||
| 569 | if (isset($this->entityDeletions[$oid])) { |
||
| 570 | throw new \InvalidArgumentException('Entity is removed'); |
||
| 571 | } |
||
| 572 | if (!isset($this->entityUpdates[$oid]) && !isset($this->entityInsertions[$oid])) { |
||
| 573 | $this->entityUpdates[$oid] = $entity; |
||
| 574 | } |
||
| 575 | } |
||
| 576 | |||
| 577 | /** |
||
| 578 | * Checks whether an entity is registered as dirty in the unit of work. |
||
| 579 | * Note: Is not very useful currently as dirty entities are only registered |
||
| 580 | * at commit time. |
||
| 581 | * |
||
| 582 | * @param object $entity |
||
| 583 | * |
||
| 584 | * @return boolean |
||
| 585 | */ |
||
| 586 | public function isScheduledForUpdate($entity) |
||
| 587 | { |
||
| 588 | return isset($this->entityUpdates[spl_object_hash($entity)]); |
||
| 589 | } |
||
| 590 | |||
| 591 | /** |
||
| 592 | * Checks whether an entity is registered to be checked in the unit of work. |
||
| 593 | * |
||
| 594 | * @param object $entity |
||
| 595 | * |
||
| 596 | * @return boolean |
||
| 597 | */ |
||
| 598 | public function isScheduledForDirtyCheck($entity) |
||
| 599 | { |
||
| 600 | $rootEntityName = $this->manager->getClassMetadata(get_class($entity))->getRootEntityName(); |
||
| 601 | |||
| 602 | return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]); |
||
| 603 | } |
||
| 604 | |||
| 605 | /** |
||
| 606 | * INTERNAL: |
||
| 607 | * Schedules an entity for deletion. |
||
| 608 | * |
||
| 609 | * @param object $entity |
||
| 610 | * |
||
| 611 | * @return void |
||
| 612 | */ |
||
| 613 | public function scheduleForDelete($entity) |
||
| 614 | { |
||
| 615 | $oid = spl_object_hash($entity); |
||
| 616 | if (isset($this->entityInsertions[$oid])) { |
||
| 617 | if ($this->isInIdentityMap($entity)) { |
||
| 618 | $this->removeFromIdentityMap($entity); |
||
| 619 | } |
||
| 620 | unset($this->entityInsertions[$oid], $this->entityStates[$oid]); |
||
| 621 | |||
| 622 | return; // entity has not been persisted yet, so nothing more to do. |
||
| 623 | } |
||
| 624 | if (!$this->isInIdentityMap($entity)) { |
||
| 625 | return; |
||
| 626 | } |
||
| 627 | $this->removeFromIdentityMap($entity); |
||
| 628 | unset($this->entityUpdates[$oid]); |
||
| 629 | if (!isset($this->entityDeletions[$oid])) { |
||
| 630 | $this->entityDeletions[$oid] = $entity; |
||
| 631 | $this->entityStates[$oid] = self::STATE_REMOVED; |
||
| 632 | } |
||
| 633 | } |
||
| 634 | |||
| 635 | /** |
||
| 636 | * Checks whether an entity is registered as removed/deleted with the unit |
||
| 637 | * of work. |
||
| 638 | * |
||
| 639 | * @param object $entity |
||
| 640 | * |
||
| 641 | * @return boolean |
||
| 642 | */ |
||
| 643 | public function isScheduledForDelete($entity) |
||
| 644 | { |
||
| 645 | return isset($this->entityDeletions[spl_object_hash($entity)]); |
||
| 646 | } |
||
| 647 | |||
| 648 | /** |
||
| 649 | * Checks whether an entity is scheduled for insertion, update or deletion. |
||
| 650 | * |
||
| 651 | * @param object $entity |
||
| 652 | * |
||
| 653 | * @return boolean |
||
| 654 | */ |
||
| 655 | public function isEntityScheduled($entity) |
||
| 656 | { |
||
| 657 | $oid = spl_object_hash($entity); |
||
| 658 | |||
| 659 | return isset($this->entityInsertions[$oid]) |
||
| 660 | || isset($this->entityUpdates[$oid]) |
||
| 661 | || isset($this->entityDeletions[$oid]); |
||
| 662 | } |
||
| 663 | |||
| 664 | /** |
||
| 665 | * INTERNAL: |
||
| 666 | * Removes an entity from the identity map. This effectively detaches the |
||
| 667 | * entity from the persistence management of Doctrine. |
||
| 668 | * |
||
| 669 | * @ignore |
||
| 670 | * |
||
| 671 | * @param object $entity |
||
| 672 | * |
||
| 673 | * @return boolean |
||
| 674 | * |
||
| 675 | * @throws \InvalidArgumentException |
||
| 676 | */ |
||
| 677 | public function removeFromIdentityMap($entity) |
||
| 678 | { |
||
| 679 | $oid = spl_object_hash($entity); |
||
| 680 | $classMetadata = $this->manager->getClassMetadata(get_class($entity)); |
||
| 681 | $idHash = implode(' ', $this->entityIdentifiers[$oid]); |
||
| 682 | if ($idHash === '') { |
||
| 683 | throw new \InvalidArgumentException('Entity has no identity'); |
||
| 684 | } |
||
| 685 | $className = $classMetadata->getRootEntityName(); |
||
| 686 | if (isset($this->identityMap[$className][$idHash])) { |
||
| 687 | unset($this->identityMap[$className][$idHash]); |
||
| 688 | unset($this->readOnlyObjects[$oid]); |
||
| 689 | |||
| 690 | //$this->entityStates[$oid] = self::STATE_DETACHED; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 691 | return true; |
||
| 692 | } |
||
| 693 | |||
| 694 | return false; |
||
| 695 | } |
||
| 696 | |||
| 697 | /** |
||
| 698 | * Commits the UnitOfWork, executing all operations that have been postponed |
||
| 699 | * up to this point. The state of all managed entities will be synchronized with |
||
| 700 | * the database. |
||
| 701 | * |
||
| 702 | * The operations are executed in the following order: |
||
| 703 | * |
||
| 704 | * 1) All entity insertions |
||
| 705 | * 2) All entity updates |
||
| 706 | * 3) All collection deletions |
||
| 707 | * 4) All collection updates |
||
| 708 | * 5) All entity deletions |
||
| 709 | * |
||
| 710 | * @param null|object|array $entity |
||
| 711 | * |
||
| 712 | * @return void |
||
| 713 | * |
||
| 714 | * @throws \Exception |
||
| 715 | */ |
||
| 716 | public function commit($entity = null) |
||
| 717 | { |
||
| 718 | // Compute changes done since last commit. |
||
| 719 | if ($entity === null) { |
||
| 720 | $this->computeChangeSets(); |
||
| 721 | } elseif (is_object($entity)) { |
||
| 722 | $this->computeSingleEntityChangeSet($entity); |
||
| 723 | } elseif (is_array($entity)) { |
||
| 724 | foreach ((array)$entity as $object) { |
||
| 725 | $this->computeSingleEntityChangeSet($object); |
||
| 726 | } |
||
| 727 | } |
||
| 728 | if (!($this->entityInsertions || |
||
|
0 ignored issues
–
show
The expression
$this->entityInsertions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 729 | $this->entityDeletions || |
||
|
0 ignored issues
–
show
The expression
$this->entityDeletions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 730 | $this->entityUpdates || |
||
|
0 ignored issues
–
show
The expression
$this->entityUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 731 | $this->collectionUpdates || |
||
|
0 ignored issues
–
show
The expression
$this->collectionUpdates of type Bankiru\Api\Doctrine\Proxy\ApiCollection[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 732 | $this->collectionDeletions || |
||
|
0 ignored issues
–
show
The expression
$this->collectionDeletions of type Bankiru\Api\Doctrine\Proxy\ApiCollection[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 733 | $this->orphanRemovals) |
||
|
0 ignored issues
–
show
The expression
$this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 734 | ) { |
||
| 735 | return; // Nothing to do. |
||
| 736 | } |
||
| 737 | if ($this->orphanRemovals) { |
||
|
0 ignored issues
–
show
The expression
$this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 738 | foreach ($this->orphanRemovals as $orphan) { |
||
| 739 | $this->remove($orphan); |
||
|
0 ignored issues
–
show
The method
remove() does not exist on Bankiru\Api\Doctrine\UnitOfWork. Did you maybe mean removeFromIdentityMap()?
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...
|
|||
| 740 | } |
||
| 741 | } |
||
| 742 | // Now we need a commit order to maintain referential integrity |
||
| 743 | $commitOrder = $this->getCommitOrder(); |
||
| 744 | |||
| 745 | // Collection deletions (deletions of complete collections) |
||
| 746 | // foreach ($this->collectionDeletions as $collectionToDelete) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
58% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 747 | // //fixme: collection mutations |
||
| 748 | // $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
77% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 749 | // } |
||
| 750 | if ($this->entityInsertions) { |
||
|
0 ignored issues
–
show
The expression
$this->entityInsertions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 751 | foreach ($commitOrder as $class) { |
||
| 752 | $this->executeInserts($class); |
||
| 753 | } |
||
| 754 | } |
||
| 755 | if ($this->entityUpdates) { |
||
|
0 ignored issues
–
show
The expression
$this->entityUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 756 | foreach ($commitOrder as $class) { |
||
| 757 | $this->executeUpdates($class); |
||
| 758 | } |
||
| 759 | } |
||
| 760 | // Extra updates that were requested by persisters. |
||
| 761 | if ($this->extraUpdates) { |
||
|
0 ignored issues
–
show
The expression
$this->extraUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 762 | $this->executeExtraUpdates(); |
||
| 763 | } |
||
| 764 | // Collection updates (deleteRows, updateRows, insertRows) |
||
| 765 | foreach ($this->collectionUpdates as $collectionToUpdate) { |
||
|
0 ignored issues
–
show
|
|||
| 766 | //fixme: decide what to do with collection mutation if API does not support this |
||
| 767 | //$this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
82% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 768 | } |
||
| 769 | // Entity deletions come last and need to be in reverse commit order |
||
| 770 | if ($this->entityDeletions) { |
||
|
0 ignored issues
–
show
The expression
$this->entityDeletions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 771 | for ($count = count($commitOrder), $i = $count - 1; $i >= 0 && $this->entityDeletions; --$i) { |
||
|
0 ignored issues
–
show
The expression
$this->entityDeletions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 772 | $this->executeDeletions($commitOrder[$i]); |
||
| 773 | } |
||
| 774 | } |
||
| 775 | |||
| 776 | // Take new snapshots from visited collections |
||
| 777 | foreach ($this->visitedCollections as $coll) { |
||
| 778 | $coll->takeSnapshot(); |
||
| 779 | } |
||
| 780 | |||
| 781 | // Clear up |
||
| 782 | $this->entityInsertions = |
||
| 783 | $this->entityUpdates = |
||
| 784 | $this->entityDeletions = |
||
| 785 | $this->extraUpdates = |
||
| 786 | $this->entityChangeSets = |
||
| 787 | $this->collectionUpdates = |
||
| 788 | $this->collectionDeletions = |
||
| 789 | $this->visitedCollections = |
||
| 790 | $this->scheduledForSynchronization = |
||
| 791 | $this->orphanRemovals = []; |
||
| 792 | } |
||
| 793 | |||
| 794 | /** |
||
| 795 | * Gets the changeset for an entity. |
||
| 796 | * |
||
| 797 | * @param object $entity |
||
| 798 | * |
||
| 799 | * @return array |
||
| 800 | */ |
||
| 801 | public function & getEntityChangeSet($entity) |
||
| 802 | { |
||
| 803 | $oid = spl_object_hash($entity); |
||
| 804 | $data = []; |
||
| 805 | if (!isset($this->entityChangeSets[$oid])) { |
||
| 806 | return $data; |
||
| 807 | } |
||
| 808 | |||
| 809 | return $this->entityChangeSets[$oid]; |
||
| 810 | } |
||
| 811 | |||
| 812 | /** |
||
| 813 | * Computes the changes that happened to a single entity. |
||
| 814 | * |
||
| 815 | * Modifies/populates the following properties: |
||
| 816 | * |
||
| 817 | * {@link _originalEntityData} |
||
| 818 | * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) |
||
| 819 | * then it was not fetched from the database and therefore we have no original |
||
| 820 | * entity data yet. All of the current entity data is stored as the original entity data. |
||
| 821 | * |
||
| 822 | * {@link _entityChangeSets} |
||
| 823 | * The changes detected on all properties of the entity are stored there. |
||
| 824 | * A change is a tuple array where the first entry is the old value and the second |
||
| 825 | * entry is the new value of the property. Changesets are used by persisters |
||
| 826 | * to INSERT/UPDATE the persistent entity state. |
||
| 827 | * |
||
| 828 | * {@link _entityUpdates} |
||
| 829 | * If the entity is already fully MANAGED (has been fetched from the database before) |
||
| 830 | * and any changes to its properties are detected, then a reference to the entity is stored |
||
| 831 | * there to mark it for an update. |
||
| 832 | * |
||
| 833 | * {@link _collectionDeletions} |
||
| 834 | * If a PersistentCollection has been de-referenced in a fully MANAGED entity, |
||
| 835 | * then this collection is marked for deletion. |
||
| 836 | * |
||
| 837 | * @ignore |
||
| 838 | * |
||
| 839 | * @internal Don't call from the outside. |
||
| 840 | * |
||
| 841 | * @param ApiMetadata $class The class descriptor of the entity. |
||
| 842 | * @param object $entity The entity for which to compute the changes. |
||
| 843 | * |
||
| 844 | * @return void |
||
| 845 | */ |
||
| 846 | public function computeChangeSet(ApiMetadata $class, $entity) |
||
| 847 | { |
||
| 848 | $oid = spl_object_hash($entity); |
||
| 849 | if (isset($this->readOnlyObjects[$oid])) { |
||
| 850 | return; |
||
| 851 | } |
||
| 852 | // if ( ! $class->isInheritanceTypeNone()) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
60% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 853 | // $class = $this->em->getClassMetadata(get_class($entity)); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
59% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 854 | // } |
||
| 855 | // $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
47% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 856 | // if ($invoke !== ListenersInvoker::INVOKE_NONE) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
47% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 857 | // $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 858 | // } |
||
| 859 | $actualData = []; |
||
| 860 | foreach ($class->getReflectionProperties() as $name => $refProp) { |
||
| 861 | $value = $refProp->getValue($entity); |
||
| 862 | if ($class->isCollectionValuedAssociation($name) && $value !== null) { |
||
| 863 | if ($value instanceof ApiCollection) { |
||
| 864 | if ($value->getOwner() === $entity) { |
||
| 865 | continue; |
||
| 866 | } |
||
| 867 | $value = new ArrayCollection($value->getValues()); |
||
| 868 | } |
||
| 869 | // If $value is not a Collection then use an ArrayCollection. |
||
| 870 | if (!$value instanceof Collection) { |
||
| 871 | $value = new ArrayCollection($value); |
||
| 872 | } |
||
| 873 | $assoc = $class->getAssociationMapping($name); |
||
| 874 | // Inject PersistentCollection |
||
| 875 | $value = new ApiCollection( |
||
| 876 | $this->manager, |
||
| 877 | $this->manager->getClassMetadata($assoc['target']), |
||
| 878 | $value |
||
| 879 | ); |
||
| 880 | $value->setOwner($entity, $assoc); |
||
| 881 | $value->setDirty(!$value->isEmpty()); |
||
| 882 | $class->getReflectionProperty($name)->setValue($entity, $value); |
||
| 883 | $actualData[$name] = $value; |
||
| 884 | continue; |
||
| 885 | } |
||
| 886 | if (!$class->isIdentifier($name)) { |
||
| 887 | $actualData[$name] = $value; |
||
| 888 | } |
||
| 889 | } |
||
| 890 | if (!isset($this->originalEntityData[$oid])) { |
||
| 891 | // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). |
||
| 892 | // These result in an INSERT. |
||
| 893 | $this->originalEntityData[$oid] = $actualData; |
||
| 894 | $changeSet = []; |
||
| 895 | foreach ($actualData as $propName => $actualValue) { |
||
| 896 | View Code Duplication | if (!$class->hasAssociation($propName)) { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 897 | $changeSet[$propName] = [null, $actualValue]; |
||
| 898 | continue; |
||
| 899 | } |
||
| 900 | $assoc = $class->getAssociationMapping($propName); |
||
| 901 | if ($assoc['isOwningSide'] && $assoc['type'] & ApiMetadata::TO_ONE) { |
||
| 902 | $changeSet[$propName] = [null, $actualValue]; |
||
| 903 | } |
||
| 904 | } |
||
| 905 | $this->entityChangeSets[$oid] = $changeSet; |
||
| 906 | } else { |
||
| 907 | // Entity is "fully" MANAGED: it was already fully persisted before |
||
| 908 | // and we have a copy of the original data |
||
| 909 | $originalData = $this->originalEntityData[$oid]; |
||
| 910 | $isChangeTrackingNotify = false; |
||
| 911 | $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) |
||
| 912 | ? $this->entityChangeSets[$oid] |
||
| 913 | : []; |
||
| 914 | foreach ($actualData as $propName => $actualValue) { |
||
| 915 | // skip field, its a partially omitted one! |
||
| 916 | if (!(isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { |
||
| 917 | continue; |
||
| 918 | } |
||
| 919 | $orgValue = $originalData[$propName]; |
||
| 920 | // skip if value haven't changed |
||
| 921 | if ($orgValue === $actualValue) { |
||
| 922 | continue; |
||
| 923 | } |
||
| 924 | // if regular field |
||
| 925 | View Code Duplication | if (!$class->hasAssociation($propName)) { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 926 | if ($isChangeTrackingNotify) { |
||
| 927 | continue; |
||
| 928 | } |
||
| 929 | $changeSet[$propName] = [$orgValue, $actualValue]; |
||
| 930 | continue; |
||
| 931 | } |
||
| 932 | $assoc = $class->getAssociationMapping($propName); |
||
| 933 | // Persistent collection was exchanged with the "originally" |
||
| 934 | // created one. This can only mean it was cloned and replaced |
||
| 935 | // on another entity. |
||
| 936 | if ($actualValue instanceof ApiCollection) { |
||
| 937 | $owner = $actualValue->getOwner(); |
||
| 938 | if ($owner === null) { // cloned |
||
| 939 | $actualValue->setOwner($entity, $assoc); |
||
| 940 | } else { |
||
| 941 | if ($owner !== $entity) { // no clone, we have to fix |
||
| 942 | if (!$actualValue->isInitialized()) { |
||
| 943 | $actualValue->initialize(); // we have to do this otherwise the cols share state |
||
| 944 | } |
||
| 945 | $newValue = clone $actualValue; |
||
| 946 | $newValue->setOwner($entity, $assoc); |
||
| 947 | $class->getReflectionProperty($propName)->setValue($entity, $newValue); |
||
| 948 | } |
||
| 949 | } |
||
| 950 | } |
||
| 951 | if ($orgValue instanceof ApiCollection) { |
||
| 952 | // A PersistentCollection was de-referenced, so delete it. |
||
| 953 | $coid = spl_object_hash($orgValue); |
||
| 954 | if (isset($this->collectionDeletions[$coid])) { |
||
| 955 | continue; |
||
| 956 | } |
||
| 957 | $this->collectionDeletions[$coid] = $orgValue; |
||
| 958 | $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. |
||
| 959 | continue; |
||
| 960 | } |
||
| 961 | if ($assoc['type'] & ApiMetadata::TO_ONE) { |
||
| 962 | if ($assoc['isOwningSide']) { |
||
| 963 | $changeSet[$propName] = [$orgValue, $actualValue]; |
||
| 964 | } |
||
| 965 | if ($orgValue !== null && $assoc['orphanRemoval']) { |
||
| 966 | $this->scheduleOrphanRemoval($orgValue); |
||
| 967 | } |
||
| 968 | } |
||
| 969 | } |
||
| 970 | if ($changeSet) { |
||
| 971 | $this->entityChangeSets[$oid] = $changeSet; |
||
| 972 | $this->originalEntityData[$oid] = $actualData; |
||
| 973 | $this->entityUpdates[$oid] = $entity; |
||
| 974 | } |
||
| 975 | } |
||
| 976 | // Look for changes in associations of the entity |
||
| 977 | foreach ($class->getAssociationMappings() as $field => $assoc) { |
||
| 978 | if (($val = $class->getReflectionProperty($field)->getValue($entity)) === null) { |
||
| 979 | continue; |
||
| 980 | } |
||
| 981 | $this->computeAssociationChanges($assoc, $val); |
||
| 982 | if (!isset($this->entityChangeSets[$oid]) && |
||
| 983 | $assoc['isOwningSide'] && |
||
| 984 | $assoc['type'] == ApiMetadata::MANY_TO_MANY && |
||
| 985 | $val instanceof ApiCollection && |
||
| 986 | $val->isDirty() |
||
| 987 | ) { |
||
| 988 | $this->entityChangeSets[$oid] = []; |
||
| 989 | $this->originalEntityData[$oid] = $actualData; |
||
| 990 | $this->entityUpdates[$oid] = $entity; |
||
| 991 | } |
||
| 992 | } |
||
| 993 | } |
||
| 994 | |||
| 995 | /** |
||
| 996 | * Computes all the changes that have been done to entities and collections |
||
| 997 | * since the last commit and stores these changes in the _entityChangeSet map |
||
| 998 | * temporarily for access by the persisters, until the UoW commit is finished. |
||
| 999 | * |
||
| 1000 | * @return void |
||
| 1001 | */ |
||
| 1002 | public function computeChangeSets() |
||
| 1003 | { |
||
| 1004 | // Compute changes for INSERTed entities first. This must always happen. |
||
| 1005 | $this->computeScheduleInsertsChangeSets(); |
||
| 1006 | // Compute changes for other MANAGED entities. Change tracking policies take effect here. |
||
| 1007 | foreach ($this->identityMap as $className => $entities) { |
||
| 1008 | $class = $this->manager->getClassMetadata($className); |
||
| 1009 | // Skip class if instances are read-only |
||
| 1010 | if ($class->isReadOnly()) { |
||
| 1011 | continue; |
||
| 1012 | } |
||
| 1013 | // If change tracking is explicit or happens through notification, then only compute |
||
| 1014 | // changes on entities of that type that are explicitly marked for synchronization. |
||
| 1015 | switch (true) { |
||
| 1016 | case ($class->isChangeTrackingDeferredImplicit()): |
||
| 1017 | $entitiesToProcess = $entities; |
||
| 1018 | break; |
||
| 1019 | case (isset($this->scheduledForSynchronization[$className])): |
||
| 1020 | $entitiesToProcess = $this->scheduledForSynchronization[$className]; |
||
| 1021 | break; |
||
| 1022 | default: |
||
| 1023 | $entitiesToProcess = []; |
||
| 1024 | } |
||
| 1025 | foreach ($entitiesToProcess as $entity) { |
||
| 1026 | // Ignore uninitialized proxy objects |
||
| 1027 | if ($entity instanceof Proxy && !$entity->__isInitialized__) { |
||
|
0 ignored issues
–
show
Accessing
__isInitialized__ on the interface Doctrine\Common\Proxy\Proxy 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
Loading history...
|
|||
| 1028 | continue; |
||
| 1029 | } |
||
| 1030 | // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. |
||
| 1031 | $oid = spl_object_hash($entity); |
||
| 1032 | View Code Duplication | if (!isset($this->entityInsertions[$oid]) && |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1033 | !isset($this->entityDeletions[$oid]) && |
||
| 1034 | isset($this->entityStates[$oid]) |
||
| 1035 | ) { |
||
| 1036 | $this->computeChangeSet($class, $entity); |
||
| 1037 | } |
||
| 1038 | } |
||
| 1039 | } |
||
| 1040 | } |
||
| 1041 | |||
| 1042 | /** |
||
| 1043 | * INTERNAL: |
||
| 1044 | * Schedules an orphaned entity for removal. The remove() operation will be |
||
| 1045 | * invoked on that entity at the beginning of the next commit of this |
||
| 1046 | * UnitOfWork. |
||
| 1047 | * |
||
| 1048 | * @ignore |
||
| 1049 | * |
||
| 1050 | * @param object $entity |
||
| 1051 | * |
||
| 1052 | * @return void |
||
| 1053 | */ |
||
| 1054 | public function scheduleOrphanRemoval($entity) |
||
| 1055 | { |
||
| 1056 | $this->orphanRemovals[spl_object_hash($entity)] = $entity; |
||
| 1057 | } |
||
| 1058 | |||
| 1059 | public function loadCollection(ApiCollection $collection) |
||
| 1060 | { |
||
| 1061 | $assoc = $collection->getMapping(); |
||
| 1062 | $persister = $this->getEntityPersister($assoc['target']); |
||
| 1063 | switch ($assoc['type']) { |
||
| 1064 | case ApiMetadata::ONE_TO_MANY: |
||
| 1065 | $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); |
||
| 1066 | break; |
||
| 1067 | } |
||
| 1068 | $collection->setInitialized(true); |
||
| 1069 | } |
||
| 1070 | |||
| 1071 | public function getCollectionPersister($association) |
||
| 1072 | { |
||
| 1073 | $role = isset($association['cache']) |
||
| 1074 | ? $association['sourceEntity'] . '::' . $association['fieldName'] |
||
| 1075 | : $association['type']; |
||
| 1076 | if (array_key_exists($role, $this->collectionPersisters)) { |
||
| 1077 | return $this->collectionPersisters[$role]; |
||
| 1078 | } |
||
| 1079 | $this->collectionPersisters[$role] = new CollectionPersister($this->manager); |
||
|
0 ignored issues
–
show
The call to
CollectionPersister::__construct() has too many arguments starting with $this->manager.
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. In this case you can add the Loading history...
|
|||
| 1080 | |||
| 1081 | return $this->collectionPersisters[$role]; |
||
| 1082 | } |
||
| 1083 | |||
| 1084 | public function scheduleCollectionDeletion(Collection $collection) |
||
|
0 ignored issues
–
show
|
|||
| 1085 | { |
||
| 1086 | } |
||
| 1087 | |||
| 1088 | public function cancelOrphanRemoval($value) |
||
|
0 ignored issues
–
show
|
|||
| 1089 | { |
||
| 1090 | } |
||
| 1091 | |||
| 1092 | /** |
||
| 1093 | * INTERNAL: |
||
| 1094 | * Sets a property value of the original data array of an entity. |
||
| 1095 | * |
||
| 1096 | * @ignore |
||
| 1097 | * |
||
| 1098 | * @param string $oid |
||
| 1099 | * @param string $property |
||
| 1100 | * @param mixed $value |
||
| 1101 | * |
||
| 1102 | * @return void |
||
| 1103 | */ |
||
| 1104 | public function setOriginalEntityProperty($oid, $property, $value) |
||
| 1105 | { |
||
| 1106 | if (!array_key_exists($oid, $this->originalEntityData)) { |
||
| 1107 | $this->originalEntityData[$oid] = new \stdClass(); |
||
| 1108 | } |
||
| 1109 | |||
| 1110 | $this->originalEntityData[$oid]->$property = $value; |
||
| 1111 | } |
||
| 1112 | |||
| 1113 | public function scheduleExtraUpdate($entity, $changeset) |
||
| 1114 | { |
||
| 1115 | $oid = spl_object_hash($entity); |
||
| 1116 | $extraUpdate = [$entity, $changeset]; |
||
| 1117 | if (isset($this->extraUpdates[$oid])) { |
||
| 1118 | list(, $changeset2) = $this->extraUpdates[$oid]; |
||
| 1119 | $extraUpdate = [$entity, $changeset + $changeset2]; |
||
| 1120 | } |
||
| 1121 | $this->extraUpdates[$oid] = $extraUpdate; |
||
| 1122 | } |
||
| 1123 | |||
| 1124 | /** |
||
| 1125 | * Refreshes the state of the given entity from the database, overwriting |
||
| 1126 | * any local, unpersisted changes. |
||
| 1127 | * |
||
| 1128 | * @param object $entity The entity to refresh. |
||
| 1129 | * |
||
| 1130 | * @return void |
||
| 1131 | * |
||
| 1132 | * @throws InvalidArgumentException If the entity is not MANAGED. |
||
| 1133 | */ |
||
| 1134 | public function refresh($entity) |
||
| 1135 | { |
||
| 1136 | $visited = []; |
||
| 1137 | $this->doRefresh($entity, $visited); |
||
| 1138 | } |
||
| 1139 | |||
| 1140 | /** |
||
| 1141 | * Clears the UnitOfWork. |
||
| 1142 | * |
||
| 1143 | * @param string|null $entityName if given, only entities of this type will get detached. |
||
| 1144 | * |
||
| 1145 | * @return void |
||
| 1146 | */ |
||
| 1147 | public function clear($entityName = null) |
||
| 1148 | { |
||
| 1149 | if ($entityName === null) { |
||
| 1150 | $this->identityMap = |
||
| 1151 | $this->entityIdentifiers = |
||
| 1152 | $this->originalEntityData = |
||
| 1153 | $this->entityChangeSets = |
||
| 1154 | $this->entityStates = |
||
| 1155 | $this->scheduledForSynchronization = |
||
| 1156 | $this->entityInsertions = |
||
| 1157 | $this->entityUpdates = |
||
| 1158 | $this->entityDeletions = |
||
| 1159 | $this->collectionDeletions = |
||
| 1160 | $this->collectionUpdates = |
||
| 1161 | $this->extraUpdates = |
||
| 1162 | $this->readOnlyObjects = |
||
| 1163 | $this->visitedCollections = |
||
| 1164 | $this->orphanRemovals = []; |
||
| 1165 | } else { |
||
| 1166 | $this->clearIdentityMapForEntityName($entityName); |
||
|
0 ignored issues
–
show
The method
clearIdentityMapForEntityName() does not seem to exist on object<Bankiru\Api\Doctrine\UnitOfWork>.
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...
|
|||
| 1167 | $this->clearEntityInsertionsForEntityName($entityName); |
||
|
0 ignored issues
–
show
The method
clearEntityInsertionsForEntityName() does not seem to exist on object<Bankiru\Api\Doctrine\UnitOfWork>.
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...
|
|||
| 1168 | } |
||
| 1169 | } |
||
| 1170 | |||
| 1171 | /** |
||
| 1172 | * @param PersistentCollection $coll |
||
| 1173 | * |
||
| 1174 | * @return bool |
||
| 1175 | */ |
||
| 1176 | public function isCollectionScheduledForDeletion(PersistentCollection $coll) |
||
| 1177 | { |
||
| 1178 | return isset($this->collectionDeletions[spl_object_hash($coll)]); |
||
| 1179 | } |
||
| 1180 | |||
| 1181 | /** |
||
| 1182 | * Schedules an entity for dirty-checking at commit-time. |
||
| 1183 | * |
||
| 1184 | * @param object $entity The entity to schedule for dirty-checking. |
||
| 1185 | * |
||
| 1186 | * @return void |
||
| 1187 | * |
||
| 1188 | * @todo Rename: scheduleForSynchronization |
||
| 1189 | */ |
||
| 1190 | public function scheduleForDirtyCheck($entity) |
||
| 1191 | { |
||
| 1192 | $rootClassName = |
||
| 1193 | $this->manager->getClassMetadata(get_class($entity))->getRootEntityName(); |
||
| 1194 | $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity; |
||
| 1195 | } |
||
| 1196 | |||
| 1197 | /** |
||
| 1198 | * @param ApiMetadata $class |
||
| 1199 | * |
||
| 1200 | * @return \Doctrine\Common\Persistence\ObjectManagerAware|object |
||
| 1201 | */ |
||
| 1202 | private function newInstance(ApiMetadata $class) |
||
| 1203 | { |
||
| 1204 | $entity = $class->newInstance(); |
||
| 1205 | |||
| 1206 | if ($entity instanceof ObjectManagerAware) { |
||
| 1207 | $entity->injectObjectManager($this->manager, $class); |
||
| 1208 | } |
||
| 1209 | |||
| 1210 | return $entity; |
||
| 1211 | } |
||
| 1212 | |||
| 1213 | /** |
||
| 1214 | * @param ApiMetadata $classMetadata |
||
| 1215 | * |
||
| 1216 | * @return EntityDataCacheInterface |
||
| 1217 | */ |
||
| 1218 | private function createEntityCache(ApiMetadata $classMetadata) |
||
| 1219 | { |
||
| 1220 | $configuration = $this->manager->getConfiguration()->getCacheConfiguration($classMetadata->getName()); |
||
| 1221 | $cache = new VoidEntityCache($classMetadata); |
||
| 1222 | if ($configuration->isEnabled() && $this->manager->getConfiguration()->getApiCache()) { |
||
| 1223 | $cache = |
||
| 1224 | new LoggingCache( |
||
| 1225 | new ApiEntityCache( |
||
| 1226 | $this->manager->getConfiguration()->getApiCache(), |
||
| 1227 | $classMetadata, |
||
| 1228 | $configuration |
||
| 1229 | ), |
||
| 1230 | $this->manager->getConfiguration()->getApiCacheLogger() |
||
| 1231 | ); |
||
| 1232 | |||
| 1233 | return $cache; |
||
| 1234 | } |
||
| 1235 | |||
| 1236 | return $cache; |
||
| 1237 | } |
||
| 1238 | |||
| 1239 | /** |
||
| 1240 | * @param ApiMetadata $classMetadata |
||
| 1241 | * |
||
| 1242 | * @return CrudsApiInterface |
||
| 1243 | */ |
||
| 1244 | private function createApi(ApiMetadata $classMetadata) |
||
| 1245 | { |
||
| 1246 | $client = $this->manager->getConfiguration()->getRegistry()->get($classMetadata->getClientName()); |
||
| 1247 | |||
| 1248 | $api = $this->manager |
||
| 1249 | ->getConfiguration() |
||
| 1250 | ->getResolver() |
||
| 1251 | ->resolve($classMetadata->getApiName()) |
||
| 1252 | ->createApi( |
||
| 1253 | $client, |
||
| 1254 | $classMetadata |
||
| 1255 | ); |
||
| 1256 | |||
| 1257 | return $api; |
||
| 1258 | } |
||
| 1259 | |||
| 1260 | private function doPersist($entity, $visited) |
||
| 1261 | { |
||
| 1262 | $oid = spl_object_hash($entity); |
||
| 1263 | if (isset($visited[$oid])) { |
||
| 1264 | return; // Prevent infinite recursion |
||
| 1265 | } |
||
| 1266 | $visited[$oid] = $entity; // Mark visited |
||
| 1267 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1268 | // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation). |
||
| 1269 | // If we would detect DETACHED here we would throw an exception anyway with the same |
||
| 1270 | // consequences (not recoverable/programming error), so just assuming NEW here |
||
| 1271 | // lets us avoid some database lookups for entities with natural identifiers. |
||
| 1272 | $entityState = $this->getEntityState($entity, self::STATE_NEW); |
||
| 1273 | switch ($entityState) { |
||
| 1274 | case self::STATE_MANAGED: |
||
| 1275 | $this->scheduleForDirtyCheck($entity); |
||
| 1276 | break; |
||
| 1277 | case self::STATE_NEW: |
||
| 1278 | $this->persistNew($class, $entity); |
||
| 1279 | break; |
||
| 1280 | case self::STATE_REMOVED: |
||
| 1281 | // Entity becomes managed again |
||
| 1282 | unset($this->entityDeletions[$oid]); |
||
| 1283 | $this->addToIdentityMap($entity); |
||
| 1284 | $this->entityStates[$oid] = self::STATE_MANAGED; |
||
| 1285 | break; |
||
| 1286 | case self::STATE_DETACHED: |
||
| 1287 | // Can actually not happen right now since we assume STATE_NEW. |
||
| 1288 | throw new \InvalidArgumentException('Detached entity cannot be persisted'); |
||
| 1289 | default: |
||
| 1290 | throw new \UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity)); |
||
| 1291 | } |
||
| 1292 | $this->cascadePersist($entity, $visited); |
||
| 1293 | } |
||
| 1294 | |||
| 1295 | /** |
||
| 1296 | * Cascades the save operation to associated entities. |
||
| 1297 | * |
||
| 1298 | * @param object $entity |
||
| 1299 | * @param array $visited |
||
| 1300 | * |
||
| 1301 | * @return void |
||
| 1302 | * @throws \InvalidArgumentException |
||
| 1303 | * @throws MappingException |
||
| 1304 | */ |
||
| 1305 | private function cascadePersist($entity, array &$visited) |
||
| 1306 | { |
||
| 1307 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1308 | $associationMappings = []; |
||
| 1309 | foreach ($class->getAssociationNames() as $name) { |
||
| 1310 | $assoc = $class->getAssociationMapping($name); |
||
| 1311 | if ($assoc['isCascadePersist']) { |
||
| 1312 | $associationMappings[$name] = $assoc; |
||
| 1313 | } |
||
| 1314 | } |
||
| 1315 | foreach ($associationMappings as $assoc) { |
||
| 1316 | $relatedEntities = $class->getReflectionProperty($assoc['field'])->getValue($entity); |
||
| 1317 | switch (true) { |
||
| 1318 | case ($relatedEntities instanceof ApiCollection): |
||
| 1319 | // Unwrap so that foreach() does not initialize |
||
| 1320 | $relatedEntities = $relatedEntities->unwrap(); |
||
| 1321 | // break; is commented intentionally! |
||
| 1322 | case ($relatedEntities instanceof Collection): |
||
| 1323 | case (is_array($relatedEntities)): |
||
| 1324 | if (($assoc['type'] & ApiMetadata::TO_MANY) <= 0) { |
||
| 1325 | throw new \InvalidArgumentException('Invalid association for cascade'); |
||
| 1326 | } |
||
| 1327 | foreach ($relatedEntities as $relatedEntity) { |
||
| 1328 | $this->doPersist($relatedEntity, $visited); |
||
| 1329 | } |
||
| 1330 | break; |
||
| 1331 | case ($relatedEntities !== null): |
||
| 1332 | if (!$relatedEntities instanceof $assoc['target']) { |
||
| 1333 | throw new \InvalidArgumentException('Invalid association for cascade'); |
||
| 1334 | } |
||
| 1335 | $this->doPersist($relatedEntities, $visited); |
||
| 1336 | break; |
||
| 1337 | default: |
||
| 1338 | // Do nothing |
||
| 1339 | } |
||
| 1340 | } |
||
| 1341 | } |
||
| 1342 | |||
| 1343 | /** |
||
| 1344 | * @param ApiMetadata $class |
||
| 1345 | * @param object $entity |
||
| 1346 | * |
||
| 1347 | * @return void |
||
| 1348 | */ |
||
| 1349 | private function persistNew($class, $entity) |
||
|
0 ignored issues
–
show
|
|||
| 1350 | { |
||
| 1351 | $oid = spl_object_hash($entity); |
||
| 1352 | // $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
53% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1353 | // if ($invoke !== ListenersInvoker::INVOKE_NONE) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
47% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1354 | // $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1355 | // } |
||
| 1356 | // $idGen = $class->idGenerator; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
45% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1357 | // if ( ! $idGen->isPostInsertGenerator()) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
60% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1358 | // $idValue = $idGen->generate($this->em, $entity); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
59% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1359 | // if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
47% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1360 | // $idValue = array($class->identifier[0] => $idValue); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
64% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1361 | // $class->setIdentifierValues($entity, $idValue); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
73% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1362 | // } |
||
| 1363 | // $this->entityIdentifiers[$oid] = $idValue; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
59% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1364 | // } |
||
| 1365 | $this->entityStates[$oid] = self::STATE_MANAGED; |
||
| 1366 | $this->scheduleForInsert($entity); |
||
| 1367 | } |
||
| 1368 | |||
| 1369 | /** |
||
| 1370 | * Gets the commit order. |
||
| 1371 | * |
||
| 1372 | * @param array|null $entityChangeSet |
||
| 1373 | * |
||
| 1374 | * @return array |
||
| 1375 | */ |
||
| 1376 | private function getCommitOrder(array $entityChangeSet = null) |
||
| 1377 | { |
||
| 1378 | if ($entityChangeSet === null) { |
||
| 1379 | $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); |
||
| 1380 | } |
||
| 1381 | $calc = $this->getCommitOrderCalculator(); |
||
| 1382 | // See if there are any new classes in the changeset, that are not in the |
||
| 1383 | // commit order graph yet (don't have a node). |
||
| 1384 | // We have to inspect changeSet to be able to correctly build dependencies. |
||
| 1385 | // It is not possible to use IdentityMap here because post inserted ids |
||
| 1386 | // are not yet available. |
||
| 1387 | /** @var ApiMetadata[] $newNodes */ |
||
| 1388 | $newNodes = []; |
||
| 1389 | foreach ((array)$entityChangeSet as $entity) { |
||
| 1390 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1391 | if ($calc->hasNode($class->getName())) { |
||
| 1392 | continue; |
||
| 1393 | } |
||
| 1394 | $calc->addNode($class->getName(), $class); |
||
| 1395 | $newNodes[] = $class; |
||
| 1396 | } |
||
| 1397 | // Calculate dependencies for new nodes |
||
| 1398 | while ($class = array_pop($newNodes)) { |
||
| 1399 | foreach ($class->getAssociationMappings() as $assoc) { |
||
| 1400 | if (!($assoc['isOwningSide'] && $assoc['type'] & ApiMetadata::TO_ONE)) { |
||
| 1401 | continue; |
||
| 1402 | } |
||
| 1403 | $targetClass = $this->manager->getClassMetadata($assoc['target']); |
||
| 1404 | if (!$calc->hasNode($targetClass->getName())) { |
||
| 1405 | $calc->addNode($targetClass->getName(), $targetClass); |
||
| 1406 | $newNodes[] = $targetClass; |
||
| 1407 | } |
||
| 1408 | $calc->addDependency($targetClass->getName(), $class->name, (int)empty($assoc['nullable'])); |
||
| 1409 | // If the target class has mapped subclasses, these share the same dependency. |
||
| 1410 | if (!$targetClass->getSubclasses()) { |
||
| 1411 | continue; |
||
| 1412 | } |
||
| 1413 | foreach ($targetClass->getSubclasses() as $subClassName) { |
||
| 1414 | $targetSubClass = $this->manager->getClassMetadata($subClassName); |
||
| 1415 | if (!$calc->hasNode($subClassName)) { |
||
| 1416 | $calc->addNode($targetSubClass->name, $targetSubClass); |
||
|
0 ignored issues
–
show
Accessing
name on the interface Bankiru\Api\Doctrine\Mapping\ApiMetadata 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
Loading history...
|
|||
| 1417 | $newNodes[] = $targetSubClass; |
||
| 1418 | } |
||
| 1419 | $calc->addDependency($targetSubClass->name, $class->name, 1); |
||
|
0 ignored issues
–
show
Accessing
name on the interface Bankiru\Api\Doctrine\Mapping\ApiMetadata 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
Loading history...
|
|||
| 1420 | } |
||
| 1421 | } |
||
| 1422 | } |
||
| 1423 | |||
| 1424 | return $calc->sort(); |
||
| 1425 | } |
||
| 1426 | |||
| 1427 | /** |
||
| 1428 | * Helper method to show an object as string. |
||
| 1429 | * |
||
| 1430 | * @param object $obj |
||
| 1431 | * |
||
| 1432 | * @return string |
||
| 1433 | */ |
||
| 1434 | private static function objToStr($obj) |
||
| 1435 | { |
||
| 1436 | return method_exists($obj, '__toString') ? (string)$obj : get_class($obj) . '@' . spl_object_hash($obj); |
||
| 1437 | } |
||
| 1438 | |||
| 1439 | private function getCommitOrderCalculator() |
||
| 1440 | { |
||
| 1441 | return new Utility\CommitOrderCalculator(); |
||
| 1442 | } |
||
| 1443 | |||
| 1444 | /** |
||
| 1445 | * Only flushes the given entity according to a ruleset that keeps the UoW consistent. |
||
| 1446 | * |
||
| 1447 | * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! |
||
| 1448 | * 2. Read Only entities are skipped. |
||
| 1449 | * 3. Proxies are skipped. |
||
| 1450 | * 4. Only if entity is properly managed. |
||
| 1451 | * |
||
| 1452 | * @param object $entity |
||
| 1453 | * |
||
| 1454 | * @return void |
||
| 1455 | * |
||
| 1456 | * @throws \InvalidArgumentException |
||
| 1457 | */ |
||
| 1458 | private function computeSingleEntityChangeSet($entity) |
||
| 1459 | { |
||
| 1460 | $state = $this->getEntityState($entity); |
||
| 1461 | if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) { |
||
| 1462 | throw new \InvalidArgumentException( |
||
| 1463 | "Entity has to be managed or scheduled for removal for single computation " . self::objToStr($entity) |
||
| 1464 | ); |
||
| 1465 | } |
||
| 1466 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1467 | // Compute changes for INSERTed entities first. This must always happen even in this case. |
||
| 1468 | $this->computeScheduleInsertsChangeSets(); |
||
| 1469 | if ($class->isReadOnly()) { |
||
| 1470 | return; |
||
| 1471 | } |
||
| 1472 | // Ignore uninitialized proxy objects |
||
| 1473 | if ($entity instanceof Proxy && !$entity->__isInitialized__) { |
||
|
0 ignored issues
–
show
Accessing
__isInitialized__ on the interface Doctrine\Common\Proxy\Proxy 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
Loading history...
|
|||
| 1474 | return; |
||
| 1475 | } |
||
| 1476 | // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. |
||
| 1477 | $oid = spl_object_hash($entity); |
||
| 1478 | View Code Duplication | if (!isset($this->entityInsertions[$oid]) && |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1479 | !isset($this->entityDeletions[$oid]) && |
||
| 1480 | isset($this->entityStates[$oid]) |
||
| 1481 | ) { |
||
| 1482 | $this->computeChangeSet($class, $entity); |
||
| 1483 | } |
||
| 1484 | } |
||
| 1485 | |||
| 1486 | /** |
||
| 1487 | * Computes the changesets of all entities scheduled for insertion. |
||
| 1488 | * |
||
| 1489 | * @return void |
||
| 1490 | */ |
||
| 1491 | private function computeScheduleInsertsChangeSets() |
||
| 1492 | { |
||
| 1493 | foreach ($this->entityInsertions as $entity) { |
||
| 1494 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1495 | $this->computeChangeSet($class, $entity); |
||
| 1496 | } |
||
| 1497 | } |
||
| 1498 | |||
| 1499 | /** |
||
| 1500 | * Computes the changes of an association. |
||
| 1501 | * |
||
| 1502 | * @param array $assoc The association mapping. |
||
| 1503 | * @param mixed $value The value of the association. |
||
| 1504 | * |
||
| 1505 | * @throws \InvalidArgumentException |
||
| 1506 | * @throws \UnexpectedValueException |
||
| 1507 | * |
||
| 1508 | * @return void |
||
| 1509 | */ |
||
| 1510 | private function computeAssociationChanges($assoc, $value) |
||
| 1511 | { |
||
| 1512 | if ($value instanceof Proxy && !$value->__isInitialized__) { |
||
|
0 ignored issues
–
show
Accessing
__isInitialized__ on the interface Doctrine\Common\Proxy\Proxy 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
Loading history...
|
|||
| 1513 | return; |
||
| 1514 | } |
||
| 1515 | if ($value instanceof ApiCollection && $value->isDirty()) { |
||
| 1516 | $coid = spl_object_hash($value); |
||
| 1517 | $this->collectionUpdates[$coid] = $value; |
||
| 1518 | $this->visitedCollections[$coid] = $value; |
||
| 1519 | } |
||
| 1520 | // Look through the entities, and in any of their associations, |
||
| 1521 | // for transient (new) entities, recursively. ("Persistence by reachability") |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
45% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1522 | // Unwrap. Uninitialized collections will simply be empty. |
||
| 1523 | $unwrappedValue = ($assoc['type'] & ApiMetadata::TO_ONE) ? [$value] : $value->unwrap(); |
||
| 1524 | $targetClass = $this->manager->getClassMetadata($assoc['target']); |
||
| 1525 | $targetClassName = $targetClass->getName(); |
||
| 1526 | foreach ($unwrappedValue as $key => $entry) { |
||
| 1527 | if (!($entry instanceof $targetClassName)) { |
||
| 1528 | throw new \InvalidArgumentException('Invalid association'); |
||
| 1529 | } |
||
| 1530 | $state = $this->getEntityState($entry, self::STATE_NEW); |
||
| 1531 | if (!($entry instanceof $assoc['target'])) { |
||
| 1532 | throw new \UnexpectedValueException('Unexpected association'); |
||
| 1533 | } |
||
| 1534 | switch ($state) { |
||
| 1535 | case self::STATE_NEW: |
||
| 1536 | if (!$assoc['isCascadePersist']) { |
||
| 1537 | throw new \InvalidArgumentException('New entity through relationship'); |
||
| 1538 | } |
||
| 1539 | $this->persistNew($targetClass, $entry); |
||
| 1540 | $this->computeChangeSet($targetClass, $entry); |
||
| 1541 | break; |
||
| 1542 | case self::STATE_REMOVED: |
||
| 1543 | // Consume the $value as array (it's either an array or an ArrayAccess) |
||
| 1544 | // and remove the element from Collection. |
||
| 1545 | if ($assoc['type'] & ApiMetadata::TO_MANY) { |
||
| 1546 | unset($value[$key]); |
||
| 1547 | } |
||
| 1548 | break; |
||
| 1549 | case self::STATE_DETACHED: |
||
| 1550 | // Can actually not happen right now as we assume STATE_NEW, |
||
| 1551 | // so the exception will be raised from the DBAL layer (constraint violation). |
||
| 1552 | throw new \InvalidArgumentException('Detached entity through relationship'); |
||
| 1553 | break; |
||
|
0 ignored issues
–
show
break; does not seem to be reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last Loading history...
|
|||
| 1554 | default: |
||
| 1555 | // MANAGED associated entities are already taken into account |
||
| 1556 | // during changeset calculation anyway, since they are in the identity map. |
||
| 1557 | } |
||
| 1558 | } |
||
| 1559 | } |
||
| 1560 | |||
| 1561 | private function executeInserts(ApiMetadata $class) |
||
| 1562 | { |
||
| 1563 | $className = $class->getName(); |
||
| 1564 | $persister = $this->getEntityPersister($className); |
||
| 1565 | foreach ($this->entityInsertions as $oid => $entity) { |
||
| 1566 | if ($this->manager->getClassMetadata(get_class($entity))->getName() !== $className) { |
||
| 1567 | continue; |
||
| 1568 | } |
||
| 1569 | $persister->pushNewEntity($entity); |
||
| 1570 | unset($this->entityInsertions[$oid]); |
||
| 1571 | } |
||
| 1572 | $postInsertIds = $persister->flushNewEntities(); |
||
| 1573 | if ($postInsertIds) { |
||
| 1574 | // Persister returned post-insert IDs |
||
| 1575 | foreach ($postInsertIds as $postInsertId) { |
||
| 1576 | $id = $postInsertId['generatedId']; |
||
| 1577 | $entity = $postInsertId['entity']; |
||
| 1578 | $oid = spl_object_hash($entity); |
||
| 1579 | $idField = $class->getIdentifierFieldNames()[0]; |
||
| 1580 | $class->getReflectionProperty($idField)->setValue($entity, $id); |
||
| 1581 | $this->entityIdentifiers[$oid] = [$idField => $id]; |
||
| 1582 | $this->entityStates[$oid] = self::STATE_MANAGED; |
||
| 1583 | $this->originalEntityData[$oid][$idField] = $id; |
||
| 1584 | $this->addToIdentityMap($entity); |
||
| 1585 | } |
||
| 1586 | } |
||
| 1587 | } |
||
| 1588 | |||
| 1589 | private function executeUpdates($class) |
||
| 1590 | { |
||
| 1591 | $className = $class->name; |
||
| 1592 | $persister = $this->getEntityPersister($className); |
||
| 1593 | foreach ($this->entityUpdates as $oid => $entity) { |
||
| 1594 | if ($this->manager->getClassMetadata(get_class($entity))->getName() !== $className) { |
||
| 1595 | continue; |
||
| 1596 | } |
||
| 1597 | $this->recomputeSingleEntityChangeSet($class, $entity); |
||
| 1598 | |||
| 1599 | if (!empty($this->entityChangeSets[$oid])) { |
||
| 1600 | $persister->update($entity); |
||
| 1601 | } |
||
| 1602 | unset($this->entityUpdates[$oid]); |
||
| 1603 | } |
||
| 1604 | } |
||
| 1605 | |||
| 1606 | /** |
||
| 1607 | * Executes a detach operation on the given entity. |
||
| 1608 | * |
||
| 1609 | * @param object $entity |
||
| 1610 | * @param array $visited |
||
| 1611 | * @param boolean $noCascade if true, don't cascade detach operation. |
||
| 1612 | * |
||
| 1613 | * @return void |
||
| 1614 | */ |
||
| 1615 | private function doDetach($entity, array &$visited, $noCascade = false) |
||
| 1616 | { |
||
| 1617 | $oid = spl_object_hash($entity); |
||
| 1618 | if (isset($visited[$oid])) { |
||
| 1619 | return; // Prevent infinite recursion |
||
| 1620 | } |
||
| 1621 | $visited[$oid] = $entity; // mark visited |
||
| 1622 | switch ($this->getEntityState($entity, self::STATE_DETACHED)) { |
||
| 1623 | case self::STATE_MANAGED: |
||
| 1624 | if ($this->isInIdentityMap($entity)) { |
||
| 1625 | $this->removeFromIdentityMap($entity); |
||
| 1626 | } |
||
| 1627 | unset( |
||
| 1628 | $this->entityInsertions[$oid], |
||
| 1629 | $this->entityUpdates[$oid], |
||
| 1630 | $this->entityDeletions[$oid], |
||
| 1631 | $this->entityIdentifiers[$oid], |
||
| 1632 | $this->entityStates[$oid], |
||
| 1633 | $this->originalEntityData[$oid] |
||
| 1634 | ); |
||
| 1635 | break; |
||
| 1636 | case self::STATE_NEW: |
||
| 1637 | case self::STATE_DETACHED: |
||
| 1638 | return; |
||
| 1639 | } |
||
| 1640 | if (!$noCascade) { |
||
| 1641 | $this->cascadeDetach($entity, $visited); |
||
| 1642 | } |
||
| 1643 | } |
||
| 1644 | |||
| 1645 | /** |
||
| 1646 | * Executes a refresh operation on an entity. |
||
| 1647 | * |
||
| 1648 | * @param object $entity The entity to refresh. |
||
| 1649 | * @param array $visited The already visited entities during cascades. |
||
| 1650 | * |
||
| 1651 | * @return void |
||
| 1652 | * |
||
| 1653 | * @throws ORMInvalidArgumentException If the entity is not MANAGED. |
||
| 1654 | */ |
||
| 1655 | private function doRefresh($entity, array &$visited) |
||
| 1656 | { |
||
| 1657 | $oid = spl_object_hash($entity); |
||
| 1658 | if (isset($visited[$oid])) { |
||
| 1659 | return; // Prevent infinite recursion |
||
| 1660 | } |
||
| 1661 | $visited[$oid] = $entity; // mark visited |
||
| 1662 | $class = $this->em->getClassMetadata(get_class($entity)); |
||
|
0 ignored issues
–
show
The property
em does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
Loading history...
|
|||
| 1663 | if ($this->getEntityState($entity) !== self::STATE_MANAGED) { |
||
| 1664 | throw ORMInvalidArgumentException::entityNotManaged($entity); |
||
| 1665 | } |
||
| 1666 | $this->getEntityPersister($class->name)->refresh( |
||
| 1667 | array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), |
||
| 1668 | $entity |
||
| 1669 | ); |
||
| 1670 | $this->cascadeRefresh($entity, $visited); |
||
| 1671 | } |
||
| 1672 | |||
| 1673 | /** |
||
| 1674 | * Cascades a refresh operation to associated entities. |
||
| 1675 | * |
||
| 1676 | * @param object $entity |
||
| 1677 | * @param array $visited |
||
| 1678 | * |
||
| 1679 | * @return void |
||
| 1680 | */ |
||
| 1681 | View Code Duplication | private function cascadeRefresh($entity, array &$visited) |
|
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1682 | { |
||
| 1683 | $class = $this->em->getClassMetadata(get_class($entity)); |
||
| 1684 | $associationMappings = array_filter( |
||
| 1685 | $class->associationMappings, |
||
| 1686 | function ($assoc) { |
||
| 1687 | return $assoc['isCascadeRefresh']; |
||
| 1688 | } |
||
| 1689 | ); |
||
| 1690 | foreach ($associationMappings as $assoc) { |
||
| 1691 | $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); |
||
| 1692 | switch (true) { |
||
| 1693 | case ($relatedEntities instanceof PersistentCollection): |
||
|
0 ignored issues
–
show
The class
Bankiru\Api\Doctrine\PersistentCollection does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. Loading history...
|
|||
| 1694 | // Unwrap so that foreach() does not initialize |
||
| 1695 | $relatedEntities = $relatedEntities->unwrap(); |
||
| 1696 | // break; is commented intentionally! |
||
| 1697 | case ($relatedEntities instanceof Collection): |
||
| 1698 | case (is_array($relatedEntities)): |
||
| 1699 | foreach ($relatedEntities as $relatedEntity) { |
||
| 1700 | $this->doRefresh($relatedEntity, $visited); |
||
| 1701 | } |
||
| 1702 | break; |
||
| 1703 | case ($relatedEntities !== null): |
||
| 1704 | $this->doRefresh($relatedEntities, $visited); |
||
| 1705 | break; |
||
| 1706 | default: |
||
| 1707 | // Do nothing |
||
| 1708 | } |
||
| 1709 | } |
||
| 1710 | } |
||
| 1711 | |||
| 1712 | /** |
||
| 1713 | * Cascades a detach operation to associated entities. |
||
| 1714 | * |
||
| 1715 | * @param object $entity |
||
| 1716 | * @param array $visited |
||
| 1717 | * |
||
| 1718 | * @return void |
||
| 1719 | */ |
||
| 1720 | View Code Duplication | private function cascadeDetach($entity, array &$visited) |
|
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1721 | { |
||
| 1722 | $class = $this->em->getClassMetadata(get_class($entity)); |
||
| 1723 | $associationMappings = array_filter( |
||
| 1724 | $class->associationMappings, |
||
| 1725 | function ($assoc) { |
||
| 1726 | return $assoc['isCascadeDetach']; |
||
| 1727 | } |
||
| 1728 | ); |
||
| 1729 | foreach ($associationMappings as $assoc) { |
||
| 1730 | $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); |
||
| 1731 | switch (true) { |
||
| 1732 | case ($relatedEntities instanceof PersistentCollection): |
||
|
0 ignored issues
–
show
The class
Bankiru\Api\Doctrine\PersistentCollection does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. Loading history...
|
|||
| 1733 | // Unwrap so that foreach() does not initialize |
||
| 1734 | $relatedEntities = $relatedEntities->unwrap(); |
||
| 1735 | // break; is commented intentionally! |
||
| 1736 | case ($relatedEntities instanceof Collection): |
||
| 1737 | case (is_array($relatedEntities)): |
||
| 1738 | foreach ($relatedEntities as $relatedEntity) { |
||
| 1739 | $this->doDetach($relatedEntity, $visited); |
||
| 1740 | } |
||
| 1741 | break; |
||
| 1742 | case ($relatedEntities !== null): |
||
| 1743 | $this->doDetach($relatedEntities, $visited); |
||
| 1744 | break; |
||
| 1745 | default: |
||
| 1746 | // Do nothing |
||
| 1747 | } |
||
| 1748 | } |
||
| 1749 | } |
||
| 1750 | |||
| 1751 | /** |
||
| 1752 | * Cascades a merge operation to associated entities. |
||
| 1753 | * |
||
| 1754 | * @param object $entity |
||
| 1755 | * @param object $managedCopy |
||
| 1756 | * @param array $visited |
||
| 1757 | * |
||
| 1758 | * @return void |
||
| 1759 | */ |
||
| 1760 | private function cascadeMerge($entity, $managedCopy, array &$visited) |
||
|
0 ignored issues
–
show
|
|||
| 1761 | { |
||
| 1762 | $class = $this->manager->getClassMetadata(get_class($entity)); |
||
| 1763 | $associationMappings = array_filter( |
||
| 1764 | $class->getAssociationMappings(), |
||
| 1765 | function ($assoc) { |
||
| 1766 | return $assoc['isCascadeMerge']; |
||
| 1767 | } |
||
| 1768 | ); |
||
| 1769 | foreach ($associationMappings as $assoc) { |
||
| 1770 | $relatedEntities = $class->getReflectionProperty($assoc['field'])->getValue($entity); |
||
| 1771 | if ($relatedEntities instanceof Collection) { |
||
| 1772 | if ($relatedEntities === $class->getReflectionProperty($assoc['field'])->getValue($managedCopy)) { |
||
| 1773 | continue; |
||
| 1774 | } |
||
| 1775 | if ($relatedEntities instanceof ApiCollection) { |
||
| 1776 | // Unwrap so that foreach() does not initialize |
||
| 1777 | $relatedEntities = $relatedEntities->unwrap(); |
||
| 1778 | } |
||
| 1779 | foreach ($relatedEntities as $relatedEntity) { |
||
| 1780 | $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); |
||
|
0 ignored issues
–
show
The method
doMerge() does not seem to exist on object<Bankiru\Api\Doctrine\UnitOfWork>.
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...
|
|||
| 1781 | } |
||
| 1782 | } else { |
||
| 1783 | if ($relatedEntities !== null) { |
||
| 1784 | $this->doMerge($relatedEntities, $visited, $managedCopy, $assoc); |
||
|
0 ignored issues
–
show
The method
doMerge() does not seem to exist on object<Bankiru\Api\Doctrine\UnitOfWork>.
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...
|
|||
| 1785 | } |
||
| 1786 | } |
||
| 1787 | } |
||
| 1788 | } |
||
| 1789 | |||
| 1790 | /** |
||
| 1791 | * Cascades the delete operation to associated entities. |
||
| 1792 | * |
||
| 1793 | * @param object $entity |
||
| 1794 | * @param array $visited |
||
| 1795 | * |
||
| 1796 | * @return void |
||
| 1797 | */ |
||
| 1798 | private function cascadeRemove($entity, array &$visited) |
||
|
0 ignored issues
–
show
|
|||
| 1799 | { |
||
| 1800 | $class = $this->em->getClassMetadata(get_class($entity)); |
||
| 1801 | $associationMappings = array_filter( |
||
| 1802 | $class->associationMappings, |
||
| 1803 | function ($assoc) { |
||
| 1804 | return $assoc['isCascadeRemove']; |
||
| 1805 | } |
||
| 1806 | ); |
||
| 1807 | $entitiesToCascade = []; |
||
| 1808 | foreach ($associationMappings as $assoc) { |
||
| 1809 | if ($entity instanceof Proxy && !$entity->__isInitialized__) { |
||
|
0 ignored issues
–
show
Accessing
__isInitialized__ on the interface Doctrine\Common\Proxy\Proxy 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
Loading history...
|
|||
| 1810 | $entity->__load(); |
||
| 1811 | } |
||
| 1812 | $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); |
||
| 1813 | switch (true) { |
||
| 1814 | case ($relatedEntities instanceof Collection): |
||
| 1815 | case (is_array($relatedEntities)): |
||
| 1816 | // If its a PersistentCollection initialization is intended! No unwrap! |
||
| 1817 | foreach ($relatedEntities as $relatedEntity) { |
||
| 1818 | $entitiesToCascade[] = $relatedEntity; |
||
| 1819 | } |
||
| 1820 | break; |
||
| 1821 | case ($relatedEntities !== null): |
||
| 1822 | $entitiesToCascade[] = $relatedEntities; |
||
| 1823 | break; |
||
| 1824 | default: |
||
| 1825 | // Do nothing |
||
| 1826 | } |
||
| 1827 | } |
||
| 1828 | foreach ($entitiesToCascade as $relatedEntity) { |
||
| 1829 | $this->doRemove($relatedEntity, $visited); |
||
|
0 ignored issues
–
show
The method
doRemove() does not seem to exist on object<Bankiru\Api\Doctrine\UnitOfWork>.
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...
|
|||
| 1830 | } |
||
| 1831 | } |
||
| 1832 | |||
| 1833 | /** |
||
| 1834 | * Executes any extra updates that have been scheduled. |
||
| 1835 | */ |
||
| 1836 | private function executeExtraUpdates() |
||
| 1837 | { |
||
| 1838 | foreach ($this->extraUpdates as $oid => $update) { |
||
| 1839 | list ($entity, $changeset) = $update; |
||
| 1840 | $this->entityChangeSets[$oid] = $changeset; |
||
| 1841 | $this->getEntityPersister(get_class($entity))->update($entity); |
||
| 1842 | } |
||
| 1843 | $this->extraUpdates = []; |
||
| 1844 | } |
||
| 1845 | |||
| 1846 | private function executeDeletions(ApiMetadata $class) |
||
| 1847 | { |
||
| 1848 | $className = $class->getName(); |
||
| 1849 | $persister = $this->getEntityPersister($className); |
||
| 1850 | foreach ($this->entityDeletions as $oid => $entity) { |
||
| 1851 | if ($this->manager->getClassMetadata(get_class($entity))->name !== $className) { |
||
|
0 ignored issues
–
show
Accessing
name on the interface Bankiru\Api\Doctrine\Mapping\ApiMetadata 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
Loading history...
|
|||
| 1852 | continue; |
||
| 1853 | } |
||
| 1854 | $persister->delete($entity); |
||
| 1855 | unset( |
||
| 1856 | $this->entityDeletions[$oid], |
||
| 1857 | $this->entityIdentifiers[$oid], |
||
| 1858 | $this->originalEntityData[$oid], |
||
| 1859 | $this->entityStates[$oid] |
||
| 1860 | ); |
||
| 1861 | // Entity with this $oid after deletion treated as NEW, even if the $oid |
||
| 1862 | // is obtained by a new entity because the old one went out of scope. |
||
| 1863 | //$this->entityStates[$oid] = self::STATE_NEW; |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
62% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1864 | // if ( ! $class->isIdentifierNatural()) { |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
60% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1865 | // $class->getReflectionProperty($class->getIdentifierFieldNames()[0])->setValue($entity, null); |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
79% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 1866 | // } |
||
| 1867 | } |
||
| 1868 | } |
||
| 1869 | |||
| 1870 | /** |
||
| 1871 | * @param object $entity |
||
| 1872 | * @param object $managedCopy |
||
| 1873 | * |
||
| 1874 | * @throws ORMException |
||
| 1875 | * @throws OptimisticLockException |
||
| 1876 | * @throws TransactionRequiredException |
||
| 1877 | */ |
||
| 1878 | private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) |
||
|
0 ignored issues
–
show
|
|||
| 1879 | { |
||
| 1880 | $class = $this->em->getClassMetadata(get_class($entity)); |
||
| 1881 | foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) { |
||
|
0 ignored issues
–
show
The property
reflectionPropertiesGetter does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
Loading history...
|
|||
| 1882 | $name = $prop->name; |
||
| 1883 | $prop->setAccessible(true); |
||
| 1884 | if (!isset($class->associationMappings[$name])) { |
||
| 1885 | if (!$class->isIdentifier($name)) { |
||
| 1886 | $prop->setValue($managedCopy, $prop->getValue($entity)); |
||
| 1887 | } |
||
| 1888 | } else { |
||
| 1889 | $assoc2 = $class->associationMappings[$name]; |
||
| 1890 | if ($assoc2['type'] & ClassMetadata::TO_ONE) { |
||
| 1891 | $other = $prop->getValue($entity); |
||
| 1892 | if ($other === null) { |
||
| 1893 | $prop->setValue($managedCopy, null); |
||
| 1894 | } else { |
||
| 1895 | if ($other instanceof Proxy && !$other->__isInitialized()) { |
||
| 1896 | // do not merge fields marked lazy that have not been fetched. |
||
| 1897 | continue; |
||
| 1898 | } |
||
| 1899 | if (!$assoc2['isCascadeMerge']) { |
||
| 1900 | if ($this->getEntityState($other) === self::STATE_DETACHED) { |
||
| 1901 | $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); |
||
| 1902 | $relatedId = $targetClass->getIdentifierValues($other); |
||
| 1903 | if ($targetClass->subClasses) { |
||
| 1904 | $other = $this->em->find($targetClass->name, $relatedId); |
||
| 1905 | } else { |
||
| 1906 | $other = $this->em->getProxyFactory()->getProxy( |
||
| 1907 | $assoc2['targetEntity'], |
||
| 1908 | $relatedId |
||
| 1909 | ); |
||
| 1910 | $this->registerManaged($other, $relatedId, []); |
||
|
0 ignored issues
–
show
array() is of type array, but the function expects a null|object<stdClass>.
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
| 1911 | } |
||
| 1912 | } |
||
| 1913 | $prop->setValue($managedCopy, $other); |
||
| 1914 | } |
||
| 1915 | } |
||
| 1916 | } else { |
||
| 1917 | $mergeCol = $prop->getValue($entity); |
||
| 1918 | if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) { |
||
|
0 ignored issues
–
show
The class
Bankiru\Api\Doctrine\PersistentCollection does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. Loading history...
|
|||
| 1919 | // do not merge fields marked lazy that have not been fetched. |
||
| 1920 | // keep the lazy persistent collection of the managed copy. |
||
| 1921 | continue; |
||
| 1922 | } |
||
| 1923 | $managedCol = $prop->getValue($managedCopy); |
||
| 1924 | if (!$managedCol) { |
||
| 1925 | $managedCol = new PersistentCollection( |
||
| 1926 | $this->em, |
||
| 1927 | $this->em->getClassMetadata($assoc2['targetEntity']), |
||
| 1928 | new ArrayCollection |
||
| 1929 | ); |
||
| 1930 | $managedCol->setOwner($managedCopy, $assoc2); |
||
| 1931 | $prop->setValue($managedCopy, $managedCol); |
||
| 1932 | } |
||
| 1933 | if ($assoc2['isCascadeMerge']) { |
||
| 1934 | $managedCol->initialize(); |
||
| 1935 | // clear and set dirty a managed collection if its not also the same collection to merge from. |
||
| 1936 | if (!$managedCol->isEmpty() && $managedCol !== $mergeCol) { |
||
| 1937 | $managedCol->unwrap()->clear(); |
||
| 1938 | $managedCol->setDirty(true); |
||
| 1939 | if ($assoc2['isOwningSide'] |
||
| 1940 | && $assoc2['type'] == ClassMetadata::MANY_TO_MANY |
||
| 1941 | && $class->isChangeTrackingNotify() |
||
| 1942 | ) { |
||
| 1943 | $this->scheduleForDirtyCheck($managedCopy); |
||
| 1944 | } |
||
| 1945 | } |
||
| 1946 | } |
||
| 1947 | } |
||
| 1948 | } |
||
| 1949 | if ($class->isChangeTrackingNotify()) { |
||
| 1950 | // Just treat all properties as changed, there is no other choice. |
||
| 1951 | $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); |
||
| 1952 | } |
||
| 1953 | } |
||
| 1954 | } |
||
| 1955 | |||
| 1956 | } |
||
| 1957 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.