Action Pipeline
From TYPO3Wiki
| Teams Committees | This project/information is related to the Extension coordination team |
Keywords (Tags)
Action Pipeline, Design Pattern, MVC, PAC, SPL, ECT, lib, OOP
Overview
Generally you can vitalize the actions methods of lib/div with any code you like, but the library also offers a standard system to build the actions. This standad way is done by the use of objects, that share the API due to inheritance from a common parent class (tx_lib_processor).
Different data has to be transported and processed along the request cycle from the request to the response. This happens in form of a daisy chain of processor objects, following the design pattern of Pipes and Filters . The filters are the processing objects. We call them processors. Processing in this wide sense includes for example the loading and storing taskes of the data.
(Source: http://de.wikipedia.org/wiki/Pipes_and_Filters)
TODO: Replace the PNG with one in english.
If it is not sufficiant to process the data in a linear way, the flow of data can be routed. The processors return result status, to which different pipes can be assigned.
The pipes are the methods by use of which the processors are plugged to each other. This methods are part of the inherited SPL interfaces.
The workflow of the action pipeline can be either controlled by plain PHP actions or by actions done as TS setup.
Why to Choose Action Pipelines
There are different requirements for lib/div that need to be tied:
- Programming should be easy, even for beginners.
- Programming should be done by object orientated.
- The difficult request cycle needs to mastered.
- Flexibility to create and to extend should be given.
The handling of the request cycle can be mastered by a ready made object, that providins hooks and handlers for the objects of the user, that have to by cycled. The drawbacks of this solution are, that there is no full flexibility and that the procedure isn't that transparent for the user.
The action pipeline is an alternative approach to this. It gives the control of the request cycle fully to the hand of the user. If such a complex task needs to be done by an unexperienced user, it is important, to find a way to make the handling as easy as possible. The natural way to think of a request cycle is to imagine it as a simple linear process. The design pattern of pipes and filters is a very modular and flexible way, to handle such a linar process by the use of objects.
By the use of defined API it is even possible to order and configure a pipeline of pipes and filters by a configuration language like TS. That speeds up the development. It's easy to reconfigure and extend it, even without knowlege of PHP.
Usage Examples
By TS Setup
plugin.tx_bananas.form = USER_INT
plugin.tx_bananas.form.userFunc = tx_bananas_controllers_form->main
plugin.tx_bananas.form.configurations < temp.tx_bananas.common
plugin.tx_bananas.form.actions {
clearAction {
START = view
view = tx_bananas_views_form
view {
do.1 = render
do.1.1 = formTemplate
go.TX_LIB_APS_OK = translator
}
translator = tx_lib_translator
translator {
do.1 = translate
go.TX_LIB_APS_OK = storeSession
}
storeSession = tx_lib_sessionProcessor
}
}
By Plain PHP
This is a current development snapshop of bananas (bananas) (contact: elmar.hinz). It still doesn't show the final solution.
public function defaultAction() {
// Model.
$pipe = $this->makeInstance('tx_bananas_models_board');
$pipe->setTotalResultCountKey('totalResultCount'); // For the result browser.
$pipe->setResultListKey('resultList'); // For the view.
$pipe->load();
if(!$pipe->getStatus() == TX_LIB_APS_OK) $this->_die('Unexpected result statuts in', __FILE__, __LINE__);
// Result browser.
$pipe = $this->makeInstance('tx_lib_resultBrowserProcessor', $pipe);
$pipe->setTotalResultCountKey('totalResultCount'); // From the model.
$pipe->setResultBrowserKey('resultBrowserWidget'); // For the template.
$pipe->build();
if(!$pipe->getStatus() == TX_LIB_APS_OK) $this->_die('Unexpected result statuts in', __FILE__, __LINE__);
// View.
$pipe = $this->makeInstance('tx_lib_phpTemplateEngine', $pipe);
$pipe->castList('resultList', 'tx_lib_phpTemplateEngine', 'tx_lib_phpTemplateEngine');
$pipe->setResultKey('result'); // For the translator.
$pipe->render('listTemplate');
if(!$pipe->getStatus() == TX_LIB_APS_OK) $this->_die('Unexpected result statuts in', __FILE__, __LINE__);
// Translator.
$pipe = $this->makeInstance('tx_lib_translator', $pipe);
$pipe->translate();
if(!$pipe->getStatus() == TX_LIB_APS_OK) $this->_die('Unexpected result statuts in', __FILE__, __LINE__);
return $pipe->get('result'); // From the view.
}
TS Actions versus PHP Actions
The same processor objects can be joined to action pipeplines by two different languages, the one is TypoScript the other is PHP. They are reffered to as TS actions or PHP actions. Both types can be mixed within the same controller, but not within the same action.
The PHP action is normal PHP code inside the action methods of the plugin controller. It creates and joins the processor objects to the pipeline and controlls switches within the workflow based on the result status of the processors.
TS actions are fully configured in the TS setup of the controller. The same processor properties, methods and result status are addressed like by the PHP action. The pipe of processors is used analoguous. A special subpart of the controller discovers the TS action setup, interpretes it and runs the pipe. It doesn't compile the TS to a PHP action before, so that both technologies can't be mixed within the same action.
TS actions are fast to create and to reconfigure. Simple applications can fully be written in TS this way, using predifined PHP processors from libraries. They are limited to processors that strictly implement the required processor API. PHP actions are more flexible. They can mix such processors with any other PHP code.
The Action Pipeline Implements the Request Cycle
When the content is good enough, please change the {{draft}} tag to {{review}} .The action pipeline as a way to implement of the request cycle. Often it starts with loading parameters from the sessions and from the incomming request, does validation and updates the model. Then the view is rendered and the response is send.
Internal Data of the Processor Pipeline
Terms
All data that is processed along the request cycle is shiped inside the SPL array of the processors. The SPL array is the internal data array of SPL objects, that can be accessed by the methods defined by the SPL API that is inherited by all processors.
Data:
Data is the term for the values that are processed through the action pipeline.
Fields:
Fields is the term, that can be used to refer to the keys of the internal data.
Example names:
- fieldsToHighlight
- fieldsToValidate
Naming pattern of data keys
If you implement the action in the TS way, you can make few use of the getters and setters for the internal data. The processor objects handle that for you. The processors use default keys to address the internal data. The keys are regular object properties. If there are naming conflicts for any reasons, you can customize the properties by the technologies and precedency rules described in the section below.
PHP Example:
$processor->setProperty('dataKeyOfSearchString', 'searchString');
TS Example:
{
[...]
highligther {
set.dataKeyOfSearchString = searchString
do [ ... ]
go [ ... ]
}
[...]
}
Setters and Getters for the Internal Data
You make few use of the set() and get() methods of processors to handle the data, from the action, because it is shipped internally of the object pipe. Internally the processors use the setters and getters a lot.
You can use the set() method, to set literal strings from the TS setup. The get() method is used in the PHP action,to return the final result.
Properties of Processsors
Generally you can say that local settings of properties overwrite the more global settings.
Precedency from lowest to highest:
- default object property
- TS setup
- flexform setup
- local object property
- method parameters
Default Object Property
This are regular class variables. They are coded into the object directly and serve as final fallback, if no setting is found on a different level.
var $pathToTemplateDirectory = 'fileadmin/templates/';
TS Setup
The TS setup is the level used in the most cases for practical configuration. Many settings end on this level.
[...] {
configuration {
pathToTemplateDirectory = EXT:myextension/templates/';
}
}
Flexform Setup
Properties set in a flexform overwrite settings of the lower levels TS and default object properties.
Local Object Property
The local object property is not a hardcoded object property like the default object property, but a way to set properties from within actions, either from TS actions or from PHP actions.
(in this example using Smarty) as template engine.
$pipe->setProperty('pathToTemplateDirectory', 'EXT:myextensions/templates/smarty/');
singleViewAction {
model [...]
view = tx_toptop_view
view {
set.pathToTemplateDirectory = EXT:myextensions/templates/smarty/
do.render.1 = singleView.tpl
go.OK = translator
}
translator [...]
}
This properties are stored into a special array of the processor object called actionProperties. From there they are taken, when the processor requests a property by the method findProperty($key).
Method Parameters
Method parameters have the highest precedency of all. This are optional parameters for methods. If omitted they fall back to the same property taken from lover levels.
$processor->render('singleView.tpl', 'EXT:myextension/templates/smarty');
singleViewAction {
model [...]
view = tx_toptop_view
view {
do.render.1 = singleView.tpl
do.render.2 = EXT:myextensions/templates/smarty/
go.OK = translator
}
translator [...]
}
The second parameter sets the property 'pathToTemplateDirectory'. If it isn't provided, the rendering method falls back to lower levels for this property.
Methods of Processors
Result Status of Processors
After the processing task is done each processor has a result status. The next processor in the action chain is determined by this result status. The result status is accessed by the method getStatus().
The assignment of successing processors to result status is either be done in PHP or in TS depending on the requirements. Many processors only have one result status TX_LIB_APS_OK and one successor. Other processors like validators or captchas have at least two different result status.
Result status are integers that are used in form of PHP constants. This constants are defined in the file: EXT:lib/ext_localconf.php:
define('TX_LIB_APS_ERROR', tx_lib_processor::generateStatus()); // Something unexpected happend.
define('TX_LIB_APS_OK', tx_lib_processor::generateStatus()); // Simple normal status.
define('TX_LIB_APS_PASSED', tx_lib_processor::generateStatus()); // Used by checks and tests.
define('TX_LIB_APS_FAILED', tx_lib_processor::generateStatus()); // Used by checks and tests.
define('TX_LIB_APS_CREATED', tx_lib_processor::generateStatus()); // Additional status for creating processors.
APS stands for Action Processor Status. The values of the constants are generated by the static method tx_lib_processor::generateStatus(). This method cares that each constant is unique APS identifier within the whole request.
Class Hierarchy
Simplified hierarchy of action processor objects:
tx_lib_SelfAwareness (abstract introspection) ^ | tx_lib_objectBase (abstract data carrier in SPL style) ^ | tx_lib_processor (abstract processor) ^ | tx_yours_xyzProcessor (objects of your action pipe)
Class Interface
Apart from the inherited methods the interface of a processor is quite simple:
class tx_lib_processor extends tx_lib_objectBase {
// status variable
var $status = TX_LIB_APS_OK;
// Find a property by precedency from different sources
function findProperty() { ... }
// Static method used by EXT:lib/ext_localconf.php to generate the status.
function generateStatus() { ... }
// Setter of the status
function setStatus() { ... }
// Getter of the status
function getStatus() { ... }
// Checkers for frequent status function hasStatusError(); function hasStatusOk(); function hasStatusPassed(); function hasStatusFailed(); function hasStatusCreated();
}
