Umwandlung von Bildfeldern zur Nutzung des Bild-Asset-Feldtyps in eZ Platform

Eines der kennzeichnenden Features der eZ Platform Content Engine ist die Flexibilität. Entwicklern steht es frei, ihr Inhaltsmodell auf der Grundlage von Feldtypen zu erstellen. eZ Platform wird mit vielen wichtigen Feldtypen ausgeliefert. Darüber hinaus haben wir im Laufe der Zeit weitere nützliche Feldtypen hinzugefügt, die Sie für Ihre Projekte verwenden können.

In eZ Platform 2.3 haben wir einen Bild-Asset-Feldtyp hinzugefügt, um den klassischen Bildtyp zu ergänzen. Der neue Feldtyp ermöglicht die Wiederverwendung indem spezifische Bildobjekte und -beziehungen anstelle eines Bildes innerhalb einer bestimmten Stelle in einem Inhaltsobjekt verwendet werden. Dies ist eine nützliche Verbesserung, allerdings kann es schwierig sein, die Feldtypen von einem zum anderen zu wechseln. Im Folgenden erfahren wir, wie Bildfelder automatisch in Bildobjekte umgewandelt werden können.

Automatische Änderungen an der Inhaltsstruktur lassen wir hier außer Betracht. Der vollständige Workflow für die Änderung des Bildfeldtyps eines einzelnen Inhaltstyps in einen Bild-Asset-Feldtyp:

  • Neues Image Asset-Feld zum Inhaltstyp hinzufügen
  • Migrationsskript ausführen (dies ist das, was wir in diesem Artikel erstellen werden)
  • Änderungen am Code und an den Templates vornehmen, die für die Verwendung des neuen Feldes erforderlich sind
  • Testen, testen, testen!!!
  • Migrationsskript erneut ausführen, wenn sich Daten geändert haben
  • Bildfeld aus dem Inhalt entfernen.

Zur Erstellung unseres Datenmigrationsskripts für Feldtypen werden wir einen Symfony-Befehl verwenden, so wie in einem früheren Blog-Beitrag, in dem wir ein Audit-Trail-Protokoll für die eZ Platform erstellt haben. Dieses Mal werden wir das Symfony MakerBundle installieren und verwenden, um den Boilerplate-Code für unseren Befehl zu erstellen:

janit@Turska:~/Sites/ezplatform (dev)*$ ./bin/console make:command

 Choose a command name (e.g. app:agreeable-pizza):
 > app:migrate-image-to-asset

 created: src/Command/MigrateImageToAssetCommand.php

  Success! 

 Next: open your new command class and customize it!
 Find the documentation at https://symfony.com/doc/current/console.html

Da es sich hierbei um eine wiederholbare Aufgabe handelt, fügen wir zusätzliche Parameter hinzu, damit unser Befehl wiederverwendbar ist:

  • Inhalts-Typ-Kennung
  • Quellbild-Feldkennung
  • Zielbild-Assetfeld-Kennung
  • Zielort, an dem die Bildobjekte platziert werden sollen

In den Befehlsklassen wird die Einstellung der Optionen in der Konfigurationsmethode wie folgt vorgenommen:

protected function configure(): void
{
    $this
        ->setDescription('Copies image field type contents to an image asset field')
        ->addArgument('type_identifier', InputArgument::REQUIRED, 'Identifier of content type whose to data is to be modified')
        ->addArgument('source_field', InputArgument::REQUIRED, 'Source field identifier')
        ->addArgument('target_field', InputArgument::REQUIRED, 'Target field identifier')
        ->addArgument('target_location', InputArgument::REQUIRED, 'Target location id where image objects should be created');
}

In der Hauptausführungsmethode fügen wir die Hauptlogik hinzu, um sich als Admin-Benutzer anzumelden, alle Inhaltsobjekte zu finden und zu aktualisieren:

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $io = new SymfonyStyle($input, $output);
    $contentTypeIdentifier = $input->getArgument('type_identifier');
    $sourceFieldIdentifier = $input->getArgument('source_field');
    $targetFieldIdentifier = $input->getArgument('target_field');
    $imageTargetLocationId = $input->getArgument('target_location');

    $this->permissionResolver->setCurrentUserReference(
        $this->userService->loadUser(self::IMPORT_USER)
    );

    $searchResults = $this->loadContentObjects($contentTypeIdentifier);

    foreach ($searchResults as $searchHit) {
        /** @var ContentObject $contentObject */
        $contentObject = $searchHit->valueObject;

        try {
            $this->updateContentObject($contentObject, $sourceFieldIdentifier, $targetFieldIdentifier, $imageTargetLocationId);
            $io->writeln('Updated ' . $contentObject->contentInfo->name . ' (' . $contentObject->id . ')');
        } catch (RepositoryException $e) {
            $io->error(sprintf(
                'Unable to update %s (%d): %s',
                $contentObject->contentInfo->name,
                $contentObject->contentInfo->id,
                $e->getMessage()
            ));

            return self::MIGRATION_ERROR;
        }
    }

    return self::MIGRATION_SUCCESS;
}

Die obige Hauptmethode verwendet einige Hilfsmethoden. Die erste ist die loadContentObjects-Methode, die eine Suche über den Suchdienst mit einem einzigen Kriterium, dem Content Type Identifier, ausführt:

private function loadContentObjects($contentTypeIdentifier): SearchResult
{
    $query = new Query();
    $query->query = new Query\Criterion\ContentTypeIdentifier($contentTypeIdentifier);
    $query->limit = 1000;

    return $this->searchService->findContent($query);
}

Jedes Inhaltsobjekt aus der Suchergebnismenge ist der Feed für die updateContentObject-Methode, die die Erstellung und Verknüpfung von Bildobjekten mithilfe der PHP-API der eZ Platform handhabt:

private function updateContentObject(ContentObject $contentObject, $sourceFieldIdentifier, $targetFieldIdentifier, $imageTargetLocationId): void
{
    $imageObjectRemoteId = $this->getImageRemoteId($contentObject, $sourceFieldIdentifier);

    $imageFieldValue = $contentObject->getFieldValue($sourceFieldIdentifier);
    $imageObject = $this->createOrUpdateImage($imageObjectRemoteId, $imageTargetLocationId, $imageFieldValue);

    $contentDraft = $this->contentService->createContentDraft($contentObject->contentInfo);

    $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
    $contentUpdateStruct->initialLanguageCode = self::IMAGE_LANGUAGE;

    $contentUpdateStruct->setField($targetFieldIdentifier, $imageObject->id);

    $draft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
    $content = $this->contentService->publishVersion($draft->versionInfo);
}

Das obige Code-Snippet ruft eine weitere Hilfsmethode auf, createOrUpdateImage, die eine Inhalts-ID auf der Grundlage des oben explizit definierten Metadatenfeldes Remote ID erstellt oder aktualisiert. Ähnlich wie die Hauptobjekt-Aktualisierungsmethode verwendet es die von der öffentlichen PHP-API bereitgestellten Dienste:

private function createOrUpdateImage(string $remoteId, int $parentLocationId, ImageFieldValue $imageFieldValue): ContentObject
{
    $contentType = $this->contentTypeService->loadContentTypeByIdentifier(self::IMAGE_CONTENT_TYPE);

    $imageName = $imageFieldValue->fileName;
    $imagePath = getcwd() . '/public' . $imageFieldValue->uri;

    try {
        $contentObject = $this->contentService->loadContentByRemoteId($remoteId, [self::IMAGE_LANGUAGE]);

        $contentDraft = $this->contentService->createContentDraft($contentObject->contentInfo);

        $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
        $contentUpdateStruct->initialLanguageCode = self::IMAGE_LANGUAGE;

        $contentUpdateStruct->setField('name', $imageName);
        $contentUpdateStruct->setField('image', $imagePath);

        $draft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
        $content = $this->contentService->publishVersion($draft->versionInfo);
    } catch (NotFoundException $e) {
        // Not found, create new object

        $contentCreateStruct = $this->contentService->newContentCreateStruct($contentType, self::IMAGE_LANGUAGE);
        $contentCreateStruct->remoteId = $remoteId;

        $contentCreateStruct->setField('name', $imageName);
        $contentCreateStruct->setField('image', $imagePath);

        $locationCreateStruct = $this->locationService->newLocationCreateStruct($parentLocationId);
        $draft = $this->contentService->createContent($contentCreateStruct, [$locationCreateStruct]);
        $content = $this->contentService->publishVersion($draft->versionInfo);
    }

    return $content;
}

Der vollständige Quellcode ist auf GitHub verfügbar: github.com/janit/ezplatform-migrate-image-asset

Schlussfolgerung

Bereits eingeführte Implementierungen neigen dazu, sich an die ursprünglichen Feldtypen zu halten, die zuvor verwendet wurden. Mit diesem Befehl können Sie Daten schneller von einem Bildfeld in ein Bild-Asset-Feld verschieben und sparen dadurch Zeit bei der manuellen Arbeit. Auf diese Weise wird die Hürde für solche Migrationen in bestehenden Projekten gesenkt.

Entwickler neigen dazu, sich auf das Upgrade und Refactoring von Code zu konzentrieren. Es stimmt, dass eine nicht gewartete technische Grundlage zu Softwareverfall führen wird. Was aber oft vergessen wird, ist die Pflege des Datenmodells. Das Hinzufügen neuer Felder als Anforderung ist eine häufige Aufgabe bei der kontinuierlichen Entwicklung.

Die Entfernung überholter Felder oder der Wechsel von Feldtypen ist jedoch viel seltener. Dies führt zu einer Beeinträchtigung der administrativen Erfahrung. Dies ist vergleichbar mit einem nicht mehr benutzten Code, der trotzdem noch jahrelang "nur um sicher zu gehen" einfach bestehen bleibt. Sowohl veraltete Datenmodellelemente als auch unbenutzter Code sollten von Zeit zu Zeit gnadenlos bereinigt werden.

Insights and news