From 07b1264f90c348080eba26e362935716ab08ae26 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sun, 7 Aug 2016 18:20:44 +0200 Subject: [PATCH] Cleanup --- DataStorage/Database/DataMapperAbstract.php | 489 +++++++++++--------- 1 file changed, 273 insertions(+), 216 deletions(-) diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php index 0bbc6d83a..71c59ab9c 100644 --- a/DataStorage/Database/DataMapperAbstract.php +++ b/DataStorage/Database/DataMapperAbstract.php @@ -78,6 +78,8 @@ class DataMapperAbstract implements DataMapperInterface /** * Relations. * + * Relation is defined in a relation table + * * @var string[] * @since 1.0.0 */ @@ -86,38 +88,18 @@ class DataMapperAbstract implements DataMapperInterface /** * Relations. * - * @var string[] - * @since 1.0.0 - */ - protected static $ownsMany = []; - - /** - * Relations. + * Relation is defined in the model * * @var string[] * @since 1.0.0 */ protected static $hasOne = []; - /** - * Extending other mappers. - * - * @var string[] - * @since 1.0.0 - */ - protected static $isExtending = []; - - /** - * Extending relations. - * - * @var string[] - * @since 1.0.0 - */ - protected static $extends = []; - /** * Relations. * + * Relation is defined in current mapper + * * @var string[] * @since 1.0.0 */ @@ -151,7 +133,6 @@ class DataMapperAbstract implements DataMapperInterface 'columns' => [], 'hasMany' => [], 'hasOne' => [], - 'ownsMany' => [], 'ownsOne' => [], 'table' => [], ]; @@ -233,7 +214,6 @@ class DataMapperAbstract implements DataMapperInterface self::$collection['columns'][] = $class::$columns; self::$collection['hasMany'][] = $class::$hasMany; self::$collection['hasOne'][] = $class::$hasOne; - self::$collection['ownsMany'][] = $class::$ownsMany; self::$collection['ownsOne'][] = $class::$ownsOne; self::$collection['table'][] = $class::$table; @@ -290,10 +270,7 @@ class DataMapperAbstract implements DataMapperInterface self::$createdAt = ''; self::$columns = []; self::$hasMany = []; - self::$ownsMany = []; self::$hasOne = []; - self::$isExtending = []; - self::$extends = []; self::$ownsOne = []; self::$table = ''; self::$fields = []; @@ -301,7 +278,6 @@ class DataMapperAbstract implements DataMapperInterface 'primaryField' => [], 'createdAt' => [], 'columns' => [], - 'hasMany' => [], 'hasOne' => [], 'ownsMany' => [], 'ownsOne' => [], @@ -350,173 +326,282 @@ class DataMapperAbstract implements DataMapperInterface { self::extend(__CLASS__); - $query = new Builder(self::$db); - $query->prefix(self::$db->getPrefix()) - ->into(static::$table); - $reflectionClass = new \ReflectionClass(get_class($obj)); - $properties = $reflectionClass->getProperties(); - $extendedIds = []; + $objId = self::createModel($reflectionClass, $obj); - /* Create extended */ - foreach (static::$isExtending as $member => $rel) { - /** @var DataMapperAbstract $mapper */ - $mapper = $rel['mapper']; - $extendedIds[$member] = $mapper::create($obj, $relations); + if ($relations === RelationType::ALL) { + self::createHasMany($reflectionClass, $obj, $objId); } + self::setObjectId($reflectionClass, $obj, $objId); + + return $objId; + } + + /** + * Create base model. + * + * @param \ReflectionClass $reflectionClass Reflection class + * @param Object $obj Model to create + * + * @return mixed + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function createModel(\ReflectionClass $reflectionClass, $obj) + { + $query = new Builder(self::$db); + $query->prefix(self::$db->getPrefix())->into(static::$table); + + $properties = $reflectionClass->getProperties(); + foreach ($properties as $property) { - $property->setAccessible(true); + $propertyName = $property->getName(); - if (isset(static::$hasMany[($pname = $property->getName())])) { + if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) { continue; - } else { - /* is not a has many property */ - foreach (static::$columns as $key => $column) { - /* Insert hasOne first */ - if (isset(static::$hasOne[$pname]) && is_object($relObj = $property->getValue($obj))) { - /* only insert if not already inserted */ - /** @var DataMapperAbstract $mapper */ - $mapper = static::$hasOne[$pname]['mapper']; + } - $relReflectionClass = new \ReflectionClass(get_class($relObj)); - /** @var array $columns */ - $relProperty = $relReflectionClass->getProperty($mapper::$columns[$mapper::$primaryField]['internal']); - $relProperty->setAccessible(true); - $primaryKey = $relProperty->getValue($relObj); - $relProperty->setAccessible(false); + if (!($isPublic = $property->isPublic())) { + $property->setAccessible(true); + } - if (empty($primaryKey)) { - $primaryKey = $mapper::create($property->getValue($obj)); - } + foreach (static::$columns as $key => $column) { + if (isset(static::$ownsOne[$propertyName])) { + self::createOwnsOne($propertyName, $property, $obj); + } elseif ($column['internal'] === $propertyName) { + $value = self::parseValue($column['type'], $property->getValue($obj)); - //$property->setValue($obj, $primaryKey); - } - - if ($column['internal'] === $pname) { - if (isset($extendedIds[$pname])) { - /* Set extended id */ - $value = $extendedIds[$pname]; - $property->setValue($obj, $value); - } else { - $value = $property->getValue($obj); - } - - if ($column['type'] === 'DateTime') { - $value = isset($value) ? $value->format('Y-m-d H:i:s') : null; - } elseif ($column['type'] === 'json') { - $value = isset($value) ? json_encode($value) : ''; - } elseif ($column['type'] === 'Serializable') { - $value = $value->serialize(); - } elseif ($column['type'] === 'jsonSerializable') { - $value = $value->jsonSerializable(); - } elseif (is_object($value)) { - $value = $value->getId(); - } - - $query->insert($column['name']) - ->value($value, $column['type']); - break; - } + $query->insert($column['name'])->value($value, $column['type']); + break; } } - // todo: do i have to reverse the accessibility or is there no risk involved here? + if (!($isPublic)) { + $property->setAccessible(false); + } } self::$db->con->prepare($query->toSql())->execute(); - $objId = self::$db->con->lastInsertId(); - // handle relations - if ($relations === RelationType::ALL) { - foreach (static::$hasMany as $member => $rel) { - /* is a has many property */ - $property = $reflectionClass->getProperty($member); // throws ReflectionException - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - } - - $values = $property->getValue($obj); - $temp = reset($values); - $pname = $property->getName(); // todo: isn't this just member? and not necessary? - - if (!($isPublic)) { - $property->setAccessible(false); - } - - if (is_object($temp)) { - // todo: only create if object doesn't exists... get primaryKey field, then get member name based on this - // now check if id is null or set. - $mapper = static::$hasMany[$pname]['mapper']; - $objsIds = []; - - if (isset(static::$hasMany[$pname]['mapper']) && !isset(static::$hasMany[$pname]['relationmapper'])) { - $relReflectionClass = new \ReflectionClass(get_class($temp)); - } else { - // todo: init other $relReflectionClass?! - throw new \Exception('This should never happen, I guess?!.'); - } - - foreach ($values as $key => &$value) { - // Skip if already in db/has key - /** @noinspection PhpUndefinedVariableInspection */ - $relProperty = $relReflectionClass->getProperty($mapper::$columns[$mapper::$primaryField]['internal']); - $relProperty->setAccessible(true); - $primaryKey = $relProperty->getValue($value); - $relProperty->setAccessible(false); - - if (!empty($primaryKey)) { - continue; - } - - // Setting relation value for relation (since the relation is not stored in an extra relation table) - if (isset(static::$hasMany[$pname]['mapper']) && !isset(static::$hasMany[$pname]['relationmapper'])) { - $relProperty = $relReflectionClass->getProperty($mapper::$columns[static::$hasMany[$pname]['dst']]['internal']); - $relProperty->setAccessible(true); - $relProperty->setValue($value, $objId); - $relProperty->setAccessible(false); - } - - $objsIds[$key] = $mapper::create($value); - } - } elseif (is_scalar($temp)) { - $objsIds = $values; - } else { - throw new \Exception('Unexpected value for relational data mapping.'); - } - - if (isset(static::$hasMany[$pname]['mapper']) && isset(static::$hasMany[$pname]['relationmapper'])) { - /* is many->many */ - $relQuery = new Builder(self::$db); - $relQuery->prefix(self::$db->getPrefix()) - ->into(static::$hasMany[$pname]['table']) - ->insert(static::$hasMany[$pname]['src'], static::$hasMany[$pname]['dst']); - - foreach ($objsIds as $key => $src) { - $relQuery->values($src, $objId); - } - - self::$db->con->prepare($relQuery->toSql())->execute(); - } - } - } + return self::$db->con->lastInsertId(); + } + /** + * Set id to model + * + * @param \ReflectionClass $reflectionClass Reflection class + * @param Object $obj Object to create + * @param mixed $objId Id to set + * + * @return void + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function setObjectId(\ReflectionClass $reflectionClass, $obj, $objId) + { $reflectionProperty = $reflectionClass->getProperty(static::$columns[static::$primaryField]['internal']); - // todo: can't i just set it accessible anyways and not set it to private afterwards? - if (!($accessible = $reflectionProperty->isPublic())) { + if (!($isPublic = $reflectionProperty->isPublic())) { $reflectionProperty->setAccessible(true); } settype($objId, static::$columns[static::$primaryField]['type']); $reflectionProperty->setValue($obj, $objId); - if (!$accessible) { + if (!$isPublic) { $reflectionProperty->setAccessible(false); } + } - return $objId; + /** + * Create has many + * + * @param \ReflectionClass $reflectionClass Reflection class + * @param Object $obj Object to create + * @param mixed $objId Id to set + * + * @return void + * + * @throws + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function createHasMany(\ReflectionClass $reflectionClass, $obj, $objId) + { + foreach (static::$hasMany as $propertyName => $rel) { + $property = $reflectionClass->getProperty($propertyName); + + if (!($isPublic = $property->isPublic())) { + $property->setAccessible(true); + } + + $values = $property->getValue($obj); + + if (!($isPublic)) { + $property->setAccessible(false); + } + + if (!is_object($temp = reset($values))) { + throw new \Exception('Unexpected value for relational data mapping.'); + } + + if (!isset(static::$hasMany[$propertyName]['mapper'])) { + throw new \Exception('No mapper set for relation object.'); + } + + $mapper = static::$hasMany[$propertyName]['mapper']; + $objsIds = []; + $relReflectionClass = new \ReflectionClass(get_class($temp)); + + foreach ($values as $key => &$value) { + /** @noinspection PhpUndefinedVariableInspection */ + $relProperty = $relReflectionClass->getProperty($mapper::$columns[$mapper::$primaryField]['internal']); + + if (!($isPublic = $relProperty->isPublic())) { + $relProperty->setAccessible(true); + } + + $primaryKey = $relProperty->getValue($value); + + if (!($isPublic)) { + $relProperty->setAccessible(false); + } + + // already in db + if (!empty($primaryKey)) { + continue; + } + + // Setting relation value (id) for relation (since the relation is not stored in an extra relation table) + /** @var string $table */ + if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table) { + $relProperty = $relReflectionClass->getProperty($mapper::$columns[static::$hasMany[$propertyName]['dst']]['internal']); + + if (!$isPublic) { + $relProperty->setAccessible(true); + } + + $relProperty->setValue($value, $objId); + + if (!($isPublic)) { + $relProperty->setAccessible(false); + } + } + + $objsIds[$key] = $mapper::create($value); + } + + self::createRelationTable($propertyName, $objsIds, $objId); + } + } + + private static function createHasOne() + { + foreach (static::$hasOne as $propertyName => $rel) { + + } + } + + /** + * Create owns one + * + * The reference is stored in the main model + * + * @param string $propertyName Property name to initialize + * @param \ReflectionProperty $property Reflection property + * @param Object $obj Object to create + * + * @return mixed + * + * @throws + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function createOwnsOne(string $propertyName, \ReflectionProperty $property, $obj) + { + if (is_object($relObj = $property->getValue($obj))) { + $mapper = static::$ownsOne[$propertyName]['mapper']; + $relReflectionClass = new \ReflectionClass(get_class($relObj)); + /** @var array $columns */ + $relProperty = $relReflectionClass->getProperty($mapper::$columns[$mapper::$primaryField]['internal']); + $relProperty->setAccessible(true); + $primaryKey = $relProperty->getValue($relObj); + $relProperty->setAccessible(false); + + if (empty($primaryKey)) { + $primaryKey = $mapper::create($property->getValue($obj)); + } + + return $primaryKey; + } + + return 0; + } + + /** + * Create relation table entry + * + * In cas of a many to many relation the relation has to be stored in a relation table + * + * @param string $propertyName Property name to initialize + * @param array $objsIds Object ids to insert + * @param mixed $objId Model to reference + * + * @return mixed + * + * @throws + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function createRelationTable(string $propertyName, array $objsIds, $objId) + { + /** @var string $table */ + if (static::$hasMany[$propertyName]['table'] !== static::$table && static::$hasMany[$propertyName]['table'] !== static::$hasMany[$propertyName]['mapper']::$table) { + $relQuery = new Builder(self::$db); + $relQuery->prefix(self::$db->getPrefix()) + ->into(static::$hasMany[$propertyName]['table']) + ->insert(static::$hasMany[$propertyName]['src'], static::$hasMany[$propertyName]['dst']); + + foreach ($objsIds as $key => $src) { + $relQuery->values($src, $objId); + } + + self::$db->con->prepare($relQuery->toSql())->execute(); + } + } + + /** + * Parse value + * + * @param string $type Value type + * @param mixed $value Value to parse + * + * @return mixed + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private static function parseValue(string $type, $value) + { + if ($type === 'DateTime') { + return isset($value) ? $value->format('Y-m-d H:i:s') : null; + } elseif ($type === 'json') { + return isset($value) ? json_encode($value) : ''; + } elseif ($type === 'Serializable') { + return $value->serialize(); + } elseif ($type === 'jsonSerializable') { + return $value->jsonSerializable(); + } elseif (is_object($value)) { + return $value->getId(); + } + + return $value; } /** @@ -545,28 +630,32 @@ class DataMapperAbstract implements DataMapperInterface $properties = $reflectionClass->getProperties(); foreach ($properties as $property) { - $property->setAccessible(true); + if (!($isPublic = $property->isPublic())) { + $property->setAccessible(true); + } - if (isset(static::$hasMany[($pname = $property->getName())])) { + if (isset(static::$hasMany[($propertyName = $property->getName())])) { continue; - } else { - /* is not a has many property */ - foreach (static::$columns as $key => $column) { - if ($column['internal'] === $pname) { - $value = $property->getValue($obj); + } - if ($column['type'] === 'DateTime') { - $value = isset($value) ? $value->format('Y-m-d H:i:s') : null; - } + /* is not a has many property */ + foreach (static::$columns as $key => $column) { + if ($column['internal'] === $propertyName) { + $value = $property->getValue($obj); - $query->update($column['name']) - ->value($value); - break; + if ($column['type'] === 'DateTime') { + $value = isset($value) ? $value->format('Y-m-d H:i:s') : null; } + + $query->update($column['name']) + ->value($value); + break; } } - // todo: do i have to reverse the accessibility or is there no risk involved here? + if (!$isPublic) { + $property->setAccessible(false); + } } self::$db->con->prepare($query->toSql())->execute(); @@ -629,33 +718,6 @@ class DataMapperAbstract implements DataMapperInterface return self::populateAbstract($result, $obj); } - /** - * Populate data. - * - * Is overwriting the hasOne id stored in the member variable by the object. - * todo: hasMany needs to be implemented somehow?!?! - * - * @param $obj Object to add the relations to - * @param int $relations Relations type - * - * @return mixed - * - * @since 1.0.0 - * @author Dennis Eichhorn - */ - public static function populateExtending($obj, int $relations = RelationType::ALL) - { - $reflectionClass = new \ReflectionClass(get_class($obj)); - - foreach (static::$isExtending as $member => $rel) { - $reflectionProperty = $reflectionClass->getProperty($member); - - /** @var DataMapperAbstract $mapper */ - $mapper = $rel['mapper']; - $mapper::get($reflectionProperty->getValue($obj), $relations, $obj); - } - } - /** * Populate data. * @@ -957,16 +1019,11 @@ class DataMapperAbstract implements DataMapperInterface */ public static function fillRelations(array &$obj, int $relations = RelationType::ALL) { - $hasMany = count(static::$hasMany) > 0; - $hasOne = count(static::$hasOne) > 0; - $isExtending = count(static::$isExtending) > 0; + $hasMany = count(static::$hasMany) > 0; + $hasOne = count(static::$hasOne) > 0; - if (($relations !== RelationType::NONE && ($hasMany || $hasOne)) || $isExtending) { + if ($relations !== RelationType::NONE && ($hasMany || $hasOne)) { foreach ($obj as $key => $value) { - if ($isExtending) { - self::populateExtending($obj[$key], $relations); - } - /* loading relations from relations table and populating them and then adding them to the object */ if ($relations !== RelationType::NONE) { if ($hasMany) {