Complex classes like UnitOfWork often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use UnitOfWork, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 59 | class UnitOfWork implements PropertyChangedListener  | 
            ||
| 60 | { | 
            ||
| 61 | /**  | 
            ||
| 62 | * An entity is in MANAGED state when its persistence is managed by an EntityManager.  | 
            ||
| 63 | */  | 
            ||
| 64 | const STATE_MANAGED = 1;  | 
            ||
| 65 | |||
| 66 | /**  | 
            ||
| 67 | * An entity is new if it has just been instantiated (i.e. using the "new" operator)  | 
            ||
| 68 | * and is not (yet) managed by an EntityManager.  | 
            ||
| 69 | */  | 
            ||
| 70 | const STATE_NEW = 2;  | 
            ||
| 71 | |||
| 72 | /**  | 
            ||
| 73 | * A detached entity is an instance with persistent state and identity that is not  | 
            ||
| 74 | * (or no longer) associated with an EntityManager (and a UnitOfWork).  | 
            ||
| 75 | */  | 
            ||
| 76 | const STATE_DETACHED = 3;  | 
            ||
| 77 | |||
| 78 | /**  | 
            ||
| 79 | * A removed entity instance is an instance with a persistent identity,  | 
            ||
| 80 | * associated with an EntityManager, whose persistent state will be deleted  | 
            ||
| 81 | * on commit.  | 
            ||
| 82 | */  | 
            ||
| 83 | const STATE_REMOVED = 4;  | 
            ||
| 84 | |||
| 85 | /**  | 
            ||
| 86 | * Hint used to collect all primary keys of associated entities during hydration  | 
            ||
| 87 | * and execute it in a dedicated query afterwards  | 
            ||
| 88 | * @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql  | 
            ||
| 89 | */  | 
            ||
| 90 | const HINT_DEFEREAGERLOAD = 'deferEagerLoad';  | 
            ||
| 91 | |||
| 92 | /**  | 
            ||
| 93 | * The identity map that holds references to all managed entities that have  | 
            ||
| 94 | * an identity. The entities are grouped by their class name.  | 
            ||
| 95 | * Since all classes in a hierarchy must share the same identifier set,  | 
            ||
| 96 | * we always take the root class name of the hierarchy.  | 
            ||
| 97 | *  | 
            ||
| 98 | * @var array  | 
            ||
| 99 | */  | 
            ||
| 100 | private $identityMap = [];  | 
            ||
| 101 | |||
| 102 | /**  | 
            ||
| 103 | * Map of all identifiers of managed entities.  | 
            ||
| 104 | * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_hash).  | 
            ||
| 105 | * Values are maps of entity identifiers, where its key is the column name and the value is the raw value.  | 
            ||
| 106 | *  | 
            ||
| 107 | * @var array  | 
            ||
| 108 | */  | 
            ||
| 109 | private $entityIdentifiers = [];  | 
            ||
| 110 | |||
| 111 | /**  | 
            ||
| 112 | * Map of the original entity data of managed entities.  | 
            ||
| 113 | * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_hash).  | 
            ||
| 114 | * Values are maps of entity data, where its key is the field name and the value is the converted  | 
            ||
| 115 | * (convertToPHPValue) value.  | 
            ||
| 116 | * This structure is used for calculating changesets at commit time.  | 
            ||
| 117 | *  | 
            ||
| 118 | * Internal: Note that PHPs "copy-on-write" behavior helps a lot with memory usage.  | 
            ||
| 119 | * A value will only really be copied if the value in the entity is modified by the user.  | 
            ||
| 120 | *  | 
            ||
| 121 | * @var array  | 
            ||
| 122 | */  | 
            ||
| 123 | private $originalEntityData = [];  | 
            ||
| 124 | |||
| 125 | /**  | 
            ||
| 126 | * Map of entity changes. Keys are object ids (spl_object_hash).  | 
            ||
| 127 | * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.  | 
            ||
| 128 | *  | 
            ||
| 129 | * @var array  | 
            ||
| 130 | */  | 
            ||
| 131 | private $entityChangeSets = [];  | 
            ||
| 132 | |||
| 133 | /**  | 
            ||
| 134 | * The (cached) states of any known entities.  | 
            ||
| 135 | * Keys are object ids (spl_object_hash).  | 
            ||
| 136 | *  | 
            ||
| 137 | * @var array  | 
            ||
| 138 | */  | 
            ||
| 139 | private $entityStates = [];  | 
            ||
| 140 | |||
| 141 | /**  | 
            ||
| 142 | * Map of entities that are scheduled for dirty checking at commit time.  | 
            ||
| 143 | * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.  | 
            ||
| 144 | * Keys are object ids (spl_object_hash).  | 
            ||
| 145 | *  | 
            ||
| 146 | * @var array  | 
            ||
| 147 | */  | 
            ||
| 148 | private $scheduledForSynchronization = [];  | 
            ||
| 149 | |||
| 150 | /**  | 
            ||
| 151 | * A list of all pending entity insertions.  | 
            ||
| 152 | *  | 
            ||
| 153 | * @var array  | 
            ||
| 154 | */  | 
            ||
| 155 | private $entityInsertions = [];  | 
            ||
| 156 | |||
| 157 | /**  | 
            ||
| 158 | * A list of all pending entity updates.  | 
            ||
| 159 | *  | 
            ||
| 160 | * @var array  | 
            ||
| 161 | */  | 
            ||
| 162 | private $entityUpdates = [];  | 
            ||
| 163 | |||
| 164 | /**  | 
            ||
| 165 | * Any pending extra updates that have been scheduled by persisters.  | 
            ||
| 166 | *  | 
            ||
| 167 | * @var array  | 
            ||
| 168 | */  | 
            ||
| 169 | private $extraUpdates = [];  | 
            ||
| 170 | |||
| 171 | /**  | 
            ||
| 172 | * A list of all pending entity deletions.  | 
            ||
| 173 | *  | 
            ||
| 174 | * @var array  | 
            ||
| 175 | */  | 
            ||
| 176 | private $entityDeletions = [];  | 
            ||
| 177 | |||
| 178 | /**  | 
            ||
| 179 | * All pending collection deletions.  | 
            ||
| 180 | *  | 
            ||
| 181 | * @var array  | 
            ||
| 182 | */  | 
            ||
| 183 | private $collectionDeletions = [];  | 
            ||
| 184 | |||
| 185 | /**  | 
            ||
| 186 | * All pending collection updates.  | 
            ||
| 187 | *  | 
            ||
| 188 | * @var array  | 
            ||
| 189 | */  | 
            ||
| 190 | private $collectionUpdates = [];  | 
            ||
| 191 | |||
| 192 | /**  | 
            ||
| 193 | * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.  | 
            ||
| 194 | * At the end of the UnitOfWork all these collections will make new snapshots  | 
            ||
| 195 | * of their data.  | 
            ||
| 196 | *  | 
            ||
| 197 | * @var array  | 
            ||
| 198 | */  | 
            ||
| 199 | private $visitedCollections = [];  | 
            ||
| 200 | |||
| 201 | /**  | 
            ||
| 202 | * The EntityManager that "owns" this UnitOfWork instance.  | 
            ||
| 203 | *  | 
            ||
| 204 | * @var EntityManagerInterface  | 
            ||
| 205 | */  | 
            ||
| 206 | private $em;  | 
            ||
| 207 | |||
| 208 | /**  | 
            ||
| 209 | * The entity persister instances used to persist entity instances.  | 
            ||
| 210 | *  | 
            ||
| 211 | * @var array  | 
            ||
| 212 | */  | 
            ||
| 213 | private $entityPersisters = [];  | 
            ||
| 214 | |||
| 215 | /**  | 
            ||
| 216 | * The collection persister instances used to persist collections.  | 
            ||
| 217 | *  | 
            ||
| 218 | * @var array  | 
            ||
| 219 | */  | 
            ||
| 220 | private $collectionPersisters = [];  | 
            ||
| 221 | |||
| 222 | /**  | 
            ||
| 223 | * The EventManager used for dispatching events.  | 
            ||
| 224 | *  | 
            ||
| 225 | * @var \Doctrine\Common\EventManager  | 
            ||
| 226 | */  | 
            ||
| 227 | private $eventManager;  | 
            ||
| 228 | |||
| 229 | /**  | 
            ||
| 230 | * The ListenersInvoker used for dispatching events.  | 
            ||
| 231 | *  | 
            ||
| 232 | * @var \Doctrine\ORM\Event\ListenersInvoker  | 
            ||
| 233 | */  | 
            ||
| 234 | private $listenersInvoker;  | 
            ||
| 235 | |||
| 236 | /**  | 
            ||
| 237 | * @var Instantiator  | 
            ||
| 238 | */  | 
            ||
| 239 | private $instantiator;  | 
            ||
| 240 | |||
| 241 | /**  | 
            ||
| 242 | * Orphaned entities that are scheduled for removal.  | 
            ||
| 243 | *  | 
            ||
| 244 | * @var array  | 
            ||
| 245 | */  | 
            ||
| 246 | private $orphanRemovals = [];  | 
            ||
| 247 | |||
| 248 | /**  | 
            ||
| 249 | * Read-Only objects are never evaluated  | 
            ||
| 250 | *  | 
            ||
| 251 | * @var array  | 
            ||
| 252 | */  | 
            ||
| 253 | private $readOnlyObjects = [];  | 
            ||
| 254 | |||
| 255 | /**  | 
            ||
| 256 | * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.  | 
            ||
| 257 | *  | 
            ||
| 258 | * @var array  | 
            ||
| 259 | */  | 
            ||
| 260 | private $eagerLoadingEntities = [];  | 
            ||
| 261 | |||
| 262 | /**  | 
            ||
| 263 | * @var boolean  | 
            ||
| 264 | */  | 
            ||
| 265 | protected $hasCache = false;  | 
            ||
| 266 | |||
| 267 | /**  | 
            ||
| 268 | * Helper for handling completion of hydration  | 
            ||
| 269 | *  | 
            ||
| 270 | * @var HydrationCompleteHandler  | 
            ||
| 271 | */  | 
            ||
| 272 | private $hydrationCompleteHandler;  | 
            ||
| 273 | |||
| 274 | /**  | 
            ||
| 275 | * @var NormalizeIdentifier  | 
            ||
| 276 | */  | 
            ||
| 277 | private $normalizeIdentifier;  | 
            ||
| 278 | |||
| 279 | /**  | 
            ||
| 280 | * Initializes a new UnitOfWork instance, bound to the given EntityManager.  | 
            ||
| 281 | *  | 
            ||
| 282 | * @param EntityManagerInterface $em  | 
            ||
| 283 | */  | 
            ||
| 284 | public function __construct(EntityManagerInterface $em)  | 
            ||
| 285 |     { | 
            ||
| 286 | $this->em = $em;  | 
            ||
| 287 | $this->eventManager = $em->getEventManager();  | 
            ||
| 288 | $this->listenersInvoker = new ListenersInvoker($em);  | 
            ||
| 289 | $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled();  | 
            ||
| 290 | $this->instantiator = new Instantiator();  | 
            ||
| 291 | $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);  | 
            ||
| 292 | 2290 | $this->normalizeIdentifier = new NormalizeIdentifier();  | 
            |
| 293 | }  | 
            ||
| 294 | 2290 | ||
| 295 | 2290 | /**  | 
            |
| 296 | 2290 | * Commits the UnitOfWork, executing all operations that have been postponed  | 
            |
| 297 | 2290 | * up to this point. The state of all managed entities will be synchronized with  | 
            |
| 298 | 2290 | * the database.  | 
            |
| 299 | 2290 | *  | 
            |
| 300 | 2290 | * The operations are executed in the following order:  | 
            |
| 301 | 2290 | *  | 
            |
| 302 | * 1) All entity insertions  | 
            ||
| 303 | * 2) All entity updates  | 
            ||
| 304 | * 3) All collection deletions  | 
            ||
| 305 | * 4) All collection updates  | 
            ||
| 306 | * 5) All entity deletions  | 
            ||
| 307 | *  | 
            ||
| 308 | * @return void  | 
            ||
| 309 | *  | 
            ||
| 310 | * @throws \Exception  | 
            ||
| 311 | */  | 
            ||
| 312 | public function commit()  | 
            ||
| 313 |     { | 
            ||
| 314 | // Raise preFlush  | 
            ||
| 315 |         if ($this->eventManager->hasListeners(Events::preFlush)) { | 
            ||
| 316 | $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));  | 
            ||
| 317 | }  | 
            ||
| 318 | |||
| 319 | $this->computeChangeSets();  | 
            ||
| 320 | |||
| 321 | if ( ! ($this->entityInsertions ||  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 322 | 1015 | $this->entityDeletions ||  | 
            |
| 323 | $this->entityUpdates ||  | 
            ||
| 324 | $this->collectionUpdates ||  | 
            ||
| 325 | 1015 | $this->collectionDeletions ||  | 
            |
| 326 | 2 |                 $this->orphanRemovals)) { | 
            |
| 327 | $this->dispatchOnFlushEvent();  | 
            ||
| 328 | $this->dispatchPostFlushEvent();  | 
            ||
| 329 | |||
| 330 | 1015 | return; // Nothing to do.  | 
            |
| 331 | 1007 | }  | 
            |
| 332 | 16 | ||
| 333 | 15 |         if ($this->orphanRemovals) { | 
            |
| 334 | 1 |             foreach ($this->orphanRemovals as $orphan) { | 
            |
| 335 | 1 | $this->remove($orphan);  | 
            |
| 336 | 1 | }  | 
            |
| 337 | }  | 
            ||
| 338 | |||
| 339 | $this->dispatchOnFlushEvent();  | 
            ||
| 340 | 1012 | ||
| 341 | 165 | // Now we need a commit order to maintain referential integrity  | 
            |
| 342 | 129 | $commitOrder = $this->getCommitOrder();  | 
            |
| 343 | 40 | ||
| 344 | 37 | $conn = $this->em->getConnection();  | 
            |
| 345 | 1012 | $conn->beginTransaction();  | 
            |
| 346 | 25 | ||
| 347 | 25 |         try { | 
            |
| 348 | // Collection deletions (deletions of complete collections)  | 
            ||
| 349 | 25 |             foreach ($this->collectionDeletions as $collectionToDelete) { | 
            |
| 350 | $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);  | 
            ||
| 351 | }  | 
            ||
| 352 | 1008 | ||
| 353 | 16 |             if ($this->entityInsertions) { | 
            |
| 354 | 16 |                 foreach ($commitOrder as $class) { | 
            |
| 355 | $this->executeInserts($class);  | 
            ||
| 356 | }  | 
            ||
| 357 | }  | 
            ||
| 358 | 1008 | ||
| 359 |             if ($this->entityUpdates) { | 
            ||
| 360 |                 foreach ($commitOrder as $class) { | 
            ||
| 361 | 1008 | $this->executeUpdates($class);  | 
            |
| 362 | }  | 
            ||
| 363 | 1008 | }  | 
            |
| 364 | 1008 | ||
| 365 | // Extra updates that were requested by persisters.  | 
            ||
| 366 |             if ($this->extraUpdates) { | 
            ||
| 367 | $this->executeExtraUpdates();  | 
            ||
| 368 | 1008 | }  | 
            |
| 369 | 19 | ||
| 370 | // Collection updates (deleteRows, updateRows, insertRows)  | 
            ||
| 371 |             foreach ($this->collectionUpdates as $collectionToUpdate) { | 
            ||
| 372 | 1008 | $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);  | 
            |
| 373 | 1004 | }  | 
            |
| 374 | 1004 | ||
| 375 | // Entity deletions come last and need to be in reverse commit order  | 
            ||
| 376 |             if ($this->entityDeletions) { | 
            ||
| 377 |                 foreach (array_reverse($commitOrder) as $committedEntityName) { | 
            ||
| 378 | 1007 |                     if (! $this->entityDeletions) { | 
            |
| 379 | 115 | break; // just a performance optimisation  | 
            |
| 380 | 115 | }  | 
            |
| 381 | |||
| 382 | $this->executeDeletions($committedEntityName);  | 
            ||
| 383 | }  | 
            ||
| 384 | }  | 
            ||
| 385 | 1003 | ||
| 386 | 40 | $conn->commit();  | 
            |
| 387 |         } catch (Exception $e) { | 
            ||
| 388 | $this->em->close();  | 
            ||
| 389 | $conn->rollBack();  | 
            ||
| 390 | 1003 | ||
| 391 | 531 | $this->afterTransactionRolledBack();  | 
            |
| 392 | |||
| 393 | throw $e;  | 
            ||
| 394 | }  | 
            ||
| 395 | 1003 | ||
| 396 | 62 | $this->afterTransactionComplete();  | 
            |
| 397 | 62 | ||
| 398 | // Take new snapshots from visited collections  | 
            ||
| 399 |         foreach ($this->visitedCollections as $coll) { | 
            ||
| 400 | $coll->takeSnapshot();  | 
            ||
| 401 | 1003 | }  | 
            |
| 402 | 11 | ||
| 403 | 11 | $this->dispatchPostFlushEvent();  | 
            |
| 404 | 11 | ||
| 405 | // Clear up  | 
            ||
| 406 | 11 | $this->entityInsertions =  | 
            |
| 407 | $this->entityUpdates =  | 
            ||
| 408 | 11 | $this->entityDeletions =  | 
            |
| 409 | $this->extraUpdates =  | 
            ||
| 410 | $this->entityChangeSets =  | 
            ||
| 411 | 1003 | $this->collectionUpdates =  | 
            |
| 412 | $this->collectionDeletions =  | 
            ||
| 413 | $this->visitedCollections =  | 
            ||
| 414 | 1003 | $this->scheduledForSynchronization =  | 
            |
| 415 | 530 | $this->orphanRemovals = [];  | 
            |
| 416 | }  | 
            ||
| 417 | |||
| 418 | 1003 | /**  | 
            |
| 419 | * Computes the changesets of all entities scheduled for insertion.  | 
            ||
| 420 | *  | 
            ||
| 421 | 1002 | * @return void  | 
            |
| 422 | 1002 | */  | 
            |
| 423 | 1002 | private function computeScheduleInsertsChangeSets()  | 
            |
| 424 | 1002 |     { | 
            |
| 425 | 1002 |         foreach ($this->entityInsertions as $entity) { | 
            |
| 426 | 1002 | $class = $this->em->getClassMetadata(get_class($entity));  | 
            |
| 427 | 1002 | ||
| 428 | 1002 | $this->computeChangeSet($class, $entity);  | 
            |
| 429 | 1002 | }  | 
            |
| 430 | 1002 | }  | 
            |
| 431 | 1002 | ||
| 432 | /**  | 
            ||
| 433 | * Executes any extra updates that have been scheduled.  | 
            ||
| 434 | */  | 
            ||
| 435 | private function executeExtraUpdates()  | 
            ||
| 436 |     { | 
            ||
| 437 |         foreach ($this->extraUpdates as $oid => $update) { | 
            ||
| 438 | 1014 | list ($entity, $changeset) = $update;  | 
            |
| 439 | |||
| 440 | 1014 | $this->entityChangeSets[$oid] = $changeset;  | 
            |
| 441 | 1006 | ||
| 442 | // echo 'Extra update: ';  | 
            ||
| 443 | 1006 | // \Doctrine\Common\Util\Debug::dump($changeset, 3);  | 
            |
| 444 | |||
| 445 | 1012 | $this->getEntityPersister(get_class($entity))->update($entity);  | 
            |
| 446 | }  | 
            ||
| 447 | |||
| 448 | $this->extraUpdates = [];  | 
            ||
| 449 | }  | 
            ||
| 450 | |||
| 451 | /**  | 
            ||
| 452 | * Gets the changeset for an entity.  | 
            ||
| 453 | *  | 
            ||
| 454 | * @param object $entity  | 
            ||
| 455 | *  | 
            ||
| 456 | * @return array  | 
            ||
| 457 | */  | 
            ||
| 458 | public function & getEntityChangeSet($entity)  | 
            ||
| 459 |     { | 
            ||
| 460 | $oid = spl_object_hash($entity);  | 
            ||
| 461 | 16 | $data = [];  | 
            |
| 462 | |||
| 463 | 16 |         if (!isset($this->entityChangeSets[$oid])) { | 
            |
| 464 | return $data;  | 
            ||
| 465 | 16 | }  | 
            |
| 466 | 1 | ||
| 467 | return $this->entityChangeSets[$oid];  | 
            ||
| 468 | }  | 
            ||
| 469 | 15 | ||
| 470 | /**  | 
            ||
| 471 | 15 | * Computes the changes that happened to a single entity.  | 
            |
| 472 | 14 | *  | 
            |
| 473 | * Modifies/populates the following properties:  | 
            ||
| 474 | *  | 
            ||
| 475 |      * {@link originalEntityData} | 
            ||
| 476 | 15 | * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)  | 
            |
| 477 | * then it was not fetched from the database and therefore we have no original  | 
            ||
| 478 | 15 | * entity data yet. All of the current entity data is stored as the original entity data.  | 
            |
| 479 | *  | 
            ||
| 480 |      * {@link entityChangeSets} | 
            ||
| 481 | * The changes detected on all properties of the entity are stored there.  | 
            ||
| 482 | * A change is a tuple array where the first entry is the old value and the second  | 
            ||
| 483 | 15 | * entry is the new value of the property. Changesets are used by persisters  | 
            |
| 484 | 2 | * to INSERT/UPDATE the persistent entity state.  | 
            |
| 485 | *  | 
            ||
| 486 |      * {@link entityUpdates} | 
            ||
| 487 | * If the entity is already fully MANAGED (has been fetched from the database before)  | 
            ||
| 488 | 13 | * and any changes to its properties are detected, then a reference to the entity is stored  | 
            |
| 489 | * there to mark it for an update.  | 
            ||
| 490 | 13 | *  | 
            |
| 491 | 6 |      * {@link collectionDeletions} | 
            |
| 492 | * If a PersistentCollection has been de-referenced in a fully MANAGED entity,  | 
            ||
| 493 | 12 | * then this collection is marked for deletion.  | 
            |
| 494 | *  | 
            ||
| 495 | * @ignore  | 
            ||
| 496 | *  | 
            ||
| 497 | * @internal Don't call from the outside.  | 
            ||
| 498 | 40 | *  | 
            |
| 499 | * @param ClassMetadata $class The class descriptor of the entity.  | 
            ||
| 500 | 40 | * @param object $entity The entity for which to compute the changes.  | 
            |
| 501 | 40 | *  | 
            |
| 502 | * @return void  | 
            ||
| 503 | 40 | */  | 
            |
| 504 | public function computeChangeSet(ClassMetadata $class, $entity)  | 
            ||
| 505 |     { | 
            ||
| 506 | $oid = spl_object_hash($entity);  | 
            ||
| 507 | |||
| 508 | 40 |         if (isset($this->readOnlyObjects[$oid])) { | 
            |
| 509 | return;  | 
            ||
| 510 | }  | 
            ||
| 511 | 40 | ||
| 512 | 40 |         if ($class->inheritanceType !== InheritanceType::NONE) { | 
            |
| 513 | $class = $this->em->getClassMetadata(get_class($entity));  | 
            ||
| 514 | }  | 
            ||
| 515 | |||
| 516 | $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;  | 
            ||
| 517 | |||
| 518 |         if ($invoke !== ListenersInvoker::INVOKE_NONE) { | 
            ||
| 519 | $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);  | 
            ||
| 520 | }  | 
            ||
| 521 | 1006 | ||
| 522 | $actualData = [];  | 
            ||
| 523 | 1006 | ||
| 524 | 1006 |         foreach ($class->getDeclaredPropertiesIterator() as $name => $property) { | 
            |
| 525 | $value = $property->getValue($entity);  | 
            ||
| 526 | 1006 | ||
| 527 | 1 |             if ($property instanceof ToManyAssociationMetadata && $value !== null) { | 
            |
| 528 |                 if ($value instanceof PersistentCollection && $value->getOwner() === $entity) { | 
            ||
| 529 | continue;  | 
            ||
| 530 | 1006 | }  | 
            |
| 531 | |||
| 532 | $value = $property->wrap($entity, $value, $this->em);  | 
            ||
| 533 | |||
| 534 | $property->setValue($entity, $value);  | 
            ||
| 535 | |||
| 536 | $actualData[$name] = $value;  | 
            ||
| 537 | |||
| 538 | continue;  | 
            ||
| 539 | }  | 
            ||
| 540 | |||
| 541 | if (  | 
            ||
| 542 | ( ! $class->isIdentifier($name)  | 
            ||
| 543 | || ! $class->getProperty($name) instanceof FieldMetadata  | 
            ||
| 544 | || ! $class->getProperty($name)->hasValueGenerator()  | 
            ||
| 545 | || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY  | 
            ||
| 546 |                 ) && (! $class->isVersioned() || $name !== $class->versionProperty->getName())) { | 
            ||
| 547 | $actualData[$name] = $value;  | 
            ||
| 548 | }  | 
            ||
| 549 | }  | 
            ||
| 550 | |||
| 551 |         if ( ! isset($this->originalEntityData[$oid])) { | 
            ||
| 552 | // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).  | 
            ||
| 553 | // These result in an INSERT.  | 
            ||
| 554 | $this->originalEntityData[$oid] = $actualData;  | 
            ||
| 555 | $changeSet = [];  | 
            ||
| 556 | |||
| 557 |             foreach ($actualData as $propName => $actualValue) { | 
            ||
| 558 | $property = $class->getProperty($propName);  | 
            ||
| 559 | |||
| 560 | if (($property instanceof FieldMetadata) ||  | 
            ||
| 561 |                     ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) { | 
            ||
| 562 | $changeSet[$propName] = [null, $actualValue];  | 
            ||
| 563 | }  | 
            ||
| 564 | }  | 
            ||
| 565 | |||
| 566 | $this->entityChangeSets[$oid] = $changeSet;  | 
            ||
| 567 | 1016 |         } else { | 
            |
| 568 | // Entity is "fully" MANAGED: it was already fully persisted before  | 
            ||
| 569 | 1016 | // and we have a copy of the original data  | 
            |
| 570 | $originalData = $this->originalEntityData[$oid];  | 
            ||
| 571 | 1016 | $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;  | 
            |
| 572 | 2 | $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))  | 
            |
| 573 | ? $this->entityChangeSets[$oid]  | 
            ||
| 574 | : [];  | 
            ||
| 575 | 1016 | ||
| 576 | 307 |             foreach ($actualData as $propName => $actualValue) { | 
            |
| 577 | // skip field, its a partially omitted one!  | 
            ||
| 578 |                 if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { | 
            ||
| 579 | 1016 | continue;  | 
            |
| 580 | }  | 
            ||
| 581 | 1016 | ||
| 582 | 137 | $orgValue = $originalData[$propName];  | 
            |
| 583 | |||
| 584 | // skip if value haven't changed  | 
            ||
| 585 | 1016 |                 if ($orgValue === $actualValue) { | 
            |
| 586 | continue;  | 
            ||
| 587 | 1016 | }  | 
            |
| 588 | 1016 | ||
| 589 | $property = $class->getProperty($propName);  | 
            ||
| 590 | 1016 | ||
| 591 | 777 | // Persistent collection was exchanged with the "originally"  | 
            |
| 592 | 200 | // created one. This can only mean it was cloned and replaced  | 
            |
| 593 | 200 | // on another entity.  | 
            |
| 594 |                 if ($actualValue instanceof PersistentCollection) { | 
            ||
| 595 | $owner = $actualValue->getOwner();  | 
            ||
| 596 | 5 | ||
| 597 |                     if ($owner === null) { // cloned | 
            ||
| 598 | $actualValue->setOwner($entity, $property);  | 
            ||
| 599 |                     } else if ($owner !== $entity) { // no clone, we have to fix | 
            ||
| 600 | 772 |                         if (! $actualValue->isInitialized()) { | 
            |
| 601 | 242 | $actualValue->initialize(); // we have to do this otherwise the cols share state  | 
            |
| 602 | }  | 
            ||
| 603 | |||
| 604 | 772 | $newValue = clone $actualValue;  | 
            |
| 605 | |||
| 606 | $newValue->setOwner($entity, $property);  | 
            ||
| 607 | 772 | ||
| 608 | 772 | $property->setValue($entity, $newValue);  | 
            |
| 609 | }  | 
            ||
| 610 | 772 | }  | 
            |
| 611 | 772 | ||
| 612 |                 switch (true) { | 
            ||
| 613 | 772 | case ($property instanceof FieldMetadata):  | 
            |
| 614 |                         if ($isChangeTrackingNotify) { | 
            ||
| 615 | 772 | // Continue inside switch behaves as break.  | 
            |
| 616 | // We are required to use continue 2, since we need to continue to next $actualData item  | 
            ||
| 617 | 772 | continue 2;  | 
            |
| 618 | }  | 
            ||
| 619 | |||
| 620 | 1016 | $changeSet[$propName] = [$orgValue, $actualValue];  | 
            |
| 621 | 1016 | break;  | 
            |
| 622 | 1016 | ||
| 623 | case ($property instanceof ToOneAssociationMetadata):  | 
            ||
| 624 |                         if ($property->isOwningSide()) { | 
            ||
| 625 | $changeSet[$propName] = [$orgValue, $actualValue];  | 
            ||
| 626 | 1016 | }  | 
            |
| 627 | |||
| 628 |                         if ($orgValue !== null && $property->isOrphanRemoval()) { | 
            ||
| 629 | 1012 | $this->scheduleOrphanRemoval($orgValue);  | 
            |
| 630 | 1012 | }  | 
            |
| 631 | |||
| 632 | 1012 | break;  | 
            |
| 633 | 996 | ||
| 634 | 945 | case ($property instanceof ToManyAssociationMetadata):  | 
            |
| 635 | // Check if original value exists  | 
            ||
| 636 | 945 |                         if ($orgValue instanceof PersistentCollection) { | 
            |
| 637 | // A PersistentCollection was de-referenced, so delete it.  | 
            ||
| 638 | $coid = spl_object_hash($orgValue);  | 
            ||
| 639 | 894 | ||
| 640 |                             if (!isset($this->collectionDeletions[$coid])) { | 
            ||
| 641 | 894 | $this->collectionDeletions[$coid] = $orgValue;  | 
            |
| 642 | 894 | $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored  | 
            |
| 643 | }  | 
            ||
| 644 | }  | 
            ||
| 645 | |||
| 646 | 1012 | break;  | 
            |
| 647 | |||
| 648 | default:  | 
            ||
| 649 | // Do nothing  | 
            ||
| 650 | 263 | }  | 
            |
| 651 | 263 | }  | 
            |
| 652 | 263 | ||
| 653 |             if ($changeSet) { | 
            ||
| 654 | 263 | $this->entityChangeSets[$oid] = $changeSet;  | 
            |
| 655 | $this->originalEntityData[$oid] = $actualData;  | 
            ||
| 656 | 263 | $this->entityUpdates[$oid] = $entity;  | 
            |
| 657 | }  | 
            ||
| 658 | 248 | }  | 
            |
| 659 | 7 | ||
| 660 | // Look for changes in associations of the entity  | 
            ||
| 661 |         foreach ($class->getDeclaredPropertiesIterator() as $property) { | 
            ||
| 662 | 248 |             if (! ($property instanceof AssociationMetadata) || ($value = $property->getValue($entity)) === null) { | 
            |
| 663 | continue;  | 
            ||
| 664 | }  | 
            ||
| 665 | 248 | ||
| 666 | 232 | $this->computeAssociationChanges($property, $value);  | 
            |
| 667 | |||
| 668 | if ($property instanceof ManyToManyAssociationMetadata &&  | 
            ||
| 669 | $value instanceof PersistentCollection &&  | 
            ||
| 670 | 111 | ! isset($this->entityChangeSets[$oid]) &&  | 
            |
| 671 | 57 | $property->isOwningSide() &&  | 
            |
| 672 |                 $value->isDirty()) { | 
            ||
| 673 | |||
| 674 | $this->entityChangeSets[$oid] = [];  | 
            ||
| 675 | 57 | $this->originalEntityData[$oid] = $actualData;  | 
            |
| 676 | $this->entityUpdates[$oid] = $entity;  | 
            ||
| 677 | 57 | }  | 
            |
| 678 | }  | 
            ||
| 679 | }  | 
            ||
| 680 | 58 | ||
| 681 | /**  | 
            ||
| 682 | * Computes all the changes that have been done to entities and collections  | 
            ||
| 683 | * since the last commit and stores these changes in the _entityChangeSet map  | 
            ||
| 684 | * temporarily for access by the persisters, until the UoW commit is finished.  | 
            ||
| 685 | 58 | *  | 
            |
| 686 | 8 | * @return void  | 
            |
| 687 | 8 | */  | 
            |
| 688 | public function computeChangeSets()  | 
            ||
| 689 | 8 |     { | 
            |
| 690 | // Compute changes for INSERTed entities first. This must always happen.  | 
            ||
| 691 | $this->computeScheduleInsertsChangeSets();  | 
            ||
| 692 | |||
| 693 | // Compute changes for other MANAGED entities. Change tracking policies take effect here.  | 
            ||
| 694 |         foreach ($this->identityMap as $className => $entities) { | 
            ||
| 695 | $class = $this->em->getClassMetadata($className);  | 
            ||
| 696 | |||
| 697 | // Skip class if instances are read-only  | 
            ||
| 698 |             if ($class->isReadOnly()) { | 
            ||
| 699 | 58 | continue;  | 
            |
| 700 | }  | 
            ||
| 701 | 8 | ||
| 702 | // If change tracking is explicit or happens through notification, then only compute  | 
            ||
| 703 | 8 | // changes on entities of that type that are explicitly marked for synchronization.  | 
            |
| 704 |             switch (true) { | 
            ||
| 705 | case ($class->changeTrackingPolicy === ChangeTrackingPolicy::DEFERRED_IMPLICIT):  | 
            ||
| 706 | $entitiesToProcess = $entities;  | 
            ||
| 707 | 8 | break;  | 
            |
| 708 | 8 | ||
| 709 | case (isset($this->scheduledForSynchronization[$className])):  | 
            ||
| 710 | 8 | $entitiesToProcess = $this->scheduledForSynchronization[$className];  | 
            |
| 711 | break;  | 
            ||
| 712 | |||
| 713 | 50 | default:  | 
            |
| 714 | 49 | $entitiesToProcess = [];  | 
            |
| 715 | 21 | ||
| 716 | }  | 
            ||
| 717 | |||
| 718 | 49 |             foreach ($entitiesToProcess as $entity) { | 
            |
| 719 | 50 | // Ignore uninitialized proxy objects  | 
            |
| 720 |                 if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) { | 
            ||
| 721 | continue;  | 
            ||
| 722 | }  | 
            ||
| 723 | |||
| 724 | 263 | // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.  | 
            |
| 725 | 84 | $oid = spl_object_hash($entity);  | 
            |
| 726 | 84 | ||
| 727 | 84 |                 if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) { | 
            |
| 728 | $this->computeChangeSet($class, $entity);  | 
            ||
| 729 | }  | 
            ||
| 730 | }  | 
            ||
| 731 | }  | 
            ||
| 732 | 1016 | }  | 
            |
| 733 | 894 | ||
| 734 | 639 | /**  | 
            |
| 735 | * Computes the changes of an association.  | 
            ||
| 736 | *  | 
            ||
| 737 | 865 | * @param AssociationMetadata $association The association mapping.  | 
            |
| 738 | * @param mixed $value The value of the association.  | 
            ||
| 739 | 857 | *  | 
            |
| 740 | 857 | * @throws ORMInvalidArgumentException  | 
            |
| 741 | 857 | * @throws ORMException  | 
            |
| 742 | 857 | *  | 
            |
| 743 | 857 | * @return void  | 
            |
| 744 | */  | 
            ||
| 745 | 35 | private function computeAssociationChanges(AssociationMetadata $association, $value)  | 
            |
| 746 | 35 |     { | 
            |
| 747 | 857 |         if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) { | 
            |
| 748 | return;  | 
            ||
| 749 | }  | 
            ||
| 750 | 1008 | ||
| 751 |         if ($value instanceof PersistentCollection && $value->isDirty()) { | 
            ||
| 752 | $coid = spl_object_hash($value);  | 
            ||
| 753 | |||
| 754 | $this->collectionUpdates[$coid] = $value;  | 
            ||
| 755 | $this->visitedCollections[$coid] = $value;  | 
            ||
| 756 | }  | 
            ||
| 757 | |||
| 758 | // Look through the entities, and in any of their associations,  | 
            ||
| 759 | 1007 |         // for transient (new) entities, recursively. ("Persistence by reachability") | 
            |
| 760 | // Unwrap. Uninitialized collections will simply be empty.  | 
            ||
| 761 | $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();  | 
            ||
| 762 | 1007 | $targetEntity = $association->getTargetEntity();  | 
            |
| 763 | $targetClass = $this->em->getClassMetadata($targetEntity);  | 
            ||
| 764 | |||
| 765 | 1005 |         foreach ($unwrappedValue as $key => $entry) { | 
            |
| 766 | 447 |             if (! ($entry instanceof $targetEntity)) { | 
            |
| 767 | throw ORMInvalidArgumentException::invalidAssociation($targetClass, $association, $entry);  | 
            ||
| 768 | }  | 
            ||
| 769 | 447 | ||
| 770 | 1 | $state = $this->getEntityState($entry, self::STATE_NEW);  | 
            |
| 771 | |||
| 772 |             if (! ($entry instanceof $targetEntity)) { | 
            ||
| 773 | throw ORMException::unexpectedAssociationValue(  | 
            ||
| 774 | $association->getSourceEntity(),  | 
            ||
| 775 | $association->getName(),  | 
            ||
| 776 | 446 | get_class($entry),  | 
            |
| 777 | 444 | $targetEntity  | 
            |
| 778 | 444 | );  | 
            |
| 779 | }  | 
            ||
| 780 | 3 | ||
| 781 | 3 |             switch ($state) { | 
            |
| 782 | 3 | case self::STATE_NEW:  | 
            |
| 783 |                     if ( ! in_array('persist', $association->getCascade())) { | 
            ||
| 784 | throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($association, $entry);  | 
            ||
| 785 | 1 | }  | 
            |
| 786 | |||
| 787 | $this->persistNew($targetClass, $entry);  | 
            ||
| 788 | $this->computeChangeSet($targetClass, $entry);  | 
            ||
| 789 | 446 | break;  | 
            |
| 790 | |||
| 791 | 426 | case self::STATE_REMOVED:  | 
            |
| 792 | 35 | // Consume the $value as array (it's either an array or an ArrayAccess)  | 
            |
| 793 | // and remove the element from Collection.  | 
            ||
| 794 |                     if ($association instanceof ToManyAssociationMetadata) { | 
            ||
| 795 | unset($value[$key]);  | 
            ||
| 796 | 425 | }  | 
            |
| 797 | break;  | 
            ||
| 798 | 425 | ||
| 799 | 446 | case self::STATE_DETACHED:  | 
            |
| 800 | // Can actually not happen right now as we assume STATE_NEW,  | 
            ||
| 801 | // so the exception will be raised from the DBAL layer (constraint violation).  | 
            ||
| 802 | throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);  | 
            ||
| 803 | 1005 | break;  | 
            |
| 804 | |||
| 805 | default:  | 
            ||
| 806 | // MANAGED associated entities are already taken into account  | 
            ||
| 807 | // during changeset calculation anyway, since they are in the identity map.  | 
            ||
| 808 | }  | 
            ||
| 809 | }  | 
            ||
| 810 | }  | 
            ||
| 811 | |||
| 812 | /**  | 
            ||
| 813 | * @param \Doctrine\ORM\Mapping\ClassMetadata $class  | 
            ||
| 814 | * @param object $entity  | 
            ||
| 815 | *  | 
            ||
| 816 | 865 | * @return void  | 
            |
| 817 | */  | 
            ||
| 818 | 865 | private function persistNew($class, $entity)  | 
            |
| 819 | 28 |     { | 
            |
| 820 | $oid = spl_object_hash($entity);  | 
            ||
| 821 | $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);  | 
            ||
| 822 | 864 | ||
| 823 | 533 |         if ($invoke !== ListenersInvoker::INVOKE_NONE) { | 
            |
| 824 | $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);  | 
            ||
| 825 | 533 | }  | 
            |
| 826 | 533 | ||
| 827 | $generationPlan = $class->getValueGenerationPlan();  | 
            ||
| 828 | $persister = $this->getEntityPersister($class->getClassName());  | 
            ||
| 829 | $generationPlan->executeImmediate($this->em, $entity);  | 
            ||
| 830 | |||
| 831 |         if (! $generationPlan->containsDeferred()) { | 
            ||
| 832 | 864 | $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));  | 
            |
| 833 | 864 | $this->entityIdentifiers[$oid] = $id;  | 
            |
| 834 | }  | 
            ||
| 835 | 864 | ||
| 836 | 722 | $this->entityStates[$oid] = self::STATE_MANAGED;  | 
            |
| 837 | 6 | ||
| 838 | $this->scheduleForInsert($entity);  | 
            ||
| 839 | }  | 
            ||
| 840 | 716 | ||
| 841 | /**  | 
            ||
| 842 | 716 | * INTERNAL:  | 
            |
| 843 | * Computes the changeset of an individual entity, independently of the  | 
            ||
| 844 | * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().  | 
            ||
| 845 | *  | 
            ||
| 846 | * The passed entity must be a managed entity. If the entity already has a change set  | 
            ||
| 847 | 716 | * because this method is invoked during a commit cycle then the change sets are added.  | 
            |
| 848 | 39 | * whereby changes detected in this method prevail.  | 
            |
| 849 | 4 | *  | 
            |
| 850 | * @ignore  | 
            ||
| 851 | *  | 
            ||
| 852 | 35 | * @param ClassMetadata $class The class descriptor of the entity.  | 
            |
| 853 | 35 | * @param object $entity The entity for which to (re)calculate the change set.  | 
            |
| 854 | 35 | *  | 
            |
| 855 | * @return void  | 
            ||
| 856 | 710 | *  | 
            |
| 857 | * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.  | 
            ||
| 858 | * @throws \RuntimeException  | 
            ||
| 859 | 4 | */  | 
            |
| 860 | 3 | public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void  | 
            |
| 861 |     { | 
            ||
| 862 | 4 | $oid = spl_object_hash($entity);  | 
            |
| 863 | |||
| 864 | 710 |         if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) { | 
            |
| 865 | throw ORMInvalidArgumentException::entityNotManaged($entity);  | 
            ||
| 866 | }  | 
            ||
| 867 | |||
| 868 | // skip if change tracking is "NOTIFY"  | 
            ||
| 869 |         if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) { | 
            ||
| 870 | 713 | return;  | 
            |
| 871 | }  | 
            ||
| 872 | |||
| 873 |         if ($class->inheritanceType !== InheritanceType::NONE) { | 
            ||
| 874 | $class = $this->em->getClassMetadata(get_class($entity));  | 
            ||
| 875 | 856 | }  | 
            |
| 876 | |||
| 877 | $actualData = [];  | 
            ||
| 878 | |||
| 879 |         foreach ($class->getDeclaredPropertiesIterator() as $name => $property) { | 
            ||
| 880 |             switch (true) { | 
            ||
| 881 | case ($property instanceof VersionFieldMetadata):  | 
            ||
| 882 | // Ignore version field  | 
            ||
| 883 | 1031 | break;  | 
            |
| 884 | |||
| 885 | 1031 | case ($property instanceof FieldMetadata):  | 
            |
| 886 | 1031 | if (! $property->isPrimaryKey()  | 
            |
| 887 | || ! $property->getValueGenerator()  | 
            ||
| 888 | 1031 |                         || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) { | 
            |
| 889 | 139 | $actualData[$name] = $property->getValue($entity);  | 
            |
| 890 | }  | 
            ||
| 891 | |||
| 892 | 1031 | break;  | 
            |
| 893 | |||
| 894 | 1031 | case ($property instanceof ToOneAssociationMetadata):  | 
            |
| 895 | 269 | $actualData[$name] = $property->getValue($entity);  | 
            |
| 896 | break;  | 
            ||
| 897 | 269 | }  | 
            |
| 898 | 1 | }  | 
            |
| 899 | |||
| 900 | 1 |         if ( ! isset($this->originalEntityData[$oid])) { | 
            |
| 901 |             throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.'); | 
            ||
| 902 | }  | 
            ||
| 903 | 269 | ||
| 904 | $originalData = $this->originalEntityData[$oid];  | 
            ||
| 905 | $changeSet = [];  | 
            ||
| 906 | 1031 | ||
| 907 |         foreach ($actualData as $propName => $actualValue) { | 
            ||
| 908 | 1031 | $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;  | 
            |
| 909 | 1031 | ||
| 910 |             if ($orgValue !== $actualValue) { | 
            ||
| 911 | $changeSet[$propName] = [$orgValue, $actualValue];  | 
            ||
| 912 | }  | 
            ||
| 913 | }  | 
            ||
| 914 | |||
| 915 |         if ($changeSet) { | 
            ||
| 916 |             if (isset($this->entityChangeSets[$oid])) { | 
            ||
| 917 | $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);  | 
            ||
| 918 |             } else if ( ! isset($this->entityInsertions[$oid])) { | 
            ||
| 919 | $this->entityChangeSets[$oid] = $changeSet;  | 
            ||
| 920 | $this->entityUpdates[$oid] = $entity;  | 
            ||
| 921 | }  | 
            ||
| 922 | $this->originalEntityData[$oid] = $actualData;  | 
            ||
| 923 | }  | 
            ||
| 924 | }  | 
            ||
| 925 | |||
| 926 | /**  | 
            ||
| 927 | * Executes all entity insertions for entities of the specified type.  | 
            ||
| 928 | *  | 
            ||
| 929 | 16 | * @param ClassMetadata $class  | 
            |
| 930 | *  | 
            ||
| 931 | 16 | * @return void  | 
            |
| 932 | */  | 
            ||
| 933 | 16 | private function executeInserts(ClassMetadata $class) : void  | 
            |
| 934 |     { | 
            ||
| 935 | $className = $class->getClassName();  | 
            ||
| 936 | $persister = $this->getEntityPersister($className);  | 
            ||
| 937 | $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);  | 
            ||
| 938 | 16 | $generationPlan = $class->getValueGenerationPlan();  | 
            |
| 939 | |||
| 940 |         foreach ($this->entityInsertions as $oid => $entity) { | 
            ||
| 941 |             if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) { | 
            ||
| 942 | 16 | continue;  | 
            |
| 943 | 3 | }  | 
            |
| 944 | |||
| 945 | $persister->insert($entity);  | 
            ||
| 946 | 16 | ||
| 947 |             if ($generationPlan->containsDeferred()) { | 
            ||
| 948 | 16 | // Entity has post-insert IDs  | 
            |
| 949 | 16 | $oid = spl_object_hash($entity);  | 
            |
| 950 | 16 | $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));  | 
            |
| 951 | 16 | ||
| 952 | 16 | $this->entityIdentifiers[$oid] = $id;  | 
            |
| 953 | $this->entityStates[$oid] = self::STATE_MANAGED;  | 
            ||
| 954 | $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];  | 
            ||
| 955 | |||
| 956 | 16 | $this->addToIdentityMap($entity);  | 
            |
| 957 | }  | 
            ||
| 958 | |||
| 959 | unset($this->entityInsertions[$oid]);  | 
            ||
| 960 | 16 | ||
| 961 | 16 |             if ($invoke !== ListenersInvoker::INVOKE_NONE) { | 
            |
| 962 | $eventArgs = new LifecycleEventArgs($entity, $this->em);  | 
            ||
| 963 | 16 | ||
| 964 | 16 | $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);  | 
            |
| 965 | }  | 
            ||
| 966 | 16 | }  | 
            |
| 967 | 16 | }  | 
            |
| 968 | |||
| 969 | /**  | 
            ||
| 970 | * Executes all entity updates for entities of the specified type.  | 
            ||
| 971 | 16 | *  | 
            |
| 972 | 7 | * @param \Doctrine\ORM\Mapping\ClassMetadata $class  | 
            |
| 973 | 6 | *  | 
            |
| 974 | 1 | * @return void  | 
            |
| 975 | 1 | */  | 
            |
| 976 | 1 | private function executeUpdates($class)  | 
            |
| 977 |     { | 
            ||
| 978 | 7 | $className = $class->getClassName();  | 
            |
| 979 | $persister = $this->getEntityPersister($className);  | 
            ||
| 980 | 16 | $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);  | 
            |
| 981 | $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);  | 
            ||
| 982 | |||
| 983 |         foreach ($this->entityUpdates as $oid => $entity) { | 
            ||
| 984 |             if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) { | 
            ||
| 985 | continue;  | 
            ||
| 986 | }  | 
            ||
| 987 | |||
| 988 |             if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) { | 
            ||
| 989 | 1004 | $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);  | 
            |
| 990 | |||
| 991 | 1004 | $this->recomputeSingleEntityChangeSet($class, $entity);  | 
            |
| 992 | 1004 | }  | 
            |
| 993 | 1004 | ||
| 994 | 1004 |             if ( ! empty($this->entityChangeSets[$oid])) { | 
            |
| 995 | // echo 'Update: ';  | 
            ||
| 996 | 1004 | // \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);  | 
            |
| 997 | |||
| 998 | 1004 | $persister->update($entity);  | 
            |
| 999 | 855 | }  | 
            |
| 1000 | |||
| 1001 | unset($this->entityUpdates[$oid]);  | 
            ||
| 1002 | 1004 | ||
| 1003 |             if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) { | 
            ||
| 1004 | 1004 | $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);  | 
            |
| 1005 | }  | 
            ||
| 1006 | 1004 | }  | 
            |
| 1007 | 1004 | }  | 
            |
| 1008 | |||
| 1009 | /**  | 
            ||
| 1010 | * Executes all entity deletions for entities of the specified type.  | 
            ||
| 1011 | 1004 | *  | 
            |
| 1012 | * @param \Doctrine\ORM\Mapping\ClassMetadata $class  | 
            ||
| 1013 | 1004 | *  | 
            |
| 1014 | * @return void  | 
            ||
| 1015 | 918 | */  | 
            |
| 1016 | 918 | private function executeDeletions($class)  | 
            |
| 1017 | 918 |     { | 
            |
| 1018 | 918 | $className = $class->getClassName();  | 
            |
| 1019 | 918 | $persister = $this->getEntityPersister($className);  | 
            |
| 1020 | $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);  | 
            ||
| 1021 | 918 | ||
| 1022 |         foreach ($this->entityDeletions as $oid => $entity) { | 
            ||
| 1023 | 918 |             if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) { | 
            |
| 1024 | 918 | continue;  | 
            |
| 1025 | 918 | }  | 
            |
| 1026 | |||
| 1027 | 918 | $persister->delete($entity);  | 
            |
| 1028 | |||
| 1029 | unset(  | 
            ||
| 1030 | $this->entityDeletions[$oid],  | 
            ||
| 1031 | 1004 | $this->entityIdentifiers[$oid],  | 
            |
| 1032 | 135 | $this->originalEntityData[$oid],  | 
            |
| 1033 | $this->entityStates[$oid]  | 
            ||
| 1034 | 1004 | );  | 
            |
| 1035 | |||
| 1036 | // Entity with this $oid after deletion treated as NEW, even if the $oid  | 
            ||
| 1037 | // is obtained by a new entity because the old one went out of scope.  | 
            ||
| 1038 | //$this->entityStates[$oid] = self::STATE_NEW;  | 
            ||
| 1039 |             if (! $class->isIdentifierComposite()) { | 
            ||
| 1040 | $property = $class->getProperty($class->getSingleIdentifierFieldName());  | 
            ||
| 1041 | |||
| 1042 |                 if ($property instanceof FieldMetadata && $property->hasValueGenerator()) { | 
            ||
| 1043 | 115 | $property->setValue($entity, null);  | 
            |
| 1044 | }  | 
            ||
| 1045 | 115 | }  | 
            |
| 1046 | 115 | ||
| 1047 | 115 |             if ($invoke !== ListenersInvoker::INVOKE_NONE) { | 
            |
| 1048 | 115 | $eventArgs = new LifecycleEventArgs($entity, $this->em);  | 
            |
| 1049 | |||
| 1050 | 115 | $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);  | 
            |
| 1051 | 115 | }  | 
            |
| 1052 | 74 | }  | 
            |
| 1053 | }  | 
            ||
| 1054 | |||
| 1055 | 115 | /**  | 
            |
| 1056 | 13 | * Gets the commit order.  | 
            |
| 1057 | *  | 
            ||
| 1058 | 13 | * @return array  | 
            |
| 1059 | */  | 
            ||
| 1060 | private function getCommitOrder()  | 
            ||
| 1061 | 115 |     { | 
            |
| 1062 | $calc = new Internal\CommitOrderCalculator();  | 
            ||
| 1063 | |||
| 1064 | // See if there are any new classes in the changeset, that are not in the  | 
            ||
| 1065 | 81 | // commit order graph yet (don't have a node).  | 
            |
| 1066 | // We have to inspect changeSet to be able to correctly build dependencies.  | 
            ||
| 1067 | // It is not possible to use IdentityMap here because post inserted ids  | 
            ||
| 1068 | 111 | // are not yet available.  | 
            |
| 1069 | $newNodes = [];  | 
            ||
| 1070 | 111 | ||
| 1071 | 111 |         foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) { | 
            |
| 1072 | $class = $this->em->getClassMetadata(get_class($entity));  | 
            ||
| 1073 | |||
| 1074 | 111 |             if ($calc->hasNode($class->getClassName())) { | 
            |
| 1075 | continue;  | 
            ||
| 1076 | }  | 
            ||
| 1077 | |||
| 1078 | $calc->addNode($class->getClassName(), $class);  | 
            ||
| 1079 | |||
| 1080 | $newNodes[] = $class;  | 
            ||
| 1081 | }  | 
            ||
| 1082 | |||
| 1083 | 62 | // Calculate dependencies for new nodes  | 
            |
| 1084 |         while ($class = array_pop($newNodes)) { | 
            ||
| 1085 | 62 |             foreach ($class->getDeclaredPropertiesIterator() as $property) { | 
            |
| 1086 | 62 |                 if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) { | 
            |
| 1087 | 62 | continue;  | 
            |
| 1088 | }  | 
            ||
| 1089 | 62 | ||
| 1090 | 62 | $targetClass = $this->em->getClassMetadata($property->getTargetEntity());  | 
            |
| 1091 | 26 | ||
| 1092 |                 if ( ! $calc->hasNode($targetClass->getClassName())) { | 
            ||
| 1093 | $calc->addNode($targetClass->getClassName(), $targetClass);  | 
            ||
| 1094 | 62 | ||
| 1095 | $newNodes[] = $targetClass;  | 
            ||
| 1096 | }  | 
            ||
| 1097 | 62 | ||
| 1098 | 62 | $weight = ! array_filter(  | 
            |
| 1099 | 62 | $property->getJoinColumns(),  | 
            |
| 1100 | 62 |                     function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); } | 
            |
| 1101 | );  | 
            ||
| 1102 | |||
| 1103 | $calc->addDependency($targetClass->getClassName(), $class->getClassName(), $weight);  | 
            ||
| 1104 | |||
| 1105 | // If the target class has mapped subclasses, these share the same dependency.  | 
            ||
| 1106 | 62 |                 if ( ! $targetClass->getSubClasses()) { | 
            |
| 1107 | 52 | continue;  | 
            |
| 1108 | }  | 
            ||
| 1109 | |||
| 1110 | 62 |                 foreach ($targetClass->getSubClasses() as $subClassName) { | 
            |
| 1111 | 62 | $targetSubClass = $this->em->getClassMetadata($subClassName);  | 
            |
| 1112 | |||
| 1113 |                     if ( ! $calc->hasNode($subClassName)) { | 
            ||
| 1114 | 61 | $calc->addNode($targetSubClass->getClassName(), $targetSubClass);  | 
            |
| 1115 | |||
| 1116 | $newNodes[] = $targetSubClass;  | 
            ||
| 1117 | }  | 
            ||
| 1118 | |||
| 1119 | $calc->addDependency($targetSubClass->getClassName(), $class->getClassName(), 1);  | 
            ||
| 1120 | }  | 
            ||
| 1121 | }  | 
            ||
| 1122 | }  | 
            ||
| 1123 | 1008 | ||
| 1124 | return $calc->sort();  | 
            ||
| 1125 | 1008 | }  | 
            |
| 1126 | 1008 | ||
| 1127 | /**  | 
            ||
| 1128 | * Schedules an entity for insertion into the database.  | 
            ||
| 1129 | 1008 | * If the entity already has an identifier, it will be added to the identity map.  | 
            |
| 1130 | *  | 
            ||
| 1131 | * @param object $entity The entity to schedule for insertion.  | 
            ||
| 1132 | *  | 
            ||
| 1133 | * @return void  | 
            ||
| 1134 | *  | 
            ||
| 1135 | * @throws ORMInvalidArgumentException  | 
            ||
| 1136 | 1008 | * @throws \InvalidArgumentException  | 
            |
| 1137 | */  | 
            ||
| 1138 | 1008 | public function scheduleForInsert($entity)  | 
            |
| 1167 | 839 | ||
| 1168 | /**  | 
            ||
| 1169 | * Checks whether an entity is scheduled for insertion.  | 
            ||
| 1170 | 839 | *  | 
            |
| 1171 | 832 | * @param object $entity  | 
            |
| 1172 | *  | 
            ||
| 1173 | * @return boolean  | 
            ||
| 1174 | 217 | */  | 
            |
| 1175 | 217 | public function isScheduledForInsert($entity)  | 
            |
| 1179 | |||
| 1180 | 189 | /**  | 
            |
| 1181 | * Schedules an entity for being updated.  | 
            ||
| 1182 | *  | 
            ||
| 1183 | 217 | * @param object $entity The entity to schedule for being updated.  | 
            |
| 1184 | *  | 
            ||
| 1185 | * @return void  | 
            ||
| 1186 | *  | 
            ||
| 1187 | * @throws ORMInvalidArgumentException  | 
            ||
| 1188 | 1008 | */  | 
            |
| 1189 | public function scheduleForUpdate($entity) : void  | 
            ||
| 1205 | |||
| 1206 | 1032 | /**  | 
            |
| 1207 | * INTERNAL:  | 
            ||
| 1208 | * Schedules an extra update that will be executed immediately after the  | 
            ||
| 1209 | * regular entity updates within the currently running commit cycle.  | 
            ||
| 1210 | 1032 | *  | 
            |
| 1211 | 1 | * Extra updates for entities are stored as (entity, changeset) tuples.  | 
            |
| 1212 | *  | 
            ||
| 1213 | 1032 | * @ignore  | 
            |
| 1214 | 1 | *  | 
            |
| 1215 | * @param object $entity The entity for which to schedule an extra update.  | 
            ||
| 1216 | * @param array $changeset The changeset of the entity (what to update).  | 
            ||
| 1217 | 1032 | *  | 
            |
| 1218 | 1 | * @return void  | 
            |
| 1219 | */  | 
            ||
| 1220 | public function scheduleExtraUpdate($entity, array $changeset) : void  | 
            ||
| 1233 | |||
| 1234 | /**  | 
            ||
| 1235 | * Checks whether an entity is registered as dirty in the unit of work.  | 
            ||
| 1236 | * Note: Is not very useful currently as dirty entities are only registered  | 
            ||
| 1237 | * at commit time.  | 
            ||
| 1238 | *  | 
            ||
| 1239 | 631 | * @param object $entity  | 
            |
| 1240 | *  | 
            ||
| 1241 | 631 | * @return boolean  | 
            |
| 1242 | */  | 
            ||
| 1243 | public function isScheduledForUpdate($entity) : bool  | 
            ||
| 1247 | |||
| 1248 | /**  | 
            ||
| 1249 | * Checks whether an entity is registered to be checked in the unit of work.  | 
            ||
| 1250 | *  | 
            ||
| 1251 | * @param object $entity  | 
            ||
| 1252 | *  | 
            ||
| 1253 | 1 | * @return boolean  | 
            |
| 1254 | */  | 
            ||
| 1255 | 1 | public function isScheduledForDirtyCheck($entity) : bool  | 
            |
| 1261 | 1 | ||
| 1262 | /**  | 
            ||
| 1263 | * INTERNAL:  | 
            ||
| 1264 | * Schedules an entity for deletion.  | 
            ||
| 1265 | 1 | *  | 
            |
| 1266 | 1 | * @param object $entity  | 
            |
| 1267 | *  | 
            ||
| 1268 | 1 | * @return void  | 
            |
| 1269 | */  | 
            ||
| 1270 | public function scheduleForDelete($entity)  | 
            ||
| 1297 | |||
| 1298 | /**  | 
            ||
| 1299 | * Checks whether an entity is registered as removed/deleted with the unit  | 
            ||
| 1300 | * of work.  | 
            ||
| 1301 | *  | 
            ||
| 1302 | * @param object $entity  | 
            ||
| 1303 | *  | 
            ||
| 1304 | * @return boolean  | 
            ||
| 1305 | */  | 
            ||
| 1306 | public function isScheduledForDelete($entity)  | 
            ||
| 1310 | |||
| 1311 | /**  | 
            ||
| 1312 | * Checks whether an entity is scheduled for insertion, update or deletion.  | 
            ||
| 1313 | *  | 
            ||
| 1314 | * @param object $entity  | 
            ||
| 1315 | *  | 
            ||
| 1316 | * @return boolean  | 
            ||
| 1317 | */  | 
            ||
| 1318 | public function isEntityScheduled($entity)  | 
            ||
| 1326 | |||
| 1327 | /**  | 
            ||
| 1328 | * INTERNAL:  | 
            ||
| 1329 | * Registers an entity in the identity map.  | 
            ||
| 1330 | * Note that entities in a hierarchy are registered with the class name of  | 
            ||
| 1331 | * the root entity.  | 
            ||
| 1332 | *  | 
            ||
| 1333 | * @ignore  | 
            ||
| 1334 | 65 | *  | 
            |
| 1335 | * @param object $entity The entity to register.  | 
            ||
| 1336 | 65 | *  | 
            |
| 1337 | * @return boolean TRUE if the registration was successful, FALSE if the identity of  | 
            ||
| 1338 | 65 | * the entity in question is already managed.  | 
            |
| 1339 | 1 | *  | 
            |
| 1340 | * @throws ORMInvalidArgumentException  | 
            ||
| 1341 | */  | 
            ||
| 1342 | public function addToIdentityMap($entity)  | 
            ||
| 1362 | |||
| 1363 | /**  | 
            ||
| 1364 | * Gets the state of an entity with regard to the current unit of work.  | 
            ||
| 1365 | *  | 
            ||
| 1366 | * @param object $entity  | 
            ||
| 1367 | * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).  | 
            ||
| 1368 | * This parameter can be set to improve performance of entity state detection  | 
            ||
| 1369 | * by potentially avoiding a database lookup if the distinction between NEW and DETACHED  | 
            ||
| 1370 | 17 | * is either known or does not matter for the caller of the method.  | 
            |
| 1371 | *  | 
            ||
| 1372 | 17 | * @return int The entity state.  | 
            |
| 1373 | */  | 
            ||
| 1374 | public function getEntityState($entity, $assume = null)  | 
            ||
| 1446 | 1039 | ||
| 1447 | 1035 | /**  | 
            |
| 1448 | * INTERNAL:  | 
            ||
| 1449 | * Removes an entity from the identity map. This effectively detaches the  | 
            ||
| 1450 | * entity from the persistence management of Doctrine.  | 
            ||
| 1451 | *  | 
            ||
| 1452 | * @ignore  | 
            ||
| 1453 | *  | 
            ||
| 1454 | 13 | * @param object $entity  | 
            |
| 1455 | 13 | *  | 
            |
| 1456 | * @return boolean  | 
            ||
| 1457 | 13 | *  | 
            |
| 1458 | 5 | * @throws ORMInvalidArgumentException  | 
            |
| 1459 | */  | 
            ||
| 1460 | public function removeFromIdentityMap($entity)  | 
            ||
| 1483 | |||
| 1484 | 4 | /**  | 
            |
| 1485 | * INTERNAL:  | 
            ||
| 1486 | 5 | * Gets an entity in the identity map by its identifier hash.  | 
            |
| 1487 | *  | 
            ||
| 1488 | * @ignore  | 
            ||
| 1489 | *  | 
            ||
| 1490 | * @param string $idHash  | 
            ||
| 1491 | * @param string $rootClassName  | 
            ||
| 1492 | *  | 
            ||
| 1493 | * @return object  | 
            ||
| 1494 | */  | 
            ||
| 1495 | public function getByIdHash($idHash, $rootClassName)  | 
            ||
| 1499 | |||
| 1500 | /**  | 
            ||
| 1501 | * INTERNAL:  | 
            ||
| 1502 | * Tries to get an entity by its identifier hash. If no entity is found for  | 
            ||
| 1503 | * the given hash, FALSE is returned.  | 
            ||
| 1504 | 5 | *  | 
            |
| 1505 | * @ignore  | 
            ||
| 1506 | *  | 
            ||
| 1507 | * @param mixed $idHash (must be possible to cast it to string)  | 
            ||
| 1508 | * @param string $rootClassName  | 
            ||
| 1509 | *  | 
            ||
| 1510 | * @return object|bool The found entity or FALSE.  | 
            ||
| 1511 | */  | 
            ||
| 1512 | public function tryGetByIdHash($idHash, $rootClassName)  | 
            ||
| 1520 | |||
| 1521 | 76 | /**  | 
            |
| 1522 | * Checks whether an entity is registered in the identity map of this UnitOfWork.  | 
            ||
| 1523 | 76 | *  | 
            |
| 1524 | 76 | * @param object $entity  | 
            |
| 1525 | 76 | *  | 
            |
| 1526 | * @return boolean  | 
            ||
| 1527 | 76 | */  | 
            |
| 1528 | public function isInIdentityMap($entity)  | 
            ||
| 1541 | |||
| 1542 | /**  | 
            ||
| 1543 | * INTERNAL:  | 
            ||
| 1544 | * Checks whether an identifier hash exists in the identity map.  | 
            ||
| 1545 | *  | 
            ||
| 1546 | * @ignore  | 
            ||
| 1547 | *  | 
            ||
| 1548 | * @param string $idHash  | 
            ||
| 1549 | * @param string $rootClassName  | 
            ||
| 1550 | *  | 
            ||
| 1551 | * @return boolean  | 
            ||
| 1552 | */  | 
            ||
| 1553 | public function containsIdHash($idHash, $rootClassName)  | 
            ||
| 1557 | |||
| 1558 | 6 | /**  | 
            |
| 1559 | * Persists an entity as part of the current unit of work.  | 
            ||
| 1560 | *  | 
            ||
| 1561 | * @param object $entity The entity to persist.  | 
            ||
| 1562 | *  | 
            ||
| 1563 | * @return void  | 
            ||
| 1564 | */  | 
            ||
| 1565 | public function persist($entity)  | 
            ||
| 1571 | |||
| 1572 | /**  | 
            ||
| 1573 | 34 | * Persists an entity as part of the current unit of work.  | 
            |
| 1574 | *  | 
            ||
| 1575 | 34 | * This method is internally called during persist() cascades as it tracks  | 
            |
| 1576 | * the already visited entities to prevent infinite recursions.  | 
            ||
| 1577 | 34 | *  | 
            |
| 1578 | 34 | * @param object $entity The entity to persist.  | 
            |
| 1579 | 34 | * @param array $visited The already visited entities.  | 
            |
| 1580 | *  | 
            ||
| 1581 | * @return void  | 
            ||
| 1582 | *  | 
            ||
| 1583 | * @throws ORMInvalidArgumentException  | 
            ||
| 1584 | * @throws UnexpectedValueException  | 
            ||
| 1585 | */  | 
            ||
| 1586 | private function doPersist($entity, array &$visited)  | 
            ||
| 1634 | 1028 | ||
| 1635 | 1021 | /**  | 
            |
| 1636 | * Deletes an entity as part of the current unit of work.  | 
            ||
| 1637 | *  | 
            ||
| 1638 | * @param object $entity The entity to remove.  | 
            ||
| 1639 | *  | 
            ||
| 1640 | * @return void  | 
            ||
| 1641 | */  | 
            ||
| 1642 | public function remove($entity)  | 
            ||
| 1648 | |||
| 1649 | /**  | 
            ||
| 1650 | * Deletes an entity as part of the current unit of work.  | 
            ||
| 1651 | 1028 | *  | 
            |
| 1652 | * This method is internally called during delete() cascades as it tracks  | 
            ||
| 1653 | 1028 | * the already visited entities to prevent infinite recursions.  | 
            |
| 1654 | *  | 
            ||
| 1655 | 1028 | * @param object $entity The entity to delete.  | 
            |
| 1656 | 109 | * @param array $visited The map of the already visited entities.  | 
            |
| 1657 | *  | 
            ||
| 1658 | * @return void  | 
            ||
| 1659 | 1028 | *  | 
            |
| 1660 | * @throws ORMInvalidArgumentException If the instance is a detached entity.  | 
            ||
| 1661 | 1028 | * @throws UnexpectedValueException  | 
            |
| 1662 | */  | 
            ||
| 1663 | private function doRemove($entity, array &$visited)  | 
            ||
| 1703 | |||
| 1704 | /**  | 
            ||
| 1705 | * Refreshes the state of the given entity from the database, overwriting  | 
            ||
| 1706 | * any local, unpersisted changes.  | 
            ||
| 1707 | 64 | *  | 
            |
| 1708 | * @param object $entity The entity to refresh.  | 
            ||
| 1709 | 64 | *  | 
            |
| 1710 | * @return void  | 
            ||
| 1711 | 64 | *  | 
            |
| 1712 | 64 | * @throws InvalidArgumentException If the entity is not MANAGED.  | 
            |
| 1713 | */  | 
            ||
| 1714 | public function refresh($entity)  | 
            ||
| 1715 |     { | 
            ||
| 1716 | $visited = [];  | 
            ||
| 1717 | |||
| 1720 | |||
| 1721 | /**  | 
            ||
| 1722 | * Executes a refresh operation on an entity.  | 
            ||
| 1723 | *  | 
            ||
| 1724 | * @param object $entity The entity to refresh.  | 
            ||
| 1725 | * @param array $visited The already visited entities during cascades.  | 
            ||
| 1726 | *  | 
            ||
| 1727 | * @return void  | 
            ||
| 1728 | 64 | *  | 
            |
| 1729 | * @throws ORMInvalidArgumentException If the entity is not MANAGED.  | 
            ||
| 1730 | 64 | */  | 
            |
| 1731 | private function doRefresh($entity, array &$visited)  | 
            ||
| 1754 | 64 | ||
| 1755 | 8 | /**  | 
            |
| 1756 | * Cascades a refresh operation to associated entities.  | 
            ||
| 1757 | *  | 
            ||
| 1758 | 64 | * @param object $entity  | 
            |
| 1759 | 64 | * @param array $visited  | 
            |
| 1760 | *  | 
            ||
| 1761 | * @return void  | 
            ||
| 1762 | */  | 
            ||
| 1763 | private function cascadeRefresh($entity, array &$visited)  | 
            ||
| 1796 | |||
| 1797 | /**  | 
            ||
| 1798 | * Cascades the save operation to associated entities.  | 
            ||
| 1799 | *  | 
            ||
| 1800 | * @param object $entity  | 
            ||
| 1801 | * @param array $visited  | 
            ||
| 1802 | *  | 
            ||
| 1803 | 40 | * @return void  | 
            |
| 1804 | */  | 
            ||
| 1805 | 40 | private function cascadePersist($entity, array &$visited)  | 
            |
| 1862 | 1 | ||
| 1863 | /**  | 
            ||
| 1864 | 1 | * Cascades the delete operation to associated entities.  | 
            |
| 1865 | *  | 
            ||
| 1866 | * @param object $entity  | 
            ||
| 1867 | * @param array $visited  | 
            ||
| 1868 | 38 | *  | 
            |
| 1869 | 4 | * @return void  | 
            |
| 1870 | 4 | */  | 
            |
| 1871 | private function cascadeRemove($entity, array &$visited)  | 
            ||
| 1909 | |||
| 1910 | /**  | 
            ||
| 1911 | * Acquire a lock on the given entity.  | 
            ||
| 1912 | 38 | *  | 
            |
| 1913 | * @param object $entity  | 
            ||
| 1914 | 38 | * @param int $lockMode  | 
            |
| 1915 | * @param int $lockVersion  | 
            ||
| 1916 | *  | 
            ||
| 1917 | * @return void  | 
            ||
| 1918 | *  | 
            ||
| 1919 | * @throws ORMInvalidArgumentException  | 
            ||
| 1920 | * @throws TransactionRequiredException  | 
            ||
| 1921 | * @throws OptimisticLockException  | 
            ||
| 1922 | */  | 
            ||
| 1923 | public function lock($entity, $lockMode, $lockVersion = null)  | 
            ||
| 1976 | |||
| 1977 | 15 | /**  | 
            |
| 1978 | * Clears the UnitOfWork.  | 
            ||
| 1979 | *  | 
            ||
| 1980 | * @return void  | 
            ||
| 1981 | 15 | */  | 
            |
| 1982 | public function clear()  | 
            ||
| 2007 | |||
| 2008 | /**  | 
            ||
| 2009 | * INTERNAL:  | 
            ||
| 2010 | * Schedules an orphaned entity for removal. The remove() operation will be  | 
            ||
| 2011 | * invoked on that entity at the beginning of the next commit of this  | 
            ||
| 2012 | * UnitOfWork.  | 
            ||
| 2013 | *  | 
            ||
| 2014 | * @ignore  | 
            ||
| 2015 | *  | 
            ||
| 2016 | * @param object $entity  | 
            ||
| 2017 | *  | 
            ||
| 2018 | 16 | * @return void  | 
            |
| 2019 | */  | 
            ||
| 2020 | 16 | public function scheduleOrphanRemoval($entity)  | 
            |
| 2024 | |||
| 2025 | /**  | 
            ||
| 2026 | * INTERNAL:  | 
            ||
| 2027 | * Cancels a previously scheduled orphan removal.  | 
            ||
| 2028 | *  | 
            ||
| 2029 | * @ignore  | 
            ||
| 2030 | *  | 
            ||
| 2031 | * @param object $entity  | 
            ||
| 2032 | *  | 
            ||
| 2033 | * @return void  | 
            ||
| 2034 | */  | 
            ||
| 2035 | 16 | public function cancelOrphanRemoval($entity)  | 
            |
| 2039 | 16 | ||
| 2040 | /**  | 
            ||
| 2041 | * INTERNAL:  | 
            ||
| 2042 | * Schedules a complete collection for removal when this UnitOfWork commits.  | 
            ||
| 2043 | 16 | *  | 
            |
| 2044 | * @param PersistentCollection $coll  | 
            ||
| 2045 | 16 | *  | 
            |
| 2046 | * @return void  | 
            ||
| 2047 | 16 | */  | 
            |
| 2048 | public function scheduleCollectionDeletion(PersistentCollection $coll)  | 
            ||
| 2058 | |||
| 2059 | /**  | 
            ||
| 2060 | * @param PersistentCollection $coll  | 
            ||
| 2061 | *  | 
            ||
| 2062 | * @return bool  | 
            ||
| 2063 | */  | 
            ||
| 2064 | public function isCollectionScheduledForDeletion(PersistentCollection $coll)  | 
            ||
| 2068 | |||
| 2069 | 16 | /**  | 
            |
| 2070 | * INTERNAL:  | 
            ||
| 2071 | 16 | * Creates a new instance of the mapped class, without invoking the constructor.  | 
            |
| 2072 | 16 | * This is only meant to be used internally, and should not be consumed by end users.  | 
            |
| 2073 | *  | 
            ||
| 2074 | * @ignore  | 
            ||
| 2075 | *  | 
            ||
| 2076 | 16 | * @param ClassMetadata $class  | 
            |
| 2077 | 5 | *  | 
            |
| 2078 | * @return EntityManagerAware|object  | 
            ||
| 2079 | */  | 
            ||
| 2080 | 5 | public function newInstance(ClassMetadata $class)  | 
            |
| 2090 | 5 | ||
| 2091 | /**  | 
            ||
| 2092 | * INTERNAL:  | 
            ||
| 2093 | * Creates an entity. Used for reconstitution of persistent entities.  | 
            ||
| 2094 | *  | 
            ||
| 2095 | * Internal note: Highly performance-sensitive method.  | 
            ||
| 2096 | 5 | *  | 
            |
| 2097 | * @ignore  | 
            ||
| 2098 | *  | 
            ||
| 2099 | * @param string $className The name of the entity class.  | 
            ||
| 2100 | 16 | * @param array $data The data for the entity.  | 
            |
| 2101 | * @param array $hints Any hints to account for during reconstitution/lookup of the entity.  | 
            ||
| 2102 | *  | 
            ||
| 2103 | * @return object The managed entity instance.  | 
            ||
| 2104 | *  | 
            ||
| 2105 | * @todo Rename: getOrCreateEntity  | 
            ||
| 2106 | */  | 
            ||
| 2107 | public function createEntity($className, array $data, &$hints = [])  | 
            ||
| 2401 | 1218 | ||
| 2402 | 7 | /**  | 
            |
| 2403 | * @return void  | 
            ||
| 2404 | 1218 | */  | 
            |
| 2405 | public function triggerEagerLoads()  | 
            ||
| 2427 | |||
| 2428 | /**  | 
            ||
| 2429 | * Initializes (loads) an uninitialized persistent collection of an entity.  | 
            ||
| 2430 | *  | 
            ||
| 2431 | * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.  | 
            ||
| 2432 | *  | 
            ||
| 2433 | 112 | * @return void  | 
            |
| 2434 | *  | 
            ||
| 2435 | 112 | * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.  | 
            |
| 2436 | 112 | */  | 
            |
| 2437 | public function loadCollection(PersistentCollection $collection)  | 
            ||
| 2450 | |||
| 2451 | /**  | 
            ||
| 2452 | 13 | * Gets the identity map of the UnitOfWork.  | 
            |
| 2453 | *  | 
            ||
| 2454 | 13 | * @return array  | 
            |
| 2455 | 13 | */  | 
            |
| 2456 | public function getIdentityMap()  | 
            ||
| 2460 | |||
| 2461 | /**  | 
            ||
| 2462 | * Gets the original data of an entity. The original data is the data that was  | 
            ||
| 2463 | * present at the time the entity was reconstituted from the database.  | 
            ||
| 2464 | *  | 
            ||
| 2465 | * @param object $entity  | 
            ||
| 2466 | *  | 
            ||
| 2467 | * @return array  | 
            ||
| 2468 | */  | 
            ||
| 2469 | public function getOriginalEntityData($entity)  | 
            ||
| 2477 | 4 | ||
| 2478 | /**  | 
            ||
| 2479 | * @ignore  | 
            ||
| 2480 | 668 | *  | 
            |
| 2481 | * @param object $entity  | 
            ||
| 2482 | * @param array $data  | 
            ||
| 2483 | *  | 
            ||
| 2484 | * @return void  | 
            ||
| 2485 | */  | 
            ||
| 2486 | public function setOriginalEntityData($entity, array $data)  | 
            ||
| 2490 | |||
| 2491 | /**  | 
            ||
| 2492 | * INTERNAL:  | 
            ||
| 2493 | * Sets a property value of the original data array of an entity.  | 
            ||
| 2494 | *  | 
            ||
| 2495 | * @ignore  | 
            ||
| 2496 | *  | 
            ||
| 2497 | * @param string $oid  | 
            ||
| 2498 | * @param string $property  | 
            ||
| 2499 | 806 | * @param mixed $value  | 
            |
| 2500 | *  | 
            ||
| 2501 | 806 | * @return void  | 
            |
| 2502 | */  | 
            ||
| 2503 | public function setOriginalEntityProperty($oid, $property, $value)  | 
            ||
| 2507 | 806 | ||
| 2508 | 310 | /**  | 
            |
| 2509 | 310 | * Gets the identifier of an entity.  | 
            |
| 2510 | * The returned value is always an array of identifier values. If the entity  | 
            ||
| 2511 | * has a composite identifier then the identifier values are in the same  | 
            ||
| 2512 | 310 | * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().  | 
            |
| 2513 | 310 | *  | 
            |
| 2514 | 310 | * @param object $entity  | 
            |
| 2515 | 310 | *  | 
            |
| 2516 | 310 | * @return array The identifier values.  | 
            |
| 2517 | */  | 
            ||
| 2518 | public function getEntityIdentifier($entity)  | 
            ||
| 2522 | 2 | ||
| 2523 | 2 | /**  | 
            |
| 2524 | * Processes an entity instance to extract their identifier values.  | 
            ||
| 2525 | *  | 
            ||
| 2526 | 2 | * @param object $entity The entity instance.  | 
            |
| 2527 | *  | 
            ||
| 2528 | * @return mixed A scalar value.  | 
            ||
| 2529 | 308 | *  | 
            |
| 2530 | 21 | * @throws \Doctrine\ORM\ORMInvalidArgumentException  | 
            |
| 2531 | */  | 
            ||
| 2532 | 21 | public function getSingleIdentifierValue($entity)  | 
            |
| 2547 | |||
| 2548 | 111 | /**  | 
            |
| 2549 | 3 | * @param array $id  | 
            |
| 2550 | * @param string $rootClassName  | 
            ||
| 2551 | *  | 
            ||
| 2552 | 308 | * @return GhostObjectInterface|object  | 
            |
| 2553 | */  | 
            ||
| 2554 | private function tryGetByIdOrLoadProxy(array $id, string $rootClassName)  | 
            ||
| 2580 | |||
| 2581 | /**  | 
            ||
| 2582 | 702 | * Tries to find an entity with the given identifier in the identity map of  | 
            |
| 2583 | * this UnitOfWork.  | 
            ||
| 2584 | 702 | *  | 
            |
| 2585 | * @param mixed $id The entity identifier to look for.  | 
            ||
| 2586 | * @param string $rootClassName The name of the root class of the mapped entity hierarchy.  | 
            ||
| 2587 | *  | 
            ||
| 2588 | * @return object|bool Returns the entity with the specified identifier if it exists in  | 
            ||
| 2589 | 702 | * this UnitOfWork, FALSE otherwise.  | 
            |
| 2590 | 33 | */  | 
            |
| 2591 | public function tryGetById($id, $rootClassName)  | 
            ||
| 2599 | 564 | ||
| 2600 | /**  | 
            ||
| 2601 | * Schedules an entity for dirty-checking at commit-time.  | 
            ||
| 2602 | 564 | *  | 
            |
| 2603 | 485 | * @param object $entity The entity to schedule for dirty-checking.  | 
            |
| 2604 | *  | 
            ||
| 2605 | * @return void  | 
            ||
| 2606 | 64 | */  | 
            |
| 2607 | public function scheduleForSynchronization($entity)  | 
            ||
| 2613 | 2 | ||
| 2614 | /**  | 
            ||
| 2615 | * Checks whether the UnitOfWork has any pending insertions.  | 
            ||
| 2616 | *  | 
            ||
| 2617 | 62 | * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.  | 
            |
| 2618 | */  | 
            ||
| 2619 | 62 | public function hasPendingInsertions()  | 
            |
| 2623 | 485 | ||
| 2624 | 38 | /**  | 
            |
| 2625 | 38 | * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the  | 
            |
| 2626 | * number of entities in the identity map.  | 
            ||
| 2627 | 38 | *  | 
            |
| 2628 | * @return integer  | 
            ||
| 2629 | */  | 
            ||
| 2630 | 478 | public function size()  | 
            |
| 2634 | 478 | ||
| 2635 | 478 | /**  | 
            |
| 2636 | * Gets the EntityPersister for an Entity.  | 
            ||
| 2637 | 478 | *  | 
            |
| 2638 | * @param string $entityName The name of the Entity.  | 
            ||
| 2639 | 287 | *  | 
            |
| 2640 | * @return \Doctrine\ORM\Persisters\Entity\EntityPersister  | 
            ||
| 2641 | 287 | */  | 
            |
| 2642 | public function getEntityPersister($entityName)  | 
            ||
| 2678 | |||
| 2679 | /**  | 
            ||
| 2680 | * Gets a collection persister for a collection-valued association.  | 
            ||
| 2681 | 166 | *  | 
            |
| 2682 | * @param ToManyAssociationMetadata $association  | 
            ||
| 2683 | 192 | *  | 
            |
| 2684 | * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister  | 
            ||
| 2685 | */  | 
            ||
| 2686 | public function getCollectionPersister(ToManyAssociationMetadata $association)  | 
            ||
| 2711 | |||
| 2712 | 163 | /**  | 
            |
| 2713 | 163 | * INTERNAL:  | 
            |
| 2714 | 163 | * Registers an entity as managed.  | 
            |
| 2715 | *  | 
            ||
| 2716 | * @param object $entity The entity.  | 
            ||
| 2717 | 163 | * @param array $id Map containing identifier field names as key and its associated values.  | 
            |
| 2718 | 163 | * @param array $data The original entity data.  | 
            |
| 2719 | *  | 
            ||
| 2720 | * @return void  | 
            ||
| 2721 | */  | 
            ||
| 2722 | 163 | public function registerManaged($entity, array $id, array $data)  | 
            |
| 2737 | |||
| 2738 | /**  | 
            ||
| 2739 | 486 | * INTERNAL:  | 
            |
| 2740 | * Clears the property changeset of the entity with the given OID.  | 
            ||
| 2741 | *  | 
            ||
| 2742 | * @param string $oid The entity's OID.  | 
            ||
| 2743 | *  | 
            ||
| 2744 | 486 | * @return void  | 
            |
| 2745 | */  | 
            ||
| 2746 | 3 | public function clearEntityChangeSet($oid)  | 
            |
| 2750 | |||
| 2751 | 3 | /* PropertyChangedListener implementation */  | 
            |
| 2752 | |||
| 2753 | /**  | 
            ||
| 2754 | * Notifies this UnitOfWork of a property change in an entity.  | 
            ||
| 2755 | 486 | *  | 
            |
| 2756 | 486 | * @param object $entity The entity that owns the property.  | 
            |
| 2757 | 486 | * @param string $propertyName The name of the property that changed.  | 
            |
| 2758 | * @param mixed $oldValue The old value of the property.  | 
            ||
| 2759 | 486 | * @param mixed $newValue The new value of the property.  | 
            |
| 2760 | 486 | *  | 
            |
| 2761 | * @return void  | 
            ||
| 2762 | 486 | */  | 
            |
| 2763 | 4 | public function propertyChanged($entity, $propertyName, $oldValue, $newValue)  | 
            |
| 2780 | |||
| 2781 | /**  | 
            ||
| 2782 | * Gets the currently scheduled entity insertions in this UnitOfWork.  | 
            ||
| 2783 | 861 | *  | 
            |
| 2784 | * @return array  | 
            ||
| 2785 | 861 | */  | 
            |
| 2786 | 861 | public function getScheduledEntityInsertions()  | 
            |
| 2790 | 6 | ||
| 2791 | 6 | /**  | 
            |
| 2792 | * Gets the currently scheduled entity updates in this UnitOfWork.  | 
            ||
| 2793 | 6 | *  | 
            |
| 2794 | 6 | * @return array  | 
            |
| 2795 | */  | 
            ||
| 2796 | public function getScheduledEntityUpdates()  | 
            ||
| 2800 | 6 | ||
| 2801 | 6 | /**  | 
            |
| 2802 | * Gets the currently scheduled entity deletions in this UnitOfWork.  | 
            ||
| 2803 | *  | 
            ||
| 2804 | 6 | * @return array  | 
            |
| 2805 | */  | 
            ||
| 2806 | public function getScheduledEntityDeletions()  | 
            ||
| 2810 | |||
| 2811 | /**  | 
            ||
| 2812 | * Gets the currently scheduled complete collection deletions  | 
            ||
| 2813 | *  | 
            ||
| 2814 | * @return array  | 
            ||
| 2815 | 142 | */  | 
            |
| 2816 | public function getScheduledCollectionDeletions()  | 
            ||
| 2820 | 142 | ||
| 2821 | 142 | /**  | 
            |
| 2822 | 76 | * Gets the currently scheduled collection inserts, updates and deletes.  | 
            |
| 2823 | 76 | *  | 
            |
| 2824 | * @return array  | 
            ||
| 2825 | 80 | */  | 
            |
| 2826 | 80 | public function getScheduledCollectionUpdates()  | 
            |
| 2830 | 142 | ||
| 2831 | 142 | /**  | 
            |
| 2832 | * Helper method to initialize a lazy loading proxy or persistent collection.  | 
            ||
| 2833 | *  | 
            ||
| 2834 | * @param object $obj  | 
            ||
| 2835 | *  | 
            ||
| 2836 | * @return void  | 
            ||
| 2837 | */  | 
            ||
| 2838 | 2 | public function initializeObject($obj)  | 
            |
| 2850 | |||
| 2851 | 115 | /**  | 
            |
| 2852 | * Helper method to show an object as string.  | 
            ||
| 2853 | 115 | *  | 
            |
| 2854 | * @param object $obj  | 
            ||
| 2855 | 115 | *  | 
            |
| 2856 | 112 | * @return string  | 
            |
| 2857 | 115 | */  | 
            |
| 2858 | private static function objToStr($obj)  | 
            ||
| 2862 | |||
| 2863 | /**  | 
            ||
| 2864 | * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().  | 
            ||
| 2865 | *  | 
            ||
| 2866 | * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information  | 
            ||
| 2867 | * on this object that might be necessary to perform a correct update.  | 
            ||
| 2868 | *  | 
            ||
| 2869 | * @param object $object  | 
            ||
| 2870 | *  | 
            ||
| 2871 | * @return void  | 
            ||
| 2872 | *  | 
            ||
| 2873 | * @throws ORMInvalidArgumentException  | 
            ||
| 2874 | */  | 
            ||
| 2875 | public function markReadOnly($object)  | 
            ||
| 2883 | |||
| 2884 | /**  | 
            ||
| 2885 | 313 | * Is this entity read only?  | 
            |
| 2886 | *  | 
            ||
| 2887 | 313 | * @param object $object  | 
            |
| 2888 | 313 | *  | 
            |
| 2889 | * @return bool  | 
            ||
| 2890 | *  | 
            ||
| 2891 | * @throws ORMInvalidArgumentException  | 
            ||
| 2892 | */  | 
            ||
| 2893 | public function isReadOnly($object)  | 
            ||
| 2901 | |||
| 2902 | 842 | /**  | 
            |
| 2903 | * Perform whatever processing is encapsulated here after completion of the transaction.  | 
            ||
| 2904 | */  | 
            ||
| 2905 | private function afterTransactionComplete()  | 
            ||
| 2911 | |||
| 2912 | /**  | 
            ||
| 2913 | * Perform whatever processing is encapsulated here after completion of the rolled-back.  | 
            ||
| 2914 | 126 | */  | 
            |
| 2915 | private function afterTransactionRolledBack()  | 
            ||
| 2921 | |||
| 2922 | 126 | /**  | 
            |
| 2923 | 113 | * Performs an action after the transaction.  | 
            |
| 2924 | 126 | *  | 
            |
| 2925 | * @param callable $callback  | 
            ||
| 2926 | 126 | */  | 
            |
| 2927 | private function performCallbackOnCachedPersister(callable $callback)  | 
            ||
| 2939 | 522 | ||
| 2940 | private function dispatchOnFlushEvent()  | 
            ||
| 2946 | |||
| 2947 | private function dispatchPostFlushEvent()  | 
            ||
| 2953 | |||
| 2954 | /**  | 
            ||
| 2955 | * Verifies if two given entities actually are the same based on identifier comparison  | 
            ||
| 2956 | *  | 
            ||
| 2957 | 5 | * @param object $entity1  | 
            |
| 2958 | * @param object $entity2  | 
            ||
| 2959 | 5 | *  | 
            |
| 2960 | * @return bool  | 
            ||
| 2961 | 5 | */  | 
            |
| 2962 | 5 | private function isIdentifierEquals($entity1, $entity2)  | 
            |
| 2989 | |||
| 2990 | /**  | 
            ||
| 2991 | * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.  | 
            ||
| 2992 | * Unit of work able to fire deferred events, related to loading events here.  | 
            ||
| 2993 | *  | 
            ||
| 2994 | 1063 | * @internal should be called internally from object hydrators  | 
            |
| 2995 | */  | 
            ||
| 2996 | 1063 | public function hydrationComplete()  | 
            |
| 3000 | }  | 
            ||
| 3001 | 
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.