In an extbase extension, how to access the persistence layer from a scheduler task? - typo3

What sounds a bit academic in the title is actually quite straightforward: I have set up a TYPO3 6.1 extbase extension that I've equipped with a scheduler task. The task is supposed to import a CSV file and save it into the extension's database fields.
But how do I tell the scheduler task to use the extension's model etc. and save the received data into the persistence layer?
I've seen this answer to a similar question: Execute repository functions in scheduler task and I think it points the right way, but I think need a full example to start understanding how the dependency injection works.

First you have to consider the aspect of performance:
If you want to insert a big amount of data, you should not use the Extbase persistence for such a task. Because if you do so, it will generate an object for each row you want to insert and persist it immediately. This is quite slow and has a big memory footprint.
If you don't have much data or you split the jobs (e.g. perform 100 import jobs per scheduler run), then use the Extbase persistence.
You can have both in CommandController context, and since CommandControllers are straight-forward to set up, you should go for them instead of an own Scheduler task.
Using Extbase persistence
In the CommandController, inject your repository:
/**
* myRepository
*
* #var \Venor\Myext\Domain\Repository\MyRepository
* #inject
*/
protected $myRepository
Then iterate through the rows you want to import (foreach) and create a new object for every row and add it to your repository:
$myObject = $this->objectManager->get('Vendor\Myext\Domain\Model\MyModel');
$myObject->setProperty('foo');
$myObject->setOtherProperty('bar');
$this->myRepository->add($myObject);
To actually save the objects to the database, you need to persist them. So you also inject the persistenceManager:
/**
* #var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
* #inject
*/
protected $persistenceManager;
And use it:
$this->persistenceManager->persistAll();
You should not do that for every single object (for performance reasons); but for memory usage reasons you should not wait until after thousands of objects to persist, either. So you just insert an iterator to your foreach loop and persist every 20th, 40th, or whatever loop.
Please don't forget that the Scheduler works in Backend context, so the TypoScript must be available by module.tx_yourext. If you want to share the same settings/storagePid with the frontend part of your app, use
module.tx_yourext.persistence < plugin.tx_yourext.persistence
[...]
The TypoScript needs to be present in the root page of your website for backend modules/CommandControllers to use them. I suggest you add the stuff to myext/Configuration/TypoScript/setup.txt and add the static template of your extension to the root page.
Using DataHandler
The TYPO3 DataHandler (formerly TCEmain) is the engine the TYPO3 backend uses for inserting and modifying database records. It is very powerful.
Instead of an object, inside your loop you create an array containing all the data. The first array index is the table, the next level is the affected record, where NEW means that a new record is created. Then you can just set every field of a table with the desired value
$data = array();
$data['be_users']['NEW'] = array(
'pid' => 0,
'username' => $staffMember['IDPerson'],
'password' => md5(GeneralUtility::generateRandomBytes(40)), // random password
'usergroup' => '1,2',
'email' => $staffMember['Email'],
'realName' => $staffMember['Nachname'] . ' ' . $staffMember['Vorname'],
'lang' => 'de',
);
Now you can make an Instance of DataHandler and persist the changes:
/** #var $tce t3lib_TCEmain */
$tce = GeneralUtility::makeInstance('TYPO3\CMS\Core\DataHandling\DataHandler');
$tce->bypassAccessCheckForRecords = TRUE;
$tce->start($data, array());
$tce->admin = TRUE;
$tce->process_datamap();
$newRecordsUidArray = $tce->substNEWwithIDs['NEW'];
Please note the line $tce->admin = TRUE. This suggests to DataHandler that an admin is performing the action. This is convenient because you don't have to set allowed exclude fields for the Scheduler user and can also insert records to PID 0. But it is a possible security flaw, so carefully consider its usage.
Records inserted/updated by DataHandler logged correctly, can be reverted etc.. You can find some examples (such as adding pictures, resolving MM relations) here. In this case all DataHandler related functions were moved to an external repository class that is injected to the CommandController as described above (it's just named in Extbase convention).
A good overview of DataHandler functions can be found here.

In Addition to lorenz's answer: Beginner's Guide to set up a Command Controller Scheduler task:
My example is an import task. Change the Name part "Import" to your needs.
Create a new file EXT:Classes/Controller/ImportCommandController.php
<?php
namespace NAMESPACE\Myextension\Controller;
/***************************************************************
* Copyright notice
*
* (c) 2014
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
*
*
* #package Myextension
* #license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later
*
*/
class ImportCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\CommandController {
/**
* itemRepository
*
* #var \NAMESPACE\Myextension\Domain\Repository\ItemRepository
* #inject
*/
protected $itemRepository;
/**
* #var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
* #inject
*/
protected $persistenceManager;
/**
*
* #param \integer $storagePid
* #param \string $url
*
* #return bool
*/
// very nice: parameters will be fields in the scheduler!
public function importCommand($storagePid = 0,$url = NULL) {
$source = utf8_encode(utf8_encode(file_get_contents($url)));
// set storage page ourselves
// not sure if really necessary
$querySettings = $this->itemRepository->createQuery()->getQuerySettings();
$querySettings->setRespectStoragePage(FALSE);
$this->itemRepository->setDefaultQuerySettings($querySettings);
// do your stuff here
$source = 'foo';
$rendered = 'bar';
// then store it
// this seems to be only necessary if we don't have an existing item yet
// but as we don't know that here, we have to do it
$item = $this->objectManager->get('STUBR\Therapiestellen\Domain\Model\Item');
// find all existing items
$all = $this->itemRepository->findAll();
// if we have an item already, take the first (and only one)
if(count($all) > 0){
$item = $all->getFirst();
}
// set / update properties
$item->setSource($source);
$item->setRendered($r);
$item->setPid($storagePid);
// either update or add it
if(count($all) > 0){
$this->itemRepository->update($item);
}
else {
$this->itemRepository->add($item);
}
// persist it
$this->persistenceManager->persistAll();
}
}
?>
In EXT:ext_localconf.php, add the command controller:
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = 'NAMESPACE\\Myextension\\Controller\\ImportCommandController';
Configure in Scheduler:
That's basically it!

Related

Is there a way to create domain-object-translations over extbase in TYPO3 9?

I'm importing some products with JSON into my TYPO3 extension.
I'm having problems with translation creating over my ImportService, as I upgraded from TYPO3 8 to TYPO3 9. It seems, that the l10n_parent in the database cannot be set, although _localizedUid and _versionedUid are being set.
/**
* #param DomainObjectInterface $object
* #param $targetLanguageUid
* #return DomainObjectInterface|null
*/
public function translate($object, $targetLanguageUid)
{
/** #var AbstractDomainObject $objectCopy */
$objectCopy = new $this->objectType;
$properties = ObjectAccess::getGettableProperties($object);
foreach ($properties as $propertyName => $propertyValue) {
ObjectAccess::setProperty($objectCopy, $propertyName, $propertyValue);
}
$objectCopy->_setProperty('_localizedUid', $object->getUid());
$objectCopy->_setProperty('_languageUid', $targetLanguageUid);
$objectCopy->_setProperty('_versionedUid', $object->getUid());
return $objectCopy;
}
Expected Result: l10n_parent in database is e.g. 403 (uid of original object)
Actual Result: l10n_parent in database is 0
When dealing with TYPO3 internal data structures, the recommendation is to use the DataHandler for this. In your case, you just need to provide the config array and set the localize field correctly. You can use this in your own scripts with a backend scope, examples are provided in the documentation. In your import script, you would have to create the records in the default language first, then create each translation for it.

Typo3 accessing an existing table to consume data of it

I tried to integrate an existing table to my extension. The problem is that the content of the table isn't taken over. I created a new model with the name of the existing table and named the properties according to the existing column names. I also implemented the corresponding getters and setters of the properties.
The name of the existing table is tx_institutsseminarverwaltung_domain_model_event.
What is my failure in this case? Just want to access the data of an existing table from another extension.
Thanks in advance.
UPDATE:
I tried this:
/**
* Protected Variable objectManager wird mit NULL initialisiert.
*
* #var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
* #inject
*/
protected $objectManager = NULL;
and listAction():
/**
* action list
*
* #return void
*/
public function listAction() {
echo "test";
$theRepository = $this->objectManager->get('\TYPO3\institutsseminarverwaltung\Domain\Repository\EventRepository');
$yourRecords = $theRepository->findAll();
$this->view->assign('events', $yourRecords);
}
But no results returned.
You should use the repository linked to this table. Something like this :
$theRepository = $this->objectManager->get(\Your\VendorName\Domain\Repository\TheRepository::class);
$yourRecords = $theRepository->findAll();
How are you trying to "consume" or access the data from the other table in your extension?
Do you have a repository for the existing table (maybe there is already an existing repository, that you can reuse)?
See german typo3 board mapping existing tables and SO thread TYPO3 / How to make repository from existing table fe_users?
solution is:
$querySettings = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
$querySettings->setRespectStoragePage(FALSE);
$theRepository = $this->objectManager->get('TYPO3\\institutsseminarverwaltung\\Domain\\Repository\\EventRepository');
$theRepository->setDefaultQuerySettings($querySettings);
$yourRecords = $theRepository->findAll();
$this->view->assign('events', $yourRecords);

TYPO3 Extbase individual code on backend-deletion of an object

I would like to execute some individual code when one of my Extbase domain objects is deleted from the list view in TYPO3 backend.
Thought that it could / would work by overwriting the remove( $o ) method in the according repository like
public function remove( $object ) {
parent::__remove( $object );
do_something_i_want();
}
, but that won't work I guess. Looks like the repository-methods are only called / used by actions of my extension (e.g. if I had some delete-action in a FE- or BE-plugin) but not when the object is just deleted from list view in the backend? I don't use (up to now) any FE/BE-plugin / -actions - only the simple add/edit/delete functions in the backends list view of my storage folder.
Background: I have e.g. two models with some 1:n relation (let's say singer and song), where one object includes some uploaded file (album_cover > pointing to the file's URL in /uploads/myext/ folder); using #cascade works fine for deleting every song belonging to a singer that is deleted, but it won't touch the file uploaded (only) for song.album_cover - leading to quite some waste over time. So I would love to do some sort of onDeletionOfSinger() { deleteAllFilesForHisSongs(); }-thing.
(Same thing would apply on deletion of let's say a single song and it's album_cover-file.)
Sounds quite easy & common, but I just don't get behind it and found nothing useful - would love some hint / pointing to the right direction :-).
List view uses TCEmain hooks during its operations, so you can use one of them to intersect delete action, i.e.: processCmdmap_deleteAction
Register your hooks class in typo3conf/ext/your_ext/ext_tables.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = 'VENDORNAME\\YourExt\\Hooks\\ProcessCmdmap';
Create a class with valid namespace and path (according to previous step)
file: typo3conf/ext/your_ext/Classes/Hooks/ProcessCmdmap.php
<?php
namespace VENDORNAME\YourExt\Hooks;
class ProcessCmdmap {
/**
* hook that is called when an element shall get deleted
*
* #param string $table the table of the record
* #param integer $id the ID of the record
* #param array $record The accordant database record
* #param boolean $recordWasDeleted can be set so that other hooks or
* #param DataHandler $tcemainObj reference to the main tcemain object
* #return void
*/
function processCmdmap_postProcess($command, $table, $id, $value, $dataHandler) {
if ($command == 'delete' && $table == 'tx_yourext_domain_model_something') {
// Perform something before real delete
// You don't need to delete the record here it will be deleted by CMD after the hook
}
}
}
Don't forget to clear system cache after registering new hook's class
In addition to biesiors answer I want to point out, that there is also a signalSlot for this. So you can rather register on that signal than hooking into tcemain.
in your ext_localconf.php put:
$signalSlotDispatcher =
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
$signalSlotDispatcher->connect(
'TYPO3\CMS\Extbase\Persistence\Generic\Backend',
'afterRemoveObject',
'Vendor\MxExtension\Slots\MyAfterRemoveObjectSlot',
'myAfterRemoveObjectMethod'
);
So in your Slot you have this PHP file:
namespace Vendor\MxExtension\Slots;
class MyAfterRemoveObjectSlot {
public function myAfterRemoveObjectMethod($object) {
// do something
}
}
Note thet $object will be the $object that was just removed from the DB.
For more information, see https://usetypo3.com/signals-and-hooks-in-typo3.html

after_relationship_add logic hook update query doesn't work in sugarcrm

I have created a after_relationship_add logic hook in cases module and in this module there is a custom field for one of the custom module relationship data to be inserted. The hook is called properly, and everything is working fine. but when I am updating the case record in hook logic, the update query is not working. But if I add a die(); statement after update query execution, the record gets updated. logic hook code is given below
public function updateData($bean, $event, $arguments){
$caseid = $bean->id;
$dataid = $arguments['related_id'];
$query = "SELECT name FROM data1_data where id = '" .$dataid. "'";
$dataresult = $GLOBALS['db']->query($query , false);
$dataname = "";
while (($row = $GLOBALS['db']->fetchByAssoc($dataresult )) != null) {
$dataname = $row['name'];
}
$newQuery = 'UPDATE cases_cstm SET data_c = "'.$dataname.'" where id_c = "'.$caseid.'" ';
$newResult = $GLOBALS['db']->query($newQuery);
/* here when die() statement is added update query executes properly and
* after removing die(); statement nothing happens.*/
die();
}
Can any one help me in this issues?
In SugarCRM, you should practically never interact with the database directly. Almost anything you'll need to do can be done with the SugarBean object and it's extensions. What you're seeing here is a great example of why: your update is hitting the database, but the rest of the SugarCRM update that loads immediately afterwards is wiping it out.
I've rewritten your function using SugarBean and BeanFactory. Notice how much less code is needed, and I expect you'll find that it works because it won't cause additional updates.
The one bit I'm not sure about is whether or not you really need the $bean->save(); at the end. If we were in a before_save logic hook it wouldn't be needed, but I use the after_relationship_add less frequently, so it might be necessary here.
/**
* #param $bean aCase object
* #param $event string, or specifically 'after_relationship_add'
* #param $arguments array
*/
public function updateData($bean, $event, $arguments){
/*
* Instead of loading the data1_data information from the database
* directly, consider using the SugarBean PHP object, as this is a SugarCRM
* best practice.
*
* Note that we return early if the data1_data object cannot be found or
* if the 'name' value is blank (as that would make the rest of this script
* useless)
*/
$data = BeanFactory::getBean('data1_data',$arguments['related_id']);
if(empty($data->name)) return;
$dataname = $data->name;
/*
* Instead of sending an update query directly to the database, use the
* SugarBean objects, one's loaded already in $bean. Saving objects
* with the SugarBean objects instead of direct SQL will ensure that
* all workflows and logic hooks are executed correctly. Further,
* updating with direct SQL *before* a pending update is sent (such as
* in a logic hook) will likely overwrite whatever update we're making
* in SQL.
*/
$bean->data_c = $dataname;
$bean->save();
}

How can I protect my methods bodies (not the attached JavaDoc and Signature) using Acceleo code-generator

I use Acceleo in order to generate code with a model I have made. I managed to protect my methods in order to protect them usinig "#generated NOT" in case I need to regenerate my code with Acceleo. The problem is that adding #generated NOT protect all the method content, that is to say the body, the signature and JavaDocs.
The thing is that I only need to keep the method body, or at least the method body and its signature, but I need the doc to be updated. How can I do this ?
Just for information here is an example of a potential generated class :
/*
* #generated
*/
public class ActeurRefEntrepriseServicesImpl implements ActeurRefEntrepriseServices {
#Autowired
HelloWorldService helloWorldService;
/**
* Service which say hello
*
* #param name
* user name
* #return print Hello username
*
* #generated NOT
*/
#Override
public void sayHello(final String name) {
helloWorldService.print(name);
}
}
Baptiste,
The #generated tags use the standard EMF protection rules : "#generated" means that the body of the block for which it is set will be generated, anything else means no re-generation. If you set something as "#generated" in any of your metamodels' generated code, you will see that there, too, the javadoc is preserved whatever the edits you do.
In short, you cannot tell EMF to re-generate anything other than the code itself.
If you need to have the body protected but not the javadoc, you have to shift from the "#generated" protection to Acceleo's [protected] blocks. i.e, change your template from :
[template generatedMethod(methodName : String)]
/**
* Some doc.
* #param param1
* param documentation.
* #generated
*/
[generateSignature(methodName)/] {
[generateBody()/]
}
[/template]
to something using a protected block :
[template generatedMethod(methodName : String)]
/**
* Some doc.
* #param param1
* param documentation.
*/
[protected (methodName)]
[generateSignature(methodName)/] {
[generateBody()/]
}
[/protected]
[/template]
With this paradigm, anything that is outside of the protected area will be regenerated, everything else will remain untouched by a regeneration.
See also the full documentation available from the Acceleo website.
If you absolutely need to use the "#generated" protection method for your model, you will need to tamper with the JMerger API from EMF and alter the launcher Acceleo generated for you in order to use your own merging strategy (see the getGenerationStrategy method from that launcher). Note that this is by no means an easy task.