Obj::merge() 

\nn\t3::Obj()->merge($model = NULL, $overlay = NULL); 

Merge an array into an object

\nn\t3::Obj( \My\Doman\Model )->merge(['title'=>'New title']);
Copied!

This can even be used to write / overwrite FileReferences. In this example, $data is merged with an existing model. | falMedia is an ObjectStorage in the example. The first element in falMedia already exists already exists in the database(uid = 12). Only the title is updated here. The second element in the array (without uid) is new. For this, a new | sys_file_reference is automatically created in the database.

$data = [
    'uid' => 10,
    'title' => 'The title',
    'falMedia' => [
        ['uid'=>12, 'title'=>'1st image title'],
        ['title'=>'NEW image title', 'publicUrl'=>'fileadmin/_tests/5e505e6b6143a.jpg'],
    ]
];
$oldModel = $repository->findByUid( $data['uid'] );
$mergedModel = \nn\t3::Obj($oldModel)->merge($data);
Copied!

Hint To create a new model with data from an array, there is the method there is the method $newModel = \nn\t3::Convert($data)->toModel( \My\Model\Name::class );

| @return object

Source Code 

public function merge( $model = null, $overlay = null )
{
	$overlay = $this->initialArgument !== null ? $model : $overlay;
	$model = $this->initialArgument !== null ? $this->initialArgument : $model;
	$schema = \nn\t3::Obj()->getClassSchema($model);
	$modelProperties = $schema->getProperties();
	if (!is_array($overlay)) return $model;
	foreach ($overlay as $propName=>$value) {
		if ($propInfo = $modelProperties[$propName] ?? false) {
			// Typ für Property des Models, z.B. `string`
			$propType = $this->get( $propInfo, 'type');
			$isSysFile = is_a( $propType, \TYPO3\CMS\Core\Resource\File::class, true );
			if ($this->isSimpleType($propType)) {
				// -----
				// Es ist ein "einfacher Typ" (`string`, `int` etc.). Kann direkt gesetzt werden!
				$this->set( $model, $propName, $value );
				continue;
			}
			if (!class_exists($propType)) {
				\nn\t3::Exception( "Class of type `{$propType}` is not defined." );
			}
			$curPropValue = $this->get( $model, $propName );
			if (!$isSysFile) {
				// Es ist ein `Model`, `FileReference` etc.
			$child = \nn\t3::newClass( $propType );
			}
			if ($isSysFile) {
				$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
				$uid = false;
				// '1'
				if (is_numeric($value)) {
					$uid = $value;
				}
				// ['uid'=>1]
				if (!$uid && is_array($value)) {
					$uid = $value['uid'] ?? false;
				}
				// 'index.php?eID=dumpFile&t=f&f=255&token=...' or 'https://www.website.com/fileadmin/file.txt'
				if (!$uid && is_string($value)) {
					$queryParams = [];
					$parsedUrl = parse_url($value);
					parse_str($parsedUrl['query'], $queryParams);
					if (($queryParams['eID'] ?? false) == 'dumpFile' && $uid = intval($queryParams['f'] ?? 0)) {
						$value = $resourceFactory->getFileObject($uid);
					} else if ($parsedUrl['host'] ?? false) {
						$value = ltrim($parsedUrl['path'], '/');
					}
				}
				if ($uid) {
					$value = $resourceFactory->getFileObject(intval($uid));
				} else {
					try {
						// '/var/www/path/to/file.txt' or '1:/path/to/file.txt' or '/fileadmin/path/to/file.txt'
						$value = $resourceFactory->getFileObjectFromCombinedIdentifier($value);
					} catch( \Exception $e ) {
						$value = null;
					}
				}
			} else if ($this->isFileReference($child)) {
				// -----
				// Die Property ist eine einzelne `SysFileReference` – keine `ObjectStorage`
				$curPublicUrl = \nn\t3::File()->getPublicUrl( $curPropValue );
				$publicUrl = \nn\t3::File()->getPublicUrl( $value );
				if ($curPublicUrl == $publicUrl) {
					// An der URL hat sich nichts geändert. Bisherige `SysFileReference` weiter verwenden.
					$value = $curPropValue;
				} else {
					// Neue URL. Falls bereits ein FAL am Model: Entfernen
					if ($this->isFileReference($curPropValue)) {
						$persistenceManager = GeneralUtility::makeInstance( PersistenceManager::class );
						$persistenceManager->remove( $curPropValue );
					}
					// ... und neues FAL erzeugen
					if ($value) {
						\nn\t3::Fal()->attach( $model, $propName, $value );
						continue;
					} else {
						$value = null;
					}
				}
			} else if ($this->isStorage($child)) {
				// -----
				// Die Property ist eine `ObjectStorage`
				$value = $this->forceArray( $value );
				$childPropType = \nn\t3::Obj()->get($propInfo, 'elementType');
				if (!class_exists($childPropType)) {
					\nn\t3::Exception( "Class of type `{$childPropType}` is not defined." );
				}
				// sys_file stored in the ObjectStorage?
				$isSysFile = is_a($childPropType, \TYPO3\CMS\Core\Resource\File::class, true);
				$isFileReference = false;
				if (!$isSysFile) {
					$storageItemInstance = \nn\t3::newClass( $childPropType );
					$isFileReference = $this->isFileReference( $storageItemInstance );
				}
				// Array der existierende Items in der `ObjectStorage` holen. Key ist `uid` oder `publicUrl`
				$existingStorageItemsByUid = [];
				if ($curPropValue) {
					foreach ($curPropValue as $item) {
						$uid = $isFileReference ? \nn\t3::File()->getPublicUrl( $item ) : $this->get( $item, 'uid' );
						if (!isset($existingStorageItemsByUid)) {
							$existingStorageItemsByUid[$uid] = [];
						}
						$existingStorageItemsByUid[$uid][] = $item;
					}
				}
				$storageClassName = get_class($child);
				$objectStorage =  \nn\t3::newClass( $storageClassName );
				// Jedes Item in die Storage einfügen. Dabei werden bereits vorhandene Items aus der alten Storage verwendet.
				foreach ($value as $itemData) {
					$uid = false;
					// `[1, ...]`
					if (is_numeric($itemData)) $uid = $itemData;
					// `[['publicUrl'=>'bild.jpg'], ...]` oder `[['bild.jpg'], ...]`
					if (!$uid && $isFileReference) $uid = \nn\t3::File()->getPublicUrl( $itemData );
					// `[['uid'=>'1'], ...]`
					if (!$uid) $uid = $this->get( $itemData, 'uid' );
					// Gibt es das Item bereits? Dann vorhandenes Item verwenden, kein neues erzeugen!
					$arrayReference = $existingStorageItemsByUid[$uid] ?? [];
					$item = array_shift($arrayReference);
					// Item bereits vorhanden?
					if ($item) {
						// ... dann das bisherige Item verwenden.
						// $item = \nn\t3::Obj( $item )->merge( $itemData );
					} else if ($isSysFile) {
						\nn\t3::Exception( "Converting to SysFile not supported yet." );
					} else if ($isFileReference) {
						// sonst: Falls eine FileReference gewünscht ist, dann neu erzeugen!
						$item = \nn\t3::Fal()->createForModel( $model, $propName, $itemData );
					} else if ($uid) {
						// Alles AUSSER `FileReference` – und `uid` übergeben/bekannt? Dann das Model aus der Datenbank laden.
						$item = \nn\t3::Db()->get( $uid, $childPropType );
						// Model nicht in DB gefunden? Dann ignorieren.
						if (!$item) continue;
					} else {
						// Keine `FileReference` und KEINE `uid` übergeben? Dann neues Model erzeugen.
						$item = \nn\t3::newClass( $childPropType );
					}
					// Model konnte nicht erzeugt / gefunden werden? Dann ignorieren!
					if (!$item) continue;
					// Merge der neuen Overlay-Daten und ans Storage hängen
					$item = \nn\t3::Obj( $item )->merge( $itemData );
					$objectStorage->attach( $item );
				}
				$value = $objectStorage;
			}
			else if ( is_a($child, \DateTime::class, true )) {
				// -----
				// Die Property ist ein `DateTime`
				if ($value) {
					$value = (new \DateTime())->setTimestamp( $value );
				} else {
					$value = null;
				}
			}
			else {
				// -----
				// Property enthält eine einzelne Relation, ist aber weder eine `FileReference` noch eine `ObjectStorage`
				if ($uid = is_numeric($value) ? $value : $this->get( $value, 'uid' )) {
					$child = \nn\t3::Db()->get( $uid, get_class($child) );
					if (!$child) $value = null;
				}
				if ($value) {
					$value = \nn\t3::Obj( $child )->merge( $value );
				}
			}
			$this->set( $model, $propName, $value );
		}
	}
	return $model;
}
Copied!