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);
} 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;

View File

@ -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;

View File

@ -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

View File

@ -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});

View File

@ -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<DataMapperFactory> $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;

View File

@ -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<DataMapperFactory> $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<DataMapperFactory> $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);