Translations
Info
All page names need to be in English.
en da  de  fr  it  ja  km  nl  ru  zh

Bananas

From TYPO3Wiki
Jump to: navigation, search

notice - Note

The lib/div extensions are not actively maintained and should be considered deprecated!!
This page belongs to the Extension coordination team (category ECT)

Author: Elmar Hinz

Intro

The extension bananas is a typical guestbook based on for the MVC architecture. It consists of two plugins, the form and the entry list. The entry list can be installed alone, to use it as an announcement board, that is filled by use of the backend.

The range of features includes a simple captcha, form validation, session handling, internationalization, localization and submit moderation.

The first part of this manual is a short handbook for extension users. The major part is the bananas tutorial, which comments the sources of this extension as an example for lib/div programming.

Handbook

Installation

Install the extension

Go to the module Tools/Ext Manager and install the extension bananas in the typical way. You will discover that it depends on the extension lib which depends on div.

Include the static template

Go to the module Web/Template and open the template, where you want to configure the extension, in the Info/Modify view. In the select box Include static (from extensions) you select the template Bananas (bananas). Save it.

Create an entry folder

Create a sysfolder where the postings shall be stored.

Plugin the plugins

Go to the module Web/Page and open a page. Insert in the plugin Bananas Form and the plugin Bananas List. As Startingpoint you choose the entry folder. Save. In the frontend you should now find a guestbook.

Configuration

The bananas plantation

Doing your first steps into the garden of real object orientated development (OOP) with the apples tutorial apples you already have discoverd the leitmotifs of MVC programming based on the lib/div libraries. Now you long for more and you are prepared to learn that handcraft on the bananas plantation. So let's start directly with the bananas tour.

Still programming PHP4?

You can do lib/div programming for PHP4. However TYPO3 has been migrated to PHP5 since a while, so the extension bananas is done for PHP5 only.

In this section we show the small differences that apply, if you still have to do PHP4 for some reasons. The PHP5 style is shorter. But don't worry, if you do something the PHP4 way. PHP4 code will still run, when the server switches to PHP5.

Object iteration

All lib/div objects implement the interface ArrayIterator. In PHP version 5 you iterate them with a foreach loop, just like an array. In PHP4 you do the same with a for loop, wich requires a little more typing.

PHP5:
PHP script:
  foreach($object as $key => $value) {
 	[...]
  }
PHP4:
PHP script:
  for($object->rewind(); $object->valid(); $object->next()){
 	$key = $object->key();
 	$value = $object->current();
 	[...]
  }

Object access

All lib/div objects implement the interface ArrayAcces. In PHP5 you can directly access the values by keys in array style. In PHP4 you do the same with with set() and get() methods.

PHP5:
PHP script:
  $object[$key] = $value;
  $value = $object[$key];
PHP4:
PHP script:
  $object->set($key, $value); 
  $value = $object->get($key);

The structure of the file system

Although there are many options to organize the file system, we take the same as you already know it from the apples example, for reasons of simplicity. So there isn't much to explain.

There are some additional files in the base directory that are required for the handling of database tables and for translations. This are the files ext_tables.sql, locallang.xlf, locallang_db.xlf and tca.php. Common rules of TYPO3 development apply for them. Please consult the documents TYPO3 Core API and TYPO3 Coding Guidelines.

Bananas contains two plugins, so that you find two controller classes in the controllers directory. Because the board consists of a form and a list with multiple entries, there are also several classes in the views and the templates directory.

Configurations

The file configurations/setup.txt is much bigger than in apples. There are some new elements, but the fundamental structure of the file is the same. The entire setup file is divided into three parts. In the first part the configuration properties are set. This configurations are shared by all plugins. In the second part the controller classes are instantiated. In the third part the controllers are connected with the tt_content entries.

The configuration properties

Below the path temp.tx_bananas you find many configuration properties. The most of them are simple key => value pairs.
TS TypoScript:
  temp.tx_bananas {
 	[...properties...]
  }

The names of the properties pathToLanguageFile and pathToTemplateDirectory are standard names. If set, the values of this properties are used automatically, for translator objects and rendering objects. If you want to use differnt property names or if you want to take the values from different sources, you have to apply special setter methods of the objects in question.

It would be a good idea, to implement those values with TS constants, that are daily configured by John Doe.

The components of the path have to match the coding guidelines of TYPO3 to avoid conflicts. The part temp.tx_bananas addresses the namespace of the plugin.

Validation rules

TS TypoScript:
  temp.tx_bananas {
 	[... properties ...]
 	validationRules {
 		10 {
 			field = title
 			pattern = /.+/
 			message = %%%emptyTitleError%%%
 		}
 		[... more validation rules ...]
 	}
  }

validationRules is a substructure of the configurations, to take up the form validation rules. It is an array that contains three key => value pairs for each rule. field is the form parameter to check, pattern is regular expression of the perl type that is applied to the parameter. message is the Message that is shown, when the validation fails. In this example we work with translation markers, that are replaced by translations the translator object as the last element of the processing chain.

The controller setup

Each controller object needs to be instantiated, which is configured by three lines like this:
TS TypoScript:
  plugin.tx_bananas.form = USER_INT
  plugin.tx_bananas.form.userFunc = tx_bananas_controllers_form->main
  plugin.tx_bananas.form.configurations < temp.tx_bananas

The components of the path have to match the coding guidelines of TYPO3 to avoid conflicts. The part plugin.tx_bananas addresses the namespace of the plugin. The part form addresses a single controller of that extension.

The first line creates the object as USER or USER_INT object. The second line sets the user method. The third line copies the whole previous configuration to the path of the object.

After instantiation all properties can be accessed by the configuration object, that is typically addressed with the call $this->controller->configurations.

tt_content connection

Finally the configured controllers have to be connected to plugins, wich are entries of the table tt_content. The plugins are identified by the controller id. The controller ids are

tx_bananas_controllers_form and tx_bananas_controllers_list.
TS TypoScript:
  tt_content.list.20.tx_bananas_controllers_form =< plugin.tx_bananas.form
  tt_content.list.20.tx_bananas_controllers_list =< plugin.tx_bananas.list

Because the controllers are objects we can do this assignment as reference.

MVC classes

Controllers

tx_bananas_controllers_list

The only action method in this controller is the defaultAction(). As you may guess the method defaultAction() action is called by default, if nothing else is configured and no action parameter has been send. There is no use case for the list to react synchronized to action parameters of the form. To avoid the receivement of the forms (action) parameters, simply a different designator can be set for the list plugin:
PHP script:
   var $defaultDesignator = 'tx_bananas_controllers_list';

By this "trick" the lists default action is always called, because nobody uses this designator to send any other actions.

The function makeProcessor() of the controller does those things that are typically done for processor objects of the Action Pipeline. It especially pipes the internal array content from one processor to the next. In the action pipe the model, the resultbrowser and the view are created and processed. Finally the view is translated.

The first element is the model, which loads the result list. The query is internally controlled by the resultbrowsers offset parameter and the configurations resultsPerView setting. The values are taken from the objects parameters and configurations.

The method totalResultCountKey sets a name by which the total result count is stored into the controllers register. From there it will be fetched by the resultbrowser processor.

The model data is now piped into the resulbrowser processor. The job of this processor is to call a MVC sub-plugin (tx_lib_resultBrowser_controller), which returns the resultbrowser string. The resultbrowser string is stored with the key 'resultBrowser' into the controllers register. From there it will be fetched by the view processor.

The data is now piped into the view processor. The method castElements transforms all model entries in the piped array list to view objects of the type tx_bananas_views_entry.

The the last processor is the translator. It is applied as last step of the processing chain. It replaces all text strings of the format %%%someKey%%% against a translation from the language file, where someKey matches an key from the language file.

If no language file is defiend by the method setPathToLanguageFile(), the language file is expected in the configuration object under the key pathToLanguageFile. This behaviour is analogous to the configuration of the template path of the view object.

tx_bananas_controllers_form

This controller contains multiple actions, wich is a typical characteristic of lib/div architecture. Depending on the action parameter, that is send by the form, one of the methods ending with Action is called. You find the actions captchaAction(), clearAction(), editAction(), insertAction() and previewAction(). They mirror the possible actions of the user while he is editing the form. In the chapter Dealing with forms, we go into details.

Models

The model class tx_bananas_models_board is a typical model class to query the database. It contains two methods, one to load the list and one to save the validated form datas. Both times it acts as an array object. The load method fills the object with a list of rows, wich are iterated by the list controller. The form controller fills the model directly upon instantiation with the validated form values. They are read from the object in the insert method.

The queries themselves are done according the the TYPO3 coding guidelines by use of the object $GLOBALS['TYPO3_DB'].

Views

There are 3 view classes. tx_bananas_views_form renders the form. tx_bananas_views_list renders the list and tx_bananas_views_entry renders the single list entries.

tx_bananas_views_entry

All methods that are required to fill the template, are inherited from the parent class tx_lib_phpTemplateEngine.

tx_bananas_views_list

All methods that are required to fill the template, are inherited from the parent class tx_lib_phpTemplateEngine.

tx_bananas_views_form

The method preset() presets the empty form. This is the first function, that is called by the constructur, wich in inherited from tx_lib_object. Incomming parameters overwrite the preset values of the object. The method printFormTagStart() creates the opening tag of the form. All other methods that are required to fill the template, are inherited from the parent class tx_lib_phpTemplateEngine.

Templates

list.php

In the list template the list view $this is iterated to create an unordered list. The rendering of each entry is done by the entry objects itself, calling rendering methods

like $entry->printAsUrl('url').
PHP script:
  foreach($this as $entry): 
 	[...]
 	$entry->doSomething();
 	[...]
  endforeach;

We discover one big advantage of PHP templates, that it shares with the more powerfull rendering engines like Smarty. It is very simple to check in the template if a list is empty

and to drop some HTML elements in if not needed for an empty list.
PHP script:
  <?php if($this->isNotEmpty()): ?>
 	  <nowiki><ul></nowiki>
  <?php endif; ?>
 	  [... the list ...]
  <?php if($this->isNotEmpty()): ?>
 	  <nowiki></ul></nowiki>
  <?php endif; ?>
A similar check is quickly done for single elements.
PHP script:
  [...]
  <?php if($entry->has('url')): ?>
 	  %%%url%%%<span class="url"> <?php $entry->printAsUrl('url'); ?> </span>
  <?php endif; ?>
  [...]

Just like in the configuration of the validation rules we can use language markers, that are replaced by the translator in the last step of the processing chain.

Some general considerations regarding list templates

As an interesting alternative to the wholistic template design shown here, it is possible to use one template for the list and a second template for the elments, just like we use one class for the list and a second class for the elements. It could look like this, if you write a simple method loop() for the entry class,

that calls the entry template 'entry.php':
PHP script:
  <?php if($this->isNotEmpty()): ?>
 	  <nowiki><ul></nowiki>
  <?php endif; ?>
  <?php $this->loop('entry.php'); ?>
  <?php if($this->isNotEmpty()): ?>
 	  <nowiki></ul></nowiki>
  <?php endif; ?>

You have to ask your template designer, wich style he likes best. A designer will maybe prefer the wholistic style. The advantage of the separated design is, that the desinger doesn't need to learn the foreach loop.

form.php

The form template will meanwhile be self explaining for you. The method printErrorList() is inherited from the class tx_lib_phpTemplateEngine. It lists error messages in form of an unoderdered list with the class attribute 'errors'.

Please take a look how the name attributes of the form elements are used, to match the TYPO3 coding guidelines of namespace separation. They match the default designator, which is identical with the extension key. If you want to use a different one, it should

either start with the extension key of with the extension key prefixed by tx_.
PHP script:
  <dd><input id="bananas_title" name="bananas[title]" value="<?php $this->printAsForm('title'); ?>" size="60"/></dd>

Have an additional look to the name attributes of the submit

buttons.
PHP script:
	<input type="submit" value="%%%preview%%%" name="bananas[action][preview]" />

The first array key marks it as an action, the second sets the action value. To send actions the value of the submit buttons is unimportant, so it can be freely used. In this case we use it with a translation marker.

preview.php

The preview is a simplified variation of the form template. Mark that the submit actions are differnt.

Where are the form values kept during the preview? In hidden fields? There are none. The values are kept in the session.

captcha.php

The captcha is more or less identical to the preview apart from the submit actions

and the call of captcha question and input field.
PHP script:
  <?php print $this->getCaptchaQuestion(); ?>
  <?php print $this->getCaptchaInput('bananas_captcha'); ?>

General conceptions

Localization

There is nothing special to do the localization, that isn't known from TYPO3 concepts in general.

Localization is specially important for the formatting and output of data and time. The different date and time formatting functions, that are inherited by the view classes from the class tx_lib_viewBase take a formatting string as second attribute as known from the PHP function strftime. A general precofiguration for this functions can be done in the TS configuration setup, with the keys dataFormat and 'timeFormat.

Translation handling

PHP script:
%%%someKey%%%.

Translation handling is really simple. It's done by putting translation markers into output strings, where a translation is needed. This markers are replaced by the translation matching the key in the marker. The extension markers can be used in every place, where output strings are set, in the setup, in the classes, in the template files. The translator object is typically the last object of the processing chain. In the controller section it has been shown, how the translator is used and configured.

Below the translator works the general TYPO3 language object. As a result of this, you can use the complete system of TYPO3 language files, to store translations. The typical way is to configure the path of a common language file for the extension in the TS setup with the key pathToLanguageFile.

Dealing with forms

Overview of the actions of tx_bananas_controllers_form

What's to do to build a form like in bananas? First we observe that there are no features, to edit or delete the entries in the frontend, because it is a simple guestbook. There is the empty form first. We call it the clearAction() because we also see it, when by clicking the clear button of the form. After input the result needs to be saved. That we call the insertAction(). But wait. There is more. A preview of the input is offert. So we need a previewAction(). If the user isn't content with the result of the preview, she needs to alter the input before saving. That we call the alterAction(). To prevent spam, we additionally have build in a captcha. We call it captchaAction().

The session

Where is the user input while the user calls a preview or while the captcha tests are done? You could keep the input in hidden input fields. The alternative is to store it into the session. That's what we do here.

You can store more into the session, than the mere input data. You could also store status informations of a form, for example the latest view state of a date selector or of a category tree that are part of the form. By keeping the status informations with the session (or into hidden input fields), the form gets the nature of a desktop application.

Using the session is simple. You just store the whole SPL object that carries the form data.
PHP script:
  $viewClassName = tx_div::makeInstanceClassName('tx_bananas_views_form');
  $view = new $viewClassName($this, $object);
  $view->storeToSession($this->getClassName()); 
  $view->render($this->configurations[$template]);

The view object is stored into the session immediately before it is rendered, because the internal data array of the array object is exchanged against the output string, during the rendering process. You now interject, that objects are referenced in PHP5, so that the view object will be altered although it is stored before the rendering process. Have a look into the method tx_lib_object->storeToSession(). It works with a copy of the data internally. It only stores the array data of the objects.

The method storeToSession() takes an id as argument. This id is needed when to load the content from the session. Normally you can simply take the classname as id. The drawback is, that the user can only edit one form at one time. If she should be able to edit multiple forms in parallel you need to create an individual id for each form, that you can keep an a hidden field.

The request response cycle of a form

Let's consider the full request response cycle of a form. Either you load the form data from the database to edit it (not done in bananas) or you process the incomming data after the previous submit of the form.

When the request of a forms submit is comming in, first the session data of the form is loaded, if you don't work with hidden input fields only. The session can contain form data and other status data of the form. The submitted form data now has to be validated and merged with the session data. The validation is done either before or after merging it with the session data. Upon merging submitted form data overwrites session data.

If the validation fails, the data has to be displayed as a form again together with helpful error messages. If the validation is successfull, the data is given to the model to be stored. After storing the data, it's a good idea to do a header based redirect to clear the data and to prevent a second submit of the same data by accidental reload.

For the case, that the validation fails, we process the full request-responce cycle from the receivement of the data to the redisplay of the form. For the case, that the validation is successfull, this process forks, stores the data and responses with a redirection.

As we will see in one of the next sections, this model of the request response cycle is rather abstract. Preview and captcha require different modifications of this model. It's one of the main task of the controller to controll the request response cycle.

SPL objects and special keys

To transport the data from the request to the response, bananas consequently makes use of the features of SPL objects. We also speak of an object chain or the request response chain.

Apart from the form parameters some metadata need to be transported, like the error messages from the validation. This meta data uses special keys starting with underscore like _errorList, _errorCount. For this reason form parameters should not use names, starting with underscore.

Revisiting the class tx_bananas_controllers_form

Beginning and end of the request cycle

Apart from the action methods, there are two helper methods getValidator() and display(). This two methods handle the beginning and the end of the forms request cycle.

The method getValidator() creates a validator object of the class tx_lib_validator merges the parameters of the session and the form into and validates them. The validator object is returned by the method.

The method display() stores the data into the session, renders and populates the templates, and finally calls the translator. It takes two arguments, the name of the template file to render and optionally a data object.

Forking by the validation result

The result of the validation is asked in two actions in the previewAction() and in the

captchaAction().
PHP script:
  if(!$validator->ok()) { ... } else { ... }

In the previewAction() it forks between the redisplay of the form and the preview view. In the captchaAction() it forks between the redisplay of the form and the captcha view. bananas abstains from a last validation in the insertAction() which should surely be done in cases where security matters.

The validator object
As validator the class tx_lib_validator is used.
PHP script:
  protected function getValidator() {
 	  $validatorClassName = tx_div::makeInstanceClassName('tx_lib_validator');
 	  $validator = new $validatorClassName($this);
 	  $validator->loadFromSession($this->getClassName());
 	  $validator->overwriteArray($this->parameters);  
 	  $validator->useRules('validationRules.');
 	  $validator->validate();
 	  return $validator;
  }

The validator internally accesses the configuration the standard way by $this->controller->configurations. As you have seen in the discription of the setup, there is a sublevel for the validation rules. The whole sublevel can be selected within the configurations object by the function useRules(). The parameter is the relative path of the sublevel.

The method loadFromSession() is responsible to load the session data. The parameter is the id that you used, when you stored the data into the session.

The method overwriteArray() is inherited from the parent class tx_lib_object. It takes the parameters object as argument to overwrite the session data.

Finally the method validate() is called to execute the validation.

Error messages are collected inside the validation object. They are shipped inside the validation chain, with the special keys _errorList and _errorCount.

The captcha object
As captche the class tx_lib_captcha is used.
PHP script:
  $captchaClassName = tx_div::makeInstanceClassName('tx_lib_captcha');
  $captcha = new $captchaClassName($this, $validator); 
  $captcha->createTest($this->getClassName()); 
  return $this->display('captchaTemplate', $captcha);

The validator object $validator is feeded into the SPL object, only for the purpose to use the captcha object as the next data carrier in the SPL based request response chain.

The method createTest() takes an identifier as argument, to identify the answer, wich is automatically handled to the session. The captcha values are accessed by 3 special keys, in the captcha template.

  • _captchaQuestion: The question string.
  • _captchaInput: The input element for the form.
  • _captchaAnswer: The expected answer.
The answer is checked in the method insertAction() identified by the mentioned id as parameter.
PHP script:
  if(!$captcha->ok($this->getClassName())) {  ... }


Insert and redirect

It's now obvious to you, how the data is stored by the model. As already mentioned bananas abstains from a final validation check, because it has been done in previous steps.

You should do a final validation for critical data here.
PHP script:
  $validator = $this->getValidator(); // Load the data from the session.
  $modelClassName = tx_div::makeInstanceClassName('tx_bananas_models_board');
  $model = new $modelClassName($this, $validator);
  $model->insert(); // Finally store it.
Here is the final redirect, that is using the redirect method of the tx_lib_link object.
PHP script:
  $linkClassName = tx_div::makeInstanceClassName('tx_lib_link');
  $link = new $linkClassName();
  $link->destination($this->getDestination());
  $link->designator($this->getDesignator());
  $link->noHash();
  $link->redirect();