Convertir des champs d'image pour utiliser le type de champ de ressource d'image dans eZ Platform

L'une des caractéristiques principales du moteur de contenu eZ Platform est sa flexibilité. Les développeurs sont libres de créer leur modèle de contenu en fonction des types de champs. eZ Platform est fourni avec de nombreux types de champs de base, et au fil du temps, nous avons ajouté des plus (+) utiles que vous pouvez utiliser dans vos projets.

Dans eZ Platform 2.3, nous avons ajouté un type de champ d'élément d'image pour compléter celui de l'image classique. Le nouveau type de champ permet la réutilisation en utilisant des objets et des relations d'image dédiés au lieu d'avoir uniquement une image statique dans un objet de contenu. Cette amélioration est utile, mais le passage d'un type de champs à l'autre peut être délicat. Découvrez comment convertir automatiquement les champs d'image en éléments d'image.

Nous laisserons ici toute modification automatique de la structure du contenu. Le flux de travail complet pour changer le type de champ d'image d'un type de contenu unique en un type de champ d'élément d'image est le suivant :

  • Ajouter un nouveau champ Image Asset au type de contenu
  • Exécuter le script de migration (c'est ce que nous allons construire dans cet article)
  • Apportez les modifications de code et de modèle nécessaires pour utiliser le nouveau champ
  • Tester, tester, tester !!!
  • Exécutez à nouveau le script de migration si les données ont changé
  • Supprimer le champ d'image du contenu

Pour créer notre script de migration de données de type champ, nous utiliserons une commande Symfony, comme nous l'avons fait dans un article de blog précédent où nous avons créé un journal de suivi d'audit pour eZ Platform. Cette fois-ci, nous allons installer et utiliser Symfony MakerBundle pour créer le code passe-partout pour notre commande :

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

Comme il s'agit d'une tâche reproductible, nous ajouterons des arguments afin que notre commande soit réutilisable :

  • Identifiant du type de contenu
  • Identifiant du champ de l'image source
  • Identifiant du champ de l'image source
  • Identifiant du champ de l'élément d'image cible
  • Emplacement cible où les objets d'image seront placés


Dans le paramétrage des classes de commandes, les options se font dans la méthode configure comme suit :

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

Dans la méthode d'exécution principale, nous ajouterons la logique principale pour vous connecter en tant qu'utilisateur administrateur, rechercher et mettre à jour tous les objets de contenu :

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

La méthode principale ci-dessus utilise certaines méthodes d'assistance. La première est la méthode loadContentObjects qui exécute une recherche en utilisant le service de recherche avec un seul critère, l'identifiant de type de contenu :

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

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

Cet objet de contenu de l'ensemble de résultats de recherche est le flux de la méthode updateContentObject, qui gère la création et la liaison des objets image à l'aide de l'API PHP eZ Platform :

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

L'extrait de code ci-dessus appelle une autre méthode d'assistance, createOrUpdateImage, qui crée ou met à jour un ID de contenu en fonction du champ de métadonnées d'ID distant défini explicitement ci-dessus. Similaire à la méthode de mise à jour des objets principaux, elle utilise les services fournis par l'API publique PHP :

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

Le code source complet est disponible sur GitHub : github.com/janit/ezplatform-migrate-image-asset

Une fois le code en place, vous pouvez exécuter la conversion des données à l'aide d'une commande avec des arguments :

./bin/console app:migrate-image-to-asset blog_post image image_asset 9372

Une fois terminé, le script aura copié le contenu du champ vers l'élément d'image lié. Vous devrez toujours effectuer les étapes mentionnées ci-dessus pour utiliser ce champ dans votre code et nettoyer le champ d'image. Chaque fois que vous exécutez le script, il actualise le contenu de l'image, vous pouvez donc l'exécuter juste avant de supprimer le champ d'image d'origine pour être sûr de ne pas perdre les mises à jour.

Conclusion

Les implémentations établies ont tendance à s'en tenir aux types de champs d'origine qui étaient initialement utilisés. Avec cette commande, vous pouvez accélérer le déplacement des données d'un champ d'image vers un champ de ressource d'image par rapport au travail manuel. Cela abaisse le seuil de telles migrations dans les projets existants.

Les développeurs ont tendance à se concentrer sur la mise à niveau et la refactorisation du code. Et il est vrai qu'une base technique non entretenue entraînera la pourriture des logiciels. Mais ce qui est souvent négligé, c'est la maintenance du modèle de données. L'ajout de nouveaux champs comme exigence est une tâche courante dans le développement continu.

Mais supprimer des champs obsolètes ou changer de type de champ est beaucoup plus rare. Cela dégradera l'expérience d'administration. Ceci est similaire au code mort, qui n'est plus utilisé mais reste facilement pendant des années "juste pour être sûr". Les éléments de modèle de données obsolètes et le code inutilisé doivent être épuré de temps en temps de façon assidue.

 

Insights and news