Я создаю расширение 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');
}
Существует один подход, который решает этот вариант использования с другой точки зрения, а именно, что значения аргументов запроса без идентификатора автоматически помещаются в новые объекты, которые затем могут быть сохранены. Это в основном клонирует исходные объекты. Вот что вам нужно сделать:
edit
с отдельной кнопкой отправки для вызова действия clone
.initializeCloneAction()
и получите необработанные аргументы запроса через $this->request->getArguments()
.unset($arguments[<argumentName>]['__identity']);
, сделайте то же самое для каждого отношения вашего объекта, если вы хотите копии вместо общих ссылок.$this->request->setArguments($arguments)
.Вот так может выглядеть полноценный 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');
}
Я изменил код, чтобы он соответствовал моей модели, вот так: foreach (array_keys($arguments['campaign']['phase']) as $i) { unset($arguments['campaign']['phase'][$i]['__identity']); }
Но он пока не работает. Что я делаю не так?
Что именно не работает? Убедитесь, что у вас есть поле для каждого объекта, вам также нужны все свойства всех связанных объектов, по крайней мере, в виде скрытых полей.
Я получаю сообщение об ошибке: предупреждение PHP: array_keys () ожидает, что параметр 1 будет массивом с заданным значением null. Это когда я вызываю cloneAction. Все модели (Campaign-Model с двумя дочерними элементами 1: n, называемыми Phase) сейчас имеют только одно свойство title, чтобы упростить тестирование. Эти заголовки правильно отображаются в полях формы страницы, где я затем вызываю cloneAction. Есть ли какое-либо расширение в TER, о котором вы знаете, которое использует своего рода cloneAction, чтобы я мог взглянуть на весь код. Я еще не нашел.
Нет, не существует публичного расширения AFAIK. Обратите внимание, что вам следует оставить части отношения, если у вас нет банкоматов отношений. В противном случае вам необходимо убедиться, что у вас есть поля для них, как указано.
Думаю, моя ошибка в форме, как вы уже догадались. Как правильно представить в форме отношение 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>
для итерации по дочерним элементам.
Да, последний должен использовать iteration
из f:for
, а затем property = "phase.{i}" value = "{phase}"
Вот и все, это решило всю проблему. Спасибо, что сделали меня чуть менее глупым.
Спасибо, что показали мне то, что мне еще нужно добавить к своему ответу. ;-)
Извините, после того, как я установил свой вопрос на «ответил», я понял, что забыл раскомментировать часть для создания вложенных свойств, так что дочерние элементы были перемещены, а не клонированы. Когда я раскомментирую строки в контроллере, я получаю сообщение об ошибке: 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
еще не работал у меня. Я все еще работаю над этим.
Я попытался изменить PropertyMappingConfiguration, чтобы разрешить создание дочерних элементов, но теперь я получаю эту ошибку: Call to a member function forProperty() on null
Может быть, мне стоит попытаться показать полный код контроллера, шаблона и вывода, чтобы можно было увидеть мои ошибки?
Вы можете оставить существенную ссылку в комментариях здесь.
Я все еще пытаюсь и переделал тестовое расширение. Но теперь ->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true)
приводит к ошибке Class 'ABC\Copytest\Controller\PersistentObjectConverter' not found
Почему? Как это решить?
Пришлось установить use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
, что решило эту проблему.
Можете ли вы отредактировать мой комментарий или внести в него предлагаемые правки, чтобы прояснить это для других? Должен признать, что я забыл о различиях между моим предложением и вашим текущим состоянием.
Это фактический код моего тестового расширения с базовым объектом, который имеет 1: n дочерних элементов суть
Я только что обновил суть из-за одной обнаруженной мной опечатки.
Я знаю, что на этот вопрос давным-давно был дан ответ. Но я хочу предоставить свое решение для создания глубокой копии для дальнейшего использования. Проверено на 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); }
Спасибо, Мартин, я добавил эту часть.
Я наконец вернулся к проекту и теперь смог реализовать это решение. Спасибо, Маркус.
Если у вас есть экземпляр «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;
}
Спасибо за весь этот пример и идею. Я все еще пытаюсь заставить его работать. Я построил простое расширение с моделью on, которое имеет только одно отношение 1: n, чтобы иметь простой тестовый пример. Я борюсь с этой частью в initializeCloneAction прямо сейчас:
foreach (array_keys($arguments['campaign']['multiRelation']) as $i) { unset($arguments['campaign']['multiRelation'][$i]['__identity']); }
Я получаю сообщение об ошибке: Предупреждение PHP: array_keys () ожидает, что параметр 1 будет массивом, задано значение null