Zur Zeit wird gefiltert nach: Computer & Informatik
Filter zurücksetzen
ExtBase Cookbook #11 - Using ExtBase Controller as Subcontroller
During the past couple of days, we thought about how to use an ExtBase Controller as a sub controller. What we wanted to achieve was using a Controller inside another controller to process a certain action. In our project, we work on a extension rendering lists. If you want to use a list inside your plugin, it would be nice to simply call the list controller inside your plugin controller and make it render some domain objects for example. So here is how we wanted the whole thing to work:
/** * Action that is run, whenever a list of galleries should be displayed * * @return string The rendered index action */ public function indexAction() { $subcontrollerFactory = Tx_PtExtlist_Controller_SubcontrollerFactory::getInstanceByListId('gallery_list'); $subcontroller = $subcontrollerFactory->createSubcontroller('list'); $renderedList = $subcontroller->listAction(); $this->view->assign('galleries_list', $renderedList); }
After a little while we discovered some ugly side-effects that did not make it that easy as we first thought to simply use a controller as sub-controller. Most of the problems reside inside the Dispatcher and the mechanism of how requests are dispatched and what happens, whenever a redirect or forward is triggered. So we came up with the following solution which is not quite finished yet but seems to work fine.
First of all, we do not use the controller class itself as subcontroller but generate a wrapper for the controller that handles all dispatching and gives as the way of calling a controller action described above, no matter whether we have forwards or things like that:
<?php /*************************************************************** * Copyright notice * * (c) 2010 Daniel Lienert <lienert@punkt.de>, Michael Knoll <knoll@punkt.de> * 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 2 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! ***************************************************************/ /** * Wrapper class for subcontroller. Handles requests for subcontrollers * and emulates dispatching functionality like forwarding etc. * * @author Michael Knoll <knoll@punkt.de> * @package TYPO3 * @subpackage pt_extlist */ class Tx_PtExtlist_Controller_SubcontrollerWrapper extends Tx_PtExtlist_Controller_AbstractController { /** * Holds a reference of the controller to be used as subcontroller * * @var Tx_PtExtlist_Controller_AbstractController */ protected $subcontroller; /** * Holds an instance of subcontroller factory to create * instances of subcontrollers for forwardings etc. * * @var Tx_PtExtlist_Controller_SubcontrollerFactory */ protected $subcontrollerFactory; /** * Holds an instance of current request that has to be processed by subcontroller * * @var Tx_Extbase_MVC_Web_Request */ protected $request; /** * Holds an instance of current response that is manipulated by subcontroller * * @var Tx_Extbase_MVC_Web_Response */ protected $response; /** * Injector for request * * @param Tx_Extbase_MVC_Web_Request $request */ public function injectRequest(Tx_Extbase_MVC_Web_Request $request) { $this->request = $request; } /** * Injector for response * * @param Tx_Extbase_MVC_Web_Response $response */ public function injectResponse(Tx_Extbase_MVC_Web_Response $response) { $this->response = $response; } /** * Injector for subcontroller * * @param Tx_PtExtlist_Controller_AbstractController $subcontroller */ public function injectSubcontroller (Tx_PtExtlist_Controller_AbstractController $subcontroller) { $this->subcontroller = $subcontroller; } /** * Injector for subcontroller factory * * @param Tx_PtExtlist_Controller_SubcontrollerFactory $subcontrollerFactory */ public function injectSubcontrollerFactory (Tx_PtExtlist_Controller_SubcontrollerFactory $subcontrollerFactory) { $this->subcontrollerFactory = $subcontrollerFactory; } /** * Magic function that handles action-calls on subcontroller wrapper. * Checks whether given method name is a correct action and whether action exists in controller * * @param string $method Method name to be called on subcontroller * @param array $args Arguments delivered for action to be called */ public function __call($method, $args) { throw new Exception ('Given method name is not a correct action name: '. $method . ' 1283351947'); } tx_pttools_assert::isNotEmptyString($matches[1], 1283351949')); $controllerActionname = $matches[1]; throw new Exception('Trying to call action ' . $method . ' on ' . 1283351948'); } return $this->processAction($controllerActionname, $args); } /** * Processes action called on subcontroller * * @param string $controllerActionName * @param array $args * @return string HTML source: the rendered action */ protected function processAction($controllerActionName, $args) { $dispatchLoopCount = 0; // TODO insert arguments into request! // Perhaps this works like that: $this->request->setArguments(); $this->request->setControllerActionName($controllerActionName); while (!$this->request->isDispatched()) { if ($dispatchLoopCount > 0) { $this->subController = $this->subcontrollerFactory-> getPreparedSubController($this->request); } if ($dispatchLoopCount++ > 99) throw new Tx_Extbase_MVC_Exception_InfiniteLoop ('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations.', 1283351950); try { $this->subcontroller->processRequest ($this->request, $this->response); } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) { } } return $this->response->getContent(); } }
The next part is the factory that creates this subcontroller wrapper. What you can see here is rather some kind of a prototype... we have to fix some things. First of all, we have to make this thing generic, at the moment it only fits our needs in the context of the list plugin. But I hope you get the idea...
<?php /*************************************************************** * Copyright notice * * (c) 2010 Daniel Lienert <lienert@punkt.de>, Michael Knoll <knoll@punkt.de> * 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 2 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! ***************************************************************/ /** * Factory for creating instances of pt_extlist controllers to be used as subcontrollers * * @author Michael Knoll <knoll@punkt.de> * @author Daniel Lienert <lienert@punkt.de> * @package TYPO3 * @subpackage pt_extlist */ class Tx_PtExtlist_Controller_SubcontrollerFactory extends Tx_Extbase_Dispatcher { /** * Holds associative array of instances listIdentifier => factoryInstance * * @var array */ /** * Holds identifier of list, this factory creates controllers for * * @var string */ protected $listIdentifier; /** * Factory method for subcontrollerFactory * * @param string $listIdentifier * @return Tx_PtExtlist_Controller_SubcontrollerFactory */ tx_pttools_assert::isNotEmptyString($listIdentifier, empty list identifier! 1283254996')); if(self::$instancesArray[$listIdentifier] === NULL) { self::$instancesArray[$listIdentifier] = new Tx_PtExtlist_Controller_SubcontrollerFactory($listIdentifier); } return self::$instancesArray[$listIdentifier]; } /** * Constructor for subcontroller factory * * @param string $listIdentifier Identifier of list, factory should create subcontrollers for */ public function __construct($listIdentifier) { $this->cObj = $GLOBALS['TSFE']->cObj; parent::__construct(); $this->listIdentifier = $listIdentifier; } /** * Creates a controller object for list controller * * // TODO make this generic for all controllers (--> how to get dynamic TS config for plugin?) * * @param unknown_type $config * @return unknown */ "userFunc"=> "tx_extbase_dispatcher->dispatch", "pluginName"=> "pi1", "extensionName"=> "PtExtlist", "controller"=> "List", "action"=> "list", "controller"=> "List", "actions"=> "list,sort" ), "controller"=> "Filterbox", "actions"=> "show,submit,reset" ), "controller"=> "Pager", "actions"=> "show,submit" ), "controller"=> "Bookmarks", "actions"=> "show,edit,update,delete,create" ), ), "settings" => "< plugin.tx_ptextlist.settings", "persistence"=> "< plugin.tx_ptextlist.persistence", "view"=> "< plugin.tx_ptextlist.view", "_LOCAL_LANG"=> "< plugin.tx_ptextlist._LOCAL_LANG" ); $this->initializeConfigurationManagerAndFrameworkConfiguration($configuration); // TODO fake request! try { $requestBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_RequestBuilder'); $request = $requestBuilder->initialize(self::$extbaseFrameworkConfiguration); $request = $requestBuilder->build(); } catch(Exception $e) { /* TODO this is done for being testable in CLI environment! */ $actionNames = $configuration['switchableControllerActions.']['1.']['actions']; $request = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Request'); $request->setPluginName($configuration['pluginName']); $request->setControllerExtensionName($configuration['extensionName']); $request->setControllerName($configuration['controller']); $request->setControllerActionName($actions[0]); $request->setRequestURI('http://fakeuri.com'); $request->setBaseURI('http://fakeuri.com'); $request->setMethod('HTTP'); } // Remind setting list identifier in TS! plugin.tx_ptextlist.settings.listIdentifier = <listIdentifier> self::$extbaseFrameworkConfiguration = t3lib_div::array_merge_recursive_overrule( self::$extbaseFrameworkConfiguration, $config ); // we need to check the above conditions as cObj is not available in Backend. $request->setContentObjectData($this->cObj->data); $request->setIsCached($this->cObj->getUserObjectType() == tslib_cObj::OBJECTTYPE_USER); } $response = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Response'); // Request hash service $requestHashService = t3lib_div::makeInstance('Tx_Extbase_Security_Channel_RequestHashService'); // singleton $requestHashService->verifyRequest($request); $persistenceManager = self::getPersistenceManager(); $subcontroller = $this->getPreparedController($request); $subcontrollerWrapper = new Tx_PtExtlist_Controller_SubcontrollerWrapper(); $subcontrollerWrapper->injectSubcontroller($subcontroller); $subcontrollerWrapper->injectSubcontrollerFactory($this); $subcontrollerWrapper->injectRequest($request); $subcontrollerWrapper->injectResponse($response); return $subcontrollerWrapper; } /** * Returns current subcontroller * * @param Tx_Extbase_MVC_Web_Request $request * @return Tx_Extbase_MVC_Controller_ControllerInterface */ public function getPreparedSubController(Tx_Extbase_MVC_Web_Request $request) { return $this->getPreparedController($request); } }
Please understand this as a request for comment. It would be interesting to gather some ideas on how to make this feature run smooth together with ExtBase as you a have a really powerful utility here. I'm looking forward to your comments!
ExtBase Cookbook #10 - Changing Storage PID for repositories
Today I had the problem of how to change the storage pid for a single query in an ExtBase repository. Thanks to the mailinglist, I got the following solution, which I want to share with you:
// Inside your repository: public function findAllRecordsInPid($pid) { $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(FALSE); $query->matching($query->equals('pid', $pid)); return $query->execute(); }
Just to make this article a little more complete, I want to add some information that has been given to me on the mailinglist:
Steffen Ritter wrote: as a "read" list, i give a full list with all my folders where records should belong to...
for writing I use the following configuration to ensure that every object is created in the correct folder
Tx_Rsmyext_Domain_Model_Donation {
newRecordStoragePid = 37
}
Sebastian Kurfürst added: one more addition; If you want to *move* records from one sysfolder to another, just give them a "pid" property and modify this property as wanted.
Thanks for all your help! I hope, this might be helpful for someone!
ExtBase Cookbook #9 - How to change view-class for each controller and action using Typoscript
Today, I had the problem that I wanted to change the view class to be used with ExtBase ActionController depending on TypoScript settings. I need to produce CSV output for a generic plugin and want to change the behaviour of the view class depending on the concrete usage of the plugin. That's why it is not very useful for me to simply change the format of the output, which can be handled by extbase out of the box.
So what I did is using an abstract controller extending action controller as a base controller for all my plugin's controller. Then I copied the code of the resolveView() method into this controller and added some lines:
abstract class Tx_PtExtlist_Controller_AbstractController extends Tx_Extbase_MVC_Controller_ActionController { /** * Constructor for all plugin controllers * @author Michael Knoll <knoll@punkt.de> */ public function __construct() { parent::__construct(); } /** * Prepares a view for the current action and stores it in $this->view. * By default, this method tries to locate a view with a name matching * the current action. * * Configuration for view in TS: * * controller.<ControllerName>.<controllerActionName>.view = <viewClassName> * * @return void */ protected function resolveView() { // These lines have been added by Michael Knoll to make view configurable via TS $viewClassName = $this->settings['controller'] [$this->request->getControllerName()] [$this->request->getControllerActionName()]['view']; if ($viewClassName != '') { $view = $this->objectManager->getObject($viewClassName); } else { throw new Exception('View class does not exist! ' . $viewClassName); } } else { $view = $this->objectManager->getObject('Tx_Fluid_View_TemplateView'); } $controllerContext = $this->buildControllerContext(); $view->setControllerContext($controllerContext); // Template Path Override $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration(); $view->setTemplateRootPath(t3lib_div::getFileAbsFileName( $extbaseFrameworkConfiguration['view']['templateRootPath'])); } $view->setLayoutRootPath(t3lib_div::getFileAbsFileName( $extbaseFrameworkConfiguration['view']['layoutRootPath'])); } $view->setPartialRootPath(t3lib_div::getFileAbsFileName( $extbaseFrameworkConfiguration['view']['partialRootPath'])); } if ($view->hasTemplate() === FALSE) { $viewObjectName = $this->resolveViewObjectName(); $viewObjectName = 'Tx_Extbase_MVC_View_EmptyView'; $view = $this->objectManager->getObject($viewObjectName); $view->setControllerContext($controllerContext); } $view->injectSettings($this->settings); } $view->initializeView(); $view->assign('settings', $this->settings); return $view; } }
Now I can use the following TS-Settings inside my Typoscript settings part of the extension to change the view for each controller and action seperately:
plugin.<extension_name>.settings.controller.<controller_name>.<action_name>.view = <view_class_name>
Some funny anecdote on this topic is the fact that when I first started to use MVC paradigma for Typo3 extension programming, I came in touch with pt_mvc from Fabrizio Branca. Now Fabrizio seems to be a real configuration fetishist and so everything was configurable in pt_mvc - even the view to be used for a controller and action. So I was wondering why none of this found its way into ExtBase yet and hope that some day soon I will find all the great features of pt_mvc in ExtBase.
Update auf Typo3 4.4
Gerade eben habe ich auf die neue Version von Typo3 (4.4) geupdated und bin mal wieder überrascht, wie problemlos alles läuft. Die neue Version erscheint im Backend deutlich aufgeräumter und vor allem schneller - oder liegt das an der langsamen T3 Installation, mit der ich heute den ganzen Tag arbeiten musste?!?
Über die Änderungen bin ich im Detail noch nicht gestolpert, bin aber vor allem bei ExtBase sehr gespannt, was sich alles getan hat. Eine detaillierte Liste der Änderungen und Verbesserungen findet sich unter http://typo3.org/download/release-notes/typo3-44/
Konfiguration, Persistierung, Zustandssicherung und Parametrisierung von Domain-Objects
Nachdem ich jetzt schon eine Weile nichts mehr über Programmierung etc. geschrieben habe, möchte ich heute kurz von ein paar Ideen berichten, die wir während unserer Arbeit an der Neuimplementierung von pt_list hatten. Zuerst einmal zu pt_list selber: Es handelt sich um ein Plugin für Typo3 das im Prinzip aus SQL-Schnippseln eine Anfrage an eine beliebige (My)SQL-Datenbanken schicken kann und eine Frontend-Liste generiert. Diese Liste kann gefilter, sortiert, gepaged und Ge-Bookmarked werden, d.h. einmal gemachte Abfragen können gespeichert werden. Die Extension wurde bei der punkt.de GmbH entwickelt, maßgeblich von Fabrizio Branca.
Derzeit versuchen Daniel und ich, dieses Plugin auf ExtBase zu portieren, wobei das Hauptaugenmerk allerdings auf einer etwas entkoppelteren Architektur der Komponenten liegt. Bei der Erzeugung und "Füllung" der einzelnen Domänenobjekte treten meistens die selben Abläufe auf:
1. Erzeugen der Objekte aus Konfiguration (TypoScript)
2. Herstellen eines alten Zustandes aus der Session / Datenbank
3. "Parametrisieren" der Objekte aus gegebenen GET/POST-Parameter
Diesen Objekt-Lebenszyklus hat Fabrizio in einem neuen Architekturvorschlag bereits versucht durch diverse Methoden einer Komponenten-Klasse in den Objekten selbst zu implementieren. Sein Vorschlag waren Methoden wie loadFromSession(), saveToSession(), setByParameters() etc. direkt in einer Elternklasse aller pt_list-Komponenten zu implementieren.
Daniel und ich haben uns heute auf einen Vorschlag geeinigt, der es ermöglicht, die Komponenten von beliebigen Klassen erben zu lassen, also keine Komponenten-Basisklasse zu implementieren und die Funktionalität über Interfaces zur Verfügung zu stellen. Der Plan ist es jetzt, dass jedes Objekt über ein Interface "Identifiable" eine Methode getIdentifier() implementiert, welche die nötigen Parameter, sei es in einer Session oder in Form von GET/POST Parametern an eine Art Namensraum bindet. Beim Erzeugen des Objektes kann nun ein Session-Container und ein GET/POST-Parameter-Container nach entsprechenden Parametern in diesem Namensraum durchsucht werden und über ein Interface "SettableBySession" und "SettableByParameters" entsprechend mit den Daten aus Session und GET/POST-Parametern gefüttert werden.
Da die entsprechenden Methoden in der jeweiligen Klasse implementiert sind, kann auch die Klasse für sich entscheiden, welche Properties beim Setzen wie überschrieben werden. Diese Überschreibung tritt vor allem bei den Filtern in pt_list sehr häufig auf, da es dort vorkommt, dass bestimmte Werte zuerst aus der TS-Konfiguration entstammen, dann von GET/POST-Parametern überschrieben werden und schließlich je nach Anwendungsfall von der Session stammen können.
Grundsätzlich kann ein solcher Lifecycle aber nicht nur in pt_list angewendet werden, sondern wäre sicherlich eine Bereicherung für ExtBase als Framework an sich. Die Technologie für das Mappen von Relationalen Daten auf Objekte sowie von Formulardaten auf Objekte ist ja bereits vorhanden, was noch fehlt ist ein Session-Adapter und evtl. ein generischer Weg, Objekte mit ihrer Konfiguration zu "impfen".
Sobald wir erste Implementierungserfahrungen sammeln konnten, halte ich euch auf dem Laufenden!




