From cdea41f714665fa34d2762c49eecd5830c098971 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Tue, 3 Oct 2023 02:19:22 +0000 Subject: [PATCH] Reduce reflection usage by forcing private definition in mappers. --- .../Database/Mapper/DataMapperAbstract.php | 4 +- .../Database/Mapper/DataMapperFactory.php | 37 +++-- DataStorage/Database/Mapper/DeleteMapper.php | 58 ++++--- DataStorage/Database/Mapper/ReadMapper.php | 147 +++++++++++------- DataStorage/Database/Mapper/UpdateMapper.php | 59 +++++-- DataStorage/Database/Mapper/WriteMapper.php | 91 ++++++----- 6 files changed, 250 insertions(+), 146 deletions(-) diff --git a/DataStorage/Database/Mapper/DataMapperAbstract.php b/DataStorage/Database/Mapper/DataMapperAbstract.php index fdef771e7..ef2e963db 100755 --- a/DataStorage/Database/Mapper/DataMapperAbstract.php +++ b/DataStorage/Database/Mapper/DataMapperAbstract.php @@ -548,8 +548,8 @@ abstract class DataMapperAbstract return (string) \gzdeflate($value); } elseif ($type === 'Serializable') { return $value->serialize(); - } elseif (\is_object($value) && \method_exists($value, 'getId')) { - return $value->getId(); + } elseif (\is_object($value) && isset($value->id)) { + return $value->id; } return $value; diff --git a/DataStorage/Database/Mapper/DataMapperFactory.php b/DataStorage/Database/Mapper/DataMapperFactory.php index 07fe92c79..c852e9401 100755 --- a/DataStorage/Database/Mapper/DataMapperFactory.php +++ b/DataStorage/Database/Mapper/DataMapperFactory.php @@ -426,38 +426,53 @@ class DataMapperFactory /** * Get id of object * - * @param object $obj Model to create - * @param string $member Member name for the id, if it is not the primary key + * @param object $obj Model to create + * @param string $member Member name for the id, if it is not the primary key + * @param null|\ReflectionClass $refClass Reflection class * * @return mixed * * @since 1.0.0 */ - public static function getObjectId(object $obj, string $member = null) : mixed + public static function getObjectId(object $obj, string $member = null, \ReflectionClass &$refClass = null) : mixed { $propertyName = $member ?? static::COLUMNS[static::PRIMARYFIELD]['internal']; - return $obj->{$propertyName}; + if (static::COLUMNS[static::PRIMARYFIELD]['private'] ?? false) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($propertyName); + + return $refProp->getValue($obj); + } else { + return $obj->{$propertyName}; + } } /** * Set id to model * - * @param \ReflectionClass $refClass Reflection class - * @param object $obj Object to create - * @param mixed $objId Id to set + * @param object $obj Object to create + * @param mixed $objId Id to set + * @param null|\ReflectionClass $refClass Reflection class * * @return void * * @since 1.0.0 */ - public static function setObjectId(\ReflectionClass $refClass, object $obj, mixed $objId) : void + public static function setObjectId(object $obj, mixed $objId, \ReflectionClass &$refClass = null) : void { $propertyName = static::COLUMNS[static::PRIMARYFIELD]['internal']; - $refProp = $refClass->getProperty($propertyName); - \settype($objId, static::COLUMNS[static::PRIMARYFIELD]['type']); - if (!$refProp->isPublic()) { + + if (static::COLUMNS[static::PRIMARYFIELD]['private'] ?? false) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($propertyName); $refProp->setValue($obj, $objId); } else { $obj->{$propertyName} = $objId; diff --git a/DataStorage/Database/Mapper/DeleteMapper.php b/DataStorage/Database/Mapper/DeleteMapper.php index d155406ce..c68ab7877 100755 --- a/DataStorage/Database/Mapper/DeleteMapper.php +++ b/DataStorage/Database/Mapper/DeleteMapper.php @@ -71,17 +71,17 @@ final class DeleteMapper extends DataMapperAbstract */ public function executeDelete(object $obj) : mixed { - $refClass = new \ReflectionClass($obj); + $refClass = null; $objId = $this->mapper::getObjectId($obj); if (empty($objId)) { return null; } - $this->deleteSingleRelation($obj, $refClass, $this->mapper::BELONGS_TO); - $this->deleteHasMany($refClass, $obj, $objId); + $this->deleteSingleRelation($obj, $this->mapper::BELONGS_TO, $refClass); + $this->deleteHasMany($obj, $objId, $refClass); $this->deleteModel($objId); - $this->deleteSingleRelation($obj, $refClass, $this->mapper::OWNS_ONE); + $this->deleteSingleRelation($obj, $this->mapper::OWNS_ONE, $refClass); return $objId; } @@ -111,15 +111,15 @@ final class DeleteMapper extends DataMapperAbstract /** * Delete ownsOne, belongsTo relations * - * @param object $obj Object to delete - * @param \ReflectionClass $refClass Reflection of object to delete - * @param array $relation Relation data (e.g. ::BELONGS_TO, ::OWNS_ONE) + * @param object $obj Object to delete + * @param array $relation Relation data (e.g. ::BELONGS_TO, ::OWNS_ONE) + * @param null|\ReflectionClass $refClass Reflection of object to delete * * @return void * * @since 1.0.0 */ - private function deleteSingleRelation(object $obj, \ReflectionClass $refClass, array $relation) : void + private function deleteSingleRelation(object $obj, array $relation, \ReflectionClass &$refClass = null) : void { if (empty($relation)) { return; @@ -137,27 +137,36 @@ final class DeleteMapper extends DataMapperAbstract $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member); $relMapper->depth = $this->depth + 1; - $refProp = $refClass->getProperty($member); - if (!$refProp->isPublic()) { - $relMapper->execute($refProp->getValue($obj)); + $isPrivate = $relData['private'] ?? false; + + $value = null; + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($member); + $value = $refProp->getValue($obj); } else { - $relMapper->execute($obj->{$member}); + $value = $obj->{$member}; } + + $relMapper->execute($value); } } /** * Delete hasMany * - * @param \ReflectionClass $refClass Reflection of object to delete - * @param object $obj Object to delete - * @param mixed $objId Object id to delete + * @param object $obj Object to delete + * @param mixed $objId Object id to delete + * @param null|\ReflectionClass $refClass Reflection of object to delete * * @return void * * @since 1.0.0 */ - private function deleteHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void + private function deleteHasMany(object $obj, mixed $objId, \ReflectionClass &$refClass = null) : void { if (empty($this->mapper::HAS_MANY)) { return; @@ -169,9 +178,20 @@ final class DeleteMapper extends DataMapperAbstract continue; } - $objIds = []; - $refProp = $refClass->getProperty($member); - $values = $refProp->isPublic() ? $obj->{$member} : $refProp->getValue($obj); + $objIds = []; + $isPrivate = $rel['private'] ?? false; + + $values = null; + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($member); + $values = $refProp->getValue($obj); + } else { + $values = $obj->{$member}; + } if (!\is_array($values)) { // conditionals diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index a5bb79a0d..e05fc5a90 100755 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -555,7 +555,7 @@ final class ReadMapper extends DataMapperAbstract */ public function populateAbstract(array $result, object $obj) : object { - $refClass = new \ReflectionClass($obj); + $refClass = null; foreach ($this->mapper::COLUMNS as $column => $def) { $alias = $column . '_d' . $this->depth; @@ -569,28 +569,42 @@ final class ReadMapper extends DataMapperAbstract $hasPath = false; $aValue = []; $arrayPath = ''; + $refProp = null; + $isPrivate = $def['private'] ?? false; + $member = ''; + + if ($isPrivate && $refClass === null) { + $refClass = new \ReflectionClass($obj); + } if (\stripos($def['internal'], '/') !== false) { $hasPath = true; $path = \explode('/', \ltrim($def['internal'], '/')); $member = $path[0]; - $refProp = $refClass->getProperty($path[0]); - $isPublic = $refProp->isPublic(); - $aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); + if ($isPrivate) { + $refProp = $refClass->getProperty($path[0]); + $aValue = $refProp->getValue($obj); + } else { + $aValue = $obj->{$path[0]}; + } \array_shift($path); $arrayPath = \implode('/', $path); } else { - $refProp = $refClass->getProperty($def['internal']); - $isPublic = $refProp->isPublic(); - $member = $def['internal']; + if ($isPrivate) { + $refProp = $refClass->getProperty($def['internal']); + } + + $member = $def['internal']; } if (isset($this->mapper::OWNS_ONE[$def['internal']])) { $default = null; - if (!isset($this->with[$member]) && $refProp->isInitialized($obj)) { - $default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); + if (!isset($this->with[$member]) + && ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member})) + ) { + $default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; } $value = $this->populateOwnsOne($def['internal'], $result, $default); @@ -600,14 +614,16 @@ final class ReadMapper extends DataMapperAbstract $this->mapper::OWNS_ONE[$def['internal']]['mapper']::reader(db: $this->db)->loadHasManyRelations($value); } - if (!empty($value)) { + if (empty($value)) { // @todo: find better solution. this was because of a bug with the sales billing list query depth = 4. The address was set (from the client, referral or creator) but then somehow there was a second address element which was all null and null cannot be asigned to a string variable (e.g. country). The problem with this solution is that if the model expects an initialization (e.g. at lest set the elements to null, '', 0 etc.) this is now not done. - $refProp->setValue($obj, $value); + $value = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; } } elseif (isset($this->mapper::BELONGS_TO[$def['internal']])) { $default = null; - if (!isset($this->with[$member]) && $refProp->isInitialized($obj)) { - $default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); + if (!isset($this->with[$member]) + && ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member})) + ) { + $default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; } $value = $this->populateBelongsTo($def['internal'], $result, $default); @@ -616,8 +632,6 @@ final class ReadMapper extends DataMapperAbstract if (\is_object($value) && isset($this->mapper::BELONGS_TO[$def['internal']]['mapper'])) { $this->mapper::BELONGS_TO[$def['internal']]['mapper']::reader(db: $this->db)->loadHasManyRelations($value); } - - $refProp->setValue($obj, $value); } elseif (\in_array($def['type'], ['string', 'compress', 'int', 'float', 'bool'])) { if ($value !== null && $def['type'] === 'compress') { $def['type'] = 'string'; @@ -625,44 +639,44 @@ final class ReadMapper extends DataMapperAbstract $value = \gzinflate($value); } - if ($value !== null || $refProp->getValue($obj) !== null) { + $mValue = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; + if ($value !== null || $mValue !== null) { \settype($value, $def['type']); } if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); + $value ??= new \DateTime($value); if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); + $value ??= new \DateTimeImmutable($value); if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['type'] === 'Json') { if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - $refProp->setValue($obj, \json_decode($value, true)); + $value = \json_decode($value, true); } elseif ($def['type'] === 'Serializable') { - $member = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); + $mObj = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; - if ($member === null || $value === null) { - $obj->{$def['internal']} = $value; - } else { - $member->unserialize($value); + if ($mObj !== null && $value !== null) { + $mObj->unserialize($value); + $value = $mObj; } } + + if ($isPrivate) { + $refProp->setValue($obj, $value); + } else { + $obj->{$member} = $value; + } } foreach ($this->mapper::HAS_MANY as $member => $def) { @@ -677,54 +691,68 @@ final class ReadMapper extends DataMapperAbstract $hasPath = false; $aValue = null; $arrayPath = '/'; + $refProp = null; + $isPrivate = $def['private'] ?? false; + + if ($isPrivate && $refClass === null) { + $refClass = new \ReflectionClass($obj); + } if (\stripos($member, '/') !== false) { $hasPath = true; $path = \explode('/', $member); - $refProp = $refClass->getProperty($path[0]); - $isPublic = $refProp->isPublic(); + $member = $path[0]; + + if ($isPrivate) { + $refProp = $refClass->getProperty($path[0]); + } \array_shift($path); $arrayPath = \implode('/', $path); - $aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); - } else { + $aValue = $isPrivate ? $refProp->getValue($obj) : $obj->{$path[0]}; + } elseif ($isPrivate) { $refProp = $refClass->getProperty($member); - $isPublic = $refProp->isPublic(); } if (\in_array($def['mapper']::COLUMNS[$column]['type'], ['string', 'int', 'float', 'bool'])) { - if ($value !== null || $refProp->getValue($obj) !== null) { + if ($value !== null + || ($isPrivate ? $refProp->getValue($obj) !== null : $obj->{$member} !== null) + ) { \settype($value, $def['mapper']::COLUMNS[$column]['type']); } if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); + $value ??= new \DateTime($value); if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); + $value ??= new \DateTimeImmutable($value); if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - - $refProp->setValue($obj, $value); } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'Json') { if ($hasPath) { $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } - $refProp->setValue($obj, \json_decode($value, true)); + $value = \json_decode($value, true); } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'Serializable') { - $member = $isPublic ? $obj->{$member} : $refProp->getValue($obj); - $member->unserialize($value); + $mObj = $isPrivate ? $refProp->getValue($obj) : $obj->{$member}; + + if ($mObj !== null && $value !== null) { + $mObj->unserialize($value); + $value = $mObj; + } + } + + if ($isPrivate) { + $refProp->setValue($obj, $value); + } else { + $obj->{$member} = $value; } } @@ -867,6 +895,8 @@ final class ReadMapper extends DataMapperAbstract continue; } + $isPrivate = $withData['private'] ?? false; + $objectMapper = $this->createRelationMapper($many['mapper']::get(db: $this->db), $member); if ($many['external'] === null/* same as $many['table'] !== $many['mapper']::TABLE */) { $objectMapper->where($many['mapper']::COLUMNS[$many['self']]['internal'], $primaryKey); @@ -886,12 +916,12 @@ final class ReadMapper extends DataMapperAbstract continue; } - if ($refClass === null) { - $refClass = new \ReflectionClass($obj); - } + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } - $refProp = $refClass->getProperty($member); - if (!$refProp->isPublic()) { + $refProp = $refClass->getProperty($member); $refProp->setValue($obj, !\is_array($objects) && ($many['conditional'] ?? false) === false ? [$many['mapper']::getObjectId($objects) => $objects] : $objects // if conditional === true the obj will be asigned (e.g. has many localizations but only one is loaded for the model) @@ -914,15 +944,16 @@ final class ReadMapper extends DataMapperAbstract continue; } - if ($refClass === null) { - $refClass = new \ReflectionClass($obj); - } - /** @var ReadMapper $relMapper */ $relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member); - $refProp = $refClass->getProperty($member); - if (!$refProp->isPublic()) { + $isPrivate = $withData['private'] ?? false; + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($member); $relMapper->loadHasManyRelations($refProp->getValue($obj)); } else { $relMapper->loadHasManyRelations($obj->{$member}); diff --git a/DataStorage/Database/Mapper/UpdateMapper.php b/DataStorage/Database/Mapper/UpdateMapper.php index b6f52e776..881281cc8 100755 --- a/DataStorage/Database/Mapper/UpdateMapper.php +++ b/DataStorage/Database/Mapper/UpdateMapper.php @@ -73,14 +73,14 @@ final class UpdateMapper extends DataMapperAbstract */ public function executeUpdate(object $obj) : mixed { - $refClass = new \ReflectionClass($obj); + $refClass = null; $objId = $this->mapper::getObjectId($obj); if ($this->mapper::isNullModel($obj)) { return $objId === 0 ? null : $objId; } - $this->updateHasMany($refClass, $obj, $objId); + $this->updateHasMany($obj, $objId, $refClass); if (empty($objId)) { return $this->mapper::create(db: $this->db)->execute($obj); @@ -102,7 +102,7 @@ final class UpdateMapper extends DataMapperAbstract * * @since 1.0.0 */ - private function updateModel(object $obj, mixed $objId, \ReflectionClass $refClass = null) : void + private function updateModel(object $obj, mixed $objId, \ReflectionClass &$refClass = null) : void { try { // Model doesn't have anything to update @@ -124,10 +124,20 @@ final class UpdateMapper extends DataMapperAbstract continue; } - $refClass = $refClass ?? new \ReflectionClass($obj); - $property = $refClass->getProperty($propertyName); + $isPrivate = $column['private'] ?? false; + $property = null; + $tValue = null; - $tValue = $property->isPublic() ? $obj->{$propertyName} : $property->getValue($obj); + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $property = $refClass->getProperty($propertyName); + $tValue = $property->getValue($obj); + } else { + $tValue = $obj->{$propertyName}; + } if (isset($this->mapper::OWNS_ONE[$propertyName])) { $id = \is_object($tValue) ? $this->updateOwnsOne($propertyName, $tValue) : $tValue; @@ -218,9 +228,9 @@ final class UpdateMapper extends DataMapperAbstract /** * Update has many relations * - * @param \ReflectionClass $refClass Reflection of the object containing the relations - * @param object $obj Object which contains the relations - * @param mixed $objId Object id which contains the relations + * @param object $obj Object which contains the relations + * @param mixed $objId Object id which contains the relations + * @param null|\ReflectionClass $refClass Reflection of the object containing the relations * * @return void * @@ -228,7 +238,7 @@ final class UpdateMapper extends DataMapperAbstract * * @since 1.0.0 */ - private function updateHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void + private function updateHasMany(object $obj, mixed $objId, \ReflectionClass &$refClass = null) : void { if (empty($this->with) || empty($this->mapper::HAS_MANY)) { return; @@ -245,17 +255,33 @@ final class UpdateMapper extends DataMapperAbstract throw new InvalidMapperException(); } - $property = $refClass->getProperty($propertyName); + $isPrivate = $rel['private'] ?? false; + $property = null; + $values = null; - $values = ($isPublic = $property->isPublic()) ? $obj->{$propertyName} : $property->getValue($obj); + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $property = $refClass->getProperty($propertyName); + $values = $property->getValue($obj); + } else { + $values = $obj->{$propertyName}; + } if (!\is_array($values) || empty($values)) { continue; } /** @var class-string $mapper */ - $mapper = $this->mapper::HAS_MANY[$propertyName]['mapper']; - $relReflectionClass = new \ReflectionClass(\reset($values)); + $mapper = $this->mapper::HAS_MANY[$propertyName]['mapper']; + $isPrivateRel = $this->mapper::HAS_MANY[$propertyName]['private'] ?? false; + + if ($isPrivateRel) { + $relReflectionClass = new \ReflectionClass(\reset($values)); + } + $objsIds[$propertyName] = []; foreach ($values as $key => &$value) { @@ -285,9 +311,8 @@ final class UpdateMapper extends DataMapperAbstract if ($this->mapper::HAS_MANY[$propertyName]['table'] === $this->mapper::HAS_MANY[$propertyName]['mapper']::TABLE && isset($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]) ) { - $relProperty = $relReflectionClass->getProperty($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']); - - if (!$isPublic) { + if ($isPrivateRel) { + $relProperty = $relReflectionClass->getProperty($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']); $relProperty->setValue($value, $objId); } else { $value->{$mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']} = $objId; diff --git a/DataStorage/Database/Mapper/WriteMapper.php b/DataStorage/Database/Mapper/WriteMapper.php index 1ba0cf92a..eacf8ca49 100755 --- a/DataStorage/Database/Mapper/WriteMapper.php +++ b/DataStorage/Database/Mapper/WriteMapper.php @@ -74,7 +74,7 @@ final class WriteMapper extends DataMapperAbstract */ public function executeCreate(object $obj) : mixed { - $refClass = new \ReflectionClass($obj); + $refClass = null; if ($this->mapper::isNullModel($obj)) { $objId = $this->mapper::getObjectId($obj); @@ -86,10 +86,10 @@ final class WriteMapper extends DataMapperAbstract $objId = $id; } else { $objId = $this->createModel($obj, $refClass); - $this->mapper::setObjectId($refClass, $obj, $objId); + $this->mapper::setObjectId($obj, $objId, $refClass); } - $this->createHasMany($refClass, $obj, $objId); + $this->createHasMany($obj, $objId, $refClass); return $objId; } @@ -97,21 +97,19 @@ final class WriteMapper extends DataMapperAbstract /** * Create model * - * @param object $obj Object to create - * @param \ReflectionClass $refClass Reflection of the object to create + * @param object $obj Object to create + * @param null|\ReflectionClass $refClass Reflection of the object to create * * @return mixed * * @since 1.0.0 */ - private function createModel(object $obj, \ReflectionClass $refClass) : mixed + private function createModel(object $obj, \ReflectionClass &$refClass = null) : mixed { try { $query = new Builder($this->db); $query->into($this->mapper::TABLE); - $publicProperties = \get_object_vars($obj); - foreach ($this->mapper::COLUMNS as $column) { $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] @@ -123,13 +121,16 @@ final class WriteMapper extends DataMapperAbstract continue; } - if (!isset($publicProperties[$propertyName])) { + $tValue = null; + if ($column['private'] ?? false) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + $property = $refClass->getProperty($propertyName); - $property->setAccessible(true); - $tValue = $property->getValue($obj); - $property->setAccessible(false); + $tValue = $property->getValue($obj); } else { - $tValue = $publicProperties[$propertyName]; + $tValue = $obj->{$propertyName}; } if (isset($this->mapper::OWNS_ONE[$propertyName])) { @@ -231,10 +232,13 @@ final class WriteMapper extends DataMapperAbstract if (isset($this->mapper::BELONGS_TO[$propertyName]['by'])) { // has by (obj is stored as a different model e.g. model = profile but reference/db is account) - $refClass = new \ReflectionClass($obj); - $refProp = $refClass->getProperty($this->mapper::BELONGS_TO[$propertyName]['by']); - - $obj = $refProp->isPublic() ? $obj->{$this->mapper::BELONGS_TO[$propertyName]['by']} : $refProp->getValue($obj); + if ($this->mapper::BELONGS_TO[$propertyName]['private']) { + $refClass = new \ReflectionClass($obj); + $refProp = $refClass->getProperty($this->mapper::BELONGS_TO[$propertyName]['by']); + $obj = $refProp->getValue($obj); + } else { + $obj = $obj->{$this->mapper::BELONGS_TO[$propertyName]['by']}; + } } /** @var class-string $mapper */ @@ -248,9 +252,9 @@ final class WriteMapper extends DataMapperAbstract /** * Create has many models * - * @param \ReflectionClass $refClass Reflection of the object to create - * @param object $obj Object to create - * @param mixed $objId Id of the parent object + * @param object $obj Object to create + * @param mixed $objId Id of the parent object + * @param null|\ReflectionClass $refClass Reflection of the object to create * * @return void * @@ -258,15 +262,27 @@ final class WriteMapper extends DataMapperAbstract * * @since 1.0.0 */ - private function createHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void + private function createHasMany(object $obj, mixed $objId, \ReflectionClass &$refClass = null) : void { - foreach ($this->mapper::HAS_MANY as $propertyName => $_) { + foreach ($this->mapper::HAS_MANY as $propertyName => $rel) { if (!isset($this->mapper::HAS_MANY[$propertyName]['mapper'])) { throw new InvalidMapperException(); // @codeCoverageIgnore } - $property = $refClass->getProperty($propertyName); - $values = $property->isPublic() ? $obj->{$propertyName} : $property->getValue($obj); + $isPrivate = $rel['private'] ?? false; + $property = null; + $values = null; + + if ($isPrivate) { + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $property = $refClass->getProperty($propertyName); + $values = $property->getValue($obj); + } else { + $values = $obj->{$propertyName}; + } /** @var class-string $mapper */ $mapper = $this->mapper::HAS_MANY[$propertyName]['mapper']; @@ -274,17 +290,16 @@ final class WriteMapper extends DataMapperAbstract ? $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal'] : 'ERROR'; + // @todo: this or $isRelPrivate is wrong, don't know which one. + $isInternalPrivate =$mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['private'] ?? false; + if (\is_object($values)) { // conditionals - $publicProperties = \get_object_vars($values); - - if (!isset($publicProperties[$internalName])) { + if ($isInternalPrivate) { $relReflectionClass = new \ReflectionClass($values); $relProperty = $relReflectionClass->getProperty($internalName); - $relProperty->setAccessible(true); $relProperty->setValue($values, $objId); - $relProperty->setAccessible(false); } else { $values->{$internalName} = $objId; } @@ -297,7 +312,8 @@ final class WriteMapper extends DataMapperAbstract } $objsIds = []; - $relReflectionClass = empty($values) ? null : new \ReflectionClass(\reset($values)); + $isRelPrivate = $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['private'] ?? false; + $relReflectionClass = $isRelPrivate && !empty($values) ? new \ReflectionClass(\reset($values)) : null; foreach ($values as $key => $value) { if (!\is_object($value)) { @@ -307,7 +323,6 @@ final class WriteMapper extends DataMapperAbstract continue; } - /** @var \ReflectionClass $relReflectionClass */ $primaryKey = $mapper::getObjectId($value); // already in db @@ -319,26 +334,24 @@ final class WriteMapper extends DataMapperAbstract // Setting relation value (id) for relation (since the relation is not stored in an extra relation table) if (!isset($this->mapper::HAS_MANY[$propertyName]['external'])) { - $relProperty = $relReflectionClass->getProperty($internalName); - $isRelPublic = $relProperty->isPublic(); + $relProperty = null; + if ($isRelPrivate) { + $relProperty = $relReflectionClass->getProperty($internalName); + } // todo maybe consider to just set the column type to object, and then check for that (might be faster) if (isset($mapper::BELONGS_TO[$internalName]) || isset($mapper::OWNS_ONE[$internalName])) { - if (!$isRelPublic) { + if ($isRelPrivate) { $relProperty->setValue($value, $this->mapper::createNullModel($objId)); } else { $value->{$internalName} = $this->mapper::createNullModel($objId); } - } elseif (!$isRelPublic) { + } elseif ($isRelPrivate) { $relProperty->setValue($value, $objId); } else { $value->{$internalName} = $objId; } - - if (!$isRelPublic) { - $relProperty->setAccessible(false); - } } $objsIds[$key] = $mapper::create(db: $this->db)->execute($value);