Reduce reflection usage by forcing private definition in mappers.

This commit is contained in:
Dennis Eichhorn 2023-10-03 02:19:22 +00:00
parent be8ae797db
commit cdea41f714
6 changed files with 250 additions and 146 deletions

View File

@ -548,8 +548,8 @@ abstract class DataMapperAbstract
return (string) \gzdeflate($value); return (string) \gzdeflate($value);
} elseif ($type === 'Serializable') { } elseif ($type === 'Serializable') {
return $value->serialize(); return $value->serialize();
} elseif (\is_object($value) && \method_exists($value, 'getId')) { } elseif (\is_object($value) && isset($value->id)) {
return $value->getId(); return $value->id;
} }
return $value; return $value;

View File

@ -426,38 +426,53 @@ class DataMapperFactory
/** /**
* Get id of object * Get id of object
* *
* @param object $obj Model to create * @param object $obj Model to create
* @param string $member Member name for the id, if it is not the primary key * @param string $member Member name for the id, if it is not the primary key
* @param null|\ReflectionClass $refClass Reflection class
* *
* @return mixed * @return mixed
* *
* @since 1.0.0 * @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']; $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 * Set id to model
* *
* @param \ReflectionClass $refClass Reflection class * @param object $obj Object to create
* @param object $obj Object to create * @param mixed $objId Id to set
* @param mixed $objId Id to set * @param null|\ReflectionClass $refClass Reflection class
* *
* @return void * @return void
* *
* @since 1.0.0 * @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']; $propertyName = static::COLUMNS[static::PRIMARYFIELD]['internal'];
$refProp = $refClass->getProperty($propertyName);
\settype($objId, static::COLUMNS[static::PRIMARYFIELD]['type']); \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); $refProp->setValue($obj, $objId);
} else { } else {
$obj->{$propertyName} = $objId; $obj->{$propertyName} = $objId;

View File

@ -71,17 +71,17 @@ final class DeleteMapper extends DataMapperAbstract
*/ */
public function executeDelete(object $obj) : mixed public function executeDelete(object $obj) : mixed
{ {
$refClass = new \ReflectionClass($obj); $refClass = null;
$objId = $this->mapper::getObjectId($obj); $objId = $this->mapper::getObjectId($obj);
if (empty($objId)) { if (empty($objId)) {
return null; return null;
} }
$this->deleteSingleRelation($obj, $refClass, $this->mapper::BELONGS_TO); $this->deleteSingleRelation($obj, $this->mapper::BELONGS_TO, $refClass);
$this->deleteHasMany($refClass, $obj, $objId); $this->deleteHasMany($obj, $objId, $refClass);
$this->deleteModel($objId); $this->deleteModel($objId);
$this->deleteSingleRelation($obj, $refClass, $this->mapper::OWNS_ONE); $this->deleteSingleRelation($obj, $this->mapper::OWNS_ONE, $refClass);
return $objId; return $objId;
} }
@ -111,15 +111,15 @@ final class DeleteMapper extends DataMapperAbstract
/** /**
* Delete ownsOne, belongsTo relations * Delete ownsOne, belongsTo relations
* *
* @param object $obj Object to delete * @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 array $relation Relation data (e.g. ::BELONGS_TO, ::OWNS_ONE) * @param null|\ReflectionClass $refClass Reflection of object to delete
* *
* @return void * @return void
* *
* @since 1.0.0 * @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)) { if (empty($relation)) {
return; return;
@ -137,27 +137,36 @@ final class DeleteMapper extends DataMapperAbstract
$relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member); $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member);
$relMapper->depth = $this->depth + 1; $relMapper->depth = $this->depth + 1;
$refProp = $refClass->getProperty($member); $isPrivate = $relData['private'] ?? false;
if (!$refProp->isPublic()) {
$relMapper->execute($refProp->getValue($obj)); $value = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refProp = $refClass->getProperty($member);
$value = $refProp->getValue($obj);
} else { } else {
$relMapper->execute($obj->{$member}); $value = $obj->{$member};
} }
$relMapper->execute($value);
} }
} }
/** /**
* Delete hasMany * Delete hasMany
* *
* @param \ReflectionClass $refClass Reflection of object to delete * @param object $obj Object to delete
* @param object $obj Object to delete * @param mixed $objId Object id to delete
* @param mixed $objId Object id to delete * @param null|\ReflectionClass $refClass Reflection of object to delete
* *
* @return void * @return void
* *
* @since 1.0.0 * @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)) { if (empty($this->mapper::HAS_MANY)) {
return; return;
@ -169,9 +178,20 @@ final class DeleteMapper extends DataMapperAbstract
continue; continue;
} }
$objIds = []; $objIds = [];
$refProp = $refClass->getProperty($member); $isPrivate = $rel['private'] ?? false;
$values = $refProp->isPublic() ? $obj->{$member} : $refProp->getValue($obj);
$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)) { if (!\is_array($values)) {
// conditionals // conditionals

View File

@ -555,7 +555,7 @@ final class ReadMapper extends DataMapperAbstract
*/ */
public function populateAbstract(array $result, object $obj) : object public function populateAbstract(array $result, object $obj) : object
{ {
$refClass = new \ReflectionClass($obj); $refClass = null;
foreach ($this->mapper::COLUMNS as $column => $def) { foreach ($this->mapper::COLUMNS as $column => $def) {
$alias = $column . '_d' . $this->depth; $alias = $column . '_d' . $this->depth;
@ -569,28 +569,42 @@ final class ReadMapper extends DataMapperAbstract
$hasPath = false; $hasPath = false;
$aValue = []; $aValue = [];
$arrayPath = ''; $arrayPath = '';
$refProp = null;
$isPrivate = $def['private'] ?? false;
$member = '';
if ($isPrivate && $refClass === null) {
$refClass = new \ReflectionClass($obj);
}
if (\stripos($def['internal'], '/') !== false) { if (\stripos($def['internal'], '/') !== false) {
$hasPath = true; $hasPath = true;
$path = \explode('/', \ltrim($def['internal'], '/')); $path = \explode('/', \ltrim($def['internal'], '/'));
$member = $path[0]; $member = $path[0];
$refProp = $refClass->getProperty($path[0]); if ($isPrivate) {
$isPublic = $refProp->isPublic(); $refProp = $refClass->getProperty($path[0]);
$aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); $aValue = $refProp->getValue($obj);
} else {
$aValue = $obj->{$path[0]};
}
\array_shift($path); \array_shift($path);
$arrayPath = \implode('/', $path); $arrayPath = \implode('/', $path);
} else { } else {
$refProp = $refClass->getProperty($def['internal']); if ($isPrivate) {
$isPublic = $refProp->isPublic(); $refProp = $refClass->getProperty($def['internal']);
$member = $def['internal']; }
$member = $def['internal'];
} }
if (isset($this->mapper::OWNS_ONE[$def['internal']])) { if (isset($this->mapper::OWNS_ONE[$def['internal']])) {
$default = null; $default = null;
if (!isset($this->with[$member]) && $refProp->isInitialized($obj)) { if (!isset($this->with[$member])
$default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); && ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member}))
) {
$default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
} }
$value = $this->populateOwnsOne($def['internal'], $result, $default); $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); $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. // @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']])) { } elseif (isset($this->mapper::BELONGS_TO[$def['internal']])) {
$default = null; $default = null;
if (!isset($this->with[$member]) && $refProp->isInitialized($obj)) { if (!isset($this->with[$member])
$default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); && ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member}))
) {
$default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
} }
$value = $this->populateBelongsTo($def['internal'], $result, $default); $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'])) { 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); $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'])) { } elseif (\in_array($def['type'], ['string', 'compress', 'int', 'float', 'bool'])) {
if ($value !== null && $def['type'] === 'compress') { if ($value !== null && $def['type'] === 'compress') {
$def['type'] = 'string'; $def['type'] = 'string';
@ -625,44 +639,44 @@ final class ReadMapper extends DataMapperAbstract
$value = \gzinflate($value); $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']); \settype($value, $def['type']);
} }
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['type'] === 'DateTime') { } elseif ($def['type'] === 'DateTime') {
$value = $value === null ? null : new \DateTime($value); $value ??= new \DateTime($value);
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['type'] === 'DateTimeImmutable') { } elseif ($def['type'] === 'DateTimeImmutable') {
$value = $value === null ? null : new \DateTimeImmutable($value); $value ??= new \DateTimeImmutable($value);
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['type'] === 'Json') { } elseif ($def['type'] === 'Json') {
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, \json_decode($value, true)); $value = \json_decode($value, true);
} elseif ($def['type'] === 'Serializable') { } elseif ($def['type'] === 'Serializable') {
$member = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); $mObj = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
if ($member === null || $value === null) { if ($mObj !== null && $value !== null) {
$obj->{$def['internal']} = $value; $mObj->unserialize($value);
} else { $value = $mObj;
$member->unserialize($value);
} }
} }
if ($isPrivate) {
$refProp->setValue($obj, $value);
} else {
$obj->{$member} = $value;
}
} }
foreach ($this->mapper::HAS_MANY as $member => $def) { foreach ($this->mapper::HAS_MANY as $member => $def) {
@ -677,54 +691,68 @@ final class ReadMapper extends DataMapperAbstract
$hasPath = false; $hasPath = false;
$aValue = null; $aValue = null;
$arrayPath = '/'; $arrayPath = '/';
$refProp = null;
$isPrivate = $def['private'] ?? false;
if ($isPrivate && $refClass === null) {
$refClass = new \ReflectionClass($obj);
}
if (\stripos($member, '/') !== false) { if (\stripos($member, '/') !== false) {
$hasPath = true; $hasPath = true;
$path = \explode('/', $member); $path = \explode('/', $member);
$refProp = $refClass->getProperty($path[0]); $member = $path[0];
$isPublic = $refProp->isPublic();
if ($isPrivate) {
$refProp = $refClass->getProperty($path[0]);
}
\array_shift($path); \array_shift($path);
$arrayPath = \implode('/', $path); $arrayPath = \implode('/', $path);
$aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); $aValue = $isPrivate ? $refProp->getValue($obj) : $obj->{$path[0]};
} else { } elseif ($isPrivate) {
$refProp = $refClass->getProperty($member); $refProp = $refClass->getProperty($member);
$isPublic = $refProp->isPublic();
} }
if (\in_array($def['mapper']::COLUMNS[$column]['type'], ['string', 'int', 'float', 'bool'])) { 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']); \settype($value, $def['mapper']::COLUMNS[$column]['type']);
} }
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTime') { } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTime') {
$value = $value === null ? null : new \DateTime($value); $value ??= new \DateTime($value);
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTimeImmutable') { } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'DateTimeImmutable') {
$value = $value === null ? null : new \DateTimeImmutable($value); $value ??= new \DateTimeImmutable($value);
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
} }
$refProp->setValue($obj, $value);
} elseif ($def['mapper']::COLUMNS[$column]['type'] === 'Json') { } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'Json') {
if ($hasPath) { if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); $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') { } elseif ($def['mapper']::COLUMNS[$column]['type'] === 'Serializable') {
$member = $isPublic ? $obj->{$member} : $refProp->getValue($obj); $mObj = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
$member->unserialize($value);
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; continue;
} }
$isPrivate = $withData['private'] ?? false;
$objectMapper = $this->createRelationMapper($many['mapper']::get(db: $this->db), $member); $objectMapper = $this->createRelationMapper($many['mapper']::get(db: $this->db), $member);
if ($many['external'] === null/* same as $many['table'] !== $many['mapper']::TABLE */) { if ($many['external'] === null/* same as $many['table'] !== $many['mapper']::TABLE */) {
$objectMapper->where($many['mapper']::COLUMNS[$many['self']]['internal'], $primaryKey); $objectMapper->where($many['mapper']::COLUMNS[$many['self']]['internal'], $primaryKey);
@ -886,12 +916,12 @@ final class ReadMapper extends DataMapperAbstract
continue; continue;
} }
if ($refClass === null) { if ($isPrivate) {
$refClass = new \ReflectionClass($obj); if ($refClass === null) {
} $refClass = new \ReflectionClass($obj);
}
$refProp = $refClass->getProperty($member); $refProp = $refClass->getProperty($member);
if (!$refProp->isPublic()) {
$refProp->setValue($obj, !\is_array($objects) && ($many['conditional'] ?? false) === false $refProp->setValue($obj, !\is_array($objects) && ($many['conditional'] ?? false) === false
? [$many['mapper']::getObjectId($objects) => $objects] ? [$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) : $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; continue;
} }
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
/** @var ReadMapper $relMapper */ /** @var ReadMapper $relMapper */
$relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member); $relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member);
$refProp = $refClass->getProperty($member); $isPrivate = $withData['private'] ?? false;
if (!$refProp->isPublic()) { if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refProp = $refClass->getProperty($member);
$relMapper->loadHasManyRelations($refProp->getValue($obj)); $relMapper->loadHasManyRelations($refProp->getValue($obj));
} else { } else {
$relMapper->loadHasManyRelations($obj->{$member}); $relMapper->loadHasManyRelations($obj->{$member});

View File

@ -73,14 +73,14 @@ final class UpdateMapper extends DataMapperAbstract
*/ */
public function executeUpdate(object $obj) : mixed public function executeUpdate(object $obj) : mixed
{ {
$refClass = new \ReflectionClass($obj); $refClass = null;
$objId = $this->mapper::getObjectId($obj); $objId = $this->mapper::getObjectId($obj);
if ($this->mapper::isNullModel($obj)) { if ($this->mapper::isNullModel($obj)) {
return $objId === 0 ? null : $objId; return $objId === 0 ? null : $objId;
} }
$this->updateHasMany($refClass, $obj, $objId); $this->updateHasMany($obj, $objId, $refClass);
if (empty($objId)) { if (empty($objId)) {
return $this->mapper::create(db: $this->db)->execute($obj); return $this->mapper::create(db: $this->db)->execute($obj);
@ -102,7 +102,7 @@ final class UpdateMapper extends DataMapperAbstract
* *
* @since 1.0.0 * @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 { try {
// Model doesn't have anything to update // Model doesn't have anything to update
@ -124,10 +124,20 @@ final class UpdateMapper extends DataMapperAbstract
continue; continue;
} }
$refClass = $refClass ?? new \ReflectionClass($obj); $isPrivate = $column['private'] ?? false;
$property = $refClass->getProperty($propertyName); $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])) { if (isset($this->mapper::OWNS_ONE[$propertyName])) {
$id = \is_object($tValue) ? $this->updateOwnsOne($propertyName, $tValue) : $tValue; $id = \is_object($tValue) ? $this->updateOwnsOne($propertyName, $tValue) : $tValue;
@ -218,9 +228,9 @@ final class UpdateMapper extends DataMapperAbstract
/** /**
* Update has many relations * Update has many relations
* *
* @param \ReflectionClass $refClass Reflection of the object containing the relations * @param object $obj Object which contains the relations
* @param object $obj Object which contains the relations * @param mixed $objId Object id 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 * @return void
* *
@ -228,7 +238,7 @@ final class UpdateMapper extends DataMapperAbstract
* *
* @since 1.0.0 * @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)) { if (empty($this->with) || empty($this->mapper::HAS_MANY)) {
return; return;
@ -245,17 +255,33 @@ final class UpdateMapper extends DataMapperAbstract
throw new InvalidMapperException(); 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)) { if (!\is_array($values) || empty($values)) {
continue; continue;
} }
/** @var class-string<DataMapperFactory> $mapper */ /** @var class-string<DataMapperFactory> $mapper */
$mapper = $this->mapper::HAS_MANY[$propertyName]['mapper']; $mapper = $this->mapper::HAS_MANY[$propertyName]['mapper'];
$relReflectionClass = new \ReflectionClass(\reset($values)); $isPrivateRel = $this->mapper::HAS_MANY[$propertyName]['private'] ?? false;
if ($isPrivateRel) {
$relReflectionClass = new \ReflectionClass(\reset($values));
}
$objsIds[$propertyName] = []; $objsIds[$propertyName] = [];
foreach ($values as $key => &$value) { 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 if ($this->mapper::HAS_MANY[$propertyName]['table'] === $this->mapper::HAS_MANY[$propertyName]['mapper']::TABLE
&& isset($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]) && isset($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']])
) { ) {
$relProperty = $relReflectionClass->getProperty($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']); if ($isPrivateRel) {
$relProperty = $relReflectionClass->getProperty($mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']);
if (!$isPublic) {
$relProperty->setValue($value, $objId); $relProperty->setValue($value, $objId);
} else { } else {
$value->{$mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']} = $objId; $value->{$mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']} = $objId;

View File

@ -74,7 +74,7 @@ final class WriteMapper extends DataMapperAbstract
*/ */
public function executeCreate(object $obj) : mixed public function executeCreate(object $obj) : mixed
{ {
$refClass = new \ReflectionClass($obj); $refClass = null;
if ($this->mapper::isNullModel($obj)) { if ($this->mapper::isNullModel($obj)) {
$objId = $this->mapper::getObjectId($obj); $objId = $this->mapper::getObjectId($obj);
@ -86,10 +86,10 @@ final class WriteMapper extends DataMapperAbstract
$objId = $id; $objId = $id;
} else { } else {
$objId = $this->createModel($obj, $refClass); $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; return $objId;
} }
@ -97,21 +97,19 @@ final class WriteMapper extends DataMapperAbstract
/** /**
* Create model * Create model
* *
* @param object $obj Object to create * @param object $obj Object to create
* @param \ReflectionClass $refClass Reflection of the object to create * @param null|\ReflectionClass $refClass Reflection of the object to create
* *
* @return mixed * @return mixed
* *
* @since 1.0.0 * @since 1.0.0
*/ */
private function createModel(object $obj, \ReflectionClass $refClass) : mixed private function createModel(object $obj, \ReflectionClass &$refClass = null) : mixed
{ {
try { try {
$query = new Builder($this->db); $query = new Builder($this->db);
$query->into($this->mapper::TABLE); $query->into($this->mapper::TABLE);
$publicProperties = \get_object_vars($obj);
foreach ($this->mapper::COLUMNS as $column) { foreach ($this->mapper::COLUMNS as $column) {
$propertyName = \stripos($column['internal'], '/') !== false $propertyName = \stripos($column['internal'], '/') !== false
? \explode('/', $column['internal'])[0] ? \explode('/', $column['internal'])[0]
@ -123,13 +121,16 @@ final class WriteMapper extends DataMapperAbstract
continue; continue;
} }
if (!isset($publicProperties[$propertyName])) { $tValue = null;
if ($column['private'] ?? false) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$property = $refClass->getProperty($propertyName); $property = $refClass->getProperty($propertyName);
$property->setAccessible(true); $tValue = $property->getValue($obj);
$tValue = $property->getValue($obj);
$property->setAccessible(false);
} else { } else {
$tValue = $publicProperties[$propertyName]; $tValue = $obj->{$propertyName};
} }
if (isset($this->mapper::OWNS_ONE[$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'])) { 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) // has by (obj is stored as a different model e.g. model = profile but reference/db is account)
$refClass = new \ReflectionClass($obj); if ($this->mapper::BELONGS_TO[$propertyName]['private']) {
$refProp = $refClass->getProperty($this->mapper::BELONGS_TO[$propertyName]['by']); $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); $obj = $refProp->getValue($obj);
} else {
$obj = $obj->{$this->mapper::BELONGS_TO[$propertyName]['by']};
}
} }
/** @var class-string<DataMapperFactory> $mapper */ /** @var class-string<DataMapperFactory> $mapper */
@ -248,9 +252,9 @@ final class WriteMapper extends DataMapperAbstract
/** /**
* Create has many models * Create has many models
* *
* @param \ReflectionClass $refClass Reflection of the object to create * @param object $obj Object to create
* @param object $obj Object to create * @param mixed $objId Id of the parent object
* @param mixed $objId Id of the parent object * @param null|\ReflectionClass $refClass Reflection of the object to create
* *
* @return void * @return void
* *
@ -258,15 +262,27 @@ final class WriteMapper extends DataMapperAbstract
* *
* @since 1.0.0 * @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'])) { if (!isset($this->mapper::HAS_MANY[$propertyName]['mapper'])) {
throw new InvalidMapperException(); // @codeCoverageIgnore throw new InvalidMapperException(); // @codeCoverageIgnore
} }
$property = $refClass->getProperty($propertyName); $isPrivate = $rel['private'] ?? false;
$values = $property->isPublic() ? $obj->{$propertyName} : $property->getValue($obj); $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<DataMapperFactory> $mapper */ /** @var class-string<DataMapperFactory> $mapper */
$mapper = $this->mapper::HAS_MANY[$propertyName]['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'] ? $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal']
: 'ERROR'; : '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)) { if (\is_object($values)) {
// conditionals // conditionals
$publicProperties = \get_object_vars($values); if ($isInternalPrivate) {
if (!isset($publicProperties[$internalName])) {
$relReflectionClass = new \ReflectionClass($values); $relReflectionClass = new \ReflectionClass($values);
$relProperty = $relReflectionClass->getProperty($internalName); $relProperty = $relReflectionClass->getProperty($internalName);
$relProperty->setAccessible(true);
$relProperty->setValue($values, $objId); $relProperty->setValue($values, $objId);
$relProperty->setAccessible(false);
} else { } else {
$values->{$internalName} = $objId; $values->{$internalName} = $objId;
} }
@ -297,7 +312,8 @@ final class WriteMapper extends DataMapperAbstract
} }
$objsIds = []; $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) { foreach ($values as $key => $value) {
if (!\is_object($value)) { if (!\is_object($value)) {
@ -307,7 +323,6 @@ final class WriteMapper extends DataMapperAbstract
continue; continue;
} }
/** @var \ReflectionClass $relReflectionClass */
$primaryKey = $mapper::getObjectId($value); $primaryKey = $mapper::getObjectId($value);
// already in db // 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) // 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'])) { if (!isset($this->mapper::HAS_MANY[$propertyName]['external'])) {
$relProperty = $relReflectionClass->getProperty($internalName); $relProperty = null;
$isRelPublic = $relProperty->isPublic(); 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) // 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]) if (isset($mapper::BELONGS_TO[$internalName])
|| isset($mapper::OWNS_ONE[$internalName])) { || isset($mapper::OWNS_ONE[$internalName])) {
if (!$isRelPublic) { if ($isRelPrivate) {
$relProperty->setValue($value, $this->mapper::createNullModel($objId)); $relProperty->setValue($value, $this->mapper::createNullModel($objId));
} else { } else {
$value->{$internalName} = $this->mapper::createNullModel($objId); $value->{$internalName} = $this->mapper::createNullModel($objId);
} }
} elseif (!$isRelPublic) { } elseif ($isRelPrivate) {
$relProperty->setValue($value, $objId); $relProperty->setValue($value, $objId);
} else { } else {
$value->{$internalName} = $objId; $value->{$internalName} = $objId;
} }
if (!$isRelPublic) {
$relProperty->setAccessible(false);
}
} }
$objsIds[$key] = $mapper::create(db: $this->db)->execute($value); $objsIds[$key] = $mapper::create(db: $this->db)->execute($value);