Create own action to clone/duplicate TYPO3 8.7 extbase object with nested child elements - object

I build my extbased TYPO3 extension in TYPO3 8.7 . It is a Backend-Module. In the controller, i write my own action to clone the object.
In this example, i want to clone/duplicate the object 'Campaign' and safe it with a modified title, like add the 'copy' text to the title.
But the new object should have also its own new child elements that must be exact copies.
When the action is called, i get only a copy of the Object, but no childs. Is there an example or best case how to handle this task? I did not find, even i found some questions and answers that are on the same topic, but older version. i hope that upd to date, there is a more straight forward solution. Thank you for every hint that points me to the right ideas and maybe an up to date and version example. Here is what i have i my controller. How do i implement recursiv copying of all child elements (and some childs have childs, too)?
/**
* 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');
}

I am aware that this question has been answered a long time ago. But I want to provide my solution to create a deep copy for further reference. Tested on 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;
}

There is one approach which tackles this usecase from a different POV, namely that request argument values without an identity are automatically put into fresh objects which can then be persisted. This basically clones the original objects. This is what you need to do:
Add a view which has fields for all properties of your object, hidden fields are fine too. This can for example be an edit view with a separate submit button to call your clone action.
Add a initializeCloneAction() and get the raw request arguments via $this->request->getArguments().
Now do unset($arguments[<argumentName>]['__identity']);, do the same for every relation your object has if you want copies instead of shared references.
Store the raw request arguments again via $this->request->setArguments($arguments).
Finally allow the creation of new objects in the property mapping configuration of your argument and possibly all relation properties.
This is how a full initializeCloneAction() could look like:
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('*');
}
Now if you submit your form using the clone action, your clone action will get a fully populated but new object which you can store in your repository as usual. Your cloneAction() will then be very simple:
public function cloneAction(Campaign $campaign)
{
$this->campaignRepository->add($campaign);
$this->addFlashMessage('Campaign was copied successfully!');
$this->redirect('list');
}

If you have "LazyLoadingProxy" instance in your object you need add one more conditions.
if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
$objectStorage = $propertyValue->_loadRealInstance();
}
This is my solution for "deepcopy" function:
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;
}

I think a good solution is, to emulate the backend-function.
See the code-example (german text)
http://blog.marcdesign.ch/2015/05/27/typo3-extbase-objekte-kopieren/
The general idea is to extend the TYPO3\CMS\Core\DataHandling\DataHandler and use the parent-method copyRecord. You declare your predefined backend-user to $this->BE_USER in your extend class. The obejct of your predefined backenduser can you get by using the class TYPO3\\CMS\\Backend\\FrontendBackendUserAuthentication and the known name of you predefined backenduser. Your user should have admin-rights and you should define the $BE_USER->uc_default['copyLevels']= '9999'; and declare $BE_USER->uc = $BE_USER->uc_default.
I have not checked, if the declaration $GLOBALS['PAGES_TYPES'][254]['allowedTables'] = '*'; is really needed.
The method copyRecorditself needs mainly the table-name, the uid-value, the pid-value and a language-object as parameters.The languages-object can you get $GLOBALS['lang'], which can although be generated by instanciating \TYPO3\CMS\Lang\LanguageService to $GLOBALS['lang'] and \TYPO3\CMS\Core\Charset\CharsetConverter to $GLOBALS['LANG']->csConvObj.
Sorry about my poor english.

Related

Test the existence of a dynamically chosen class

I have data coming from an external source that I want to process. In order to do that, the objects I'm receiving are tagged with their original class name. Now I want to take that tag name and use it to populate a model in my own application. I'm stuck at the step where I check for that class having an equivalent in my codebase. Its going to look something like this:
this.objects.forEach((object) => {
if (typeof object.class_tag !== 'undefined') { //the problem line
//create class instance
}
});
In php I'd simply call class_exists to achieve this
<?php
if (class_exists($object->class_tag)) {}
What is the correct approach here?
I don't see the clear way to do this in a just one line.
One of the possible approaches is the way you register your existing classes.
For example if you use some kind of a namespace later on you can simply check the class for existance in the namespace.
Sample code:
class A {}
const a = "A"
const namespace = { A };
if (namespace[a]) {
// class exists, you can create object
const instance = new namespace[a]();
}
Probably, much better approach would be to make some service, that will registerClass, checkClass and createInstance for you. So your logic is wrapped in one place.
I found a way of doing it
(credit to https://stackoverflow.com/a/34656123/746549)
let logger = require('../javascripts/serverlog');
let util = require('util');
let Extension = require('../models/object/Extension');
const classes = {Extension: Extension};
/**
* Utility to emulate class exists / dynamic class naming
* #param className
* #returns {*}
*/
module.exports.dynamicClass = (className) => {
logger.debug(classes);
logger.debug(className);
if (classes[className]) {
return classes[className];
} else {
return false;
}
};
Usage:
let ClassOrFalse = dynamicClass.dynamicClass(object._class_tag);

How to convert a DTO to Domain Objects

I'm trying to apply ubiquitous language to my domain objects.
I want to convert a Data Transfer Object coming from a client into the domain object. The Aggregate's Constructor only accepts the required fields, and the rest of parameters should be passed using aggregate's API even when the Aggregate is being created(by say CreateAggregate command).
But the DTO to Aggregate mapping code becomes a bit messy:
if(DTO.RegistrantType == 0){
registrantType = RegistrantType.Person()
}
elseif(DTO.RegistrantType == 1){
registrantType = RegistrantType.Company()
}
//.....
//.....
var aggregate = new Aggregate(
title,
weight,
registrantType,
route,
callNumber,
)
//look at this one:
if(DTO.connectionType == 0){
aggregate.Route(ConnectionType.InCity(cityId))
}
elseif(DTO.connectionType == 1){
aggregate.Route(ConnectionType.Intercity(DTO.originCityId,DTO.DestinationCityId)
}
//..........
//..........
One thing I should mention is that this problem doesn't seem a domain specific problem.
How can I reduce these If-Else statements without letting my domain internals leakage, and with being sure that the aggregate(not a mapping tool) doesn't accept values that can invalide it's business rules, and with having the ubiquitous language applied?
Please don't tell me I can use AoutoMapper to do the trick. Please read the last part carefully.'
Thank you.
A typical answer would be to convert the DTO (which is effectively a message) into a Command, where the command has all of the arguments expressed as domain specific value types.
void doX(DTO dto) {
Command command = toCommand(dto)
doX(command)
}
void doX(Command command) {
// ...
aggregate.Route(command.connectionType)
}
It's fairly common for the toCommand logic use something like a Builder pattern to improve the readability of the code.
if(DTO.connectionType == 0){
aggregate.Route(ConnectionType.InCity(cityId))
}
elseif(DTO.connectionType == 1){
aggregate.Route(ConnectionType.Intercity(DTO.originCityId,DTO.DestinationCityId)
}
In cases like this one, the strategy pattern can help
ConnectionTypeFactory f = getConnectionFactory(DTO.connectionType)
ConnectionType connectionType = f.create(DTO)
Once that you recognize that ConnectionTypeFactory is a thing, you can think about building lookup tables to choose the right one.
Map<ConnectionType, ConnectionTypeFactory> lookup = /* ... */
ConnectionTypeFactory f = lookup(DTO.connectionType);
if (null == f) {
f = defaultConnectionFactory;
}
So why don't you use more inheritance
for example
class CompanyRegistration : Registration {
}
class PersonRegistraiton : Registration {
}
then you can use inheritance instead of your if/else scenario's
public class Aggregate {
public Aggregate (CompanyRegistration) {
registantType = RegistrantType.Company();
}
public Aggregate (PersonRegistration p) {
registrantType = RegistrantType.Person();
}
}
you can apply simmilar logic for say a setRoute method or any other large if/else situations.
Also, i know you don't want to hear it, you can write your own mapper (inside the aggegate) that maps and validates it's business logic
for example this idea comes from fluentmapper
var mapper = new FluentMapper.ThatMaps<Aggregate>().From<DTO>()
.ThatSets(x => x.title).When(x => x != null).From(x => x.title)
It isn't too hard to write your own mapper that allow this kind of rules and validates your properties. And i think it will improve readability

Is there a way in Geb to automatically assign the right Module to all Elements in a Form

We use Geb to run our Frontend Tests and we have some quite complex pages in our application.
Some of the pages have forms with a lot of different buttons, checkboxes and some multiselects.
I love the feature of geb/groovy that i just have to define the form in the Page Object and then can access all its elements in it.
static content = {
form { $("#form")}
}
But for them to be clickable and to query if they are readonly and more they need to be at least of type FormElement which does not happen with the above method. So I have to mention all these FormElements separately:
static content = {
form { $("#form")}
button1 { $("#button1").module(FormElement)}
button2 { $("#button2").module(FormElement)}
checkbox{ $("#checkbox").module(Checkbox)}
...
}
All those buttons, checkboxes... are already in the form variable, but cannot be clicked or checked if they are selected and so on. It's also not possible to apply the the module afterwards like this:
def "test something"() {
when:
form.button1.module(FormElement).click() //error
then:
...
}
Is there no way to automatically assign each input, checkbox, radiobutton, button,... the correct Module based on their type without the need of doing it by hand?
If someone could also point me in the right direction to understand how this "form { $("#form")}" works, that i can access all sub elements by its name by just suppying the form, that would be nice!
For your example of creating a module based on a form control you need to obtain a navigator for the control and not it's value. It's done by calling a method named the same as the control you're trying to access (it's explained in this section of The Book of Geb):
form.button1().module(FormElement).click()
If you want to automatically create modules based on the element type then you could create a Module for the form and override method missing:
class FormModule extends Module {
Object methodMissing(String name, Object args) {
def result = super.methodMissing(name, args)
if (result instanceof Navigator && result.tag() == "input") {
switch (result.#type) {
case "checkbox":
result = result.module(Checkbox)
break
default:
result = result.module(FormElement)
}
}
result
}
}
then you would use it like:
static content = {
form { $("#form").module(FormModule) }
}
form.button1().click()

Get parameter values from method at run time

I have the current method example:
public void MethodName(string param1,int param2)
{
object[] obj = new object[] { (object) param1, (object) param2 };
//Code to that uses this array to invoke dynamic methods
}
Is there a dynamic way (I am guessing using reflection) that will get the current executing method parameter values and place them in a object array? I have read that you can get parameter information using MethodBase and MethodInfo but those only have information about the parameter and not the value it self which is what I need.
So for example if I pass "test" and 1 as method parameters without coding for the specific parameters can I get a object array with two indexes { "test", 1 }?
I would really like to not have to use a third party API, but if it has source code for that API then I will accept that as an answer as long as its not a huge API and there is no simple way to do it without this API.
I am sure there must be a way, maybe using the stack, who knows. You guys are the experts and that is why I come here.
Thank you in advance, I can't wait to see how this is done.
EDIT
It may not be clear so here some extra information. This code example is just that, an example to show what I want. It would be to bloated and big to show the actual code where it is needed but the question is how to get the array without manually creating one. I need to some how get the values and place them in a array without coding the specific parameters.
Using reflection you can extract the parameters name and metadata but not the actual values :
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.testMethod("abcd", 1);
Console.ReadLine();
}
public void testMethod(string a, int b)
{
System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace();
StackFrame sf = st.GetFrame(0);
ParameterInfo[] pis = sf.GetMethod().GetParameters();
foreach (ParameterInfo pi in pis)
{
Console.Out.WriteLine(pi.Name);
}
}
}

System.Linq.Dynamic .Select("new ...") does not appear to be thread safe

I grabbed System.Linq.Dynamic.DynamicQueryable from here:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
The issue that I am running into is in code that looks like this:
var results = dataContext.GetTable<MyClass>.Select("new (MyClassID, Name, Description)").Take(5);
It appears that if that line of code is executed by multiple threads near simultaneously, Microsoft's dynamic Linq code crashes in their ClassFactory.GetDynamicClass() method, which looks like this:
public Type GetDynamicClass(IEnumerable<DynamicProperty> properties)
{
rwLock.AcquireReaderLock(Timeout.Infinite);
try
{
Signature signature = new Signature(properties);
Type type;
if (!classes.TryGetValue(signature, out type))
{
type = CreateDynamicClass(signature.properties);
classes.Add(signature, type); // <-- crashes over here!
}
return type;
}
finally
{
rwLock.ReleaseReaderLock();
}
}
The crash is a simple dictionary error: "An item with the same key has already been added."
In Ms code, The rwLock variable is a ReadWriterLock class, but it does nothing to block multiple threads from getting inside classes.TryGetValue() if statement, so clearly, the Add will fail.
I can replicate this error pretty easily in any code that creates a two or more threads that try to execute the Select("new") statement.
Anyways, I'm wondering if anyone else has run into this issue, and if there are fixes or workarounds I can implement.
Thanks.
I did the following (requires .NET 4 or later to use System.Collections.Concurrent):
changed the classes field to a ConcurrentDictionary<Signature, Type> ,
removed all the ReaderWriterLock rwLock field and all the code referring to it,
updated GetDynamicClass to:
public Type GetDynamicClass(IEnumerable<DynamicProperty> properties) {
var signature = new Signature(properties);
return classes.GetOrAdd(signature, sig => CreateDynamicClass(sig.properties));
}
removed the classCount field and updated CreateDynamicClass to use classes.Count instead:
Type CreateDynamicClass(DynamicProperty[] properties) {
string typeName = "DynamicClass" + Guid.NewGuid().ToString("N");
...

Resources