<?php
declare (strict_types=1);
namespace MailPoetVendor\Doctrine\ORM;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\Common\Collections\AbstractLazyCollection;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\Common\Collections\Collection;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\Common\Collections\Selectable;
use MailPoetVendor\Doctrine\ORM\Internal\CriteriaOrderings;
use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata;
use ReturnTypeWillChange;
use RuntimeException;
use UnexpectedValueException;
use function array_combine;
use function array_diff_key;
use function array_map;
use function array_values;
use function array_walk;
use function assert;
use function get_class;
use function is_object;
use function spl_object_id;
final class PersistentCollection extends AbstractLazyCollection implements Selectable
{
 use CriteriaOrderings;
 private $snapshot = [];
 private $owner;
 private $association;
 private $em;
 private $backRefFieldName;
 private $typeClass;
 private $isDirty = \false;
 public function __construct(EntityManagerInterface $em, $class, Collection $collection)
 {
 $this->collection = $collection;
 $this->em = $em;
 $this->typeClass = $class;
 $this->initialized = \true;
 }
 public function setOwner($entity, array $assoc) : void
 {
 $this->owner = $entity;
 $this->association = $assoc;
 $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
 }
 public function getOwner()
 {
 return $this->owner;
 }
 public function getTypeClass() : Mapping\ClassMetadataInfo
 {
 assert($this->typeClass !== null);
 return $this->typeClass;
 }
 private function getUnitOfWork() : UnitOfWork
 {
 assert($this->em !== null);
 return $this->em->getUnitOfWork();
 }
 public function hydrateAdd($element) : void
 {
 $this->unwrap()->add($element);
 // If _backRefFieldName is set and its a one-to-many association,
 // we need to set the back reference.
 if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) {
 assert($this->typeClass !== null);
 // Set back reference to owner
 $this->typeClass->reflFields[$this->backRefFieldName]->setValue($element, $this->owner);
 $this->getUnitOfWork()->setOriginalEntityProperty(spl_object_id($element), $this->backRefFieldName, $this->owner);
 }
 }
 public function hydrateSet($key, $element) : void
 {
 $this->unwrap()->set($key, $element);
 // If _backRefFieldName is set, then the association is bidirectional
 // and we need to set the back reference.
 if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) {
 assert($this->typeClass !== null);
 // Set back reference to owner
 $this->typeClass->reflFields[$this->backRefFieldName]->setValue($element, $this->owner);
 }
 }
 public function initialize() : void
 {
 if ($this->initialized || !$this->association) {
 return;
 }
 $this->doInitialize();
 $this->initialized = \true;
 }
 public function takeSnapshot() : void
 {
 $this->snapshot = $this->unwrap()->toArray();
 $this->isDirty = \false;
 }
 public function getSnapshot() : array
 {
 return $this->snapshot;
 }
 public function getDeleteDiff() : array
 {
 $collectionItems = $this->unwrap()->toArray();
 return array_values(array_diff_key(array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot), array_combine(array_map('spl_object_id', $collectionItems), $collectionItems)));
 }
 public function getInsertDiff() : array
 {
 $collectionItems = $this->unwrap()->toArray();
 return array_values(array_diff_key(array_combine(array_map('spl_object_id', $collectionItems), $collectionItems), array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot)));
 }
 public function getMapping() : array
 {
 if ($this->association === null) {
 throw new UnexpectedValueException('The underlying association mapping is null although it should not be');
 }
 return $this->association;
 }
 private function changed() : void
 {
 if ($this->isDirty) {
 return;
 }
 $this->isDirty = \true;
 if ($this->association !== null && $this->getMapping()['isOwningSide'] && $this->getMapping()['type'] === ClassMetadata::MANY_TO_MANY && $this->owner && $this->em !== null && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
 $this->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
 }
 }
 public function isDirty() : bool
 {
 return $this->isDirty;
 }
 public function setDirty($dirty) : void
 {
 $this->isDirty = $dirty;
 }
 public function setInitialized($bool) : void
 {
 $this->initialized = $bool;
 }
 public function remove($key)
 {
 // TODO: If the keys are persistent as well (not yet implemented)
 // and the collection is not initialized and orphanRemoval is
 // not used we can issue a straight SQL delete/update on the
 // association (table). Without initializing the collection.
 $removed = parent::remove($key);
 if (!$removed) {
 return $removed;
 }
 $this->changed();
 if ($this->association !== null && $this->getMapping()['type'] & ClassMetadata::TO_MANY && $this->owner && $this->getMapping()['orphanRemoval']) {
 $this->getUnitOfWork()->scheduleOrphanRemoval($removed);
 }
 return $removed;
 }
 public function removeElement($element) : bool
 {
 $removed = parent::removeElement($element);
 if (!$removed) {
 return $removed;
 }
 $this->changed();
 if ($this->association !== null && $this->getMapping()['type'] & ClassMetadata::TO_MANY && $this->owner && $this->getMapping()['orphanRemoval']) {
 $this->getUnitOfWork()->scheduleOrphanRemoval($element);
 }
 return $removed;
 }
 public function containsKey($key) : bool
 {
 if (!$this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY && isset($this->getMapping()['indexBy'])) {
 $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
 return $this->unwrap()->containsKey($key) || $persister->containsKey($this, $key);
 }
 return parent::containsKey($key);
 }
 public function contains($element) : bool
 {
 if (!$this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
 $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
 return $this->unwrap()->contains($element) || $persister->contains($this, $element);
 }
 return parent::contains($element);
 }
 public function get($key)
 {
 if (!$this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY && isset($this->getMapping()['indexBy'])) {
 assert($this->em !== null);
 assert($this->typeClass !== null);
 if (!$this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->getMapping()['indexBy'])) {
 return $this->em->find($this->typeClass->name, $key);
 }
 return $this->getUnitOfWork()->getCollectionPersister($this->getMapping())->get($this, $key);
 }
 return parent::get($key);
 }
 public function count() : int
 {
 if (!$this->initialized && $this->association !== null && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
 $persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
 return $persister->count($this) + ($this->isDirty ? $this->unwrap()->count() : 0);
 }
 return parent::count();
 }
 public function set($key, $value) : void
 {
 parent::set($key, $value);
 $this->changed();
 if (is_object($value) && $this->em) {
 $this->getUnitOfWork()->cancelOrphanRemoval($value);
 }
 }
 public function add($value) : bool
 {
 $this->unwrap()->add($value);
 $this->changed();
 if (is_object($value) && $this->em) {
 $this->getUnitOfWork()->cancelOrphanRemoval($value);
 }
 return \true;
 }
 public function offsetExists($offset) : bool
 {
 return $this->containsKey($offset);
 }
 #[\ReturnTypeWillChange]
 public function offsetGet($offset)
 {
 return $this->get($offset);
 }
 public function offsetSet($offset, $value) : void
 {
 if (!isset($offset)) {
 $this->add($value);
 return;
 }
 $this->set($offset, $value);
 }
 #[\ReturnTypeWillChange]
 public function offsetUnset($offset)
 {
 return $this->remove($offset);
 }
 public function isEmpty() : bool
 {
 return $this->unwrap()->isEmpty() && $this->count() === 0;
 }
 public function clear() : void
 {
 if ($this->initialized && $this->isEmpty()) {
 $this->unwrap()->clear();
 return;
 }
 $uow = $this->getUnitOfWork();
 $association = $this->getMapping();
 if ($association['type'] & ClassMetadata::TO_MANY && $association['orphanRemoval'] && $this->owner) {
 // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
 // hence for event listeners we need the objects in memory.
 $this->initialize();
 foreach ($this->unwrap() as $element) {
 $uow->scheduleOrphanRemoval($element);
 }
 }
 $this->unwrap()->clear();
 $this->initialized = \true;
 // direct call, {@link initialize()} is too expensive
 if ($association['isOwningSide'] && $this->owner) {
 $this->changed();
 $uow->scheduleCollectionDeletion($this);
 $this->takeSnapshot();
 }
 }
 public function __sleep() : array
 {
 return ['collection', 'initialized'];
 }
 public function slice($offset, $length = null) : array
 {
 if (!$this->initialized && !$this->isDirty && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
 $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
 return $persister->slice($this, $offset, $length);
 }
 return parent::slice($offset, $length);
 }
 public function __clone()
 {
 if (is_object($this->collection)) {
 $this->collection = clone $this->collection;
 }
 $this->initialize();
 $this->owner = null;
 $this->snapshot = [];
 $this->changed();
 }
 public function matching(Criteria $criteria) : Collection
 {
 if ($this->isDirty) {
 $this->initialize();
 }
 if ($this->initialized) {
 return $this->unwrap()->matching($criteria);
 }
 $association = $this->getMapping();
 if ($association['type'] === ClassMetadata::MANY_TO_MANY) {
 $persister = $this->getUnitOfWork()->getCollectionPersister($association);
 return new ArrayCollection($persister->loadCriteria($this, $criteria));
 }
 $builder = Criteria::expr();
 $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner);
 $expression = $criteria->getWhereExpression();
 $expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
 $criteria = clone $criteria;
 $criteria->where($expression);
 $criteria->orderBy(self::mapToOrderEnumIfAvailable(self::getCriteriaOrderings($criteria) ?: $association['orderBy'] ?? []));
 $persister = $this->getUnitOfWork()->getEntityPersister($association['targetEntity']);
 return $association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY ? new LazyCriteriaCollection($persister, $criteria) : new ArrayCollection($persister->loadCriteria($criteria));
 }
 public function unwrap() : Collection
 {
 assert($this->collection instanceof Collection);
 assert($this->collection instanceof Selectable);
 return $this->collection;
 }
 protected function doInitialize() : void
 {
 // Has NEW objects added through add(). Remember them.
 $newlyAddedDirtyObjects = [];
 if ($this->isDirty) {
 $newlyAddedDirtyObjects = $this->unwrap()->toArray();
 }
 $this->unwrap()->clear();
 $this->getUnitOfWork()->loadCollection($this);
 $this->takeSnapshot();
 if ($newlyAddedDirtyObjects) {
 $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects);
 }
 }
 private function restoreNewObjectsInDirtyCollection(array $newObjects) : void
 {
 $loadedObjects = $this->unwrap()->toArray();
 $newObjectsByOid = array_combine(array_map('spl_object_id', $newObjects), $newObjects);
 $loadedObjectsByOid = array_combine(array_map('spl_object_id', $loadedObjects), $loadedObjects);
 $newObjectsThatWereNotLoaded = array_diff_key($newObjectsByOid, $loadedObjectsByOid);
 if ($newObjectsThatWereNotLoaded) {
 // Reattach NEW objects added through add(), if any.
 array_walk($newObjectsThatWereNotLoaded, [$this->unwrap(), 'add']);
 $this->isDirty = \true;
 }
 }
}
