|null The uid of the record. The uid is only unique in the context of the database table. */ protected ?int $uid = null; /** * @var int<0, max>|null The uid of the localized record. Holds the uid of the record in default language (the translationOrigin). * * @internal * @todo make private in 13.0 and expose value via getter */ protected ?int $_localizedUid = null; /** * @var int<-1, max>|null The uid of the language of the object. This is the id of the corresponding sing language. * * @internal * @todo make private in 13.0 and expose value via getter */ protected ?int $_languageUid = null; /** * The uid of the versioned record. * * @internal * @todo make private in 13.0 and expose value via getter */ protected ?int $_versionedUid = null; /** * @var int<0, max>|null The id of the page the record is "stored". */ protected ?int $pid = null; /** * TRUE if the object is a clone * * @internal */ private bool $_isClone = false; /** * @var array * * @internal */ private array $_cleanProperties = []; /** * @return int<1, max>|null */ public function getUid(): ?int { if ($this->uid !== null) { return (int)$this->uid; } return null; } /** * @param int<0, max> $pid */ public function setPid(int $pid): void { $this->pid = $pid; } /** * @return int<0, max>|null */ public function getPid(): ?int { if ($this->pid === null) { return null; } return (int)$this->pid; } /** * @internal */ public function _setProperty(string $propertyName, mixed $value): bool { if ($this->_hasProperty($propertyName)) { $this->{$propertyName} = $value; return true; } return false; } /** * @internal */ public function _getProperty(string $propertyName): mixed { return $this->_hasProperty($propertyName) && isset($this->{$propertyName}) ? $this->{$propertyName} : null; } /** * @return array a hash map of property names and property values. * * @internal */ public function _getProperties(): array { $properties = get_object_vars($this); foreach ($properties as $propertyName => $propertyValue) { if (str_starts_with($propertyName, '_')) { unset($properties[$propertyName]); } } return $properties; } /** * @param non-empty-string $propertyName * * @internal */ public function _hasProperty(string $propertyName): bool { return property_exists($this, $propertyName); } /** * Returns TRUE if the object is new (the uid was not set, yet) * * @internal */ public function _isNew(): bool { return $this->uid === null; } /** * Register an object's clean state, e.g. after it has been reconstituted * from the database. * * @param non-empty-string|null $propertyName The name of the property to be memorized. If omitted all persistable properties are memorized. */ public function _memorizeCleanState(?string $propertyName = null): void { if ($propertyName !== null) { $this->_memorizePropertyCleanState($propertyName); } else { $this->_cleanProperties = []; foreach ($this->_getProperties() as $propertyName => $propertyValue) { $this->_memorizePropertyCleanState($propertyName); } } } /** * Register a property's clean state, e.g. after it has been reconstituted * from the database. * * @param non-empty-string $propertyName The name of the property to be memorized. If omitted all persistable properties are memorized. */ public function _memorizePropertyCleanState(string $propertyName): void { $propertyValue = $this->_getProperty($propertyName); if (is_object($propertyValue) && !($propertyValue instanceof \UnitEnum)) { $propertyValueClone = clone $propertyValue; // We need to make sure the clone and the original object // are identical when compared with == (see _isDirty()). // After the cloning, the Domain Object will have the property // "isClone" set to TRUE, so we manually have to set it to FALSE // again. Possible fix: Somehow get rid of the "isClone" property, // which is currently needed in Fluid. if ($propertyValueClone instanceof AbstractDomainObject) { $propertyValueClone->_setClone(false); } $this->_cleanProperties[$propertyName] = $propertyValueClone; } else { $this->_cleanProperties[$propertyName] = $propertyValue; } } /** * Returns a hash map of clean properties and $values. * * @return array */ public function _getCleanProperties(): array { return $this->_cleanProperties; } /** * Returns the clean value of the given property. The returned value will be NULL if the clean state was not memorized before, or * if the clean value is NULL. * * @param non-empty-string $propertyName The name of the property to be memorized. * * @internal */ public function _getCleanProperty(string $propertyName): mixed { return $this->_cleanProperties[$propertyName] ?? null; } /** * Returns TRUE if the properties were modified after reconstitution * * @param non-empty-string|null $propertyName An optional name of a property to be checked if its value is dirty * * @throws TooDirtyException */ public function _isDirty(?string $propertyName = null): bool { if ($this->uid !== null && $this->_getCleanProperty(self::PROPERTY_UID) !== null && $this->uid != $this->_getCleanProperty(self::PROPERTY_UID)) { throw new TooDirtyException('The ' . self::PROPERTY_UID . ' "' . $this->uid . '" has been modified, that is simply too much.', 1222871239); } if ($propertyName === null) { foreach ($this->_getCleanProperties() as $propertyName => $cleanPropertyValue) { if ($this->isPropertyDirty($cleanPropertyValue, $this->_getProperty($propertyName)) === true) { return true; } } return false; } if ($this->isPropertyDirty($this->_getCleanProperty($propertyName), $this->_getProperty($propertyName)) === true) { return true; } return false; } /** * Checks the $value against the $cleanState. */ protected function isPropertyDirty(mixed $previousValue, mixed $currentValue): bool { // In case it is an object and it implements the ObjectMonitoringInterface, we call _isDirty() instead of a simple comparison of objects. // We do this, because if the object itself contains a lazy loaded property, the comparison of the objects might fail even if the object didn't change if (is_object($currentValue)) { $currentTypeString = null; if ($currentValue instanceof LazyLoadingProxy) { $currentTypeString = $currentValue->_getTypeAndUidString(); } elseif ($currentValue instanceof DomainObjectInterface) { $currentTypeString = $currentValue::class . ':' . $currentValue->getUid(); } if ($currentTypeString !== null) { $previousTypeString = null; if ($previousValue instanceof LazyLoadingProxy) { $previousTypeString = $previousValue->_getTypeAndUidString(); } elseif ($previousValue instanceof DomainObjectInterface) { $previousTypeString = $previousValue::class . ':' . $previousValue->getUid(); } $result = $currentTypeString !== $previousTypeString; } elseif ($currentValue instanceof ObjectMonitoringInterface) { $result = !is_object($previousValue) || $currentValue->_isDirty() || $previousValue::class !== $currentValue::class; } else { // For all other objects we do only a simple comparison (!=) as we want cloned objects to return the same values. $result = $previousValue != $currentValue; } } else { $result = $previousValue !== $currentValue; } return $result; } public function _isClone(): bool { return $this->_isClone; } /** * Setter whether this Domain Object is a clone of another one. * NEVER SET THIS PROPERTY DIRECTLY. We currently need it to make the * _isDirty check inside AbstractEntity work, but it is just a work- * around right now. * * @internal */ public function _setClone(bool $clone) { $this->_isClone = $clone; } public function __clone(): void { $this->_isClone = true; } /** * @return non-empty-string */ public function __toString(): string { return static::class . ':' . $this->uid; } }