Создать собственное действие для клонирования / дублирования объекта extbase TYPO3 8.7 с вложенными дочерними элементами

Я создаю расширение TYPO3 на основе ext в TYPO3 8.7. Это Backend-модуль. В контроллере я пишу собственное действие для клонирования объекта. В этом примере я хочу клонировать / продублировать объект «Кампания» и сохранить его с помощью измененного заголовка, например, добавить текст «копия» к заголовку. Но новый объект должен также иметь свои собственные новые дочерние элементы, которые должны быть точными копиями. Когда вызывается действие, я получаю только копию объекта, но не дочерние элементы. Есть ли пример или лучший вариант, как справиться с этой задачей? Я не нашел, даже нашел несколько вопросов и ответов по той же теме, но более старой версии. Я надеюсь, что это обновление на сегодняшний день, есть более простое решение. Спасибо за каждый намек, который указывает мне на правильные идеи и, возможно, за актуальную версию и пример. Вот что у меня есть в моем контроллере. Как реализовать рекурсивное копирование всех дочерних элементов (и у некоторых дочерних элементов тоже есть дочерние элементы)?

    /**
     * action clone
     * @param \ABC\Copytest\Domain\Model\Campaign $campaign
     * @return void
     * @var \ABC\Copytest\Domain\Model\Campaign $newCampaign
     */

    public function cloneAction(\ABC\Copytest\Domain\Model\Campaign $campaign) {
        $newCampaign = $this->objectManager->get("ABC\Copytest\Domain\Model\Campaign");
        $properties = $campaign->_getProperties();
        unset($properties['uid']);
        foreach ($properties as $key => $value) {
            $newCampaign->_setProperty($key, $value);
        }
        $newCampaign->_setProperty('title', $properties['title']. ' COPY');
        $this->campaignRepository->add($newCampaign);
        $this->addFlashMessage('Clone was created', '', \TYPO3\CMS\Core\Messaging\AbstractMessage::OK);
        $this->redirect('list');
    }
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
904
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Существует один подход, который решает этот вариант использования с другой точки зрения, а именно, что значения аргументов запроса без идентификатора автоматически помещаются в новые объекты, которые затем могут быть сохранены. Это в основном клонирует исходные объекты. Вот что вам нужно сделать:

  1. Добавьте представление, в котором есть поля для всех свойств вашего объекта, скрытые поля тоже подойдут. Это может быть, например, представление edit с отдельной кнопкой отправки для вызова действия clone.
  2. Добавьте initializeCloneAction() и получите необработанные аргументы запроса через $this->request->getArguments().
  3. Теперь выполните unset($arguments[<argumentName>]['__identity']);, сделайте то же самое для каждого отношения вашего объекта, если вы хотите копии вместо общих ссылок.
  4. Снова сохраните аргументы необработанного запроса через $this->request->setArguments($arguments).
  5. Наконец, разрешите создание новых объектов в конфигурации сопоставления свойств вашего аргумента и, возможно, всех свойств отношения.

Вот так может выглядеть полноценный initializeCloneAction():

public function initializeCloneAction()
{
    $arguments = $this->request->getArguments();

    unset(
        $arguments['campaign']['__identity'],
        $arguments['campaign']['singleRelation']['__identity'],
    );

    foreach (array_keys($arguments['campaign']['multiRelation']) as $i) {
        unset($arguments['campaign']['multiRelation'][$i]['__identity']);
    }

    $this->request->setArguments($arguments);

    // Allow object creation now that we have new objects
    $this->arguments->getArgument('campaign')->getPropertyMappingConfiguration()
        ->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true)
        ->allowCreationForSubProperty('singleRelation')
        ->getConfigurationFor('multiRelation')
            ->allowCreationForSubProperty('*');
}

Теперь, если вы отправите форму с помощью действия clone, действие clone получит полностью заполненный, но новый объект, который вы можете сохранить в своем репозитории, как обычно. Ваш cloneAction() будет очень простым:

public function cloneAction(Campaign $campaign)
{
    $this->campaignRepository->add($campaign);

    $this->addFlashMessage('Campaign was copied successfully!');
    $this->redirect('list');
}

Спасибо за весь этот пример и идею. Я все еще пытаюсь заставить его работать. Я построил простое расширение с моделью on, которое имеет только одно отношение 1: n, чтобы иметь простой тестовый пример. Я борюсь с этой частью в initializeCloneAction прямо сейчас: foreach (array_keys($arguments['campaign']['multiRelation']) as $i) { unset($arguments['campaign']['multiRelation'][$i]['__identit‌​y']); } Я получаю сообщение об ошибке: Предупреждение PHP: array_keys () ожидает, что параметр 1 будет массивом, задано значение null

DeppDeeh 04.09.2018 10:20

Я изменил код, чтобы он соответствовал моей модели, вот так: foreach (array_keys($arguments['campaign']['phase']) as $i) { unset($arguments['campaign']['phase'][$i]['__identity']); } Но он пока не работает. Что я делаю не так?

DeppDeeh 04.09.2018 10:29

Что именно не работает? Убедитесь, что у вас есть поле для каждого объекта, вам также нужны все свойства всех связанных объектов, по крайней мере, в виде скрытых полей.

Mathias Brodala 04.09.2018 10:47

Я получаю сообщение об ошибке: предупреждение PHP: array_keys () ожидает, что параметр 1 будет массивом с заданным значением null. Это когда я вызываю cloneAction. Все модели (Campaign-Model с двумя дочерними элементами 1: n, называемыми Phase) сейчас имеют только одно свойство title, чтобы упростить тестирование. Эти заголовки правильно отображаются в полях формы страницы, где я затем вызываю cloneAction. Есть ли какое-либо расширение в TER, о котором вы знаете, которое использует своего рода cloneAction, чтобы я мог взглянуть на весь код. Я еще не нашел.

DeppDeeh 04.09.2018 11:16

Нет, не существует публичного расширения AFAIK. Обратите внимание, что вам следует оставить части отношения, если у вас нет банкоматов отношений. В противном случае вам необходимо убедиться, что у вас есть поля для них, как указано.

Mathias Brodala 05.09.2018 12:13

Думаю, моя ошибка в форме, как вы уже догадались. Как правильно представить в форме отношение 1: n? У меня есть форма в представлении Preclone <f:form action = "clone" name = "campaign" object = "{campaign}" > <f:render partial = "Campaign/FormFields" arguments = "{campaign:campaign}" /> <f:form.submit value = "CLONE now" /> </f:form> и внутри Campaign / FormFields.html <f:for each = "{campaign.phase}" as = "phase">Phase:<f:form.textfield property = "phase.title" value = "{phase.title}" /><br /></f:for> для итерации по дочерним элементам.

DeppDeeh 05.09.2018 12:25

Да, последний должен использовать iteration из f:for, а затем property = "phase.{i}" value = "{phase}"

Mathias Brodala 05.09.2018 17:03

Вот и все, это решило всю проблему. Спасибо, что сделали меня чуть менее глупым.

DeppDeeh 06.09.2018 10:45

Спасибо, что показали мне то, что мне еще нужно добавить к своему ответу. ;-)

Mathias Brodala 06.09.2018 15:02

Извините, после того, как я установил свой вопрос на «ответил», я понял, что забыл раскомментировать часть для создания вложенных свойств, так что дочерние элементы были перемещены, а не клонированы. Когда я раскомментирую строки в контроллере, я получаю сообщение об ошибке: Exception while property mapping at property path "phase.Array": Creation of objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_CREATION_ALLOWED" to TRUE Ваш код контроллера после того, как // Allow object creation now that we have new objects еще не работал у меня. Я все еще работаю над этим.

DeppDeeh 10.09.2018 14:19

Я попытался изменить PropertyMappingConfiguration, чтобы разрешить создание дочерних элементов, но теперь я получаю эту ошибку: Call to a member function forProperty() on null Может быть, мне стоит попытаться показать полный код контроллера, шаблона и вывода, чтобы можно было увидеть мои ошибки?

DeppDeeh 11.09.2018 12:42

Вы можете оставить существенную ссылку в комментариях здесь.

Mathias Brodala 11.09.2018 14:17

Я все еще пытаюсь и переделал тестовое расширение. Но теперь ->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true) приводит к ошибке Class 'ABC\Copytest\Controller\PersistentObjectConverter' not found Почему? Как это решить?

DeppDeeh 16.10.2018 12:14

Пришлось установить use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectCon‌​verter;, что решило эту проблему.

DeppDeeh 16.10.2018 12:43

Можете ли вы отредактировать мой комментарий или внести в него предлагаемые правки, чтобы прояснить это для других? Должен признать, что я забыл о различиях между моим предложением и вашим текущим состоянием.

Mathias Brodala 17.10.2018 16:14

Это фактический код моего тестового расширения с базовым объектом, который имеет 1: n дочерних элементов суть

DeppDeeh 18.10.2018 11:31

Я только что обновил суть из-за одной обнаруженной мной опечатки.

DeppDeeh 24.10.2018 11:36
Ответ принят как подходящий

Я знаю, что на этот вопрос давным-давно был дан ответ. Но я хочу предоставить свое решение для создания глубокой копии для дальнейшего использования. Проверено на TYPO3 9.5.8.

private function deepcopy($object)
{
    $clone = $this->objectManager->get(get_class($object));
    $properties = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettableProperties($object);
    foreach ($properties as $propertyName => $propertyValue) {
        if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
            $v = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\ObjectStorage::class);
            foreach($propertyValue as $subObject) {
                $subClone = $this->deepcopy($subObject);
                $v->attach($subClone);
            }
        } else { 
            $v = $propertyValue;
        }
        if ($v !== null) {
            \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($clone, $propertyName, $v);
        }
    }
    return $clone;
}

Спасибо! Одна небольшая вещь, которую нужно добавить, если, например, изображение пусто: if (null! == $ v) {\ TYPO3 \ CMS \ Extbase \ Reflection \ ObjectAccess :: setProperty (‌ $ clone, $ propertyName, $ v); }

matin 30.12.2019 14:13

Спасибо, Мартин, я добавил эту часть.

Markus 15.01.2020 15:18

Я наконец вернулся к проекту и теперь смог реализовать это решение. Спасибо, Маркус.

DeppDeeh 17.07.2020 12:04

Если у вас есть экземпляр «LazyLoadingProxy» в вашем объекте, вам нужно добавить еще одно условие.

if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
   $objectStorage = $propertyValue->_loadRealInstance();
}

Это мое решение для функции "deepcopy":

    private function deepcopy($object)
    {
        $clone = $this->objectManager->get(get_class($object));
        $properties = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettableProperties($object);
        foreach ($properties as $propertyName => $propertyValue) {
            if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
                $objectStorage = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\ObjectStorage::class);
                foreach ($propertyValue as $subObject) {
                    $subClone = $this->deepcopy($subObject);
                    $objectStorage->attach($subClone);
                }
            } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
                $objectStorage = $propertyValue->_loadRealInstance();
            } else {
                $objectStorage = $propertyValue;
            }
            if ($objectStorage !== null) {
                \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($clone, $propertyName, $objectStorage);
            }
        }

        return $clone;
    }

Другие вопросы по теме