$rel) { $property = $reflectionClass->getProperty($propertyName); if (!($isPublic = $property->isPublic())) { $property->setAccessible(true); } $values = $property->getValue($obj); if (!($isPublic)) { $property->setAccessible(false); } if (!isset(static::$hasMany[$propertyName]['mapper'])) { throw new InvalidMapperException(); } /** @var DataMapperAbstract $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; $objsIds = []; $relReflectionClass = null; foreach ($values as $key => &$value) { if (!is_object($value)) { // Is scalar => already in database $objsIds[$key] = $value; continue; } if (!isset($relReflectionClass)) { $relReflectionClass = new \ReflectionClass(get_class($value)); } $primaryKey = $mapper::getObjectId($value, $relReflectionClass); // already in db if (!empty($primaryKey)) { $objsIds[$key] = $value; continue; } // create if not existing /** @var string $table */ /** @var array $columns */ 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::updateRelationTable($propertyName, $objsIds, $objId); } } /** * Update relation table entry * * Deletes old entries and creates new ones * * @param string $propertyName Property name to initialize * @param array $objsIds Object ids to insert * @param mixed $objId Model to reference * * @return mixed * * @throws \Exception * * @since 1.0.0 */ private static function updateRelationTable(string $propertyName, array $objsIds, $objId) { /** @var string $table */ if ( !empty($objsIds) && static::$hasMany[$propertyName]['table'] !== static::$table && static::$hasMany[$propertyName]['table'] !== static::$hasMany[$propertyName]['mapper']::$table ) { $many = self::getHasManyRaw($objId); foreach(static::$hasMany as $member => $value) { // todo: definately an error here. needs testing throw new \Exception(); $removes = array_diff_key($many[$member], $objsIds[$member]); $adds = array_diff_key($objsIds[$member], $many[$member]); if(!empty($removes)) { self::deleteRelationTable($propertyName, $removes, $objId); } if(!empty($adds)) { self::createRelationTable($propertyName, $adds, $objId); } } } } /** * Update owns one * * The reference is stored in the main model * * @param string $propertyName Property name to initialize * @param Object $obj Object to update * * @return mixed * * @since 1.0.0 */ private static function updateOwnsOne(string $propertyName, $obj) { if (is_object($obj)) { /** @var DataMapperAbstract $mapper */ $mapper = static::$ownsOne[$propertyName]['mapper']; // todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one? return $mapper::update($obj); } return $obj; } /** * Update owns one * * The reference is stored in the main model * * @param string $propertyName Property name to initialize * @param Object $obj Object to update * * @return mixed * * @since 1.0.0 */ private static function updateBelongsTo(string $propertyName, $obj) { if (is_object($obj)) { /** @var DataMapperAbstract $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; return $mapper::update($obj); } return $obj; } /** * Update object in db. * * @param Object $obj Model to update * @param mixed $objId Model id * @param \ReflectionClass $reflectionClass Reflection class * * @return mixed * * @since 1.0.0 */ private static function updateModel($obj, $objId, \ReflectionClass $reflectionClass = null) /* : void */ { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix()) ->into(static::$table) ->where(static::$primaryField, '=', $objId); $properties = $reflectionClass->getProperties(); foreach ($properties as $property) { $propertyName = $property->getName(); if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) { continue; } if (!($isPublic = $property->isPublic())) { $property->setAccessible(true); } // todo: the order of updating could be a problem. maybe looping through ownsOne and belongsTo first is better. foreach (static::$columns as $key => $column) { if (isset(static::$ownsOne[$propertyName]) && $column['internal'] === $propertyName) { $id = self::updateOwnsOne($propertyName, $property->getValue($obj)); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed $query->update($column['name'])->value($value, $column['type']); break; } elseif (isset(static::$belongsTo[$propertyName]) && $column['internal'] === $propertyName) { $id = self::updateBelongsTo($propertyName, $property->getValue($obj)); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed $query->update($column['name'])->value($value, $column['type']); break; } elseif ($column['internal'] === $propertyName && $column['type'] !== static::$primaryField) { $value = self::parseValue($column['type'], $property->getValue($obj)); $query->update($column['name'])->value($value, $column['type']); break; } } if (!($isPublic)) { $property->setAccessible(false); } } self::$db->con->prepare($query->toSql())->execute(); } /** * Update object in db. * * @param mixed $obj Object reference (gets filled with insert id) * @param int $relations Create all relations as well * * @return int * * @since 1.0.0 */ public static function update($obj, int $relations = RelationType::ALL) : int { self::extend(__CLASS__); $reflectionClass = new \ReflectionClass(get_class($obj)); $objId = self::getObjectId($obj, $reflectionClass); $update = true; if(empty($objId)) { $update = false; self::create($obj, $relations); } if ($relations === RelationType::ALL) { self::updateHasMany($reflectionClass, $obj, $objId); } if($update) { self::updateModel($obj, $objId, $reflectionClass); } return $objId; } }