Developing TYPO3 Extensions with Extbase and Fluid

TYPO3 is a free enterprise web content management system licensed under the GPL.

Authors:

Sebastian Kurfürst <sebastian@typo3.org>

Jochen Rau <jochen.rau@typoplanet.de>

Copyright:

Since 2010, Sebastian Kurfürst, Jochen Rau

Translation:

Translated to English by the TYPO3 community

Rendered:

2019-04-24 11:53

Sitemap

Preface

About This Manual

This is an extensive introduction into developing TYPO3 extensions with the Extbase framework and the Fluid templating engine.

Target Audience

This book is for TYPO3 extension developers who have a basic understanding of PHP programming and experience working with and administering TYPO3. The book gives a compact introduction to the Extbase framework and to the Fluid template engine.

We are aiming at the following target audiences.

  • Beginners, who want to use Extbase and Fluid right from scratch as a basis for their own extensions.
  • Experienced developers, who want to learn Extbase and Fluid before beginning a new project.
  • Deciders, who want to gain a technical overview of the new framework.

Structure of this book

This chapter is structured into ten sub chapters and three appendices. The chapters discuss the following topics:

Chapter 1, Installation, leads you through the installation of Extbase and Fluid. To make extension development as effective as possible, we give suggestions for development environments as well as tips and tricks for debugging.

Chapter 2, Basic principles, begins with an overview of the concepts of object oriented programming, which are essential for working with Extbase. After that, we dive into Domain-Driven Design, a programming paradigm which is a core principle of Extbase. After that, you'll learn the design pattern Model-View-Controller, which is the technical basis of every Extbase Extension. Finally, the chapter explains Test-Driven Development to the reader.

Chapter 3, Journey through the Blog Example, should give you a feeling how the concepts from chapter 2 are implemented in Extbase. Based on a provided example extension, we explain how a blog post is created and progresses through the different system stages until it is displayed.

In chapter 4, Creating a first extension, we show you a minimal extension. With this extension, data is managed through the TYPO3 backend and displayed in the Frontend.

Chapter 5, Modeling the Domain, shows Domain-Driven Design with a practical example. It shows how a model can be planned and implemented.

Once the domain model is finished, the necessary TYPO3 infrastructure must be created: database tables and backend editing forms. The relevant information is explained in chapter 6, Setting up the persistence layer.

After chapters 5 and 6 have explained the model layer in detail, we focus on the application flow of the extension in chapter 7, Controlling the flow with controllers. These are implemented in the controller layer.

Next, the book explains the output layer of the extension: the so-called view. In chapter 8, Styling the output with Fluid, Fluid is explained in detail and its function is shown through several examples. At the end of the chapter, the sample functions are combined and demonstrated within the example extension.

Chapter 9, Internationalization, validation and security, deals with advanced topics and tasks. This includes the multilingual capabilities of extensions, the validation of data, and the handling of security aspects.

Chapter 10, Outlook, gives a glimpse into code which is currently being developed. The focus lies on the kickstarter and the use of Extbase in the TYPO3 Backend.

Extbase mostly uses the conventions of FLOW. In Appendix A, Coding Guidelines, they are summarized.

Appendix B, Reference of Extbase, contains an overview of important Extbase concepts and an alphabetical listing of the API.

In Appendix C, Reference of Fluid, you can find a reference of all standard Fluid ViewHelpers and of the API which is needed to create your own ViewHelpers.

Typographic conventions

This book uses the following typographic conventions:

File names and directories

emphasized content

class names,

method names,

inline code or, better: inline code

Substituted text here... is used to have certain code part automatically replaced.

Note

This stands for a general advice or hint.

Tip

This stands for a tip or a suggestion.

Warning

With this symbol, certain special behavior is explained, which could lead to problems or impose a risk.

Each and every reST file of *.rst should include Includes.txt at the very beginning. Specify the relative path.

Headlines in the reST source look nicer when the punctuation lines are of the same length as the text.

To switch the default highlighting use the 'highlight' directive:

.. highlight:: php
.. highlight:: javascript
.. highlight:: typoscript

Thanks

from Jochen and Sebastian

Above all, we want to thank our families and partners for their understanding and their support when we spent our afternoons and evenings with the book instead of with them. Also, our customers had to be patient from time to time, when we developed Extbase or wrote the book instead of working on their project.

TYPO3 would not exist without the dedication and vision of Kasper Skårhøj and without the tireless work of all community members, especially the TYPO3 core team. Here, we specifically want to thank the members who discovered and implemented many future-proof technologies for TYPO3 v6. Extbase would not be possible without this inspiration and these guidelines.

Also when creating the book, we had generous support: a thank you goes to Patrick Lobacher, as he wrote the section about Object Oriented Programming.

Our special thanks goes to our editors, Alexandra Follenius and Inken Kiupel, who gave us a lot of feedback and comments to our texts, and thus had a great impact on the creating of this book. Also we want to thank the many unknown helping hands at O'Reilly, who ultimately created this book.

Additionally, you are right now reading the English version of this book, so we want to thank our translators for the dedication and work they have put into this project over the last months!

Last but not least, we want to thank you: That you want to use Extbase and Fluid!

Introduction

TYPO3 is a powerful and mature content management system containing many features and a large degree of flexibility. However, the architecture of version 4 and the programming techniques which form the basis of it were the state of the art around the year 2000. That's why in the year 2006, after a thorough analysis of the current status, the decision was made to rewrite TYPO3 from scratch. A separation into a framework part and the real content management system TYPO3 seemed to make sense. Today, the framework FLOW is used and TYPO3 CMS v6 has been widely adopted. The birth of Extbase and Fluid also took place in this phase of re-orientation.

Extbase is a PHP-based framework which supports developers in creating clean and easily maintainable TYPO3 extensions. The template engine Fluid makes sure that the user interface of the extension can easily be created individually.

Extbase ensures a clear separation between different concerns, which makes maintenance a lot more simple thanks to modular code. Because of this modular design, the development time and associated costs are reduced for initial development and for adjustments. Extbase also lessens the burden on the developer when it comes to security-relevant and repetitive tasks: for example the validation of arguments, the persistence of data, and access to TypoScript and FlexForm settings. Developers can therefore focus on solving the problems of their clients much more efficiently.

Because of the modern architecture and the usage of up-to-date software development paradigms, using Extbase needs different knowledge than before. Instead of "hacking together" an extension, programmers now must understand some concepts like Domain-Driven Design, whilst planning and modeling the extension more thoroughly before implementation. In return, the source code of the extension becomes a lot more readable, more flexible and more extensible. Moreover, the concepts are also applicable within other programming languages and frameworks, as development paradigms like Domain-Driven Design or Model-View-Controller architecture can be used universally.

We hope that Extbase and Fluid are the introduction to a completely new world of programming for you. You will learn a lot of new concepts but over time, you will notice that Extbase and Fluid makes you a lot more productive. With a bit of luck, you'll hopefully get into the "flow" which drove us while we developed Extbase and Fluid.

A short history of Extbase and Fluid

After the implementation of NEOS (formerly TYPO3 v5) and FLOW (formerly FLOW3) as the basis framework started, the development of TYPO3 v4 and NEOS was happening almost completely independent from each other. In October 2008, the core developers of both branches met for the TYPO3 Transition Days in Berlin. There, the developers wanted to work on a common vision and strategy for the transition from TYPO3 version 4 to the coming version 5. The core points of this strategy were communicated as a manifesto (see the box "The Berlin Manifesto").

With the background of this manifesto, the decision was made to re-implement two parts of TYPO3 v4:

  • A modern successor for the base class tslib_piBase, on which by now the majority of the 3600 extensions for TYPO3 builds on. From there, Extbase emerged.
  • A new template engine for outputting data, which connects flexibility, ease of use and extensibility: Fluid.

Discussions about the new template engine started already on the Transition Days, where Bastian Waidelich and Sebastian Kurfürst discussed how such a new template engine should work and behave. Shortly after, a prototype was implemented. The development of Extbase began two months later, when a few core members met in Karlsruhe. There, they agreed on staying as close as possible to the concepts, the architecture and the APIs of FLOW.

After that followed an intensive development phase, where Jochen Rau developed the biggest part of Extbase, while Sebastian Kurfürst did code reviews and gave feedback. Additionally, Fluid has reached the beta stage in that time.

The first public presentation of Extbase happened in March 2009 at the T3BOARD09 in Laax (CH). With stormy weather, 2228 m over sea level, core developers and interested people could see the current state of Extbase. In a stimulating discussion, the last open topics like naming conventions and the name of the framework, were decided upon. Additionally, the decision was made to include Fluid in version 4.3, instead in version 4.4 as planned before.

In the following days, the Fluid team developed a small program (named Backporter) which could take the code of Fluid for FLOW, and transform it to code for TYPO3 v4. That's how the first working version of Fluid for TYPO3 v4 came into being at the end of the T3BOARD09. Additionally, it was now pretty easy to keep Fluid for FLOW in sync with Fluid for TYPO3 v4.

The first real presentation of Extbase and Fluid happened in April 2009 on the american TYPO3 Conference in Dallas, and a month later on the TYPO3 Developer Days in Elmshorn near Hamburg. After that, a lot of positive feedback and constructive criticism emerged from the community.

During the next months, a lot of work was done to clean up the details: The syntax of Fluid was improved, ViewHelpers were written, and the persistence layer of Extbase was improved and refactored multiple times. Functionalities for the security of the framework were implemented, the MVC framework was cleaned up, and more functionalities were implemented which were needed for practical usage.

At the T3CON09 in autumn 2009, Extbase and Fluid could be presented as a beta version. Afterwards, only errors were corrected, but no new functionalities were implemented. With the release of TYPO3 4.3.0 in November 2009, Extbase and Fluid were included in the TYPO3 core and are thus available in every TYPO3 installation.

After a well-deserved break for the development team after the release, work started again with smaller bugfixes and a roadmap for upcoming functionalities. That's how many details of the framework were improved, and the persistence layer was once again streamlined and cleaned up.

Installation

In this chapter we want to help you to set up an efficient working environment. There are some notes how to set up the development server to start with. After that there will be an explanation how to install the two TYPO3 Extensions extbase and fluid which this book is about. Furthermore we will give you some recommendations how set up your development environment (IDE), to have code completion and the IDE integrated Debugger right at your hand. A list of some more TYPO3 Extensions that might be helpful to you, will round up this chapter - including information from which sources to get them.

Tip

We assume that you already have some knowledge how to set up a TYPO3 environment and therefore we will concentrate on the installation of Extbase and Fluid and their related components.

Configuring the Server

Since TYPO3 is written in the PHP scripting language, you will need a web server like Apache with PHP. For the appropriate PHP version, look at "system requirements" at the TYPO3 download page. (https://get.typo3.org/) Additionally TYPO3 requires a SQL database for data storage. If you don't have a local development server yet, we recommend the XAMPP package (http://www.apachefriends.org/xampp.html). It will install Apache, PHP, MySQL and other useful tools on all established operating systems (Linux, Windows, Mac OS X). Now you can install TYPO3 on your test system.

For production systems you are advised to enable the PHP Opcode Cache.

Configuring your IDE

An Extension based on Extbase consists of many files, so it is helpful to use a PHP development environment (IDE) instead of a simple editor. Along with syntax highlighting, an IDE offers code completion and a direct view of the code documentation. Some development environments also have an integrated debugger, which makes detecting errors easier and faster. To give you an example we'll show you how to set up NetBeans and Eclipse for Extension development. Both IDEs have comparable functionalities, so it depends on your personal preferences which one you should use.

In general you should create projects for Extbase and Fluid in NetBeans, Eclipse or PhpStorm. You will be able to have a look at the Extbase and Fluid source code and the documentation whenever you need it.

Using NetBeans, in the File menu select New Project and choose PHP as category and then the entry PHP Application with Existing Sources. On the next page of the wizard you can select the Extension folder of Fluid or Extbase. If you use the development version of Extbase or Fluid you should select the directory /path-to-your-typo3-installation/typo3conf/ext/extbase/ (or .../fluid/). If you want to use the version shipped with TYPO3, you'll find it at /path-to-your-typo3-installation/typo3/sysext/extbase/ (or .../fluid/).

Tip

By default NetBeans uses space chars for code indentation. And also the TYPO3 Coding Guidelines follow the PSR-2-standard and demand tabs for indentation. Make sure to configure NetBeans accordingly. Open the preferences dialog of NetBeans and choose the entry Editor. Now, in the section Formatting, make sure the option Expand Tabs to Spaces is activated and adjust the options Number of Spaces per Indent and Tab Size to the same values (e.g. 4).

In Eclipse creating projects for Extbase and Fluid will work like this: Click on FileNew Project and choose Create project from existing source. Then choose the according folder for Extbase or Fluid and provide a name for the project. Click on Finish to create the project with your settings.

In PhpStorm creating projects for Extbase and Fluid will also work like this: Click on FileNew Project from Existing Files... and follow the create project wizard.

When developing an Extension by yourself you should also create a dedicated project for it in NetBeans, Eclipse or PhpStorm.

Extbase and Fluid Autocompletion

When developing your own Extensions you'll often work with classes of Extbase and Fluid. That's why it helps to set up the autocompletion feature. It will enable suggestions for complete class names as soon as you press Ctrl + Space (see fig. 1-2). To activate this functionality you have to configure the project you're currently developing, making it depend on Extbase and Fluid. After that the IDE will activate autocompletion for Extbase and Fluid classes.

_images/figure-1-2.png

Figure 1-2: The autocompletion feature will show you possible class names and their code documentation.

In NetBeans right-click your Extension project and choose Properties in the opened context menu to edit the project properties. Select the category PHP Include Path and use Add Folder... to add the directories of Extbase and Fluid.

In Eclipse this works pretty similar. Right-click the project in which you want to enable the autocompletion feature and select Properties. Now choose the category PHP include Path. Now click on Projects, because you want to create a reference to another Eclipse project. Click on the Add... button and choose the Extbase and Fluid Projects that you've created before.

Debugging with Xdebug

The usage of debuggers in other programming languages is default while it is special in PHP: The most PHP developer maintain bug fixing with e.g., echo and var_dump in the source code for understanding the routine. Though it's helpful to use a real debugger to comprehend errors because you can go through the source code step-by-step and look inside variables.

For debugging you need the PHP extension Xdebug (http://xdebug.org). It changes the PHP configuration that you can run it step-by-step and you can inspect every variable. The development tools NetBeans and Eclipse can connect to Xdebug and enable graphical output.

But back to Xdebug: You can install this PHP extension in different ways. We advise to install it over the packet management (like APT for Debian/Ubuntu or MacPorts for Mac OS X) or you install it in PECL (PHP Extension Community Library). The last method can be run with the following command as user root:

pecl install xdebug

After a successful installation you have to introduce Xdebug to PHP. For that add the following line to the php.ini:

zend_extension="/usr/local/php/modules/xdebug.so"

In this process you have to declare the correct path to the file xdebug.so.

Tip

For Windows you can download an already compiled version of Xdebug from the Xdebug website.

After a restart of Apache you can find an xdebug section in phpinfo(). For the cooperation of Xdebug with Eclipse or NetBeans you have to configure it. For that you have to set the following lines in php.ini:

xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=9000

Again after a restart of Apache this options can be found in phpinfo(). Now you also have to configure the development environment for a correct start of Xdebug. In NetBeans you have to open the properties of a project by clicking with the right mouse button on the project and choose properties. Now you must change the run configuration: Declared the base URL as project URL in which your TYPO3 frontend of your test system is available, eg. http://localhost/typo3v4. Click to Advanced... to set the Debug URL into the settings to Ask Every Time. NetBeans is now ready for debugging.

You can set so-called breakpoints in the source code of projects. At these points your program stops and you can check the program state, so you can investigate variables or continue step-by-step. If you want to set a breakpoint, click on the numbering in front of a line of the accordingly source code file. This line is marked red.

To run the debugging you have to select Debug -> Debug Project. It will open a pop-up window in which you have to enter the running URL. It's advised to copy these URL from the browser. For example if you want to debug the blog example: First open the blog module in the TYPO3 backend, second click with the right mouse button to the content frame and select current frame -> open frame in new tab. Copy the URL of this frame and paste it into NetBeans. It will open then the URL in a browser and you can see how the page looks like. If you are going back to NetBeans the debug mode is on and the run stops at the first breakpoint. Now you can investigate variables and continue step-by-step and hope to find bug quicker.

Tip

If you deactivate the setting PHP -> Debugging -> Stop at First Line in NetBeans the run stops only at the set breakpoint.

Now let's look how to configure the settings in Eclipse. First you have to make sure in the Eclipse setting that in the section PHP->*PHP Servers* the entry is set for http://localhost. In PHP->*Debug* the entry for PHP Debugger must be set to XDebug and in Server the setting is set to the server for localhost. We advise here to deactivate Break at First Line so that Eclipse/NetBeans stops only at the set breakpoint.

You can set a breakpoint in the source code of your application if you double click with your left mouse button in the editor at the required code section. Start the debugger with the right mouse button on the file which should get debugged and select Debug as->*PHP Web Page*. In the opening dialog window, enter the URL to be called which can be found as described above.

Now change the look in Eclipse/NetBeans to PHP debug and you can start with debugging in your application. Also you can investigate variables or run the program step-by-step. If you want to stop debugging you have to select in the menu Run->*Terminate*.

Tip

You will realize at the second start you don't have to enter the URL. This is because Eclipse creates the so-called Debug Configuration at the first start and stored also the URL. If you want to change the URL you can set it new in the menu under Run->*Debug Configuration*.

Debugging with IDE is a quick and efficient method to find bugs. Even if the debugger for PHP is not as sophisticated as their counterparts in Java it helps in troubleshooting.

Extension Builder Installation

The Extension Builder (extkey: extension_builder) helps you build and manage your Extbase-based TYPO3 CMS extensions.

To install this extension open the terminal, go to the directory typo3conf/ext/ and enter:

git clone https://github.com/FriendsOfTYPO3/extension_builder.git

Then you can use the Extension Manager to install the Extension Builder.

You can also install this extension via Composer (if you already used Composer to setup your TYPO3 installation):

composer require friendsoftypo3/extension-builder
./vendor/bin/typo3 extension:activate extension_builder

See Extension Installation in "TYPO3 Explained" for more information about installing extensions and Creating an Extension With the Extension Builder for more information about the Extension Builder.

Basic Principles

TYPO3 comes with an impressive variety of available extensions. As usual with Open Source projects, these extensions have been written by various programmers. Extensions are used in all sorts of projects: some are written for use in small organizations or even in private, others are developed in big teams in the context of major projects. As a newbie writing your first extension, you may struggle with some first-time problems concerning TYPO3, as many big projects are based on homemade Frameworks. So style and architecture of today's extensions are quite heterogeneous. Hence, it is often very difficult to extend or modify existing extensions for your own projects. Before you can do that, you will have to wrap your head around the development style of the respective author or team of authors.

It's one of the aims of Extbase to reduce this inconsistency in extensions. Approved paradigms of programming lead to fast success for newbies and protect developers from having to deal with complex database queries or potential security holes like SQL-Injections or XSS-Attacks. Using Extbase, small Extensions as well as big projects can be realized in a well-structured way.

Extbase is based on four interconnected and complementary paradigms, which we would like to present in this chapter. You'll encounter these during the whole project cycle, from planning to realization and maintenance of your extension:

  • Object-Oriented Programming (OOP): describes how to encapsulate associated real world aspects to abstract objects in a software
  • Domain-Driven Design (DDD): The goal of this approach is to transcribe terms, rules and actions of the problem at hand in an adequate way.
  • Model-View-Controller (MVC): This programming paradigm leads to a clear isolation of data, control of actions, and logic of interaction.
  • Test-Driven Development (TDD): This approach is a basic technique for generating code which is stable, resilient to errors and legible - and therefore maintainable.

Each of these four paradigms are well known in professional software development and more or less widespread. This results in a big advantage when using Extbase. Until now an expert in developing TYPO3 extensions was mainly an expert in the usage (and bypassing) of the application programming interface (API) of TYPO3. In contrast, Extbase will demand additional knowledge and skills in domains that are useful and effective far beyond the TYPO3 Community. Thus, you can rely on an extensive record of experience in the form of books, forums or personal contacts – an important aspect of the future of TYPO3.

Knowledge in Object-Oriented Programming, Domain-Driven Design and the MVC-paradigm is essential for working with extbase. Knowledge of Test-Driven Development is not absolutely necessary for understanding nor using extbase. Nevertheless we would like to warmly recommend this development technique.

Object-oriented programming in PHP

Object-oriented programming is a Programming Paradigm versatilely applied in extbase and the extensions built on it. In this section we will give an overview of the basic concepts of Object Orientation.

Programs have a certain purpose which is, generally speaking, to solve a problem. "Problem" does not necessarily mean error or defect but rather an actual task. This Problem usually has a concrete counterpart in real life.

A Program could for example take care of the task of booking a cruise in the Indian Ocean. If so we obviously have a problem (a programmer that has been working too much and finally decided to go on vacation) and a program promising recuperation by booking a cabin on one of the luxury liners for him and his wife.

Object Orientation assumes that a concrete problem is to be solved by a program and a concrete problem is caused by real Objects. Therefore focus is on the Object. This can be abstract of course: it will not be something as concrete as a car or a ship all the time but can also be a reservation, an account or a graphical symbol.

Objects are "containers" for data and corresponding functionality. The data of an object is stored in its Properties. The functionality is provided by Methods which can for example alter the Properties of the Object. In regard to the cruise liner we can say that it has a certain amount of cabins, a length and width and a maximum speed. Further it has Methods to start the motor (and hopefully to stop it again also), change the direction as well as to increase thrust so you can reach your holiday destination a bit faster.

Why Object Orientation after all?

Surely some users will ask themselves why they should develop object orientated in the first place. Why not (just like until now) keep on developing procedural thus stringing together functions. If we look at the roughly 4.300 extensions available for TYPO3 at the moment, we'll see that they are built with a class by default - but have been completed by the extension developer in a procedural way in about 95% of all cases. Procedural programming has some severe disadvantages though:

  • Properties and Methods belonging together with regard to content can't be united. This methodology, called Encapsulation in Object Orientation, is necessary if only for clear arrangement.
  • It is rather difficult to re-use code
  • All Properties can be altered everywhere throughout the code. This leads to hard-to-find errors.
  • Procedural code gets confusing easily. This is called Spaghetti code.

Furthermore Object Orientation mirrors the real world: Real Objects exist and they all have properties and (most of them) methods. This fact is now represented in programming.

In the following we'll talk about the object Ship. We'll invoke this object, stock it with cabins, a motor and other useful stuff. Furthermore there will be functions moving the ship thus turning the motor on and off. Later we'll even create a luxury liner based on the general ship and equip it with a golf simulator and satellite TV.

On the following pages we'll try to be as graphic as possible (but still semantically correct) to familiarize you with object orientation. There is a specific reason: The more you can identify with the Object and its Methods, the more open you'll be for the Theory behind Object Orientated Programming. Both are necessary for successful programming – even though you'll often not be able to imagine the objects you'll later work with as clearly as in our examples.

Classes and Objects

Let's now take a step back and imagine there's a blueprint for ships in general. We now focus not on the ship but this blueprint. It is called a Class, in this case it is the Class Ship. In PHP this is written as follows:

class Ship
{
   // …
}

Tip

In this piece of code note that we kept the necessary PHP tags at the beginning and end. We will spare them in the following examples to make the listings a bit shorter.

The key word class opens the Class and inside the curly brackets Properties and Methods are written. we'll now add these Properties and Methods:

class Ship
{

   public $name;

   public $cabins;

   public $engineStatus;

   public $speed;

   function startEngine()
   {

   }

   function stopEngine()
   {

   }

   function moveTo($location)
   {

   }
}

Our ship now has a name ($name), a number of cabins ($cabins) and a speed ($speed). In addition we built in a variable containing the status of the engine ($engineStatus). A real ship, of course, has much more properties all important somehow - for our abstraction these few will be sufficient though. We'll focus on why every Property is marked with the key word public further down.

Tip

For Methods and Properties we use a notation called lowerCamelCase: The first letter is lower case and all other parts are added without blank or underscore in upper case. This is a convention used in Extbase.

We can also switch on the engine (startEngine()), travel with the ship to the desired destination (moveTo($location)) and switch off the engine again (stopEngine()). Note that all Methods are empty, i.e. we have no content at all. We'll change this in the following examples, of course. The line containing the Method name and (if available) parameters is called the Method signature or method head. Everything contained by the Method is called the Method body accordingly.

Now we will finally create an Object from our Class. The Class ship will be the blueprint and $fidelio the concrete Object.

$fidelio = new Ship();

// Display the Object

var_dump($fidelio);

The key word new is used to create a concrete Object from the Class. This Object is also called an Instance and the creation process consequentially Instantiation. We can use the command var_dump() to closely examine the object. We'll see the following:

object(Ship)#1 (3) {

   ["name"] => NULL

   ["cabins"] => NULL

   ["engineStatus"] => NULL

   ["speed"] => NULL

}

We can clearly see that our Object has 4 Properties with a concrete value, at the moment still NULL, for we did not yet assign anything. We can instantiate as many Objects from a class as we like and every single one will differ from the others – even if all of the Properties have the same values.

$fidelio1 = new Ship();
$fidelio2 = new Ship();

if ($fidelio1 === $fidelio2) {
   echo 'Objects are identical!'
} else {
   echo 'Objects are not identical!'
}

In this example the output is Objects are not identical!

The arrow operator

We are able to create an Object now but of course it's Properties are still empty. We'll hurry to change this by assigning values to the Properties. For this we use a special operator, the so called arrow operator (->). We can use it for getting access to the properties of an Object or calling Methods. In the following example we set the name of the ship and call some Methods:

$ship = new Ship();

$ship->name = 'FIDELIO';

echo 'The ship\'s Name is ' . $ship->name;

$ship->startEngine();

$ship->moveTo('Bahamas');

$ship->stopEngine();

$this

Using the arrow operator we can now comfortably access Properties and Methods of an Object. But what if we want to do this from inside a Method, e.g. to set $speed inside of the Method startEngine()? We don't know at this point what an object to be instantiated later will be called. So we need a mechanism to do this independent from the name. This is done with the special variable $this.

class Ship
{
   public $speed;

   function startEngine()
   {
      $this->speed = 200;
   }
}

With $this->speed you can access the Property "speed" in the actual Object independently of it's name.

Constructor

It can be very useful to initialize an Object at the Moment of instantiating it. Surely there will be a certain number of cabins built in right away when a new cruise liner is created - so that the future guest will not be forced to sleep in emergency accommodation. So we can define the number of cabins right when instantiating. The processing of the given value is done in a Method automatically called on creation of an Object, the so called Constructor. This special Method always has the name __construct() (the first two characters are underscores).

The values received from instantiating are now passed on to the constructor as Argument and then assigned to the Properties $cabins and $name respectively.

class Ship
{
   public $name;
   public $cabins;
   public $speed;

   function __construct($name, $numberOfCabins)
   {
      $this->name = $name;
      $this->cabins = $numberOfCabins;
      echo 'The new ship has the name: ' . $this->name;
      echo '<br />and has ' . $this->cabins . ' cabins';
   }
}

$fidelio = new Ship('Fidelio', 200);

Inheritance of Classes

With the class we created we can already do a lot. We can create many ships and send them to the oceans of the world. But of course the shipping company always works on improving the offer of cruise liners. Increasingly big and beautiful ships are built. Also new offers for the passengers are added. FIDELIO2, for example, even has a little golf course based on deck.

If we look behind the curtain of this new luxury liner though, we find that the shipping company only took a ship type FIDELIO and altered it a bit. The basis is the same. Therefore it makes no sense to completely redefine the new ship – instead we use the old definition and just add the golf course – just as the shipping company did. Technically speaking we extend an "old" Class definition by using the key word extends.

class LuxuryLiner extends Ship
{
   public $luxuryCabins;

   function golfSimulatorStart()
   {
      echo 'Golf simulator on ship ' . $this->name . ' started.';
   }

   function golfSimulatorStop()
   {
      echo 'Golf simulator on ship ' . $this->name . ' stopped.';
   }
}

$luxuryShip = new LuxuryLiner('FIDELIO2','600')

Our new luxury liner comes into existence as easy as that. We define that the luxury liner just extends the Definition of the class Ship. The extended class (in our example Ship) is called the parent class or superclass. The class formed by Extension (in our example LuxuryLiner) is called the child class or sub class.

The class LuxuryLiner now contains the complete configuration of the base class Ship (including all Properties and Methods) and defines additional Properties (like the amount of luxury cabins in $luxuryCabins) and additional Methods (like golfSimulatorStart() and golfSimulatorStop()). Inside these Methods you can again access the Properties and Methods of the parent class by using $this.

Overriding Properties and Methods

Inside an inherited class you can not only access Properties and Methods of the parent class or define new ones. It's even possible to override the original Properties and Methods. This can be very useful, e.g. for giving a Method of a child class a new functionality. Let's have a look at the Method startEngine() for example:

class Ship
{
   $engineStatus = 'OFF';

   function startEngine()
   {
      $this->engineStatus = 'ON';
   }
}

class LuxuryLiner extends Ship
{
   $additionalEngineStatus = 'OFF';

   function startEngine()
   {
      $this->engineStatus = 'ON';
      $this->additionalEngineStatus = 'ON';
   }
}

Our luxury liner (of course) has an additional motor so this has to be switched on also if the Method startEngine() is called. The child class now overrides the Method of the parent class and so only the Method startEngine() of the child class is called.

Access to the parent class through "parent"

Overriding a Method comes in handy but has a serious disadvantage. When changing the Method startEngine() in the parent class, we'd also have to change the Method in the child class. This is not only a source for errors but also kind of inconvenient. It would be better to just call the Method of the parent class and then add additional code before or after the call. That's exactly what can be done by using the key word parent. With parent::methodname() the Method of the parent class can be accessed comfortably - so our former example can be re-written in a smarter way:

class Ship
{
   $engineStatus = 'OFF';

   function startEngine()
   {
      $this->engineStatus = 'ON';
   }
}

class LuxuryLiner extends Ship
{
   $additionalEngineStatus = 'OFF';

   function startEngine()
   {
      parent::startEngine();
      $this->additionalEngineStatus = 'ON';
   }
}

Abstract classes

Sometimes it is useful to define "placeholder Methods" in the parent class which are filled in the child class. These "placeholders" are called abstract Methods. A class containing abstract Methods is called abstract Class. For our ship there could be a Method setupCabins(). Each type of ship is to be handled differently for each has a proper configuration. So each ship must have such a Method but the concrete implementation is to be done separately for each ship type.

abstract class Ship
{
   function __construct()
   {
      $this->setupCabins();
   }

   abstract function setupCabins();
}

class LuxuryLiner extends Ship
{
   function setupCabins()
   {
      echo 'Setting up cabins';
   }
}

$luxuryLiner = new LuxuryLiner();

In the parent class we have defined only the body of the Method setupCabins(). The key word abstract makes sure that the Method must be implemented in the child class. So using abstract classes we can define which Methods have to be present later without having to implement them right away.

Interfaces

Interfaces are a special case of abstract classes in which all Methods are abstract. Using Interfaces, specification and implementation of functionality can be kept apart. In our cruise example we have some ships supporting satellite TV and some who don't. The ships who do have the Methods enableTV() and disableTV(). It is useful to define an interface for that:

interface SatelliteTV
{
   public function enableTV();
   public function disableTV();
}

class LuxuryLiner extends Ship implements SatelliteTV
{

   protected $tvEnabled = FALSE;

   public function enableTV()
   {
      $this->tvEnabled = TRUE;
   }

   public function disableTV()
   {
      $this->tvEnabled = FALSE;
   }
}

Using the key word implements it is made sure that the class implements the given interface. All Methods in the interface definition then have to be realized. The object LuxuryLiner now is of the type Ship but also of the type SatelliteTV. It is also possible to implement not only one interface class but multiple separated by comma. Of course interfaces can also be inherited by other interfaces.

Visibilities: public, private and protected

Access to Properties and Methods can be restricted by different visibilities to hide implementation details of a class. The meaning of a class can be communicated better like this, for implementation details in internal Methods can not be accessed from outside. The following visibilities exist:

  • public: Properties and Methods with this visibility can be accessed from outside the Object. If no Visibility is defined, the behavior of public is used.
  • protected: Properties and Methods with visibility protected can only be accessed from inside the class and it's child classes.
  • private: Properties and Methods set to private can only be accessed from inside the class itself, not from child classes.

Access to Properties

This small example demonstrates how to work with protected properties:

abstract class Ship
{
   protected $cabins;

   abstract protected function setupCabins();
}

class LuxuryLiner extends Ship
{
   protected function setupCabins()
   {
      $this->cabins = 300;
   }
}

$LuxuryLiner = new LuxuryLiner('Fidelio', 100);
echo 'Amount of cabins: ' . $LuxuryLiner->cabins; // Does not work!

The LuxuryLiner may alter the property cabins, for this is protected. If it was private no access from inside of the child class would be possible. Access from outside of the hierarchy of inheritance (like in the last line of the example) is not possible. It would only be possible if the Property was public.

We recommend to define all Properties as protected. Like that they can not be altered any more from outside and you should use special Methods (called getter and setter) to alter or read them. We'll explain the use of these Methods in the following section.

Access to Methods

All Methods the Object makes available to the outside have to be defined as public. All Methods containing implementation details, e.g. setupCabins() in the above example, should be defined as protected. The visibility private should be used most rarely, for it prevents Methods from being overwritten or extended.

Often you'll have to read or set Properties of an Object from outside. So you'll need special Methods that are able to set or get a property. These Methods are called setter respectively getter. See the example.

class Ship
{
   protected $cabins;
   protected $classification = 'NORMAL';

   public function getCabins()
   {
      return $this->cabins;
   }

   public function setCabins($numberOfCabins)
   {
      if ($numberOfCabins > 500) {
         $this->classification = 'LARGE';
      } else {
         $this->classification = 'NORMAL';
      }
      $this->cabins = $numberOfCabins;
   }

   public function getClassification()
   {
      return $this->classification;
   }
}

We now have a Method setCabins() which sets the number of cabins. Furthermore it changes - depending on the number of cabins - the ship category. You now see the advantage: When using Methods to get and set the Properties, you can perform more complex operations, as e.g. setting of dependent Properties. This preserves consistency of the object. If you set $cabins and $classification to public, we could set the number of cabins to 1000 and classification to NORMAL - and our ship would end up being inconsistent.

Tip

In Extbase you'll find getter and setter Methods all over. No Property in extbase is set to public.

Static Methods and Properties

Until now we worked with Objects instantiated from classes. Sometimes though it does not make sense to generate a complete object just to be able to use a function of a class. For this php offers the possibility to directly access Properties and Methods. These are then referred to as static Properties respectively static Methods. Take as a rule of thumb: static Properties are necessary every time two instances of a class are to have a common Property. Static Methods are often used for function libraries.

Transferred to our example this means that all ships are constructed by the same shipyard. In case of technical emergency all ships need to know the actual emergency phone number of this shipyard. So we save this number in a static Property $shipyardSupportTelephoneNumber:

class LuxuryLiner extends Ship
{
   protected static $shipyardSupportTelephoneNumber = '+49 30 123456';

   public function reportTechnicalProblem()
   {
      echo 'There is a problem with the ship ' . $this->name . '.
            Please call ' . self::$shipyardSupportTelephoneNumber;
   }

   public static function setShipyardSupportTelephoneNumber($newNumber)
   {
      self::$shipyardSupportTelephoneNumber = $newNumber;
   }
}

$fidelio = new LuxuryLiner('Fidelio', 100);
$figaro = new LuxuryLiner('Figaro', 200);

$fidelio->reportTechnicalProblem();
$figaro->reportTechnicalProblem();

LuxuryLiner::setShipyardSupportTelephoneNumber('+01 1000');

$fidelio->reportTechnicalProblem();
$figaro->reportTechnicalProblem();

// The following text will be printed:
There is a problem with the ship Fidelio. Please call +49 30 123456
There is a problem with the ship Figaro. Please call +49 30 123456
There is a problem with the ship Fidelio. Please call +01 1000
There is a problem with the ship Figaro. Please call +01 1000

What happens here? We instantiate two different ships which both have a problem and do contact the shipyard. Inside the method reportTechnicalProblem() you see that if you want to use static properties you have to trigger them with the key word self::. If the emergency phone number now changes, the shipyard has to tell all the ships about the new number. For this is uses the static method setShipyardSupportTelephoneNumber($newNumber). For the Method is static, it is called through the scheme classname::methodname(), in our case LuxuryLiner::setShipyardSupportTelephoneNumber(...). If you check the latter two problem reports you see that all instances of the class use the new phone number. So both ship objects have access to the same static variable $shipyardSupportTelephoneNumber.

Important design- and architectural patterns

In software engineering you'll sooner or later stumble upon design problems that are connatural and solved in a similar way. Clever people thought about design patterns aiming to be a general solution to a problem. Each design pattern is so to speak a solution template for a specific problem. We by now have multiple design patterns that are successfully approved in practice and therefore have found there way in modern programming and especially extbase. In the following we don't want to focus on concrete implementation of the design patterns, for this knowledge is not necessary for the usage of extbase. Nevertheless deeper knowledge in design patterns in general is indispensable for modern programming style, so it might be fruitful for you to learn about them

Tip

Further information about design patterns can e.g. be found on <link linkend="???">http://sourcemaking.com/</link> or in the book PHP Design Patterns by Stephan Schmidt, published by O'Reilly.

From the big number of design patterns, we will have a closer look on two that are essential when programming with extbase: Singleton &amp; Prototype.

Singleton

This design pattern makes sure that only one instance of a class can exist at a time. In TYPO3 you can mark a class as singleton by letting it implement the interface \TYPO3\CMS\Core\SingletonInterface. An example: our luxury liners are all constructed in the same shipyard. So there is no sense in having more than one instance of the shipyard object:

use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class LuxuryLinerShipyard implements SingletonInterface
{
   protected $numberOfShipsBuilt = 0;

   public function getNumberOfShipsBuilt()
   {
      return $this->numberOfShipsBuilt;
   }

   public function buildShip()
   {
      $this->numberOfShipsBuilt++;
   }
}

$LuxuryLinerShipyard = GeneralUtility::makeInstance(LuxuryLinerShipyard::class);
$LuxuryLinerShipyard->buildShip();

$theSameLuxuryLinerShipyard = GeneralUtility::makeInstance(LuxuryLinerShipyard::class);
$theSameLuxuryLinerShipyard->buildShip();

echo $LuxuryLinerShipyard->getNumberOfShipsBuilt(); // 2
echo $theSameLuxuryLinerShipyard->getNumberOfShipsBuilt(); // 2

In order to have the singletons correctly created you have to use the ObjectManager.

Prototype

Prototype is sort of the antagonist to Singleton. While for each class only one object is instantiated when using Singleton, it is explicitly allowed to have multiple instances when using Prototype. Each class not implementing the Interface \TYPO3\CMS\Core\SingletonInterface automatically is of the type Prototype.

Tip

Originally for the design pattern Prototype is specified that a new Object is to be created by cloning an Object prototype. We use Prototype as counterpart to Singleton without a concrete pattern implementation in the background though. For the functionality we experience, this does not make any difference: We invariably get back a new instance of a class.

Now that we refreshed your knowledge of object oriented programming we can take a look at the deeper concepts of Extbase: Domain Driven Design, Model View Controller and Test Driven Development. You'll spot the basics we just talked about in the following frequently.

Domain-Driven Design

Software development is a creative process. You do not stand on an assembly line, but are increasingly exposed to new challenges. Each software differs from each other, and every time you start with a new program you start from scratch. You have to find out, what your client wants to achieve and after that you want to implement this. Though in general you have great freedom for your development misunderstandings between the customer and you as a developer can rise. There is hardly anyone who never has been working a project in which the requirements were documented and the specifications were written but still the client was not satisfied at the end or in worst case the software hasn't solved his problems at all.

If you develop software for customers, you must first understand their problems, so you can offer your customer a tailored solution. This problem area is called the application domain within the terms of domain-driven design. By knowing the application domain of customers, understanding their problems and having them clearly in mind, you are in a position to adequately implement this domain in software. Extbase supports you in supplying you with the required technical infrastructure.

Domain-Driven Design is a development paradigm that does not include technical concepts. Instead, by using different techniques, extbase tries to to direct the creativity of the software development to structured directions and thus channel it and make it more effective.

In domain-driven design your understanding of the relevant problems and their environment is the focus: a problem which the customer wishes to get resolved by your program. In close cooperation with the customer the problem is collaboratively explored. During this process, which proceeds iteratively (i.e. stepwise), you will develop a model together with the customer that represents the problem adequately. This model is the base of the generated program. By focusing on the model you are not distracted with the actual problem of the customer during prototyping - you can focus on the problem domain.

Note

The involvement of customers in the working phase is absolutely essential because only they know the problem well enough.

Domain-Driven Design is a very pragmatic approach. Yet at the beginning of a software project you are writing code to explore the problem and its dimensions that shall be solved. Often you will need a few prototypes and iterations, until you reach the final model. Take this time!

Extbase offers you a variety support of Domain-Driven Design. You do not have to care for e.g. storing data in the database. If the domain contains complex invariants (e.g. rules that must not be violated) you can implement these elegantly using so-called validators. In addition, you have fluid as a powerful templating engine for efficient output of data available. So you can focus on the problem domain, rather than investing much time in the output of the data and user interaction.

We now hope that your appetite is aroused! Below we show you some of the core concepts of the Domain-driven design. These include certain approaches such as the elaboration of a language used by all (the so-called Ubiquitous Language) or dealing with associations. Besides these we also show you the technical elements such as entities, value objects and repositories.

Develop a common language

In software projects, people are involved in different roles: the customer is an expert in his business area and has a problem he wants to get solved by a program. As he is very well acquainted with the application domain of this program, we refer to him as a so-called domain expert.

You are usually in the role of the developer: You are familiar with many technical possibilities but you do usually not know the business of the customer. Therefore, misunderstandings are inevitable, because the developer uses a very different language from the domain expert.

In Domain-driven design, important core terms that are needed for the characterization and solution of the problem should be determined. These will be compiled in the form a glossary to ensure always that the domain expert and the developer understand each other correctly.

This so-called ubiquitous language does not apply only during communication: it should be related in the source code (e.g. class or method names) as well. This enables experts to capture the problems within the domain on the one hand and domain expert to take decisions on the basis of the source code on the other hand on whether the business logic has been implemented correctly.

To model the domain

During your meeting with the domain expert you will start to create a model and after that you will refine it. Usually during the dialog a UML-Diagram will arise that reflects the features, functions and relationships of the relevant components of the problem area. These objects are called domain objects because of its focus on the problem domain of the user.

Domain-driven design provides some building blocks that will help you in creating a good domain model: Entities, Value Objects and Services. First, let's concentrate on the first two building blocks: Does the domain object has its own identity, which is received over time, even if the object goes through different states? Then the object is an entity. Or is it an attribute that describes other things in detail? Then the object is of type value object. Hereafter we will go into more detail on the distinction between these two types.

There are also concepts that couldn't assigned directly to be entities or value objects - this happens every time you speak of activities during the modeling phase. For this purpose the concept of services is introduced.

Entities

Entities are objects that possess a unique identity. For example, a user has an username as an identity, a product has a product number, and a student has a student number. Therefore, all examples mentioned are entities. The identity of the object remains the same over time, even if the properties of the object changes.

The identity of an object is defined by an immutable property or a combination of several immutable properties of the object. At least one property must be identity-determining. Normally extbase takes care about automatically and therefore assigns a unique value to a (hidden) identity property. You can also select this by yourself and select which combination of properties should be identity-determining.

Note

Extbase uses as an automatically generated identity property as identifier, which is generated by the underlying database (the so-called Unique identifier, UID). The word unique clearly means in this context really "unique within a database table". This will, however, possibly change in a future version of Extbase to ensure a global uniqueness.

Depending on the application, it may be useful to establish their own identity-determining properties that are important within your domain. How can I identify a human clearly? The passport number and country - taken together- perhaps are such a clear identification, but is often less practical: Who wants to give his passport number if he logs into a Web site? For an internet forum, it's practical, for example, to determine the identity of the forum member with his mail address. But if you write an e-government application, the identity card number would considered instead.

It is important that the identity-determining properties of an object are set when it is created and after that never changes: If you make these properties changeable, different identities could be converted together - but this is undesirable in most cases. Because you have to promote the new identity to all objects, which know the object under his old identity. Otherwise you will loose all connections from other objects to this object. In general, the change of identity-determining properties should be prohibited by exclusive read access to these properties.

If you have written databased applications, you may have noticed that you have often used entities subconsciously, by having provided the database tables with indexes and primary keys (in TYPO3, for example, the UID will always added as an identifier). Therefore, you may wonder why we now need a different type of object at all. In the next section we answer this question.

Value Objects

PHP offers several build-in value-types, such as integer, float or string. Often you will notice that you need domain specific values-types​​, such as colors or tags. These are represented by so-called Value Objects.

Value Objects are objects that are determined by the values ​​of their properties. Take a graphic program as an example: Somewhere it must be defined, what is meant by a "color". A color is determined only by its value, such as the three color components for red, green and blue in RGB mode. If two objects have the same RGB color values, so they are effectively the same and must not continue be distinguished.

Within value objects all properties are identity-determining. If all of the properties of two value objects have the same value, then these two objects are the same. Nevertheless, value objects are often longer more than just simple data structures for primitive data types. They can contain potentially much complex domain logic. Our color-object could - for example - use methods for converting color to other color spaces like CMYK or HSV and could include color profiles for this.

Since all properties are identity-determining and as it is not allowed to change these properties after the creation of the object, value objects are immutable. They are generated completely initialized and after that they can't change their value. You can only create a new value object and eliminate the old.

Note

Although you could provide methods for changing the internal state of a value object, it is not allowed to change the state at any time. Take the example of the color object, which could contain a new method "makeBrighter()". This method must change the color value and give back a new color object with these changed values. It must not change the existing object.

By this simple semantics value objects can easily generated, cloned, transmitted to other computers or transferred to other objects. This not only the implementation is easier, but it is also clearly communicated that these objects are just simple values​​.

In an application that optimizes the delivery of letters for the post office, addresses can be associated with other characteristics such as the name of the postman who delivered the mail. This name, however, belongs not to the identity of the object and can change over time (e.g. if a postman retired) - a clear reference to the usage of an entity.

So you see: you can not always say clearly whether objects are entities or value objects - it depends entirely on the application and the application domain.

Note

The distinction between entities and value objects will be perhaps difficult for you at the beginning and appear as an unnecessary expense. Extbase treats the two object types very different in the background. The administration of value-objects is more efficient than those of entities. The additional expenses for the administration and monitoring of the uniqueness omitted here, for example, completely.

Associations

You should never leave the implementation out of sight during the modeling. So let us talk briefly about a very complex field of implementation: associations between objects.

Domain objects are related to each other. Such relationships are called in the language of the domain with the following phrases: A "consists of" B, C 'has' D, E "processed" F, or G 'belongs to' I. These relationships are referred in abstract domain model as associations.

At a university professors and students are in relation of each other: the professor lectures and students are enrolled for classes. To reflect this relationship in our domain model, we add an association as an abstraction of the real world. Practically, this means that an professor object contains a list of pointers to the student objects, who sit with him in the lecture.

Particularly complicated to implement are many-to-many associations here, as shown in the above example, (a professor taught many students and a student is taught by various professors) - and moreover, if these associations are bidirectional. This means that the association can point from a professor to his students, but also in the other direction.

If you use many-to-many associations during the design, consider to simplify and restructure them. It is nature that you use a great number of bidirectional many-to-many associations especially at the beginning of the modeling. In the refinement of associations you can find help with the following questions:

  1. Is the many-to-many association important in the application domain?
  2. Can the association be made ​​unilaterally, as there is a main direction in which the objects are queried?
  3. Could the association be specified in greater detail, e.g. by qualifying the individual elements more closely?
  4. Is the association for the core functionality needed at all?

So remember to use very simple associations, as it is easier to implement them and they are better understandable.

Aggregates

If you build a complex domain model, you have to deal with many classes offered at the same hierarchical level. Often it is given that certain objects are part of a larger object. If you want to model an application for a garage, so maybe you have to model not only the car but the engine and the wheels too, because these are of particular importance for the garage. Intuitively, we look at the wheels and the engine of a car as part of the car, so this understanding should be visible in the model as well. We call such a relationship between closely related parts and the whole "aggregates". You see this domain model in Figure 2-1.

_images/figure-2-1.png

Figure 2-1: The domain model of an auto repair shop. Objects outside an aggregate may only reference on the aggregate root.

An aggregate possesses a root object, the so-called "aggregate root". This is responsible for the integrity of their sub-objects. Objects outside of the aggregate must only refer to the aggregate root, but never to parts of it, because otherwise the units couldn't ensure the integrity of the root objects. To the outside, the units have only an external identity: the aggregate root. As aggregate roots need an identity, which is used for referencing, their type has to be an entity.

Transferred to the car analogy it is like this: The service station may not maintain a permanent reference to the engine, but needs to remember a permanent reference to the car (e.g. by the vehicle number as the external identity). If you require a reference to engine for your work - you can reach it via the car.

Through this rules of referencing the domain will be structured further, which reduces the complexity of the application and makes it manageable.

So far we have shown how real world objects can be mapped into entities and value objects objects. However, there are concepts in the world that do not fit into this scheme. To reflect this, we introduce services.

Services

In practice there are actions while modeling an application, which could not directly assigned to certain domain objects. In object-oriented programming, you are trying to force entities or value objects to these actions, although it does not really belongs in there. To circumvent this problem, there exist so-called services. These are containers for actions that may belong to the domain of application but can't be assigned to any particular object.

A service should be stateless, ie should not use or manipulate internal states. A service should be used, without knowing its internal state to be known be taken into account. A service often receives entities or value objects as input and performs complex operations on them.

Lifecycle of objects

In the real world objects have a certain life cycle. A car is built, then it changes during its lifetime (the mileage increases, brakes are replaced, wear, ...), and at some point the car is scrapped.

Since we model a domain with domain-driven design, which has a counterpart in the real world, the life cycle of objects in our program are very similar to the objects in the real world. Objects are created at a time, then they are active and can be changed, and eventually they will be deleted. This is shown in Figure 2-2.

_images/figure-2-2.png

Figure 2-2: The life cycle of an object in the real world

We can not always keep all existing objects instantiated in memory of our program of course - our program would be unusable slow and memory-hungry (not to mention the case where the power fails and then the objects are gone).

Chapter 2.2 / page 34

So we need a way to maintain only needed objects in the memory. The active state actually consists of several sub-states, which are shown in Figure 2-3.

_images/figure-2-3.png

Figure 2-3: The life cycle of an object in extbase is more complex, because the object can be stored in the data storage (like a database).

When an object is newly created, it is transient, i.e. at the end of the current request PHP will remove the object from the memory: It will be deleted. To change object to be permanent, i.e. over multiple requests, it must be converted from a transient object to a persistent object. Repositories are responsible for this. These allow the permanent storage and retrieval of objects based on certain criteria. But how to use repositories now in practice? While you add an object to a repository, it will be persisted. Now the repository is responsible for the object. It automatically cares about the storing of an object at the end of a request.

You can also get back an object reference from the repository when you need it - the repository will reconstitute the object in this case automatically from the database.

It is important that the logical object still exists when it is stored in the database. Just for performance reasons it is not held in the memory. It is very important to distinguish between the creation of an object and the reconstitution of the object from the database. Imagine therefore that the objects continue to exist in the database, only in a different form of representation.

Note

The constructor of an object is only called when creating the object. If the object is reconstituted from the database, the constructor is not called because the object still exists logical.

You can retransform a persistent object in a transient object by explicitly removing it from the repository. This means that the repository for this object has no responsibility anymore. At the end of a request the object is then deleted.

Extbase does the work for you in terms of persistence of objects in the database as much as possible. You are not in touch with database layer directly, as Extbase cares about the whole life cycle of the objects.

Now - as you have learned many things about the life cycle of objects - we want to substantiate two pieces of the life cycle: the creation of objects and the reconstitution of objects.

Create objects with the help of factories

Now that you know the life cycle of objects more accurately, we will deal initially with the creation of objects. You are allowed to produce only self-consistent aggregates. In the car example from earlier, this means that, in preparing the car, the wheels and the engine must be created and immediately too, because the car object is in an inconsistent state otherwise.

In simple initializations, it is recommended that you use the constructor of the aggregate root for these purposes. If a complex object networks are built with many cross connections, then you should move that functionality into its own factory. This is a class that assembles complex objects and gives them back finished.

The following is an example of the initialization of the cars in the constructor of the aggregate root:

<?php
class Car
{
   protected $engine;
   protected $wheels;

   public function __construct()
   {
      $this->engine = new Engine();
      $this->wheels[0] = new Wheel();
      $this->wheels[1] = new Wheel();
      $this->wheels[2] = new Wheel();
      $this->wheels[3] = new Wheel();
   }
}

For simplicity we have omitted the base classes and the full class names to show you the essence: As the constructor is executed at creating an object, it is always built a consistent object.

Note

In TYPO3 you can not generate classes with the new operator, but with GeneralUtility::makeInstance(className). In the example above, we wanted to concentrate on the essentials, so we have used new there.

Reconstitute objects with repositories

You can imagine a repository like a library: go to the circulation desk and ask for a specific book (based on certain criteria such as the title or author). If the book is available, the librarian will get it and gives it to you. You do not have to know on which shelf the book is or whether it perhaps has to be delivered from another library for you. Now you can read the book and find perhaps a typo and correct it with a pencil. After expiry of the loan period, you have to give the book back again, and then the next person will borrow it - of course with your corrections are still exists in the book.

But how will new books get into the library? You can, for example, donate books that you have read, to the library. Here, a librarian will write the title of the book, the author and some keywords in the central library database , so the book can be found and borrowed by other users. Conversely, a book will be sorted out if it is old and broken. Of course, the entry in the library database have to be deleted, so the book can no longer be found.

With a repository, it behaves like a library.With a repository you can find objects of a certain type. If you send a query like findByTitle('Domain-Driven Design') to the BookRepository for example, you get all objects back where the title is "Domain-driven Design". If you now change a book-object (for example, by correcting a typo in the table of contents), these changes are saved automatically, and the next search operation, will return the revised object.

So how can you make a repository responsible for an object? For this the repository has the method add($object). If you want to commit a new object to the BookRepository for example, you can create it using $book = new Book('Extbase and Fluid'). A new book titled "Extbase and Fluid" can added to the BookRepository with add($book). Similarly, you can remove an object from a repository, by calling the method remove($object). Now the object is not findable in the repository anymore and therefore it is deleted from the database.

For each aggregate root exactly one repository has to exists, which is responsible for that object type and his sub objects. By using this repository, you can then locate the desired aggregate root object by different criteria. Conversely, this means: In extbase you define an object type as aggregate root object by creating a repository for this type.

We have now explained how the domain of the application can be efficiently packed into a software model. Therefore, we have explained "Domain-Driven Design" as a "toolbox" of techniques, which are supported by extbase. But an growing application consists not only of the model: presentation logic is important too. With an effective separation of model and presentation logic we will continue in the following section.

Model-View-Controller in Extbase

Object-oriented programming and Domain-Driven Design specify a structure for our Extension on different levels. Object-oriented programming provides us with the basic building blocks of software development: Objects as combination of data and associated methods. Domain Driven Design provides us tools for creating a Model that represents the real world rules in Software. However, we still lack a component that specifies how to react on user requests and what functionality the application will ultimately have. The Model View Controller Paradigm provides us exactly that. It ensures a clean separation of the Domain Model from the View. This clean Separation is the foundation of the smooth interaction between Extbase and Fluid

The Model-View-Controller design pattern is the infrastructure that we build our application on. It provides us a rough roadmap and the separation of the presentation logic from the Model.

The MVC pattern divides our application into three rough layers: the Model, which implements the Domain Model including the domain logic, the controller, which controls the flow of the application, and the view, which prepares and outputs the data to the user (see Figure 2-4).

The lowest layer, the Model encapsulates the application logic and data as well as the according access and storage logic. Domain Driven Design divides this layer even further. In a Extbase extension you can find the classes for this layer in the folder Classes/Domain.

The Controller presents externally ( that is: directly callable by the user ) functionality. It coordinates the interaction of Model and View to dispatch the actual request. It fetches Data from the Model and hands it to the View for presentation. Important: The Controller only coordinates, the actual functionality is usually implemented in the Model. Because the Controller is difficult to test, it should stay as slim as possible.

_images/figure-2-4.png

Figure 2-4: The MVC pattern divides the application into three global layers

The top layer, the View encapsulates the whole Presentation Logic and everything related with the presentation of data.

The Interaction of Model, View and Controller

The Functionality of many Applications can be split into modules. This modules can be further differentiated. The Functionality of a Blog e.g. can be split as follows:

Functionality related to Blog Posts:

  • List View of all Blog-Posts
  • Create a Blog-Post
  • Show a Blog-Post
  • Edit a Blog-Post
  • Delete a Blog-Post

Functionality related to Comments:

  • Create Comments
  • Delete Comments

These Modules are implemented in a Controller. Inside of this Controller there is a Action for every single function. In the above example we have a PostController, which implements the Actions to create, show, edit and delete Posts. In Addition there is a CommentController, which implements the Actions to create and delete Comments.

The Listing of Comments is not implemented as separate Action here, as the Comments usually should be display directly with the Blog-Post and there is no View showing all Comments.

Both Examples show that a Controller is mainly a Container for associated Actions.

Now we show by example how Model, View and Controller work together. The first request we look at should display a list of blog posts (see Figure 2-5):

The User sends a request (1). The request Object contains information about the controller and action that should be called and optionally additional parameters. In this example the Controller is Post and the action list

To respond to the request the action has to query certain data from the Model.(2) In return it receives on or several domain Objects. In our example the action queries all blog posts and receives a array with all Blog-Post Objects as response.(3)

Thereafter the action calls the according view and hands over the data for presentation (4) - the array with Blog-Posts in our case.

The View displays the data and returns the Response to the user.(5)

_images/figure-2-5.png

Figure 2-5: In this request a list of Blog-Post is displayed.

Now as the first request is completely dispatched the user has a list of all Blog-Posts displayed in the browser. Now the user clicks on a single Blog-Post and gets the complete blog post. In addition the user can add a comment to this post. With the help of Figure 2-6 we want to understand how the comment is stored.

When submitting the comment form the user creates a new request (1) containing the according controller and action. In our example the controller is Comment and the action is new. Furthermore the request contains the comment text and a reference to the commented Blog-Post.

The called action now has to modify the Model and add the new comment to the according Blog-Post. (2)

After that the action forwards to another action (3). In our case we forward to the show-Action in the PostController, which displays the Blog-Post and the freshly added comment.

Now the show-Action calls the according view and hands over the Blog-Post that should be displayed. (4)

The view now displays the data and returns the result to the user. (5)

_images/figure-2-6.png

Figure 2-6: In this request a comment is stored.

You will often see that actions can be sorted into two categories: Some actions control the display of a Model, while other actions modify the Model and usually forward to displaying actions. In the above example we first saw a displaying action and then a modifying action.

Now we have all the Modules we need for developing our Application. You got to know the Object-oriented basics, modeled the application domain with Domain Driven Design and introduced the clean separation between the Domain Model and the Presentation Logic

At last we want to introduce you to a development Model that can drastically improve the stability and quality of the source code: Test-Driven Development. This approach can be used independently of the previously introduced concepts, but is another helpful Module for the extension development with Extbase and Fluid.

Test-Driven Development

Every Developer has to test his software - for developers a not much liked theme, but one you cannot walk by. How work tests in the classic software development? Programmers in the first way construct a test case by putting certain data into the database, write little programs to test or manipulate the url parameter in the browser. A big number of little functions are often implemented at one time, before they are tested (look at figure 2-7). After that, these steps are repeated in periodic steps, as often until the function you want to have is implemented. After this the next function is in line. But this way leads to problem: the tests are not made systematic, but selective. By following this way you are implementing failures into some routines by accident. Furthermore by a test is not only a little code fragment tested, but often a more complex routine.

_images/figure-2-7.png

Figure 2-7: in the classic software development exist a strict isolation between the Development- and the Test- phase.

By using Test-Driven Development (TDD) these problems should be solved. Tests could be fast completed and reproducible. These increases the developers motivation to start the tests constantly and by this way to get faster a callback, if a failure is implemented into the existing functions.

Note

Field report

When Robert Lemke and other Core Developers suggested to make the development for FLOW test driven, I was skeptic. Test-Driven Development sounded like a nice concept, but I did not know how to test a framework this size reasonable. Also in the internet there were often only very simple academic examples to find. Until this time I had only a theoretical overview over TDD. Even when I started to test, when the Fluid development started. The first test were not Unit- but Integration tests. This means they tested Fluid in the view of a user: There were not parsed little Template-Snippets and compared with the expectations. The first tests took there time - it felt strange to test things, which were not implemented at this time. But after the first test was written and this test run through successfully, I was able to make the following development cycles extremely fast. Because of Test-Driven Development I was able during a train ride to totally reconstruct the core of Fluid. Without tests, it seriously would have took me days until all would have worked at the end. Especially the feedback I got at once, I really appreciate. You click on a button and after a few seconds you got your feedback. After this I am infected, learned about Mock- and Stub- objects, and today I do not want to miss it. (In this chapter you will get an introduction into these concepts.) If you want to learn TDD, you will jump in at the deep end, and to try it at the next project. Until the first Unit test is finished, it will take a while and after this it will go really faster. – Sebastian Kurfürst

First test, then implementing

The goal of Test-Driven Development is to make test explicit and automatic repeatable. The workflow is different of the classic programming, separated into very small iterations.

Using Test-Driven Development you write your Unit tests for your features before you write your features themselves. Unit tests are automatically repeatable tests of methods, classes and little program parts. Even during the writing of the tests, you should seriously think about the desired functionality. When you are running your tests, they naturally will fail, because the tested functionality is not implemented yet. In the next step you will have to write the code which is minimal needed to run the test successfully (have a look at figure 2-8). A new started test will finish without a problem. In the next step you should begin to think about which functionality is missing. The reason is that you have only implemented the minimal necessary code. When you know, what you want to implement you will write a new Unit test, with which you can test this new functionality. In this way the process of testing an implementing starts over.

_images/figure-2-8.png

Figure 2-8: with Test-Driven Development testing and development are alternating

The fortune of this method is obviously.

Because you are forced by writing of the tests to separate big features into smaller pieces, you have during the implementation the possibility to fully concentrate on the functionality you have actually to implement. Furthermore these change of the point of view between Developer and User of a class is the reason for better code which does what it should. TDD is especially useful for designing of APIs: Because of the testing you as developer change your perspective into the one of the user of the API and so you are able to identify inconsistencies and missing functionality much faster. In addition Unit tests are a kind of safety net against unrequested behavior. In the case of destroying a functionality when you are programming, you are getting through the Unit tests feedback directly and are able to correct the bug at once. This is the reason why the chance of regressions (producing bugs same time you are correcting another) is furthermore decreasing. According to some studies Developers who are using TDD are fast at the same rate or even faster than developers who are coding the conventional style, although the first mentioned have not only to write the Implementation but also the according tests. Furthermore is the quality of the code in the Test-Driven Development clearly higher than in the conventional Programming.

Example

Let us assume we want to model an online shop for a customer. These owns a name, which is transferred in the constructor. Through these tests we want to make sure that the name is correctly saved into the object. At first you have to install the phpunit-Extension from the TYPO3 Extension Repository (TER), because we will run the tests with this. The next step is to go to our own extension and create a folder "Tests/Unit/" in the main folder of the extension, if it not exists. This will contain later all our unit tests. Our customer object which we have to create, will be situated in Classes/Domain/Model/Customer.php because it is part of our domain model in our extension.

Similarly we create the test class in the file Tests/Unit/Domain/Model/CustomerTest.php. Now we create a minimal test case with which we get used with PHPUnit.

//code

All our test cases are named after the same name scheme like normal classes and they must be extended with TYPO3CMSCoreTestsBaseTestCase. One test class can contain many test methods. These have to be public and have to contain the annotation @test in their PHPDOC-Block, so they can be performed. Please keep in mind that the name of the test method should make clear which expectations the test should fulfill. Now we can run the test for the first time. Therefore go to the TYPO3-Backend to the module PHPUnit which is to find under the Admin Tools. Then you can choose your extension and click on Run all tests. Now you should, like it is shown in the figure 2-9, see a (yellow) bar and the Error message Not yet implemented. Because you will work much with the PHPUnit Environment, you should familiarize yourself with this. Try to run the test for extbase and fluid and also try the different Display options. For example you can let show you all tests or only the failed tests.

Now we know that our test case is running, we can write our first useful test case. This should test, if a name which is specified in the constructor, can be accessed again.

//Code

// Code

_images/figure-2-9.png

Figure 2-9: With the test runner you are able to run easily the Unit tests in the TYPO3-Backend.

When we run the test case, we will be displayed a fatal error from PHP, because the class we want to test does not exist already. Now we are changing roles: We are not the user of the class anymore, but now we are the developer, who should implement the class. At first we create in the file Classes/Domain/Model/Customer.php an empty class with the needed methods to get rid of the fatal error:

//code

When we now let run the test suite again there should not be a fatal error anymore but instead our Unit-Tests will fail because the getName() method returns the false value. Now we are able, motivated by getting the red bar fast as possible into green, to start with implementing:

//code

Now the Unit-Tests are running without failure, the expected value is given out. At this time we are not satisfied at all - finally, now is always 'Sebastian Elector', as name returned. The next step is a refactoring phase: We clean up the code and always make sure that the unit tests continue successfully passed. After several iterations we arrive at the following code:

//code

h1. Page 48

The unit tests always run through yet, and we have the desired functionality reached. Now we can once again slip into the role of the developer from the role of the user of the class and specify with other test cases additional functionality.

Test individual objects

Our first example about Unit-Tests was very simple. In this section we show you how to test classes that depend on other classes. Suppose we have a program which is writing log messages and they should be send per mail. For those there is a class EmailLogger that send the log data via e-mail. These class implements the potential complex goal of the e-mail sending on is own, but is using another class which is called EmailService. EmailService uses, depending on the used configuration a SMTP-Server or the mail() function of PHP. This is shown in the figure 2-10: The email logger class has a reference on the email service.

_images/figure-2-10.png

Figure 2-10: The EmailLogger uses for sending of the emails the EmailService.

We now want to test the class EmailLogger without using the EmailService. We do not want to send real emails with every test run. To reach that goal we need two sub elements. Dependency Injection and the use of Mock objects. Both concepts we will introduce below.

Dependency Injection

You often see classes that are constructed according to the following structure:

//code

The EmailLogger requires the EmailService to function correctly, and instantiated this in the constructor. However, this strongly coupled to these two classes together: When you create to test a new instance of the class EmailLogger, you automatically get an instance of an EmailService and this would implicitly be tested. In addition, it is not possible to exchange the EmailService at run time, without changing the source code. A solution to this dilemma is to use Dependency Injection:

This instantiates a class does not itself have dependencies but she gets from the outside passed. The EmailLogger gets a new method injectsEmailService, the EmailService in the class sets. This looks e.g. like this:

//code

Extbase offers currently not a framework support for Dependency Injection. Therefore, we recommend that the instantiation of classes and their Dependency Injection in respective factories to outsource. A possible Factory looks as follows from:

//code

We can now operate in a test case from the outside, which the EmailService the EmailLogger gets. We could write a TestEmailService, for example, which simple does nothing (to avoid a fatal error), or we use the Mock objects that are shown below.

Mock-Objects

Through the usage of Dependency Injection we are able to instantiate EmailLogger without its dependencies. Because the EmailLogger needs the EmailService to work, we must provide these in the tests.

But more than that: We also want to ensure that the EmailLogger really calls the method for sending the e-mail! Therefore we can use Mocks. Mocks are more or less "dummies" for real objects that emulate the behavior of objects. They are also help to ensure specific calls or parameters. A test that tests the EmailLogger that could be as follows:

// code

The procedure in detail: In line 6, the variable $message with our dummy message filled, we want to log. This message we need several more times, so it makes sense to store them in a variable. In lines 7 through 9, we instantiate the EmailLogger and initiate him a mock object of the EmailService.

In line 10 passes through the truly fascinating: We expect that in the EmailService an unique method call is sent, with the parameters 'logging@domain.local','Message Log', $message. Once we have specified our expectations, we can at line 11 let the EmailLogger log the message. At the end of the test cases our expectations are automatically controlled.

If the the method send was called exactly once or with the false parameter values, the test will fail with a detailed error message. What have we achieved? We have tested the EmailLogger without the use of Email-Service and still ensure that the Email-Service is called with the correct parameters. Also, we had no separate "placeholder" class to write for Email-Service, because we used the mock-functionality from PHPUnit.

Note

You have to get used to the writing style for Mock objects; But it will go on with the time in flesh and blood.

Conclusion

In this chapter you have learned to the most important concepts of Extbase. First, the basics of object-oriented programming have been refreshed, so that you now have good knowledge to understand the programming style of Extbase.

Then you have been introduced to "Domain-Driven Design" which structures the usually unstructured modeling process, and which focuses on the concepts that are important for the domain. Since you usually discuss the domain logic with the client, misunderstandings between the client and you as a developer are resolved even quicker.

After that, we've looked into the Model-View-Controller Design Pattern, which divides the applications in the data model and the user interaction.

Domain-Driven Design and the MVC architecture form the conceptual backbone of Extbase. Now we have looked at Test-Driven Development as a development paradigm, which lowers the probability of errors in the code dramatically and structures the daily work of a developer even more.

With these base concepts you are perfectly prepared for the next chapters. Now dive into the depths of Extbase with us!

A journey through the Blog Example

Attention

About the code of the blog example:

In this chapter we accompany you on a journey through a simple TYPO3 extension. While travelling on this round trip, you will get to know more about extension development with Extbase and Fluid and learn the most important stations and coordinates of extension development with the help of an example extension. You will first familiarize yourself with the geography and the typical characteristics of an extension and find out which processes run in the background. This knowledge will then help you in the creation of your own extension.

If you are looking for a manual specifically on creating an extension, chapter 4 will show you the right set of tools. However, we recommend to work on the fundamentals in this chapter. The journey that lies ahead of us could also have the title "Europe in five days." If you discover nice places, you should visit them later without the travel group.

You will find it beneficial to look at the original source code while reading the text, so you will have an easier time getting your bearings in your own extension later.

Note

If you use a debugger, it can be interesting to follow a full cycle in single step mode. For that you have to set a breakpoint in the file Dispatcher.php. You will find this class - like every other class of Extbase - in the folder typo3/sysext/extbase/Classes/.

At the end of this chapter you will find a short comparison of the traditional way to code an extension and an approach with Extbase and Fluid.

First orientation

The so called blog example is an example extension, which mainly focuses on showing the process of extension development and shows the possibilities of an extension based on Extbase. This extension is a common blog, which can be administrated either through the TYPO3 backend or in the frontend. The blog example includes the standard features, which you will know from other blogs: A blog consists of several posts and the readers of the blog are able to comment on the posts. The author of a post can add one or more tags to his post. Figure 3-1 shows an overview of the domain of the blog example and the relation among the domain terms. The asterisk (* or 0..*) means "any amount", 1 has to be translated with "exactly one". So exactly one administrator can administrate any amount of blogs. The diamond can be translated with "has", so: "One post has any amount of comments".

_images/figure-3-1.png

Figure 3-1: Domain of the blog example

Note

The blog example was originally created by the team inside the TYPO3 community that created the FLOW framework (formerly FLOW3). The blog example has been backported to TYPO3 4.x. When you work more with FLOW or TYPO3 in the future, you will find this example in a near identical form. You will find some notes about the relationship between Extbase and FLOW in the section "Migration to FLOW and NEOS" in chapter 10.

The complete source code can be found in a folder, which has the same name as the extension key. In our case the folder is called blog_example. Usually, the folder is located in the path typo3conf/ext/ in your TYPO3 installation.

In the top level of this folder there are the subfolders Classes, Resources and Configuration (see figure 3-2). There also are some files which TYPO3 requires in order to include the extension. Those files have the prefix ext_. All other configuration files needed by TYPO3 are located in the subfolder Configuration or in one of its subfolders.

_images/figure-3-2.png

Figure 3-2: folder structure of the example extension

The core of the extension is located in the folder Classes. There you will all files in which classes or interfaces are defined.

Note

If you are not familiar with the terms classes and interfaces, you should look into the section "Object oriented programming with PHP" in chapter 2, Basic principles.

In the folder Resources you will find all files which are included at runtime, but no classes or interfaces. In particular, those are icons, language packages, HTML templates, but also external libraries or scripts. These resources are structured into a public (Public) and a private (Private) block. In the folder Public files are located which are allowed to be called directly by the client - in normal cases the web browser. Files which are processed by a PHP class before they get delivered to the browser, are located in the folder Private.

The stations of the journey

Now that you have had a look at your journey destination and hopefully don't feel disoriented when we stop at the several steps, you are able to start. Figure 3-3 gives you an overview of the stations on the journey, which you will get to know in more detail during the upcoming sections.

_images/figure-3-3.png

Figure 3-3: The several stations of the journey

When an extension like the blog example is called, the following happens behind the scenes:

TYPO3 digs into the page content and discovers the content element of the extension (plugin) on the page. It does not call the extension directly, but hands over the control to the Extbase Dispatcher (1).<remark>TODO: Add callouts</remark>

The Dispatcher bundles all information of the request in a Request and sends it to the appropriate part of the extension, which takes over the flow control — the so called Controller (2).

Within the controller the appropriate storage facility which is in charge of the blogs — the Repository — is instructed to return all the stored blog posts using the method findAll() (3).

The Repository returns a collection of the already present Blog objects with all of their posts, comments and tags (4).

The Controller sends these blogs to the part of the extension responsible for the output — the View — and advises it to render the content in the requested output format (5).

The View returns the rendered content encapsulated in a Response back to the Dispatcher, which in turn returns the HTML code to the calling TYPO3 process (6).

Calling the extension

When a user opens the web page containing our blog in their browser, this request (Request) will be forwarded to the remote TYPO3 Server. Then TYPO3 starts the processing of this request straight away.

A request generally contains the identification number of the page (the so called Page-ID or PID) that should be generated (e. g. id=99). Using this PID, TYPO3 searches all relevant content elements on the specific page and converts these to HTML code one after another. While processing this page request, TYPO3 comes by the content element for our example extension, the so called plugin. This plugin should display a list of all blogs. Each with the individual title, a short description and the amount of all enclosed posts. In figure 3-4 you can see the output of the plugin in the frontend. This output is embedded within the greater overview of the page.

_images/figure-3-4.png

Figure 3-4: Output of the plugin of our example extension

The process of eradication is first forwarded to the dispatcher of Extbase by TYPO3. The dispatcher completes several preliminary tasks before it hands the further processing on to the according position within the code of our blog example:

  • It interprets the incoming request and bundles all relevant information into a Request object.
  • It prepares the Response object as a container for the result of the request.
  • It loads the configuration of our extension from the different sources and makes it available.
  • It determines whether or not the request was manipulated in an illegal manner and when this is the case deflects it (e.g. in of case maliciously added form input field).
  • It sets up the persistence layer which performs the persisting of new or changed objects.
  • It prepares the cache in which the content is stored for faster reuse.
  • It instantiates and configures the controller of our extension which controls further processing within the extension.

When these preparations are fulfilled by the Extbase dispatcher, we are able to travel to the first stop of our destination: the controller. In our example all further processing is assigned to the BlogController. A reference to the request and the response is handed over.

The class BlogController can be found in the file EXT:blog_example/Classes/Controller/BlogController.php. The complete name of the controller is \MyVendor\BlogExample\Controller\BlogController. At first this might seem long-winded but the syntax follows a very strict convention (please see box "Be careful, conventions!").

Tip

Be careful, conventions!

The name of a class is separated into individual parts, which themselves are divided by an underscore. All parts of a class name are spelled with capital camel case, where each initial letter is capitalized. This style for notation is commonly known as UpperCamelCase because each capital letter suggests the hump of a camel. For extensions the first part always is "Tx". The second part is the name of the extension - in the underlying case "BlogExample". The last part is the name of the domain object. The center between those parts builds the path to the class file below the folder Classes. In our case the file is stored directly within the folder Controller. The name of the class file is taken from the last part of the class name appended with the suffix .php.

And... action!

Our journey through the blog example is not only an educational, but also an activity holiday. We now turn to the activities. We are already in the BlogController. You can find the class file under EXT:blog_example/Classes/BlogController.php.

In software development, there are different variants of controllers. In Extbase the controllers mostly exist as ActionController. This variant is characterized by short methods, which are responsible for the control of a single action, the so called Actions. Let's have a deeper look at a shortened version of the BlogController:

Classes/BlogController.php
 <?php

 namespace MyVendor\BlogExample\Controller;

 class BlogController
       extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {

     public function indexAction()
     {
         $this->view->assign('blogs', $this->blogRepository->findAll());
     }

     public function newAction(\MyVendor\BlogExample\Domain\Model\Blog $newBlog = null)
     {
         $this->view->assign('newBlog', $newBlog);
         $this->view->assign('administrators', $this->administratorRepository->findAll());
     }

     public function createAction(\MyVendor\BlogExample\Domain\Model\Blog $newBlog)
     {
         $this->blogRepository->add($newBlog);
         $this->redirect('index');
     }

     public function editAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
     {
         $this->view->assign('blog', $blog);
         $this->view->assign('administrators', $this->administratorRepository->findAll());
     }

     public function updateAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
     {
         $this->blogRepository->update($blog);
         // this does currently not work, use $this->blogRepository->add($blog); instead
         // see issue: https://forge.typo3.org/issues/76876
         $this->redirect('index');
     }

     public function deleteAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
     {
         $this->blogRepository->remove($blog);
         $this->redirect('index');
     }

 }

The method indexAction() within the BlogController is responsible for showing a list of blogs. We also could have called it showMeTheListAction(). The only important point is, that it ends with Action in order to help Extbase to recognize it as an action. newAction() shows a form to create a new blog. The createAction() then creates a new blog with the data of the form. The pair editAction() and updateAction() have a similar functionality for the change of an existing blog. The job of the deleteAction() should be self explaining.

Tip

Who already dealt with the model-view-controller-pattern will notice, that the controller has only a little amount of code. Extbase aims for the slim controller approach . The controller is exclusively responsible for the control of the process flow. Additional logic (especially business or domain logic) needs to be separated into classes in the subfolder Domain.

Tip

The name of the action is strictly spoken only the part without the suffix Action, e.g. list, show or edit. With the suffix Action the name of the action-method is marked. But we use the action itself and its method mostly synonymous.

From the request the controller can extract which action has to be called. The call is happening without the need to write another line of code in the BlogController. This does \TYPO3\CMS\Extbase\Mvc\Controller\ActionController. The BlogController "inherits" all methods from it, by deriving it from this class.

<?php
declare(strict_types = 1);

namespace MyVendor\BlogExample\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
    // ...
}

At first call of the plugin without additional information the request will get a standard action; in our case the indexAction(). The indexAction() contains only one line in our example (as shown above), which looks more detailed like this:

<?php
declare(strict_types = 1);

namespace MyVendor\BlogExample\Controller;

use MyVendor\BlogExample\Domain\Repository\BlogRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
    protected $blogRepository;

    public function __construct(BlogRepository $blogRepository)
    {
        $this->blogRepository = $blogRepository;
    }

    public function indexAction()
    {
        $allAvailableBlogs = $this->blogRepository->findAll();
        $this->view->assign('blogs', $allAvailableBlogs);
    }
}

In the first line of the indexAction the repository is asked to fetch all available blogs. How they are saved and managed, is not of interest at this point of our journey. All files, which are defined in the repository-classes, are located in the folder EXT:blog_example/Classes/Domain/Repository/. This you can also derive directly from the Name \MyVendor\BlogExample\Domain\Repository\BlogRepository. This naming scheme is a big advantage by the way, if you search a particular class file. The name BlogRepository results from the name of the class, whose instances are managed by the repository, namely by adding Repository. A repository can only manage one single class at a time. The second line retrieves all available blogs by findAll().

Get Blog from the Repository

Lets take a look into the BlogRepository and travel into the inner core of our little action island.

<?php
namespace MyVendor\BlogExample\Domain\Repository;

class BlogRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{

}

The code is not shortened. The BlogRepository simply does not have any own code since all methods which are used very often are already implemented in the parent class \TYPO3\CMS\Extbase\Persistence\Repository. These functions are also available in all child classes. We call the method findAll(), to retrieve all blog objects.

Note

Although you don't need to implement your own logic extbase expects an existing class.

How Repositories Work

The way a repository works is much like a library of a university which works in the following way:

You tell an employee of the library to find a book matching certain criteria. After a few minutes till days you get the corresponding book. In this case it doesn't matter to you if the book is right below the counter, in a shelf, in the cellar, or is not even printed yet (except from the time issue - but students have time). It is only important that you get the book at any time. Remarks made with a pencil by you will be added permanently (hopefully without changes) when you return the book.

That is translated to a repository: Via a repository you tell a method to find an object matching certain criteria. After a few milliseconds till seconds you receive the corresponding object. In this case you don't care where the object is stored. Practically it even doesn't matter if the object is stored in the memory, must be fetched from the hard drive, is retrieved via web service from another server, or is instantiated for the first time (except from the speed - but users have time). The only important thing is that the object is instantiated and delivered to you. Object attributes changed by you will be stored when you leave the extension.

An excursion to the database

During our trip to the database bear in mind that we are in the brush of the Extbase-Framework - an area you wouldn't enter without travelling experience or a travel guide. Later you will use the Query-Object to create your own requests. If you are not interested in the background of the data storage, but trust that extbase will take care about this, you can skip this trip (chapter), or come back later. You will receive a free travel coupon then.

The BlogRepository creates a Query object with the class \TYPO3\CMS\Extbase\Persistence\Generic\Query, which is specialised for Blog objects, and executes the query ($query->execute()). The Query object is mostly abstracted from the physical storage - normally a relational database. It does not contain any information on how something is searched for. It only contains a specification of what is searched for. The Query object still allows any kind of storage method like a web service, or storage in a text file.

The Query object is handed over to the Storage-Backend. The Storage-Backend translates the request into a form that the given storage method can use directly. In our case this is an SQL-Statement. The call $query->execute() will finally be translated to SELECT * FROM tx_blogexample_domain_model_blog.

The Storage-Backend returns the "raw" results as a collection (array) of database tuples. Every database tuple corresponds to a table row and is in itself an associative array with the field names as keys and the field contents as value. It does not contain any objects yet and it also does not contain any data on posts, comments or tags which belong to the database record. The task to build a complete object tree starting from the Blog object down to the last tag of a post will be handed over to another object - the DataMapper.

Now, shortly before we head back to our common environment, the extension, we return to the persistence layer.

Paths on the Data-Map

The DataMapper object has the task to create an instance of the Blog-Class (whose name is stored in $this->className) for each tuple and "fill" this fresh instance with the data of the tuple. It is called in the Query object by the following Lines:

$this->dataMapper->map($this->getType(), $rows);

The DataMapper object also resolves all relations. This means that it starts requests for the posts and builds the objects with all included sub objects before they are written into the attribute posts of the corresponding Blog object.

When resolving the relations, the DataMapper object gets its information from different sources. First of all it knows the configuration of the database tables (stored in the Table Configuration Array, short: TCA), furthermore it "reads" the PHP comments inside the class definition standing above the definitions (or properties). Let's for example look at the definition of the property posts within the Blog class. You can find this in the file EXT:blog_example/Classes/Domain/Model/blog.php.

<?php
namespace MyVendor\BlogExample\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Blog extends AbstractEntity
{
    /**
     * The posts of this blog
     *
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\BlogExample\Domain\Model\post>
     *
     */
    protected $posts;
}

The property $posts contains within the PHP comment above some so called annotations which start with the @ character. The annotation:

@var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\BlogExample\Domain\Model\Post>

tells the DataMapper to create an ObjectStorage there and fill it with the Post objects of the class \MyVendor\BlogExample\Domain\Model\Post.

Note

The \TYPO3\CMS\Extbase\Persistence\ObjectStorage is a class of Extbase. This class takes objects and ensures that an instance is unique within the ObjectStorage. Objects within the ObjectStorage can be accessed by the methods attach(), detach() and contains() amongst others. The ObjectStorage also implements the interfaces Iterator, Countable, ArrayAccess. So it is usable in foreach constructs. Furthermore the ObjectStorage behaves like an array. The ObjectStorage of Extbase is based upon the native SplObjectStorage of PHP, which is error free since PHP-Version 5.3.1.

The notation at first seems unusual. It is based on the so called Generics of the programming language Java. In the definition of your property you have to enter the type in the annotation above the method definition. Properties of a PHP-type will look like this:

/**
 * @var int
 */
protected $amount;

It is also possible to enter a class as type:

/**
 * @var \MyVendor\BlogExample\Domain\Model\Author
 */
protected $author;

Properties which should be bound to multiple child objects require the class name of the child elements in angle brackets:

/**
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\BlogExample\Domain\Model\Tags>
 */
protected $tags;

Extbase gathers the type of the relation from the configuration of the database table column. Let's take a look at the definition of the column posts. It can be found in the file tx_blogexample_domain_model_blog.php within the path Configuration/TCA/.

<?php

return [
    // ...
    'columns' => [
        // ...
        'posts' => [
            'exclude' => 1,
            'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xml:tx_blogexample_domain_model_blog.posts',
            'config' => [
                'type' => 'inline',
                'foreign_table' => 'tx_blogexample_domain_model_post',
                'foreign_field' => 'blog',
                'foreign_sortby' => 'sorting',
                'maxitems' => 999999,
                'appearance' => [
                    'newRecordLinkPosition' => 'bottom',
                    'collapseAll' => 1,
                    'expandSingle' => 1,
                ],
            ],
        ],
        // ...
    ],
];

Extbase "reads" from the configuration the table of the child objects (foreign_table) and the key field where the unique identifier (UID) of the parent object (foreign_field) is stored. With the help of these information and the data given in the PHP-documentation above the property definition extbase can read the database records and map them onto the Post-class. This process will be continued recursively over the complete object graph - the blog with all its containing posts, comments, tags etc. - starting from the single blog as root object.

After our exhausting journey let's get back to the realm of our extension. Remember that normally you will not need to enter these paths - except the case that you are into customised journeys.

Back in the controller

You get the ready Blog objects delivered in an array. "Ready" means in this context, that every Blog object already has all it's Post objects and their Comment and Tag objects.

These blogs are delivered to the object, which is responsible for the output for further processing: the so called View. If we make no own choice, like in our example, the TemplateView of Fluid is automatically available under the class variable $this->view.

With the method assign() we "bind" the array with our blogs to the variable name "blogs" of the TemplateView. It can be addressed with this name in the template. The method render() of the TemplateView starts the generation of the HTML code.

Before we leave our small, contemplative action island and dig into the deep of the Fluid template, let's take a look at the abbreviations and simplifications Extbase offers at this point.

  • First of all there is the method initializeAction(), which is called before every action if defined in your controller.
  • Secondly you can define methods for the initialization of single actions. These methods follow a specific naming convention: initialize + actionMethodName

The following example explains this mechanism:

<?php
declare(strict_types = 1);

namespace MyVendor\MyExtension\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CompanyController extends ActionController
{
    public function initializeAction()
    {
        // this method is called before method fooAction and barAction
    }

    public function initializeFooAction()
    {
        // this method is only called before method fooAction
        // mind the uppercase F in the method name.
    }

    public function fooAction()
    {
        // foo
    }

    public function barAction()
    {
        // bar
    }
}

Come with us on another tour: dive into Fluid - the new template engine of TYPO3 - and get to know the magnificent underwater world full of colorful Fluid tags and ViewHelper.

Rendering the output with fluid

The TemplateView of Fluid now tries to load the corresponding HTML-Template. Since there is none specified by this->view->setTemplatePathAndFilename($template-PathAndFilename) Fluid searches at an place defined by conventions.

All front end templates can be found in EXT:blog_example/Resources/Private/Templates by default. There for example are the two subfolders Blog and Post. Since the call was made by the indexAction() of the BlogController fluid searches in the folder Blog for a file named Index and - if not setup up differently - the suffix .html. So every action method has its own template. Possible other formats are e.g. .pdf, .json or .xml. In table 3.1 you can find some examples for these convention.

Table 3-1: Examples for the convention of template paths

Controller Action Format Path and filename
Blog index unspecified Resources/Private/Templates/Blog/Index.html
Blog index txt Resources/Private/Templates/Blog/Index.txt
Blog new unspecified Resources/Private/Templates/Blog/New.html
Post unspecified unspecified Resources/Private/Templates/Post/Index.html

In our case the file Index.html will be loaded. The content will be parsed step by step, line by line. Here you see an extract of the template file:

Index.html
 <p>Welcome to the Blog Example!</p>
 <f:if condition="{blogs}">
     <f:then>
         <p>Here is a list of blogs:</p>
         <dl>
             <f:for each="{blogs}" as="blog">
                 <dt>
                     <f:link.action action="index" controller="Post"
                     arguments="{blog : blog}">
                         {blog.title} (<f:count subject="{blog.posts}" />)
                     </f:link.action>
                 </dt>
                 <dd>
                     <f:format.nl2br>{blog.description}</f:format.nl2br>
                     <f:link.action action="edit"
                         arguments="{blog : blog}">Edit</f:link.action>
                     <f:link.action action="delete"
                         arguments="{blog : blog}">Delete</f:link.action>
                 <dd>
             </f:for>
         </dl>
         <p>
             <f:link.action action="new">Create another blog</f:link.action>
                 <br /><f:link.action action="populate">Create example data</f:link.action>
                 <br /><f:link.action action="deleteAll">Delete all Blogs [!!!]
             </f:link.action>
         </p>
     </f:then>
     <f:else>
         <p>
             <strong><f:link.action action="new">Create your first blog
             </f:link.action></strong>
             <br /><f:link.action action="populate">Create example data</f:link.action>
         </p>
     </f:else>
 </f:if>

At first all the unknown XML tags with namespace »f« stand out, like <f:if>, <f:for> or <f:link.action>. These Tags are provided by Fluid and represent different functionalities.

  • <f:format.nl2br>[…]</f:format.nl2br> modifies linebreaks (new lines) to <br /> tags.
  • <f:link.action action="new"> creates a link tag that links to the newAction() of the actual controller.
  • <f:for each="{blogs}" as="blog">[...]</f:for> iterates over all Blog-objects found in Blogs.

Let's have a closer look at the latter example. In the variable {blogs} all blogs are "included". The curly brackets tell Fluid that it is a variable that was "assigned" to the template before. In our case this was done in the indexAction() of the BlogController. With the attribute each the for ViewHelper gets the blog objects over whom is to be iterated. The attribute as holds the name of the variable with which the blog object is available inside of <f:for>[...]</f:for>. Here it can be called with {blog}.

Note

The string "blog" is not surrounded by brackets when assigned to the as attribute since the string is passed as a name for the variable and should not be parsed by Fluid. An as="{blog}" would be parsed as if you would have liked to make the name of the variable configurable. Rule of thumb: Curly brackets in each, none in as.

Objects can not be rendered by Fluid directly. An exception make objects that have a __toString() method. The single properties of such an object can be accessed with a point-notation. If Fluid crosses a string like {blog.title} it tries to parse it. Fluid expects the variable blog to be an object. Inside of this object it searches for a method named getTitle(). The name of the method is created by extracting the part after the point, capitalizes the first letter and prefixes a »get«. With this the call looks something like this: $blog->getTitle(). The return value will replace {blog.title} in the template. Analogously {blog.description} will be replaced with the description. Parsing the point goes recursively. That means Fluid can parse a string {blog.administrator.name} by calling a method that equals $blog->getAdministrator()->getName().

Note

The return value is "tidied up" by htmlspecialchars(). That protects from Cross Site Scripting-Attacks (XSS).

As soon as Fluid is done with the whole template the result is appended to the Response object. This is done in the \TYPO3\CMS\Extbase\Mvc\Controller\ActionController by the call $this->response->appendContent($this->view->render()).

Our journey slowly comes to an end. The Request is been fully answered by a corresponding Action. The Response object carries the completely generated content. We now sally forth heavy hearted the return trip stopping once more at the dispatcher of Extbase.

Returning the result to TYPO3

In conclusion, all changes to objects that were previously only in the main memory are made permanently preserved (persists). Thus the persistence manager will now be appointed by $persistenceManager->persistAll(). The persistence manager will walk through the used repositories and collects at first the new and the deleted objects. In our case the persistence manager asks the blog repository about such objects. Since we set at run time either newly created objects in the repository nor objects which have been included from there again, the persistence manager remained at this time inactive.

We have now finally reached the end of our trip. The Dispatcher has still to return the rendered Content to the TYPO3 framework.

return $response->getContent();

In this section you had learned how the extension is a list of blogs is issued. Below we take an alternative route by creating a new post. You will learn about the chaining of several actions into a coherent sequence, exploit the possibilities of validation and deeper into fluid.

Alternative route: creating a new posting

After out first journey through the blog example, in this chapter we will follow a more complex example. As an example we have chosen the creation of a new post. The user should be offered a form in the front end, in which he could put in the title and the content of a new post entry and select an existing author for this post. After clicking the submit button, the list of the last posts of the current blog should be displayed - now with the just created post at the first place. There are multiple steps, each based on the previous step, to be implemented that are mirrored in the actions new and create. The method newAction() displays the form, while the method createAction() really creates the post, puts it in the repository and routes the process to the method indexAction().

Calling the method newAction() is done in our case with a link in the front end, that looks - a bit purged - like this:

<a href="index.php?id=99&tx_blogexample_pi1[blog]=6&tx_blogexample_pi1[action]=new&tx_blogexample_pi1[controller]=post">Create a new Post</a>

This was created with the following Fluid code in the template EXT:blog_example/Resources/Private/Templates/Post/Index.html:

<f:link.action action="new" controller="Post" argument="{blog : blog}">Create a new Post</f:link.action>

The tag <f:link.action> creates a link to a special controller action combination: tx_blogexample_pi1[controller]=post and tx_blogexample_pi1[action]=new. Also the current blog is given as an argument with tx_blogexample_pi1[blog]=6. Because the blog cannot be sent as an object, it must be translated into an unique identifier - the UID. In our case this is the UID 6. Extbase creates out of these three information the request and routes it to the according PostController. The translation of the UID back to the corresponding blog object is done automatically by Extbase.

Lets take a look at the called method newAction():

<?php
declare(strict_types = 1);

namespace MyVendor\BlogExample\Controller;

use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
    /**
     * Displays a form for creating a new post
     *
     * @param \MyVendor\BlogExample\Domain\Model\Blog $blog The blog the post belongs to
     * @param \MyVendor\BlogExample\Domain\Model\Post $newPost An invalid new post object passed by a rejected createAction()
     * @return string An HTML form for creating a new post
     * @Extbase\IgnoreValidation("newPost")
     */
    public function newAction(\MyVendor\BlogExample\Domain\Model\Blog $blog, \MyVendor\BlogExample\Domain\Model\Post $newPost = NULL)
    {
       $this->view->assign('authors', $this->authorRepository->findAll();
       $this->view->assign('blog', $blog);
       $this->view->assign('newPost', $newPost);
    }
}

The method newAction() expected a blog object and an optional post object as parameter. It should be weird at first, because we have no blog and no post object, that has to be created with the form. Actually the parameter $newPost is empty (NULL) at the first call.

Our PostController, that is derived from ActionController, prepares all parameters before an action is called. The controller delegates this to an instance of the class PropertyManager, that has mainly two functions: it converts the parameter from the call (from our link) into the target object and checks if it is valid. The target for the parameter $blog is an instance of the class \MyVendor\BlogExample\Domain\Model\Blog, for the parameter $newPost it is an instance of the class \MyVendor\BlogExample\Domain\Model\Post.

How does Extbase know what the target of the conversion is? It takes this information from the information above the argument. If there is nothing declared it takes the destination type from the PHP documentation above the method, from the line:

* @param \MyVendor\BlogExample\Domain\Model\Blog $blog The blog the post belongs to

The link is created with the name of the argument $blog. In this way the link between the request parameter and the newAction() is resolved. The link parameter:

tx_blogexample_pi1[blog]=6

is assigned to the parameter:

\MyVendor\BlogExample\Domain\Model\Blog $blog

of the newAction() with the name "blog". With the help of the UID 6 the corresponding blog object can be identified, reconstructed and given to the newAction().

In the first line of the newAction() the view gets an array of persons in the parameter authors which is taken from the AuthorRepository with the findAll() method. In the second and third line the view gets the parameter blog and newPost. The following actions are called automatically by the controller after calling newAction().

$form = $this->view()->render();
return $form;

Here you will see the shortened template new.html:

<f:form method="post" controller="Post" action="create" name="newPost" object="{newPost}" arguments="{blog: blog}">
   <label for="author">Author (required)</label><br />
   <f:form.select property="author" options="{authors}" optionLabelField="fullName">
      <select><option>dummy</option></select>
   </f:form.select><br />
   <label for="title">Title (required)</label><br />
   <f:form.textbox property="title" /><br />
   <label for="content">Content (required)</label><br />
   <f.form.textarea property="content" rows="8" cols="46" /><br />
   <f:form.submit class="submit" value="Submit" />
</f:form>

Fluid offers some comfortable tags for creating forms which names are all starting with form. The whole form is enclosed in <f:form></f:form>. Like the creating of a link the controller action combination which should be called when clicking the submit button is given here.

Note

Don't be confused by the parameter method="post". This is the transfer method of the form and has nothing to do with our domain (instead of method="post" it also could be method="get").

The form is bind with object="{newPost}" to the object that we have assigned to the variable newPost in the controller. The specific form fields have a property property="..."`. With this a form field can be filled with the content of the property of the given object. Because {newPost} is empty (= NULL) here, the form fields are empty at first.

The select tag is created by the Fluid tag <f:form.select>. Thereby it is keep in mind that the HTML code <select><option>dummy</option></select> will be completely replaced with the code generated by Fluid. This allows the preview of the template with blind text. The available options are taken by Fluid from the content of the given property options="{authors}". In our case it is an array with all persons of the AuthorRepository. The visible text of the options are created by Fluid from the parameter optionLabelField="fullName". The created HTML code of the form looks like this:

<form method="post" name="newPost" action="index.php?id=99&tx_blogexample_pi1[blog]=2&tx_blogexample_pi1[action]=create&tx_blogexample_pi1[controller]=Post">
   <label for="author">Author (required)</label><br />
   <select name="tx_blogexample_pi1[newPost][author]">
      <option value="1">Stephen Smith</option>
      <option value="2">John Doe</option>
   </select><br />
   <label for="title">Title (required)</label><br />
   <input type="text" name="tx_blogexample_pi1[newPost][title]" value="" /><br />
   <label for="content">Content (required)</label><br />
   <textarea rows="8" cols="46" name="tx_blogexample_pi1[newPost][content]"></textarea><br />
   <input class="submit" type="submit" value="Submit" />
</form>

TYPO3 takes the rendered form and includes it at the appropriate place in the HTML page (see figure 3-5).

_images/figure-3-5.png

Figure 3-5: The rendered form

Clicking the submit button calls the createAction of the PostController. Here you will see the stripped-down method:

/**
 * Creates a new post
 *
 * @param \MyVendor\BlogExample\Domain\Model\Blog $blog The blog the post belongs to
 * @param \MyVendor\BlogExample\Domain\Model\Post $newPost A fresh Post object which has not yet been persisted
 * @return void
 */
public function createAction(\MyVendor\BlogExample\Domain\Model\Blog $blog,
     \MyVendor\BlogExample\Domain\Model\Post $newPost) {
   $blog->addPost($newPost);
   $this->redirect('index', NULL, NULL, ['blog' => $blog]);
}

The arguments $blog and $post are filled and validated equivalent to the method newAction().

Note

During the conversion of the arguments into the property values of the target object, the above-mentioned PropertyManager checks if any errors encountered during the validation. The validation effected on the base of the property definitions of the target object. More about the subject validating you will find in the section "Validating domain objects" in chapter 9.

The post is added to the blog with $blog->addPost($newPost). After that the following processing is forwarded by $this->redirect([...]) to the method indexAction(). Thereby the blog - now with the new post - is passed as argument. In order that the new post is available in the blog when next called, it must be persisted. This is done automatically after the flow through the extension in the dispatcher of Extbase.

Note

Beneath the method redirect() Extbase knows the method forward(). This also forwards the further processing. But the difference is that redirect() starts a complete new page call (new request response cycle), while forward() resides in the processing of the current page call. The outcome of this is an important consequence: At redirect() the changes are persisted before the call of the target action, whereas at forward() these must be done by hand with the call of $this->objectManager->get(TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();.

Automatic persistence of the domain logic

Remarkable at this point is, that up to this time no method to save the blogs or posts was called. Only the fact that the post is added to the blog and thereby was changed is enough to initiate extbase to save the changes permanently (to persist). Like on our first route the persistence manager is assigned with it by $persistenceManager->persistAll(). This time it collects all reconstructed objects (e.g. such, that are restored from the database) that are managed by a repository. These "managed" objects represent the root objects of an object graph (aggregate). These are so called aggregate root objects.

Note

More about the object life cycle you will find out in "Domain Driven Design" in chapter 2. The states transient and persistent are also elucidated in detail there. For the topic aggregate root you will find in the section "aggregates" in chapter 2 a detailed introduction.

The collection of new and deleted objects as well as the root objects (in our case the Blog objects) are hand over from the persistence manager to the persistence backend. The backend has the task to manage the complete process in a controlled manner. The course is done in the following order:

  • All new added aggregate root objects are inserted (first without to create the child and grandchild objects).
  • All properties of the aggregate root objects are persisted.
  • All kind objects are processed recursive in a corresponding manner.
  • All removed objects were deleted.

Warning

Do not confound the persistence backend with the storage backend that we discussed before in the section "An excursion to the database" in this chapter. The persistence backend is a layer "above" the storage backend. It is responsible for the cycle of the persistence (what should be stored, deleted or changed in which order?), while the storage backend has the job to translate the abstract requests into the native language of the "physical" storage option (most the SQL dialect of a database).

In our case the persistence backend (in the following called backend) checks for every Blog object whose properties (title, description, posts and so on) if the property values have to be stored. This is the case if the corresponding objects is new or the property value was changed in the runtime. If the property refer to an object, the backend checks in the next step also these objects for changes of the property values.

Note

All methods that starts with an underline (_) are internal methods. These methods can be called from "outside" (public) in a technical view, but they should not be called inside an extension - even though it is attractive to do that.

In our example the backend find the new post while it iterates through the post objects. The storage backend is directed to store these post in the database - and with it also all of its relations to other objects. Because the post has a 1:n relation to the blog (a blog has many posts, every post is part of just one blog) the UID of the blog is stored inside the property blog of the post. With this the post refers to its blog and can be assigned when the method indexAction() is called.

We are glad that you followed us also on this second, much more exhausting route. With the holiday destination you are so far familiar that you can move around safety in the blog example in the future without us - your travel guides. Of course there is a lot more to explore!

Dirty objects

How does extbase know that a property value has changed? Every object of the domain of your extension (domain object) must enhance a defined class of extbase. For the blog class this is \TYPO3\CMS\Extbase\DomainObject\AbstractEntity. Inside this parent class a property $_cleanProperties is defined. This property is directly, after the reconstruction of the object (restored from the database), initialized with the unchanged property values with a call of _memorizeCleanState(); in our case with the title, the description, the administrator and all post objects. With this it is possible later on to check every property for changes with calling of _isDirty($propertyName).

Notes for migrating users

This section is for developers who already have some experience with the traditional extension development. Should this apply to you, you will find tips for the change here. If you are new to get on the programming of extensions you confidently skip this section. The previous manner what we call "traditional" here, even though it can be used furthermore equal is characterized by:

  • the structure of directories, files and classes oriented for (frontend) plugins and (backend) modules and accordingly.
  • the highly mixture of tasks inside less classes (which are most called pi1, pi2 and so on),
  • the usage of methods of the base class tslib_pibase, from which the most classes have to inherit from, as well as
  • the creation of extensions centered to database tables with the kickstarter.

All mentioned points are changed by Extbase in a more or less radical manner. The complete development of an extension concentrates on the domain of the problem - whose terms, sequences, rules and think pattern. Objects from the real world, like customer, invoice, member, rental car, foods and so on are come out as so called domain objects inside the extension. In relation to this,plugins and modules are playing a minor- more on input and output related role.

Plugins and modules in Extbase are not their own classes, but rather "virtual" collections of actions which can be apply to the domain objects. The displaying of a list of posts or the changing of a blog title are examples for such actions, which are found as methods - also called actions - in the extension. Were plugins and modules are so far stored as class files in own directories (pi1, pi2 and mod), you will find them now exclusively as configuration in the two files ext_localconf.php and ext_tables.php. In ext_tables.php a plugin or module can be registered for selecting it in the backend. In the ext_localconf.php consists the configuration of the action which can be called. How to register and configure plugins and modules we will explain in detail in the section "Configuring and embedding Frontend Plugins" in chapter 7.

All actions based on a domain object are combined in an own object, the controller. For example, all actions that display a blog, change it, delete it and so on are combined in the BlogController. One action is defined as the standard action. These action is called when nothing else is defined by the user of the web site. This default action is the equivalent to the method main() inside a traditional plug in class. Inside main() so far often was made the selection which action (for example the list view, single view, archive view, create a new news entry) should be called. Extbase moved the decision outside of your extension in to the connected dispatcher. This results in an increased flexibility and modularity inside your extension.

Table 3-2 shows a comparison of the directory structures and configuration files. On the left are the typical places of the most important files of a traditional extension, right aside you find the corresponding places and files, how they are used in Extbase.

Table 3-2: Comparison of the directory structures and configuration files ("EXT:" stands for the path to the extension directory.)

What So far With extbase
Basic configuration files Files ext_* on top level in the extension folder Location as before; Changes in the contents (see below)
Configuration of a plugin

In ext_localconf.php: t3lib_extMgm::addPItoST43()

In ext_tables.php: t3lib_extMgm::addPlugin()

In ext_localconf.php: \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin() (see "Configuring and embedding Frontend Plugins" in chapter 7)

In ext_tables.php: \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin() (see "Configuring and embedding Frontend Plugins" in chapter 7)

Configuration of database tables SQL definition in ext_tables.sql As before; Pay attention for the naming conventions of table and field names (see chapter 6)
Configuration of the Backend Basic configuration in ext_tables.php TCA default in EXT:my_ext/tca.php; Location configurable in ext_tables.php As before
User configuration TypoScript in EXT:my_ext/static/constants.txt and EXT:my_ext/static/setup.txt; Location configurable in ext_localconf.php TypoScript in EXT:my_ext/Configuration/TypoScript/constants.txt and EXT:my_ext/Configuration/TypoScript/setup.txt; Location configurable in ext_localconf.php

Creating a first extension

Tip

In this chapter, you will learn how to build an extension from scratch. You may also want to try out the Extension Builder which automatically creates some of the necessary source files for you.

In this chapter you learn the basics of an extension based on extbase and fluid. You build up a minimalistic extension, which is reduced to the absolutely necessary structures. So you view clearly the whole, without hassles with details. In the following chapters we will turn to a more complex example, to cover exhaustively all fundamental attributes of extbase and fluid.

You can get the extension here:

https://github.com/TYPO3-Documentation-Examples/store_inventory

The Example Extension

Our first Extension will show an inventory list of products, which we created before in a backend list-module. Each product is marked by a title, a short description and a quantity as the number of pieces in stock. The following steps are necessary for implementation:

  1. Create directory tree and the minimal configuration files
  2. Translate the problem domain in an abstract domain model
  3. Configuration of persistence layer
    • Definition of database tables
    • Configure the display of backend forms
    • Create repositories for product objects
  4. Define the application flow inside the extension (create controller and action methods)
  5. Realize design with HTML-templates
  6. Configure the plugin for list display
  7. Install and test the extension

Tip

We choose the step order inside the example extension, so the connection will stay visible and a »natural« growth of extension and knowledge is given. After gathering the first experience in programming with Extbase, you probably will work in another and quicker order. Furthermore, in the future you will have the Extension Builder, a convenient tool to create the base of an extension which is outlined in chapter 10.

Create Folder Structure And Configuration Files

Before we write the first line of code, we must arrange the infrastructure of the extension. Beside the folder structure there are some minimum needed configuration files counting. We put the unique identifier of our extension (extension-key) as store_inventory, and thus we specify at the same time the name of the extension as Store Inventory.

Tip

The name of an extension is always written in UpperCamelCase (beginning with a capital letter, then upper and lower letters; no underscore), while the extension key may only contain small letters and underscore (lower_underscore). You will find an overview of the name conventions in appendix A, Coding Guidelines.

Extensions can be stored at different places in TYPO3. Locally installed extensions are the rule. These are in the folder typo3conf/ext/. System extensions are delivered with the TYPO3-distribution and are in the folder typo3/sysext/. Extbase or Fluid are examples of system extensions. The two paths are below the installation folder of TYPO3, in which also lies the file index.php.

Then, in the folder for local extensions typo3conf/ext/ we create the folder store_inventory. The name of this folder must be written like the extension key and therefore in lower-case letters, and where appropriate, with underscores. On the uppermost level lie the folders Classes and Resources. The folder Classes contains all PHP classes, with the exception of external PHP libraries. The folder Resources contains two directories named Private and Public. The folder Resources/Private/ contains subfolders like Templates, Layouts, Partials and Language. These files can only be accessed through the file system. The folder Resources/Public/ contains subfolders like Icons, Css, Js. These files can be accessed through the web browser. Within the folder Classes are the folders Controller and Domain. In our example, the folder Controller contains only one class that will control the entire process of listing creation later. The folder Domain again contains the two folders Model and Repository. Resulting from all this, the folder structure within the extension folder store_inventory should look as in image 4-1.

_images/figure-4-1.png

Figure 4-1: The standard directory structure with the important files for the extension manager

A single configuration file named ext_emconf.php is required by TYPO3 to allow for loading the extension. The file is located in the extension's top level folder (store_inventory/). You can copy and adapt this file from an existing extension. Later on it is advisable to have it generated by the extension_builder extension.

The file ext_emconf.php contains the meta information for the extension like title, description, status, name of the author and more. It is not special in any way and does not differ from the one of any other extension. Find a complete reference in chapter Declaration File of the Core Api Reference manual.

<?php

$EM_CONF[$_EXTKEY] = [
    'title' => 'Store Inventory',
    'description' => 'An extension to manage a stock.',
    'category' => 'plugin',
    'author' => 'John Doe',
    'author_company' => 'John Doe Inc.',
    'author_email' => 'john.doe@example.com',
    'state' => 'alpha',
    'clearCacheOnLoad' => true,
    'version' => '0.0.0',
    'constraints' => [
        'depends' => [
            'typo3' => '8.7.0-8.9.99',
        ],
    ],
    'autoload' => [
        'psr-4' => [
            'MyVendor\\StoreInventory\\' => 'Classes'
        ],
    ],
];

In previous versions of TYPO3 the extension icon was named ext_icon.gif. Starting with TYPO3 8 you can choose between PNG or SVG format. The file must have the name Extension.png or Extension.svg and must be stored in the directory Resources/Public/Icons/. The icon will be displayed in the extension manager and in the TYPO3 extension repository (TER).

If the extension has namespaced classes following the PSR-4 standard, then you can add the autoload array to your ext_emconf.php file.

After the basic structure was constructed, the extension can already be shown in the extension manager and can be installed. But first we turn to our domain.

Create The Domain Model

The domain of our first extension is very simple. The essential concept of our domain is the "product". All the important properties for us of a product and its "behavior" are defined in a class with the name MyVendor\StoreInventory\Domain\Model\Product. The code of this class is stored in a file with the name Product.php. The name of the file arises through the class name and the supplements of the file extension .php. This class file is stored in the folder EXT:store_inventory/Classes/Domain/Model/.

Tip

The labels of the classes always must reflect the folder structure. For example extbase expects the class MyVendor\MyExtension\FirstFolder\SecondFolder\File in the folder my_extension/Classes/FirstFolder/SecondFolder/File.php. Pay attention to the corresponding upper case of the folder names.

Below we have a view into this file, note that the class \MyVendor\StoreInventory\Domain\Model\Product must be derived from the extbase class \TYPO3\CMS\Extbase\DomainObject\AbstractEntity.

File: Classes/Domain/Model/Product.php
 <?php

 namespace MyVendor\StoreInventory\Domain\Model;

 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

 class Product extends AbstractEntity
 {

     /**
      * The name of the product
      *
      * @var string
      **/
     protected $name = '';

     /**
      * The description of the product
      *
      * @var string
      **/
     protected $description = '';

     /**
      * The quantity in the store inventory
      *
      * @var int
      **/
     protected $quantity = 0;

     /**
      * Product constructor.
      *
      * @param string $name
      * @param string $description
      * @param int $quantity
      */
     public function __construct($name = '', $description = '', $quantity = 0)
     {
         $this->setName($name);
         $this->setDescription($description);
         $this->setQuantity($quantity);
     }

     /**
      * Sets the name of the product
      *
      * @param string $name
      */
     public function setName(string $name)
     {
         $this->name = $name;
     }

     /**
      * Gets the name of the product
      *
      * @return string
      */
     public function getName()
     {
         return $this->name;
     }

     /**
      * Sets the description of the product
      *
      * @param string $description
      */
     public function setDescription(string $description)
     {
         $this->description = $description;
     }

     /**
      * Gets the description of the product
      *
      * @return string
      */
     public function getDescription()
     {
         return $this->description;
     }

     /**
      * Sets the quantity in the store inventory of the product
      *
      * @param int $quantity
      */
     public function setQuantity(int $quantity)
     {
         $this->quantity = $quantity;
     }

     /**
      * Gets the quantity in the store inventory of the product
      *
      * @return int
      */
     public function getQuantity()
     {
         return $this->quantity;
     }

 }

The product properties are designed as class variable $name, $description and $quantity and protected (encapsulated) against direct access from outside by the keyword protected. The property values can be set and/or read only by the methods setProperty() and getProperty() declared as public. Methods in this form are used very frequently and therefore they are generically named Getter and Setter for short.

Tip

At a first view, the methods appear to be cumbersome for accessing the class variables. However, they have several advantages: The Internals of the processing can be added or changed at a later time, without needing to make changes to the calling object. Also, for example, the reading can be permitted, without simultaneously allowing writing access. Later on, the tedious work needed to code these methods will be made for you by the Extension Builder. Moreover, most development environments offer macros or snippets for this purpose. Note that in different moments Extbase internally tries to fill a property $name over a method setName().

The method __construct() serves to guarantee a well defined state at the beginning of the life cycle of the object. Here the properties of the product are set with their respectively preset values.

Warning

In the declaration of the constructor, the argument $name is set with a default value (empty string) and thereupon optional. That is necessary so that Extbase can instantiate the class "empty" without a name must be delivered. With this Extbase offends against the pure doctrine because the constructor actually should guarantee the minimal configuration of the object Organization. In Extbase, this however is done with so-called validators (see the section "validating domain objects" in chapter 9).

Make Products Persistent

From the class \MyVendor\StoreInventory\Domain\Model\Product, now we already can generate instances – therefore concrete products with individual properties – at script run time. These are available however only in volatile form in the memory and are deleted by PHP after the page was produced completely by TYPO3. So that the products are available over a longer time, we must make it "permanent". Usually this happens in that they are stored into a database. Therefore first of all we create the database table necessary for that.

Tip

The creating of the database tables can be done by the Extension Builder.

TYPO3 CMS will do this for us if we register the corresponding SQL command in the file EXT:store_inventory/ext_tables.sql:

CREATE TABLE tx_storeinventory_domain_model_product (
   uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment,
   pid int(11) DEFAULT '0' NOT NULL,

   name varchar(255) DEFAULT '' NOT NULL,
   description text NOT NULL,
   quantity int(11) DEFAULT '0' NOT NULL,

   PRIMARY KEY (uid),
   KEY parent (pid)
);

This SQL command designs a new table with the corresponding columns. The columns uid and pid serve to the internal administration. Our product characteristics name, description and quantity appear as columns again.

The entries in the database are accessed by the Backend of TYPO3. The forms of the Backend are produced on the basis of a configuration, that is stored in a PHP array, the so-called Table-Configuration-Array (shortly TCA).

Tip

You find the documentation for the Table-Configuration-Array in the TCA Reference

Within the Extension we can access the data transparently by the repositories. "Transparently" means that we don't have to think about the type of the storage of the data when accessing the repositories.

So that the Backend know, how it should show the product data in a form, we must configure this for the table in the file EXT:store_inventory/Configuration/TCA/tx_storeinventory_domain_model_product.php.

The file returns an array with the all information, that TYPO3 needs to render the list and detail view for the records of this extension.

<?php
return [
   'ctrl' => [
      'title' => 'LLL:EXT:store_inventory/Resources/Private/Language/locallang_db.xlf:tx_storeinventory_domain_model_product',
      'label' => 'name',
      'iconfile' => 'EXT:store_inventory/Resources/Public/Icons/Product.svg'
   ],
   'columns' => [
      'name' => [
         'label' => 'LLL:EXT:store_inventory/Resources/Private/Language/locallang_db.xlf:tx_storeinventory_domain_model_product.item_label',
         'config' => [
            'type' => 'input',
            'size' => '20',
            'eval' => 'trim'
         ],
      ],
      'description' => [
         'label' => 'LLL:EXT:store_inventory/Resources/Private/Language/locallang_db.xlf:tx_storeinventory_domain_model_product.item_description',
         'config' => [
            'type' => 'text',
            'eval' => 'trim'
         ],
      ],
      'quantity' => [
         'label' => 'LLL:EXT:store_inventory/Resources/Private/Language/locallang_db.xlf:tx_storeinventory_domain_model_product.stock_quantity',
         'config' => [
            'type' => 'input',
            'size' => '4',
            'eval' => 'int'
         ],
      ],
   ],
   'types' => [
      '0' => ['showitem' => 'name, description, quantity'],
   ],
];

This file comprises several sections:

  • In the section ctrl, we can set some basic characteristics, like the title or which table column is to be used as label.
  • The section columns defines how each table field is diplayed and how it behaves in the backend.
  • The section types defines in which sequence the table columns are displayed.

Tip

The possibilities to impact the TCA are immense. You can find a complete listing of all options at TYPO3 Core APIs.

Attention

In TYPO3 7 the configuration was stored in the Array $GLOBALS['TCA'] in the file EXT:store_inventory/ext_tables.php, but the TCA configuration was moved to a file with the database table name suffixed with the file extension .php as filename. So here the Coding Guidelines can't be applied, which says filenames has to be in UpperCamelCase.

If you later want to overwrite the TCA from an existing database table, then you must use the file EXT:store_inventory/Configuration/TCA/Overrides/[tablename].php.

To define the group name for the new record wizard for new records of this extension you have to use a language file in the directory EXT:store_inventory/Resources/Private/Language/. The identifier extension.title is hard-coded in the TYPO3 core and you have to use this to define your own text.

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.0">
   <file source-language="en" datatype="plaintext" original="messages" date="2017-11-27T17:38:32Z"
        product-name="store_inventory">
      <header/>
      <body>
         <trans-unit id="extension.title">
            <source>Store Inventory Records</source>
         </trans-unit>
         <trans-unit id="tx_storeinventory_domain_model_product">
            <source>Product</source>
         </trans-unit>
         <trans-unit id="tx_storeinventory_domain_model_product.item_label">
            <source>Item Label</source>
         </trans-unit>
         <trans-unit id="tx_storeinventory_domain_model_product.item_description">
            <source>Item Description</source>
         </trans-unit>
         <trans-unit id="tx_storeinventory_domain_model_product.stock_quantity">
            <source>Stock Quantity</source>
         </trans-unit>
      </body>
   </file>
</xliff>

After we installed the extension, we can create our first products in the backend.

Like shown in image 4-2, we create a sys folder that takes the products (see 1 in figure 4-2). In this, we put some few new inventory data (see 2 in figure 4-2 and 3 in 4-3).

_images/figure-4-2.png

Figure 4-2: Create a new product

_images/figure-4-3.png

Figure 4-3: The new record wizard.

In this section we create a copy (or a model) of the reality, as we transferred only a part of the properties of the real products in software, that play a role in our domain. This model, which is abstracted of the real world, is completely designed with this.

In order to access the objects created in the backend, we create a Repository for the products. The MyVendor\StoreInventory\Domain\Repository\ProductRepository is an object, in that the products are discarded. We can request a Repository to find all (or certain) products and deliver it to us. The Repository class is very short in our case:

<?php

namespace MyVendor\StoreInventory\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\Repository;

/**
 * Class ProductRepository
 *
 * @package MyVendor\StoreInventory\Domain\Repository
 */
class ProductRepository extends Repository
{

}

Our ProductRepository must be derived by \TYPO3\CMS\Extbase\Persistence\Repository and inherits by this all methods. It can remain empty therefore in our simple example. We put the class file ProductRepository.php into the directory EXT:store_inventory/Classes/Domain/Repository/.

Controlling The Flow

The inventory created in the backend should be shown as a list in the frontend now. The creation of the HTML code out of the product objects to be shown is done by the view. Extbase uses the class \TYPO3\CMS\Fluid\View\TemplateView of the extension Fluid as default for the view.

The connection between the model and the view is the controller. It controls the sequences inside the extension and is responsible for the list action in our case. This includes locating the products which are to be shown, as well as transmission of the selected products to the responsible view.

The class name of the controller must end with Controller. Because our controller controls the display of the inventory we call it \MyVendor\StoreInventory\Controller\StoreInventoryController.

Tip

When naming a controller you are free inside the described frame. We advise to name a controller by what he "controls". In big projects these are specially the aggregate root objects (see section "aggregates" in chapter 2). For this we had also named our controller MyVendor\StoreInventory\Controller\ProductController.

In our simple example the controller looks like this:

<?php

namespace MyVendor\StoreInventory\Controller;

use MyVendor\StoreInventory\Domain\Repository\ProductRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

/**
 * Class StoreInventoryController
 *
 * @package MyVendor\StoreInventory\Controller
 */
class StoreInventoryController extends ActionController
{
    /**
     * @var ProductRepository
     */
    private $productRepository;

    /**
     * Inject the product repository
     *
     * @param \MyVendor\StoreInventory\Domain\Repository\ProductRepository $productRepository
     */
    public function injectProductRepository(ProductRepository $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    /**
     * List Action
     *
     * @return void
     */
    public function listAction()
    {
        $products = $this->productRepository->findAll();
        $this->view->assign('products', $products);
    }
}

Our \MyVendor\StoreInventory\Controller\StoreInventoryController must be derived from the \TYPO3\CMS\Extbase\Mvc\Controller\ActionController. It contains only the method listAction(). Extbase identifies all methods that ends with Action as actions - so as little plan of procedures.

In the method injectProductRepository() the ProductRepository is instanced. Now we can access the repository with $this->productRepository in all actions. It is important to get the repository with the injector method, because the repository implements the SingletonInterface and therefore we are not allowed to use the new keyword to get a instance of the repository. There must always be only one repository in memory!

In the listAction we get all products by the method findAll() of the repository. This method is implemented in the class \TYPO3\CMS\Extbase\Persistence\Repository. Which methods are also still for disposition you can read in chapter 6.

As result we get a \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult object with the product objects. We pass these objects to the view with $this->view->assign(…). Without our further assistance, at the end of the action the view is invited to return the passed content rendered based on a HTML template back to TYPO3.

return $this->view->render();

This line is declined by Extbase for us, if we not initiate the rendering process ourselves. So we can omit the line in our case.

Adding a template

In Extbase frontend templates are created in a subdirectory of EXT:store_inventory/Resources/Private/Templates - if not configured otherwise. The name of the subdirectory results in the last part of the controller class name without the Controller suffix. So the class name \MyVendor\StoreInventory\Controller\StoreInventoryController results in the directory name StoreInventory.

Below the directory StoreInventory we create the file with the HTML template. The name of the file results of the name of the action that is called with the suffix .html. So the filename in our case is List.html.

Note

You have to be aware that the filename is List.html and not ListAction.html. list is the name of the action. listAction() is the name of the corresponding method in the controller. The filename must be written in UpperCamelCase. Without additional configuration Extbase expects the suffix .html. It is also possible to use templates for other formats, such as JSON or XML. How these are called is described in chapter 8, section "Using different output formats".

The HTML template in the file EXT:store_inventory/Resources/Private/Templates/StoreInventory/List.html looks like the following:

<table border="1" cellspacing="1" cellpadding="5">
   <tr>
      <td>Product name</td>
      <td>Product description</td>
      <td>Quantity</td>
   </tr>
   <f:for each="{products}" as="product">
      <tr>
         <td align="top">{product.name}</td>
         <td align="top"><f:format.crop maxCharacters="100">{product.description}</f:format.crop></td>
         <td align="top">{product.quantity}</td>
      </tr>
   </f:for>
</table>

The inventory is rendered as a table. We can access the array with the product objects that we assigned to the view in the controller via $this->view->assign('products', $products) with {products}. Tags starting with <f: are Fluid-ViewHelper tags. The code inside the for tag is repeated for each product object in products. The ViewHelper f:crop tag shortens the containing text to the desired length. Within the brackets we can access the objects. If there is a dot after the object name we use the getters of this object. So {product.description} uses the getter method getDescription() from the domain model in file EXT:store_inventory/Classes/Domain/Model/Product.php.

A more detailed introduction about how to use Fluid-ViewHelper tags can be found in chapter 8, Styling the output with Fluid and also in the reference in appendix c.

We still do not have a result in the frontend until we created a frontend plugin.

Configuring The Plugin

An extension normally offers a so called Plugin for the output of the data. A plugin is a content element, that can be placed on a page like a text element or an image. It is a "virtual" collection of one or more actions. These actions could lie completely in different controllers. In our example there is only one controller action combination, namely StoreInventory->list. This combination is registered in the file ext_localconf.php, that we create in the top level of our extension directory.

<?php
defined('TYPO3_MODE') || die('Access denied.');

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
    'MyVendor.StoreInventory',
    'Pi1',
    [
        'StoreInventory' => 'list',
    ],
    // non-cacheable actions
    [
        'StoreInventory' => '',
    ],
);

With the first line we prevent of security reasons, that the PHP code can be called directly outside of TYPO3. The static method \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin() of the class offers several arguments. With the first we assign the extension key (it follows from the name of the extension directory) prefixed by the vendor namespace followed by a dot. This indicates, that we use namespaces for our php classes. With the second argument we give an unique name for the plugin (in UpperCamelCase notation). Because of historical reasons there is often used Pi1, but maybe it is better to use more meaningful names like "InventoryList". This is used later to clearly identify the plugin amongst other plugins on the page. The third argument is an array with all controller action combinations, the plugin can execute. The array key is the name of the controller (without the suffix Controller) and the array value is a comma separated list of all actions that are executable by the plugin. In our case this is the list action (also without the suffix Action). Thus the array ['Inventory' -> 'list'] allows to execute the method listAction() in the \MyVendor\StoreInventory\Controller\StoreInventoryController by the plugin. Per default all results of the actions are stored in the cache. If it is not desired for individual actions they can be specified by a fourth, optional argument. It is an array that has the same format as the previous. Now all actions are listed whose results should not be stored in the cache.

Note

Technically this is solved, that in the automatically generated TypoScript code a condition is added that if necessary call Extbase either as content object of the type USER (cached) or of type USER_INT (not cached). If you are on the quest of caching problems it is worth to look at the generated TypoScript.

After that the registration of the plugin follows, so it appears in the selection box of the content element Plugin. For this we insert the following line into a new file Configuration/TCA/Overrides/tt_content.php:

<?php

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
    'MyVendor.StoreInventory',
    'Pi1',
    'The Store Inventory List',
    'EXT:store_inventory/Resources/Public/Icons/Extension.svg'
);

The first argument is like the method configurePlugin() again the vendor namespace and extension key and the second is the name of the plugin. The third argument is an arbitrary, not to long, title of the plugin for the selection box of the content element. After installation of the extension we can insert the plugin on a page. Don't forget to set the sys folder, in which the products are stored, as the starting point (in our case "Inventory") in the plugin. Otherwise your products are not found (see figure 4-4).

_images/figure-4-4.png

Figure 4-4: Our plugin appears in the selection box of the content element.

The next call of the page, with the plugin on it, shows the inventory as a table (figure 4-5).

_images/figure-4-5.png

Figure 4-5: The output of the inventory in the front end

With this the first little Extbase extension is finished. The example was intentional held simple. It illustrates the important steps and the conventions we have to observe. For a full-grown extension there are some ingredients missing:

  • Real domain models have a high complexity. (Products for example have different prices and are assigned to product categories.)
  • Multiple different views have to be generated (single view, list view with search and so on).
  • The user of the web site should interact with the data by different modes (edit, create, sort and so on).
  • Input from the web site user has to check (validate) for consistence.

The sample extension we show from chapter 5 on, is significant multifaceted.

Modeling the Domain

In this chapter you are going to learn how to subject an area of the real world - a so called Domain - to a process of abstraction, in order to present it within an extension - the so called Domain model. This first step of extension development is the most important as well. It provides the foundation for all following stages of development. Don't worry: You'll only need common sense and a few tools to model a domain. The latter we are going to introduce in this chapter.

The central theme is provided by a complex example, which exhausts all essential characteristics of Extbase and Fluid. We will use this central theme constantly in the following chapters. The example is based on a real project, which was worked on at the same time as writing the text on behalf of SJR (Stadtjugendring Stuttgart e.V.). The SJR is an umbrella organization for about 400 youth organizations in Stuttgart, such as sports clubs, cultural clubs and religious communities. Among other things the SJR assists those societies to publish their offers in the Greater Stuttgart. Currently a lot of effort goes into research and involves high costs by printing flyer. In the future those offers will be managed by the youth organizations via the Internet. At the same time parents, children and teenagers should be able to find suitable offers and display them easily. You can download the originated extension with the extension-key sjr_offers at the extension repository (http://typo3.org/extensions/repository/view/sjr_offers/current/).

The example is also being used to demonstrate the approach of Test-Driven Development close to reality. We manage those Unit-Tests with extension phpunit, which is also available for download at the TER.

Tip

Please note that we, the authors are also in a proceeding learning process. With our today's knowledge, the printed code can be described as

good

in terms of the right use of Extbase and Fluid. Good software does not sprout as a one-off product even of a meticulous approach but as something which has to be optimized and readjusted steadily. Therefore we are advocates of an

agile

approach and a continuous

Refactoring

of software architecture and code.

Development with Extbase starts with, as stated before, the domain's characterization and their implementation as a model. If you are not familiar with the terms of Domain Driven Design, we suggest to scan through chapter 2.

The application domain

The main difference to the common approach to develop an extension is the concentration to the domain of the client - in our case to the domain of the offer management inside the Stadtjugendrings Stuttgart. First the essential core terms are to be extracted with which the employees of the Stadtjugendring interact daily. Terms of the domain of the content management system (e.g. "list view" or "search") are not playing roles by this. After a first project meeting with the contact persons of the SJR following characteristics were defined:

  • Every member organization can edit their contact data via the front end.
  • Every member organization can add, edit and delete their offers. The offers can be searched by the users and filtered by several criteria. Filter criteria are specially:
    • the duration of validity of the offer
    • the target audience for which the offer is straightened (mostly the age span, but also physical handicapped, swimmer and so on)
    • the target area for which the offer may be for interest (one or more city districts, nationwide), as well as
    • a free selectable category to which the offer is counted among (e.g. "sport offer" or "spare time")
  • The offers are output in list form and single view in the front end.
  • A single organization can be shown with its offers in a view.
  • The offers can be collected to a flyer which contains all information to the offers.
_images/figure-5-1.png

Figure 5-1: The still incoherent terms of the domain

These terms are the result of a process. Some of them are modified many times during the discussions. So the first chosen term of vendor developed to the term organization. Thereby a part was that the domain of the offer management not exist isolated inside the SJR. It is in fact embedded in the whole business. And there the term of member organization (or short organization) make more sense.

Tip

This development of a common language of developers (also ourselves) and domain experts (also the employees of the SJR) maybe is the most important part of the Domain Driven Design. In the literature you find the slightly bulky term Ubiquitous Language. Requirement for this process is that the developer take care for contact with the domain experts. This is notably in bigger projects in many cases not the case. More about the development of an Ubiquitous Language you can read about in chapter 2 in the section "Develop a common language".

First of all the located rules and operations are fixed down on paper. Here is an excerpt of the list:

  • "An offer can be assigned to multiple townships if they are located in whose catchment area."
  • "An organization can be assigned with multiple contact persons."
  • "An offer can be assigned with a contact person. The contact persons of the organization are shown as selection."
  • "Is there an offer without a contact person, the main contact of the organization is mentioned."
  • "An offer can show different attendance fees (e.g. for member and non member)."

The terms and rules are taken in relationship now. From the past thoughts a first draft of the domain is made, which you can see in figure 5-2. Every rectangle emblematise an object of the domain.The lower half of the bin shows an extract of its properties. Properties in italic writing of a parent object are holding references to the child objects. The chaining lines with an arrow point to those parent child relations. An additional rhomb symbolized an aggregate, also a package of child objects.

_images/figure-5-2.png

Figure 5-2: First draft of the domain.

Tip

This figure was created by a drawing program. In the communication with the customer we are aware of using drawing programs or UML tools, which would be constrict the workflow in this phase. Simple hand drafts are enough and are more accessible for the customer as technical diagrams.

Tip

You should have noticed that we make a big point of using a common language between developer and domain experts at the begin of the section and now we use permanent english descriptions for classes, methods and properties. Our experience told us that in the life time of an extension also developer get in contact with the source code, which are not potent to understand other languages as their own language and english. To not exclude them without change effort we choose english as lingua franca of the source code. This is especially true for extensions that - like our case - are made public via the extension repository.

Let us improve the first draft of the domain model. The offer has several property pairs, that belong together:

  • minimumAge and maximumAge (the minimal and maximal age)
  • minimumAttendants and
  • maximumAttendants (the minimal and maximal count of attendees)
  • firstDate and lastDate (date of beginning and end)

These property pairs are subject to own rules, that are not part of a single property. The minimal age limit (minimumAge) for example should not be greater than the maximum age limit (maximumAge). The observation of this rule can be done by the offer itself by a corresponding validation. But it rather belongs to the property pair. We store each property pair in an own domain object: AgeRange, AttendanceRange and DateRange. The outcome of this is the optimized second draft (see figure 5-3).

_images/figure-5-3.png

Figure 5-3: Second draft of the domain.

The specific domain objects have a common property, they have a lower and upper value. With this we can insert a class hierarchy in which the three domain objects succeed these common property from a domain object RangeConstraint. This has two generic properties: minimumValue and maximumValue (see figure 5-4).

_images/figure-5-4.png

Figure 5-4: Third draft of the domain.

Our domain model has reached a certain level of maturity. Of course there is certain space for more optimization. The risk exists, that we lose in the details, which will be irrelevant in a later revision. We suggest that you first implement a basic model and then - with additional knowledge of the yet unknown details of the model - improve it step by step. Let's start with our first lines of code.

Implementing the domain model

With the implementation of the domain there are two different approaches to: From top to bottom or from the bottom to the top. In the first case you start with those concepts, which include other or control them in the broadest sense. In our example the organization comes into account, the contact name, address, quotes, etc. "contains". In the second case, one starts with the basic concepts, such as the age range, address or region. The decision affects one of the two variants are on the way, how tests are written for the individual sections of the code. Ultimately, this is a matter of taste. We opted for the approach from top to bottom.

So we begin with the concept of organization. The file containing the class definition we put into the folder EXT:sjr_offers/Classes/Domain/Model/ and rename it according to the last part of the class name: Organization.php.

Note

Using the abbreviation EXT: we cut off the part of the path that leads to the extension folder. For more information on possible locations of the extension folder, see Chapter 4

Note

At this point in the development process, we find ourselves facing a fundamental decision: Should we develop our code secure by testing (using the method of test-driven development), or should we test only the final result? We know that the entry is in the method of test-driven development is a barrier. However, it is worthwhile to take the step because you will notice that you can increase your efficiency with TDD and save yourself a lot of frustrating experiences (eg in the painful search for an error). More information about the test-driven development, see chapter Chapter 2 in the section "Test-Driven Development". If you still decide to test only at the end, you can create the cycles of test development and skip the class in a train. We have cautioned, however.

First, we create the corresponding test class in the appropriate folder MyVendorSjrOffersDomainModelOrganizationTest EXT: sjr_offers/Tests /Domain /Model/. After that we write our test.

class OrganizationTest extends \TYPO3\CMS\Core\Tests\BaseTestCase
{

   /**
    * @test
    */
   public function anInstanceOfTheOrganizationCanBeConstructed()
   {
      $organization = new \MyVendor\SjrOffers\Domain\Model\Organization('Name');
      $this->assertEquals('Name', $organization->getName());
   }
}

Note

In the folder Tests you can select the folder structure freely. We, however, we recommend you to build the same structure as in classes. With an increasing number of unit tests you will be able to remain focused.

Note that our test class \MyVendor\SjrOffers\Domain\Model\OrganizationTest extends the class \TYPO3\CMS\Core\Tests\BaseTestCase of Extbase. Among other things, this class initializes the autoloader, which makes the inclusion of the class files require_once() obsolete.

Remarkable is the remarkably long method name. Method names of this kind are typical for Unit-Tests, because these names could be transformed into readable sentences. So please formulate the method name so that it describes, which result demonstrates a successful test. It gives you one (always current) documentation of the functionality of your extension. The test is not able to run because the appropriate class and its method getName() note yet exist. So we create first a minimum trunk of the class and its methods.

<?php
namespace MyVendor\SjrOffers\Domain\Model;

class Organization
{
   public function getName()
   {

   }
}

The test is now able to run, but failed as expected (see Figure 5-5).

_images/figure-5-5.jpg

Figure 5-5: message of the failed first test run

Only now we add just enough code that the test is successful:

<?php

namespace MyVendor\SjrOffers\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Organization extends AbstractEntity
{

   /**
    * @var string The name of the organization
    */
   protected $name;

   /**
    * Constructor
    */
   public function __construct($name)
   {
      $this->name = $name;
   }

   /**
    * Get the name of the organization
    */
   public function getName()
   {
      return $this->name;
   }
}

Currently, the name can only be set during the instantiation of the class, because only at this moment the Constructor __construct() is called. Because it should be possible at a later moment to change the name, we introduce a public method setName($name) with the according test. Note also that we have slightly modified the code in the constructor. We now use instead of the direct $this->name = $name the respective Setter. In the comment about the definition of property $name, the type of property is specified. In our case the name of the String. Thereby our class looks like as follows:

<?php
namespace MyVendor\SjrOffers\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Organization extends AbstractEntity
{

   /**
    * @var string The name of the organization
    */
   protected $name;

   /**
    * Constructor
    */
   public function __construct($name)
   {
      $this->setName($name);
   }

   /**
    * Set the name of the organization
    */
   public function setName($name)
   {
      $this->name = $name;
   }

   /**
    * Get the name of the organization
    */
   public function getName()
   {
      return $this->name;
   }
}

Now we implement step by step the class \MyVendor\SjrOffers\Domain\Model\Organization – always protected by our tests. Here we meet the requirement that there can be multiple contact persons. We want to keep ready in the capacity of contacts. So we set there a \TYPO3\CMS\Extbase\Persistence\ObjectStorage, which later takes the instances of the class \MyVendor\SjrOffers\Domain\Model\Person (or more precisely, the references to instances).

But the test at first:

/**
 * @test
 */
public function theContactsAreInitializedAsEmptyObjectStorage()
{
   $organization = new \MyVendor\SjrOffers\Domain\Model\Organization('Youth Organization');
   $this->assertEquals('\TYPO3\CMS\Extbase\Persistence\ObjectStorage',
   get_class($organization->getContacts()));
   $this->assertEquals(0, count($organization->getContacts()));
}

The contact person should be an instance of the class \MyVendor\SjrOffers\Domain\Model\Person. Since this class does not exist, one could make the next working to implement them. Thus we would probably get bogged down and jump from one class to the other. When writing unit tests can be upheld in so-called Mocks back. A mock is an object that can behave as if it were another. To "mock" an objects means to create a replacement object in targeted areas which will behave like the target object. Lets take a look at the test as an example:

/**
 * @test
 */
public function aContactCanBeAdded()
{
   $organization = new \MyVendor\SjrOffers\Domain\Model\Organization('Youth Organization');
   $mockContact = $this->getMock('\MyVendor\SjrOffers\Domain\Model\Person');
   $organization->addContact($mockContact);
   $this->assertTrue($organization->getContacts()->contains($mockContact));
}

The variable $mockContact contains the object, which behaves like an instance of the class \MyVendor\SjrOffers\Domain\Model\Person. Because of this we can now use the two methods addContact() and implement getContacts():

/**
 * The contacts of the organization
 *
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Person>
 */
protected $contacts;

/**
 * Adds a contact to the organization
 *
 * @param \MyVendor\SjrOffers\Domain\Model\Person The contact to be added
 * @return void
 */
public function addContact(\MyVendor\SjrOffers\Domain\Model\Person $contact)
{
   $this->contacts->attach($contact);
}

/**
 * Returns the contacts of the organization
 *
 * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Person> The contacts of the organization
 */
public function getContacts()
{
   return clone $this->contacts;
}

The comment about the definition of property is $contacts of crucial importance. Extbase "reads" the comment and concludes of it, that an ObjectStorage should be created and from which class the objects should which are in it included (also see chapter 3). Omission of this information would lead to a PHP exception: »Could not determine the type of the contained objects«.

Another special feature is the key word clone. With clone the method getContacts() clones the ObjectStorage before returning to the caller. Cloning causes that the objects are copied within the ObjectStorage and the reference to the original contacts is deleted. This is necessary because the caller does not know that it gets delivered an ObjectStorage instead of a PHP array. Would the caller manipulate now the containing objects without using the keyword clone, he would change the original data by accident.

In this section you could use the procedure for Test-Driven Development to meet. If you use in your development practice this procedure, you will be rewarded with a good feeling, to write code which is always functional - or at least compliant as expected. In English, one can use the expression »green bar feeling« (see figure 5-6). In the course we will not explicitly deal with the testing. But we use it always in the background.

_images/figure-5-6.jpg

Figure 5-6: By the test-driven development there is a residual »Green-Bar-Feeling«.

Implementing relationships between domain objects

Extbase supports three different types of hierarchical relationship between domain objects.

1:1-relationship An offer has in our case, exactly one period in which it is valid. The object Offer gets therefore a property dateRange, that is exactly referenced one time to the object DateRange.

1:n-relationship An organization can have multiple contacts. The object Organization therefore gets the property contacts that refers to any number of Contact objects.

m:n-relationship An offer on the one hand could be assigned to different categories. On the other hand, offers could be assigned to one category. Therefore receives the Offer object categories as a property.

Note

In addition to these relations an n: 1 relationship is often used: A company has a representative, a representative can work for several companies. In Extbase such a relationship is always mapped by an m: n relationship in which the number of child objects (agents) from the perspective of a parent object (enterprise) is limited to just one.

In a 1:1 relationship set and get methods are implemented. At the polyhydric 1:n and m:n relationships the add and remove methods are added.

setContacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $contacts)
getContacts()
addContact(\MyVendor\SjrOffers\Domain\Model\Contact $contact)
removeContact(\MyVendor\SjrOffers\Domain\Model\Contact $contact)

Be careful about the subtle differences here. The methods setContacts() and getContacts() refer simultaneously to all contacts. They expect and hence provide an ObjectStorage. The methods addContact() and removeContact() refer to a single Contact-Object that is added to the list or removed from. To extract a single contact from the list, let us first bring all contacts with getContacts() and then draw on the methods of the ObjectStorage to individual contacts.

The property offers, we proceed to the equivalent property contacts. The definition of the property offers includes in the comment two special annotations: @TYPO3\CMS\Extbase\Annotation\ORM\Lazy and @TYPO3\CMS\Extbase\Annotation\ORM\Cascade remove.

/**
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Offer> The offers the organization has to offer
 * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
 * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
 */
protected $offers;

By default Extbase invites all child objects with the parent object (so for example all offers of an organization). This behavior is called Eager-Loading. The annotation @TYPO3\CMS\Extbase\Annotation\ORM\Lazy causes Extbase to load the objects and build only when they are actually needed (lazy loading). This can be an appropriate data structure, e.g. many organizations, each with very many offers, that lead to a significant increase in speed.

Note

Beware, however, against all the properties provided by child objects with @TYPO3\CMS\Extbase\Annotation\ORM\Lazy, because this can lead to frequent loading of child objects. The ensuing, small-scaled database accesses reduces the performance and cause then the exact opposite of what you wanted to achieve with the lazy-loading.

The annotation @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") causes if the organization is deleted, the offers will be also deleted immediately. Extbase leaves usually persist unchanged all child objects.

Besides these two there are a few more annotations available, which will be used in other contexts (e.g. in the controller). The complete list of all by Extbase supported annotations, see the index.

So far, the impression may arise that domain models consist only of setters and getters. The domain objects, however, contain the main part of the business logic. In the following section, we add to our class \MyVendor\SjrOffers\Domain\Model\Organization a small part of this business logic.

Adding business logic to the domain objects

Part in deciding which part of the business logic belongs to a particular domain model, you can be guided by what questions we can ask the domain object in the "real" world. We can ask the organization for the list of all contacts, for example. So we implement a method getAllContacts(). In contrast to the previously implemented method getContacts() this should deliver in addition to these direct contacts of the organization but also provide the contact for all services. For this the organization has to pass through all their offerings and add one if there is a existing contact to the result. This is especially useful for administrators of an organization. The implementation is as follows:

public function getAllContacts()
{
   $contacts = $this->getContacts();
   foreach ($this->getOffers() as $offer) {
      $contact = $offer->getContact();
      if (is_object($contact)) {
         $contacts->attach($contact);
      }
   }
   return $contacts;
}

The organization gets first by using getContacts() their direct contact. Therefore all the offers are iterated with foreach. The query is_object() is necessary because the offer returns NULL if a contact is missing. The contact person of the offer will be added to the ObjectStorage as the variable $contacts. At this point it becomes clear how important is the keyword clone of the method getContacts(). If the ObjectStorage would not have been cloned, we would add all the contacts of the offers of the organization as primary contacts. In addition, we benefit here by a special property of the ObjectStorage: It takes one and the same object only once. If it had not this quality, a person who is assigned to multiple offers, would appear more than once in the list.

Note

Alternatively to the method getAllOffers() in the domain object Organization, you could have also implement a method in an OfferRepository findAllContacts($organization). There it would have been possible to get the offers by a little bit more complex query direct from the database. But we follow the important basic rule of the Domain-Driven Design at this place, which says that a element of an aggregate (the totality of all the terms contained in the organization) should be accessed by the root object (Aggregate-Root). The alternative we choose only, if the iterating through all the offers causes actually a performance problem.

We finish that implementation of the class from \MyVendor\SjrOffers\Domain\Model\Organization and turn to the class \MyVendor\SjrOffers\Domain\Model\Offer. The basic approach here is not fundamentally different from the last. Let's take a look at the (shortened) class which emphasizes some peculiarities.

<?php
namespace MyVendor\SjrOffers\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Offer extends AbstractEntity
{

   /**
    * The organization of the offer
    *
    * @var \MyVendor\SjrOffers\Domain\Model\Organization
    */
   protected $organization;

   protected $title;
   protected $image;
   protected $teaser;
   protected $description;
   protected $services;
   protected $dates;
   protected $venue;

   /**
    * @var \MyVendor\SjrOffers\Domain\Model\AgeRange The age range of the offer.
    */
   protected $ageRange;

   /**
    * @var \MyVendor\SjrOffers\Domain\Model\DateRange The date range of the offer.
    */
   protected $dateRange;

   /**
    * @var \MyVendor\SjrOffers\Domain\Model\AttendanceRange The attendance range.
    */
   protected $attendanceRange;

   /**
    * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\AttendanceFee>
    */
   protected $attendanceFees;

   /**
    * The contact of the offer
    *
    * @var \MyVendor\SjrOffers\Domain\Model\Person
    */
   protected $contact;

   /**
    * The categories the offer is assigned to
    *
    * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Category>
    */
   protected $categories;

   /**
    * The regions the offer is available
    *
    * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Region>
    */
   protected $regions;

   /**
    * The title of the offer
    *
    * @param string $title
    */
   public function __construct($title)
   {
      $this->setTitle($title);
      $this->setAttendanceFees(new \TYPO3\CMS\Extbase\Persistence\ObjectStorage);
      $this->setCategories(new \TYPO3\CMS\Extbase\Persistence\ObjectStorage);
      $this->setRegions(new \TYPO3\CMS\Extbase\Persistence\ObjectStorage);
   }
   // Getter and Setter
}

The property organization of the object Offer includes a back reference of the offering organization. We have introduced them to have later a quick access at the collection of all the offers on the organization. Thus, we deviate slightly from the pure "doctrine" from the Domain-Driven Design. This means, among other things, that you access the child object Offer only on the root object Organization (Aggregate Root) should.

Note

The back reference in the form of property organization is similar in some aspects to the foreign key organization in the database table tx_sjroffers_domain_model_offer. This foreign key contains the uid and links the offer-tuple associated with the corresponding tuple from the table tx_sjroffers_domain_model_organization. We use here sent from the fact that the integer value of the uid is converted by Extbase because of the annotation @var MyVendorSjrOffersDomainModelOrganization into the corresponding object Organization.

The properties ageRange, dateRange and attendanceRange contains the objects of the type RangeConstraint. These classes we have to create at first and had created mocks for the in our tests.

Then we add the class to the necessary getters and setters. Even class internally it is advisable to access using setter and getter. So there (maybe later) the actual code is used before the setting of the property value is done. The title which is passed to the constructor is not set by $this->title = $title, but by $this->setTitle($title).

The objects that are kept at the properties attendanceFees, categories and regions, we set off in an object storage. At the three properties so we initialize an empty ObjectStorage each in the constructor.

Use inheritance in class hierarchies

The domain objects and their relationships can be mapped generally good in a tree hierarchy. Such a hierarchy can be found in figure 5-2. Organisations include Offers. In turn, offers "contained" small fees. In our domain model is however, a second kind of hierarchy: the class hierarchy. In this hierarchy we have set that the objects ageRange, dateRange and attendanceRange are a concretization. They inherit properties and methods. In modelling we consider the opposite approach: You have to search properties which are in common in that objects. This may be abstracted and then stores them in a new, higher-level class out. In Figure 5-7 and Figure 5-8, we have shown again the procedure separately.

_images/figure-5-7.png

Figure 5-7: Creating the Range Constraints

_images/figure-5-8.png

Figure 5-8: Abstraction of the properties and the shift in RangeConstraint

In the class Range Constraint all common properties and methods are gathered. The properties minimumValue and maximumValue are of the type integer by default. But the inherited class DateRange expected as property values not Numbers, but objects of type DateTime. So we »override« the type definition in the class DateRange and set the type DateTime. The class RangeConstraint looks like as follows (Comments were partly removed):

<?php

namespace MyVendor\SjrOffers\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;

abstract class RangeConstraint extends AbstractValueObject
{

   /**
    * @var int The minimum value
    */
   protected $minimumValue;

   /**
    * @var int The maximum value
    */
   protected $maximumValue;

   /**
    * @param int $minimumValue
    * @param int $maximumValue
    */
   public function __construct($minimumValue = null, $maximumValue = null)
   {
      $this->setMinimumValue($minimumValue);
      $this->setMaximumValue($maximumValue);
   }

   /**
    * @param mixed The minimum value
    * @return void
    */
   public function setMinimumValue($minimumValue = null)
   {
      $this->minimumValue = $this->normalizeValue($minimumValue);
   }

   public function getMinimumValue()
   {
      return $this->minimumValue;
   }

   /**
    * @param mixed The maximum value
    * @return void
    */
   public function setMaximumValue($maximumValue = null)
   {
      $this->maximumValue = $this->normalizeValue($maximumValue);
   }

   public function getMaximumValue()
   {
      return $this->maximumValue;
   }

   public function normalizeValue($value = null)
   {
      if ($value !== null && $value !== '') {
         $value = abs(int()$value);
      } else {
         $value = null;
      }
      return $value;
   }
}

All of this range objects have beyond their properties and methods further things in common. They have no identity other than the whole of their property values. It is not important for the offer, which age range »from 12 till 15 years« the range object is assigned to receive. Of importance is only the two values 12 and 15. Are two offers designed for the same age range, so Extbase must therefore do not pay attention to the fact that it assigns a particular age range with the values 12 and 15 to the offer. Value Objects can e.g. occur multiple times in memory, and therefore any be copied while it was driving in the major entities of the ambiguity problem. The internal handling is much more easier because of this. We thus have to Extbase to treat the object as a Value Object Constraint Range by inheriting from the appropriate Extbase class: extends TYPO3CMSExtbaseDomainObjectAbstractValueObject.

The class rank by the keyword abstract constraint was marked as abstract. Thus we prevent the Range object itself is instantiated.

We have furthermore implement a method normalizeValue(). These »adjusted« the value coming from the outside before they are assigned to a property. This is overwritten in the class DateRange together with the above mentioned type definitions:

DateRange.php
<?php
namespace MyVendor\SjrOffers\Domain\Model;

class DateRange extends \MyVendor\SjrOffers\Domain\Model\RangeConstraint
   implements \MyVendor\SjrOffers\Domain\Model\DateRangeInterface {

/**
 * The minimum value
 *
 * @var \MyVendor\SjrOffers\Domain\Model\DateTime
 */
protected $minimumValue;

/**
 * The maximum value
 *
 * @var \MyVendor\SjrOffers\Domain\Model\DateTime
 */
protected $maximumValue;

public function normalizeValue($value = NULL)
{
   if (!($value instanceof \DateTime)) {
      $value = null;
   }
   return $value;
}

The class DateRange implements furthermore the interface DateRangeInterface. The interface on is own is empty and is only used for identification. This makes especially sense for the other two Range Objects. These both implement the NumericRangeInterface. The classes AgeRange and AttendanceRange Classes are otherwise empty hulls, because they inherit all the properties and methods from the object RangeConstraint.

 <?php

 namespace MyVendor\SjrOffers\Domain\Model;

 class AgeRange extends \MyVendor\SjrOffers\Domain\Model\RangeConstraint
    implements \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface
 {
 }

class AttendanceRange extends \MyVendor\SjrOffers\Domain\Model\RangeConstraint
    implements \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface
 {
 }

 interface \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface
 {
 }

 interface \MyVendor\SjrOffers\Domain\Model\DateRangeInterface
 {
 }

We have implemented the terms age range, number of participants and offer an adequate period in domain models. Let us now turn to the object administrator. Also here we use another, less obvious class hierarchy. Extbase provides two domain models available: FrontendUser and FrontendUserGroup. They are the equivalents of the website user or user group's website, created in the backend of TYPO3 can be assigned and managed. The two Extbase classes are filled with this data, which are stored in two database tables or fe_users fe_groups. The database fields in these tables each have a corresponding property in the domain model. The names of the properties were indeed subjected to the convention that the field names lower_underscore spelling in the name of the property is converted into lowerCamelCase notation. But they are otherwise taken 1:1 and therefore - in contrast to our previous practice - not so meaningful. Behind the property isOnline we would suspect a value of the type Boolean. But it contains the date on which the website has started the last page user demand. The class hierarchy is shown in Figure 5-9.

_images/figure-5-9.png

Figure 5-9: The Administrator class inherits all the properties and methods of the class Extbase FrontendUser.

Validate the domain objects

The business logic often looks for rules as to the properties of the data domain objects needs to be. Here are some examples of such so-called invariants in our Extension:

  • The length of the title of an offer must not be under 3 characters and not over 50 characters.
  • The start date of an offer may not be later than the end date.
  • If one partner identifies an e-mail address, it must be valid.

You can implement the check of the details of these invariants directly the domain model. In the setter of the title of an offer would stand the following code:

public function setTitle($title)
{
  if (strlen($title) > 3 && strlen($title) < 50) {
     $this->title = $title;
  }
}

This has several disadvantages:

  • This examination, had to be done at any point, which manipulates the title (risk of failure to examination,

and risk of duplicated code by cut-and-paste). * The definition of the rule is more or less far away from the definition of the property (poor readability of the code). * A change in an option of a rule ("80 rather than 50 characters") requires an intervention possibly at a difficult to-find places.

Therefore Extbase offers an alternative about Annotations. Let us have a look at the definitions of the properties of the class Offer definition - this time with all the comments:

 /**
 * The title of the offer
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"minimum": 3, "maximum": 50})
 */
protected $title;

/**
 * A single image of the offer
 *
 * @var string
 */
protected $image;

/**
 * The teaser of the offer. A line of text.
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"maximum": 150})
 */
protected $teaser;

/**
 * The description of the offer. A longer text.
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"maximum": 2000})
 */
protected $description;

/**
 * The services of the offer.
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"maximum": 1000})
 */
protected $services;

/**
 * The textual description of the dates. E.g. "Monday to Friday, 8-12"
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"maximum": 1000})
 */
protected $dates;

/**
 * The venue of the offer.
 *
 * @var string
 * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"maximum": 1000})
 */
protected $venue;

/**
 * The age range of the offer.
 *
 * @var \MyVendor\SjrOffers\Domain\Model\AgeRange
 * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\SjrOffers\Domain\Validator\RangeConstraintValidator")
 */
protected $ageRange;

/**
 * The date range of the offer is valid.
 *
 * @var \MyVendor\SjrOffers\Domain\Model\DateRange
 * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\SjrOffers\Domain\Validator\RangeConstraintValidator")
 */
protected $dateRange;

/**
 * The attendance range of the offer.
 *
 * @var \MyVendor\SjrOffers\Domain\Model\AttendanceRange
 * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\SjrOffers\Domain\Validator\RangeConstraintValidator")
 */
protected $attendanceRange;

/**
 * The attendance fees of the offer
 *
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\AttendanceFee>
 */
protected $attendanceFees;

/**
 * The contact of the offer
 *
 * @var \MyVendor\SjrOffers\Domain\Model\Person
 */
protected $contact;

/**
 * The categories the offer is assigned to
 *
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Category>
 */
protected $categories;

/**
 * The regions the offer is available
 *
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\SjrOffers\Domain\Model\Region>
 */
protected $regions;

The values of some properties must be checked to control the offer being classified as valid. Which rule will narrow, about the annotation @TYPO3\CMS\Extbase\Annotation\Validate("…") is set. The annotation @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"minimum": 3, "maximum": 50}) on the property title effected, for example, that the title length is not smaller than 3 characters and not longer than 50 characters. The validator StringLength is provided by Extbase of charge. The name of the associated class is \\TYPO3\\CMS\\Extbase\\Validation\\Validator\\StringLengthValidator. The options minimum and maximum are passed to the Validator and are evaluated there.

With the validation, we conclude the modeling and implementation of the domain at first. With that achieved, it is possible to store domain objects, which where generated during a page view in memory. All data will be lost at the end of the page view. In order for the domain objects are permanently on the grouting, the persistence layer is to be set up accordingly.

Setting up the persistence layer

As already insinuated in previous chapters it is the Persistence Layer which takes care about conserving Domain Objects durably. Thus, in this chapter we will discuss the steps being important for doing that.

It is important to get a grasp of the lifecycle of a Domain Object to fully understand the Persistence Layer. When instantiating a Domain Object we essentially put its data into a certain sector of the Main Memory. At that time, it is in a transient, i.e. volatile, state. When TYPO3 delivered the rendered website the sector of the Main Memory is freed by PHP and may be overwritten with other data. Thereby the saved data will be lost together with the Domain Object.

Note

You can read more about the lifecycle of objects in Chapter 2 "Lifecycle of Objects".

If Domain Objects should be available within several page loads they have to be transferred into a persistent state. This is being done in Extbase by putting the Domain objects into a Repository. When the script is finished doing its work, the Repository takes care about saving the volatile data in a durable saving state. Normally this is the database which is used by TYPO3 but may, for example, also be a text file.

This chapter deals with the steps that have to be taken to make the data enclosed by a Domain Object durably persistent. Firstly, the Domain Objects have to be prepared to make them persistable by the underlying database. Most extensions get their input data through the Backend, thus the input forms which receive the data have to be configured. Subsequently, the Repositories are created which serve as an interface to the Domain Object.

Those steps are sufficient for most of the simple extensions, however, bigger projects often need more complex queries to the Persistence Layer as you will see with the example extension SjrOffers. This exemplary extension serves with offers for parents or teenagers for satisfying their certain needs. An example would be something like "Please show me all existing offers for a 5-years child close to the town centre". It should be possible to make such a request through the website, which should contain an input form. This form then sends the request to the extension. There, the Repository will compile the appropriate offers and send them back to the website. Thus, we will implement the method findDemanded($demand) in a final step to find those offers. Let's start with the database.

Preparing the database

The preparation of the database primarily covers the creation of the database tables. The commands for the creation are done in SQL. The code is put into the file ext_tables.sql which itself is located on the top level of the extension directory.

Note

One of the main purposes of Extbase is to abstract the access of the underlying persistence solution. Thus, you normally won't get in touch with native SQL-Queries in day-to-day development, especially when you let the kickstarter auto-generate your database tables (have a look at Chapter 10). However, you should fully understand all the peculiarities of your database.

Preparing the tables of the Domain Objects

Let's have a look at the definition of the database table which will aggregate the objects of the class \MyVendor\SjrOffers\Domain\Model\Organization:

ext_tables.sql
CREATE TABLE tx_sjroffers_domain_model_organization (
   uid int(11) unsigned DEFAULT 0 NOT NULL auto_increment,
   pid int(11) DEFAULT 0 NOT NULL,

   name varchar(255) NOT NULL,
   address text NOT NULL,
   telephone_number varchar(80) NOT NULL,
   telefax_number varchar(80) NOT NULL,
   url varchar(80) NOT NULL,
   email_address varchar(80) NOT NULL,
   description text NOT NULL,
   image varchar(255) NOT NULL,
   contacts int(11) NOT NULL,
   offers int(11) NOT NULL,
   administrator int(11) NOT NULL,

   tstamp int(11) unsigned DEFAULT 0 NOT NULL,
   crdate int(11) unsigned DEFAULT 0 NOT NULL,
   deleted tinyint(4) unsigned DEFAULT 0 NOT NULL,
   hidden tinyint(4) unsigned DEFAULT 0 NOT NULL,
   sys_language_uid int(11) DEFAULT 0 NOT NULL,
   l18n_parent int(11) DEFAULT 0 NOT NULL,
   l18n_diffsource mediumblob NOT NULL,
   access_group int(11) DEFAULT 0 NOT NULL,

   PRIMARY KEY (uid),
   KEY parent (pid),
);

CREATE TABLE instructs the database to create a new table named tx_sjroffers_domain_model_organization. The table's name is derived from the Extbase convention which describes that class names are written in lowercase retaining the underlines.

Note

The file ext_tables.sql is executed whenever the extension is installed. Nevertheless, TYPO3 is smart enough not to overwrite an existing database table. On the contrary it deduces the differences between the new and the existing table and just adds those additional information.

The definition of the database table fields name, address etc. follow in round brackets. Some of them should sound familiar since they meet the properties' names of the class Organization. However, the Extbase convention is still present: Field names are written in lowercase_underscore and are derived from the property's name by prefixing every uppercase letter with an underscore and subsequently writing the whole construct in lowercase. The value of the property address is saved in the field address. The property telephoneNumber transforms into the field name telephone_number etc.

However, the table definition contains additional fields that have no correlating property in the class Organisation. They are needed by TYPO3 for providing functionalities like Localization, Workspaces and Versioning. The according TYPO3-specific fields are:

uid Describes the unique identifier associated with every record within a database table (unique record identifier).

pid Every page within a TYPO3 installation has a unique page identifier (Page ID or PID). This may be System Folder (SysFolder) or even used to refer to the Frontend page of a Content Element.

crdate The UNIX timestamp of the date the record was created (creation date). This date may differ from the creation date of the Domain Object.

tstamp The UNIX timestamp of the date the record was changed the last time. Most often this relates to the timestamp the Domain Model was changed the last time.

deleted When this fields' value differs from 0, TYPO3 handles its corresponding record as physically deleted. Thus it won't show off neither in the Backend nor in the Frontend. It can be restored by either setting the field to 0 or much more easily be dug out by using the system extension Recycler. Extbase will set this field whenever a record is deleted if the field is existing. Additionally, it holds all the references to other records so that whole Aggregates may be restored.

hidden The record set won't show up in the Frontend if this field's value differs from 0.

starttime UNIX timestamp when the record first showed up in the Frontend. Extbase respects that when it reads the values from the database thus it creates the Domain Objects not before that time.

endtime UNIX timestamp when the record got "invisible" in the Frontend (i.e. when its hidden value got non-zero). As well as with the starttime field, Extbase respects this value when it reads from the database.

cruser_id The UID of the Backend user who created the record. Currently, Extbase neither sets nor reads this value. Whenever a Domain Object is created in the Frontend, this field is set to 0.

fe_group A list of Frontend-Usergroups which can access the record set. The logged-in Frontend-User must at least belong to this group.

sys_language_uid The language's UID which belongs to this record set. Languages may be created using the globe at the root of the page tree.

l18n_parent The UID of the translation source, i.e. the record set of the original language (default).

l18n_diffsource A serialized form of the translation source. This is useful for showing the differences between the original language and its translation in the Backend.

t3ver_oid, t3ver_id, t3ver_wsid, t3ver_label, t3ver_state, t3ver_stage, t3ver_count, t3ver_tstamp, t3ver_move_id, t3ver_origuid Those fields are used by TYPO3 for the management of the Versioning and the Workspaces. If they are not needed, they may be omitted.

All fields but uid and pid are optional. However, we highly recommend creating the fields deleted, hidden, fe_group, starttime and endtime additionally to ensure the access control. If the Domain Objects are multi-lingual the fields sys_language_uid, l18n_parent, l18n_diffsource are essential.

Note

More information about Localization and Multilingualism can be found in Chapter 9.

Note

The order of the field definitions is arbitrary. Nevertheless, it is recommended to set the fields which are frequently inspected in a SQL-tool like phpMyAdmin at the beginning since they are consequential arranged at the left in the table view and show up without any annoying scrolling.

Every line in a table definition holds various statements. The field type follows the field's name. In the following case the field tstamp takes an unsigned Integer number (unsigned). The default value that is used if no value is given when the record is created is the number 0 (DEFAULT 0). The field value mustn't be NULL (NOT NULL) and the field definitions are separated by a comma.`tstamp int(11) unsigned DEFAULT '0' NOT NULL,`

Note

Note that in case of the field tstamp the field definition is chosen somewhat awkwardly by TYPO3 since the value 0 corresponds to the UNIX timestamp of the date 1.1.1970 00:00. It would be better to use the value NULL for the meaning of 'undefined' instead of 0. However, this inconsistency draws through the whole TYPO3 core thus it is very difficult to correct this weakness.

SQL databases provide various field types. Which one of them is chosen for persisting a Domain Property depends on the kind and length of the value that is to be saved: Text strings are saved as char, varchar or text. Using char and varchar, their length may be set in round brackets. Whereas char may hold up to 255 characters with a fixed size, varchar fields can hold up to 65.535 Bytes as well as fields containing the type text. But record sets cannot be grouped or sorted by fields with type text and they cannot have a standard value. The type should, nonetheless, still be chosen if grouping, sorting and setting a standard value can be resigned. TYPO3 is usually used with the database engine MySQL which additionally provides the developer with the field types mediumtext and longtext.

Warning

Always spare memory but, on the other side, don't be too penurious with Strings since their values are simply cut-off when exceeding the datatype range. This concludes with bugs and errors that are hard to find.

Integers are meant to have the field types smallint, int and bigint. If working with a MySQL database there are additionally the fields tinyint and mediumint available. All those integer field types differ only in the number range for which they can be used (see table 6-1).

Floating-point types can be stored in fields with the type decimal or float, where decimal describes a fixed-size field type. E.g. a field defined with decimal(6,2) takes a number with 6 digits before and 2 digits after the comma, the standard value is (10,0). The keyword numeric is a synonym for decimal. The type float takes numbers from -1.79E+308 to 1.79E+308, again, the range may be limited by a number (from 1 to 53) in round brackets.

Besides of the already defined field types there are some other types that are, however, rather uncommon in the environment of TYPO3. Examples for those uncommon types are date and datetime for date values following the pattern YYYY-MM-DD resp. YYYY-MM-DD HH:MM:SS or boolean data types for the values true and false.

Note

As with field names of char and varchar the integer types may take ranges as numbers in round brackets upon their definition, e.g. int(11). But in contrast they do NOT describe the count of digits or Bytes that can be stored in that field. Instead, the number serves as a hint for SQL management tools for correctly filling up the field type's column with whitespaces. Thus, the fields defined with int(11) as well as with int(3)`can store the same value ranges from `-21.474.838.648 to +21.474.838.647. It's still useful to define integer data fields with their maximum count of digits because this befriends the database computing complex JOIN`s. Thus the rule of thumb is: Always use the maximum possible value in round brackets when defining `integer fields (see table 6-1) plus one additional space for the sign value when using signed numbers.

Table 6-1 sums up all possible use-cases with their recommended data types.

Table 6-1: Comparison of different field types

What should be saved? Field type Field range
Character strings, texts char(n) max. 255 Bytes **
(names, addresses, product descriptions etc.; images that are managed by TYPO3)

varchar(n)

text

mediumtext*

longtext*

max. n Bytes (up to max. n = 65.553)

max. 65.553 Bytes

max. 16.777.215 Bytes

max. 4.294.967.295 Bytes

Integer types

(item counts, ages etc.; in TYPO3 as well as dates and boolean properties)

tinyint[(n)] *

smallint[(n)]

mediumint[(n)] *

int[(n)]

bigint[(n)]

8 Bit
-128 to +128 (signed; n=4) 0 to 255(unsigned; n=3)
16 Bit
-32.768 to +32.767 (signed; n=6) 0 to 65535 (unsigned; n=5)
24 Bit
-8.388.608 to +8.388.607 (signed; n=9) 0 to 16.777.215 (unsigned; n=8)
32 Bit
-2.147.483.648 to +2.147.483.647 (signed; n=11) 0 to 4.294.967.295 (unsigned; n=10)
64 Bit
-9.223.372.036.854.775.808 to
+9.223.372.036.854.775.807 (signed; n=20)
0 to 18.446.744.073.709.551.615 (unsigned; n=19)
Floating-point decimal(p[,s]) (saved as string of characters)
(amounts of money, measurement values etc.) float(p[,s]) -1.79E+308 to +1.79E+308 (eventually limited through the precision)

p = precision

s = scale

n = Number of Bytes resp. Number of spaces in a column (int)

* MySQL only

** The number of signs depends on the text-encoding and may differ from the number of Bytes. E.g. Using text-encoding ISO-8859-1 one Byte contains exactly one character whereas in UTF-8 one character is saved in up to 3 Bytes (Multibyte Encoding).

Configure Relationships between Objects

There are many relations between the objects in our Domain that have to be persisted in the database for being able to resolve them at a later time. It depends on the type of relationship how they can be persisted and Extbase distinguishes between several types as already defined in Chapter 5 "Implement Relationships between Domain Objects". In memorial to Chapter 5, following a short summary of the types:

1:1-Relationship: An offer has exactly one range of time when it is valid (dateRange).

1:n-Relationship: An organisation may have several contact persons whereas each contact person is in charge for exactly one organisation.

n:1-Relationship: An organisation has exactly one administrator but this administrator may be in charge for several organisations.

m:n-Relationships: An offer may be connected with several categories and on the other hand one certain category may be attached to several offers.

There are several techniques for persisting those relationships in a Relational Database:

Comma-separated list (Comma-separated values, CSV): In a field of the parent object's table the UIDs of their child objects are stored as comma-separated values.

Foreign Keys: The UID of the child object's table is stored in a field of the parent table or vice versa.

Intermediate Table: For persisting the information of the relationships between two classes a special table is created - the Intermediate Table. The UID of the parent table as well as the UID of the child table is stored as an own data set of the Intermediate Table. Additionally, there can be stored information about assorting, the visibility and the access control information. They concern the relationship of the related objects and not the objects themselves.

Warning

Do not store data in the Intermediate Table that concern the Domain. Though TYPO3 supports this (especially in combination with Inline Relational Record Editing (IRRE) but this is always a sign that further improvements can be made to your Domain Model. Intermediate Tables are and should always be tools for storing relationships and nothing else.

Let's say you want to store a CD with its containing music tracks: CD -- m:n (Intermediate Table) -- Song. The track number number may be stored in a field of the Intermediate Table. However, the track should be stored as a separate Domain Object and the connection be realized as CD -- 1:n -- Track -- n:1 -- Song.

Not all combinations of relationship type and its technical persistence are sane. Table 6-2 lists all combinations that are y possible and useful, (y) technically possible but rarely sensible, no either technically impossible or not supported.

  1:1 1:n n:1 m:n
Comma-separated list (y) (y) n (y)
Foreign Keys y y y n
Intermediate Table n n y y
Combination of reference type and technical storage

Thus, every type of relationship has its own recommended form of persistence that will be explained subsequently. In case of a 1:1-relationship the UID of the child object will be saved in the Foreign Key field of the parent object:

CREATE TABLE tx_sjroffers_domain_model_offer(
   # …
   date_range int(11) DEFAULT '0' NOT NULL,
   # …
);

The default values of '0' (or the NULL values if they were explicitly allowed) stand for "The dateRange has not yet been assigned.". Later on, Extbase computes the DateRange-object out of the uid.

In a 1:n relationship there are two possibilities. Either every uid value is stored as comma-separated list in a field of the parent object. Or every child object contains the parental uid in a foreign key field. The further is mostly used by TYPO3 in its core but we discourage that solution because of its drawbacks: For example, comma-separated fields complicate the search and hinder the indexation in the database. Furthermore, the creation and deletion of child objects is complex and time-consuming. Thus, using comma-separated lists for modelling relationships should only be used with database tables that cannot be altered in their structure (e.g. external sources, the TYPO3-Core). We highly recommend the latter method which stores a Foreign Key in the table of the child object. In TYPO3, the parental object's table holds a separate value for counting the sum of the corresponding child objects. Consecutively, we list the definition of the relationship between the organization and its offers of the class \MyVendor\SjrOffers\Domain\Model\Organization. This will later be filled with instances of the class \MyVendor\SjrOffers\Domain\Model\Offer.

CREATE TABLE tx_sjroffers_domain_model_organization (
   # …
   offers int(11) NOT NULL,
   # …
);

The definition of the table tx_sjroffers_domain_model_offer holds the field organization as a Foreign Key.

CREATE TABLE tx_sjroffers_domain_model_offer (
   # …
   organization int(11) NOT NULL,
   # …
);

Note

Extbase stores the relationship between organization and the offer as a 1:1-relationship. This can be taken as advantage by adding the property organization to the class \MyVendor\SjrOffers\Domain\Model\Offer. Consequently, it will be filled with an instance of the class \MyVendor\SjrOffers\Domain\Model\Organization and can therefore be used as a backreference from the offer to its corresponding organization.

The n:1 and the 1:n are pretty similar to each other, it is just a matter of perspective. Concerning the persistence of them, one is served with two possibilities. Either the relationship can be stored as Foreign Key in the parent object or an Intermediate Table can be used which is described consecutively. We prefer the Foreign Key method because it is easier to manage.

The fourth kind of relationship which is known by Extbase is the m:n-relationship. This uses an Intermediate Table for persistence and stores the uid of the parent object as well as the uid of the child object. The table definitions for a relationship between offer and category are as follows:

CREATE TABLE tx_sjroffers_domain_model_offer (
     # ...
     categories int(11) NOT NULL,
     # ...
);

CREATE TABLE tx_sjroffers_offer_category_mm (
     uid int(10) unsigned DEFAULT '0' NOT NULL auto_increment,
     pid int(11) DEFAULT '0' NOT NULL,

     uid_local int(10) unsigned NOT NULL,
     uid_foreign int(10) unsigned NOT NULL,
     sorting int(10) unsigned NOT NULL,
     sorting_foreign int(10) unsigned NOT NULL,

     tstamp int(10) unsigned NOT NULL,
     crdate int(10) unsigned NOT NULL,
     hidden tinyint(3) unsigned DEFAULT '0' NOT NULL,

     PRIMARY KEY (uid),
     KEY parent (pid)
);

The table tx_sjroffers_domain_model_offer holds a field categories as a counter (and as a counter-part to the categories property). The Intermediate Table holds the field uid_local that takes the uid of an offer as well as a field uid_foreign for the uid of the category. Using the values in the fields sorting and sorting_foreign Extbase evaluates the order of the objects in the ObjectStorage. While sorting orders the categories from the perspective of an offer, sorting_foreign evaluates the order of the offers from the perspective of a category.

Note

The name of the Intermediate Table can be chosen freely. However, the following convention is recommended: tx_myext_leftobject_rightobject_mm.

For now, we have proper SQL definitions of the Domain's tables for each kind of relationship. In the next step we configure the representation of the database tables and their interaction with the Backend.

Configure the Backend Input Forms

In our sample application the data of our extension should be editable in the Backend by the editors of the youth club organisation and - within certain limitations - in the Frontend as well providing functionalities for creation, update and deletion of the organisation's data. In this chapter we firstly configure the Backend's form inputs for easy access to the database's contents. The forms providing the management functionalities are stored in a certain PHP-Array called Table Configuration Array (TCA). The TCA is stored in a file with the database table name suffixed with .php in the directory Configuration/TCA/ Example: The TCA for the database table tx_sjroffers_domain_model_organization is therefore in the file Configuration/TCA/tx_sjroffers_domain_model_organization.php.

Note

The configuration options that can be set in the TCA are very extensive and a broad description of them would cause the book being bursting at its seams. However, each and every option is well documented in the Online-documentation TCA Reference

Firstly, you should dip into the top layer of the TCA hierarchy. The TCA for table tx_sjroffers_domain_model_organization is in the file Configuration/TCA/tx_sjroffers_domain_model_organization.php and has this structure:

<?php
return [
    'ctrl' => [
        // …
    ],
    'columns' => [
        // …
    ],
    'types' => [
        // …
    ],
];

The structure for the other tables like tx_sjroffers_domain_model_offer and tx_sjroffers_domain_model_person are equal.

These returned arrays in these files will be added to one big array $GLOBALS['TCA']. You can debug the configuration for the table tx_sjroffers_domain_model_organization in the TYPO3 backend module System -> Configuration -> $GLOBALS['TCA'] (Table configuration array) -> tx_sjroffers_domain_model_organization

The associative array, that is returned, contains all information of all the tables of the TYPO3 instance. Thus, we use the key tx_sjroffers_domain_model_organization and as value we use another nested Array holding the configurations of the corresponding table. Then again, this Array is separated into several parts whose names are the key of the nested Array.

<?php
return [
   'ctrl' => [
      // …
   ],
   'interface' => [
      // …
   ],
   'types' => [
      // …
   ]
   'palettes' => [
      // …
   ],
   'columns' => [
      'first_fieldname' => [
         // …
      ],
      'second_fieldname' => [
         // …
      ],
   ],
];

Subsequently, you will find the names of the parts and their meaning.

ctrl This area contains configuration options that are used overall the scope of the table. This covers the naming of the table in the Backend, which table fields contain which meta data and the behavior of the table on creation and movement of its row sets. Meta data cover information about Visibility and Access Control (e.g. disabled, hidden, starttime, endtime, fe_group), data about the history of changes (e.g. versioningWS, crdate, tstamp as well as data for the localization of data sets (e.g. languageField).

interface This part contains information about the representation of the table data in the Backend's List Module. The key showRecordFieldList contains a comma-separated list of field values whose values will be shown in the info dialogue of the table. This dialogue may be reached through a right-click on the icon of the row set choosing the Info option. Altering the option maxDBListItems you can set how many row sets will be shown in the List Module without switching to the detail view of the database table. Then again, the number of row sets shown on a page in this perspective may be set via maxSingleDBListItems. Setting the option always_description to true the corresponding helping texts always show up.

types This section defines the appearance of the Input Form for creation and update of a row set. You can define several layout types by listing several elements in the array types. The key of all those elements is their type (usually a number) and their value is another nested array which itself usually contains one element with showItem as key and a list of comma-separated field names which should emerge at the Input Form. An example of the table tx_sjroffers_domain_model_organization is:

'types' => [
   '0' => ['showitem' => 'hidden,status,name,address;;1;;description,contacts,offers,administrator'],
],

Even though the behavior and the appearance of the table fields is configured in the section columns they must be explicitly listed in here so that they show up in the input form. This spares the trouble you would have when commenting out or moving away code that is already configured and a corresponding field should just be hidden or the overall order of the Input Form's table fields should be changed.

The behaviour and the appearance of a field may be altered through several additional parameters - as well as with the field address. The notion convention of those additional params may seem a bit unfamiliar since they are appended behind the fieldname and separated through a semi-colon. On first position there is the fieldname; on the second an alternative naming fieldname; at third place follows the number of the palette (refer to the next book section); the fourth position holds extensive options which are separated through colons and the last place contains information about the appearance (e.g. color and structure). The information at the fourth place allow the use of the Rich Text Editor. For a full list of the options refer to the already mentioned TYPO3-Online documentation for the TYPO3-Core API.

palettes Palettes are used to collect occasionally used fields and show them up on demand. The Backend user therefore has to choose the Extended View in the Backend's List module. Palettes are connected to a durable visible field. An example from the table tx_sjroffers_domain_model_organization is:

'palettes' => [
   '1' => ['showitem' => 'telephone_number,telefax_number,url,email_address'],
],

The structure is the same as in the section types where address;;1;; refers to the palette with the number 1.

columns This Array contains information about the behavior and the appearance in the Input Form of every table field. The fieldname is the key and, again, the value is a nested array holding the field's corresponding configuration. The field configuration for the input of the name of an organisation would be as follows:

'name' => [
   'exclude' => false,
   'label' => 'LLL:EXT:sjr_offers/Resources/Private/Language/locallang_db.xml:tx_sjroffers_domain_model_organization.name',
   'config' => [
      'type' => 'input',
      'size' => 20,
      'eval' => 'trim,required',
      'max' => 256
   ],
],

The field name is name. Firstly, we define some options that are independent from the field's type. This contains foremostly the field label (label), the conditions for the visibility of the field (exclude, displayCond) as well as information for its localization (l10n_mode, l10n_cat). The fieldname is, in our case, localized and will be taken from a language file (head to Ch. 9).

The array connected to config contains the field type and its corresponding configuration. TYPO3 serves with a great range of pre-defined field types, e.g. text fields, date fields or selection fields. Each and every type has its own presentation and procession options. Consecutively, you will find a list of all the field types with their usual configurations:

Field type "input"

The input field type accepts one-line character strings like names and telephone numbers. The configuration of a name field (see Fig. 6-1) looks as follows:

'name' => [
   'label' => 'Organization\'s name',
   'config' => [
      'type' => 'input',
      'size' => 20,
      'eval' => 'trim,required',
      'max' => 256
   ],
],

The given string will be truncated to 256 signs ('max' => 256), ending spaces will be dropped (trim) and the persistence of an empty field will be prevented (required).

Note

Important: When a field is defined as required, the Domain Model must have the annotation @NotEmpty for the validator.

_images/figure-6-1.png

Figure 6-1: An example for the field type "input" used as a name field.

The field type input may be used for date and time inputs:

'minimum_value' => [
   'label' => 'valid since',
   'config' => [
      'type' => 'input',
      'size' => 8,
      'checkbox' => '',
      'eval' => 'date'
   ],
],

The value then will be tested for being given in a sane date format. Simultaneously, this leads to the rendering of a collapsable calendar page in shape of an icon right to the input field which is shown in Fig. 6-2:

_images/figure-6-2.png

Figure 6-2: An example for the field type "input" used as a date field.

Field type "text"

The text field type may contain multi-line formatted or unformatted texts e.g. product descriptions, addresses or news items. The indication of the lines (rows) and the columns (cols) specifies the area of the text input field.

'address' => [
   'label' => 'Address:',
   'config' => [
      'type' => 'text',
      'cols' => 20,
      'rows' => 3
   ],
],
_images/figure-6-3.png

Figure 6-3: An example for the field type "text".

Field type "check"

The field type check allows the definition of a single option (see Fig. 6-4)
e.g. you can define whether a rowset should be hidden or not.
'hidden' => [
   'label' => 'Hide:',
   'config' => [
      'type' => 'check'
   ],
],
_images/figure-6-4.png

Figure 6-4: An example for the field type "check" for a single option.

Several related options which can be individually selected can be grouped to a field (see Fig. 6-5). This may be helpful e.g. for a selection of valid weekdays or recommended training levels of a certain exercise.

'level' => [
   'exclude' => true,
   'label' => 'Property for',
   'config' => [
      'type' => 'check',
      'eval' => 'required,unique',
      'cols' => 5,
      'default' => 31,
      'items' => [
         ['Level 1',''],
         ['Level 2',''],
         ['Level 3',''],
         ['Level 4',''],
         ['Level 5',''],
      ],
   ],
],

<!-- TODO: look, how math is being processed for the coming exp-value --> The value that is written to the database is of type Integer. This will be computed by bitwise addition of the checkboxes states (which can be 1 or 0). The first element (Level 1) is the least significant Bit (= 2^0 = 1). The second element is one level above (= 2^1 = 2), the third element will then be (= 2^2 = 4) etc. The selection in the following Figure (see Fig. 6-5) would lead to the following Bit-Pattern (= binary-written number): 00101. This binary number is equivalent to the Integer value 5.

_images/figure-6-5.png

Figure 6-5: An example for the field type "check" for several options that are grouped together.

Field type "radio"

The field type radio is for choosing one unique value for a given property (see Fig. 6-6), e.g. the sex of a person or the color of a product.

'gender' => [
   'label' => 'Sex',
   'config' => [
      'type' => 'radio',
      'default' => 'm',
      'items' => [
         ['male', 'm'],
         ['female', 'f'],
      ],
   ],
],

The options (items) are given in an array and each option is an array itself containing the label and the key used for persist the selected option in the database.

_images/figure-6-6.png

Figure 6-6: An example for the field type "radio".

Field type "select"

The field type "select" provides a space-saving way to render multiple values (see Fig. 6-7). Examples could be a member status, a product color or a region.

'status' => [
   'exclude' => false,
   'label' => 'Status',
   'config' => [
      'type' => 'select',
      'foreign_table' => 'tx_sjroffers_domain_model_status',
      'maxitems' => 1
   ],
],

The options are taken from another database table (foreign_table) and by setting maxitems to 1 (which is standard) the selection box will be limited to exactly one showed item.

_images/figure-6-7.png

Figure 6-7: An example for the field type "select" showing a selection box.

The type select may also be used to select a whole subset of values. This is used for categories, tags or contact persons (see Fig. 6-8).

'categories' => [
   'exclude' => true,
   'label' => 'Categories',
   'config' => [
      'type' => 'select',
      'size' => 10,
      'minitems' => 0,
      'maxitems' => 9999,
      'autoSizeMax' => 5,
      'multiple' => 0,
      'foreign_table' => 'tx_sjroffers_domain_model_category',
      'MM' => 'tx_sjroffers_offer_category_mm'
   ],
],

Again, this takes the options of another table but it holds the references in a temporary table tx_sjroffers_offer_category_mm.

_images/figure-6-8.png

Figure 6-8: An example for the field type "select".

Field type "group"

The "group" field type is very flexible in its use. It can be used to manage references to resources to the filesystem or rowsets of a database (see Fig. 6-9).

'images' => [
   'label' => 'Images',
   'config' => [
      'type' => 'group',
      'internal_type' => 'file',
      'allowed' => 'gif,jpg',
      'max_size' => 1000,
      'uploadfolder' => 'uploads/pics/',
      'show_thumbs' => 1,
      'size' => 3,
      'minitems' => 0,
      'maxitems' => 200,
      'autoSizeMax' => 10
   ],
],

The combination of type and internal_type specifies the field's type. Besides of file there exist several other types like file_reference, folder and db. While file leads to a copy of the original file which is then being referenced the type file_reference leads to a direct reference to the original file. db leads to a direct reference to a database rowset.

Note

Extbase currently does not resolve relations to other rowsets since the relations are currently persisted as comma-separated values in the database field (pic1.jpg,pic2.jpg,pic3.jpg). However, this can be resolved in a ViewHelper in Fluid when the data shows up (see the entry f:image in Appendix C)

_images/figure-6-9.png

Figure 6-9: An example for the field type "group".

Field type "none"

Fields of this type show up the raw data values which cannot be edited (see Fig. 6-10).

'date' => [
   'label' => 'Datum (Timestamp)',
   'config' => [
      'type' => 'none'
   ],
],

In contrast to the date field with the type input there is no evaluation as with 'eval' => 'date'. The timestamp which is set in the database will be shown as a raw number.

_images/figure-6-10.png

Figure 6-10: An example for the field type "none" for a date field.

Field type "passthrough"

The field type "passthrough" is for data which are processed internally but cannot be edited or viewed in the form. An example for that would be information to references (foreign keys).

'organization' => [
   'config' => [
      'type' => 'passthrough'
   ],
],

This field configuration in the database table tx_sjroffers_domain_model_offer has the effect that the property organization of the Offer-object will be filled with the correct object.

Field type "user"

User generates free definable form fields which can be processed by any PHP function. For further information, refer to the documentation which is available online and to the TYPO3-Code API.

Field type "flex"

The field type "flex" manages complex inline form fields (FlexForms). The form data will be saved as XML data structure in the database fields. Extbase uses FlexForms for persisting plugin configuration but not to save domain data. If your plugin data will be rather complex we encourage you to design an own backend module for them (refer to Ch. 10).

Field type "inline"

The field type "inline" is for saving complex Aggregates of the Domain (see Fig. 6-11). Basis of this field type is the so called Inline Relational Record Editing (IRRE) which powers the creation, update and deletion of Domain-objects of whole Aggregates in a single Input Form. Without IRRE the Domain-objects must be edited and connected each by itself which would require an intermediate save. This technique is a comfortable tool for managing complex Aggregates. All the possibilities provided by IRRE are well documented, refer to the TYPO3 TCA Reference.

'offers' => [
   'label' => 'Offers',
   'config' => [
      'type' => 'inline',
      'foreign_table' => 'tx_sjroffers_domain_model_offer',
      'foreign_field' => 'organization',
      'maxitems' => 9999
   ],
],

The configuration is almost identical to the field type "select". However, there are several more possibilities for the configuration of the management and the representation of the connected objects.

_images/figure-6-11.png

Figure 6-11: An example for the field type "irre".

Extbase supports the most important aspects of IRRE with only one exception: IRRE allows a temporary table of an m:n-relationship to be enhanced by additional fields which can hold Domain data. An example: Assume that we want to connect a CD to it's containing music tracks, whereas a CD can contain multiple tracks and one track can be present on several CD's. Thus, we can derive the following temporary table:

CD --1:n-- Temporary-Table --n:1-- Title

The corresponding IRRE-Configuration looks as follows:

'titles' => [
   'label' => 'Track Title',
   'config' => [
      'type' => 'inline',
      'foreign_table' => 'tx_myext_cd_title_mm',
      'foreign_field' => 'uid_local',
      'foreign_selector' => 'uid_foreign'
   ],
],

The IRRE-Tutorial describes this configuration as "state-of-the-art" for m:n-relationships. The option foreign_selector leads to a selection box for the music titles. Currently, IRRE only supports this option for m:n-relationships.

Every music track on the CD is given a unique track number. However, the track number is a neither a property of the CD nor that of a track. It's semantically corresponding to the relationship between them. Thus, IRRE provides the option to persist them within the temporary table and this can always be modelled into the Domain model which gets the following structure: CD --1:n-- Track --n:1-- Title.

Let's change the configuration of the table tx_myext_domain_model_track to a simple 1:n-relationship with cd as a foreign key.

'tracks' => [
   'label' => 'Track',
   'config' => [
      'type' => 'inline',
      'foreign_table' => 'tx_myext_domain_model_track',
      'foreign_field' => 'cd'
   ],
],

However, Extbase does not support the persistence of additional Domain data in the temporary table because the corresponding Domain object does not exist. Nevertheless, the Online documentation of the TYPO3-Core API describes the second, more correct option for configuring m:n-relationships within IRRE. It depends on a plain temporary table. The following example shows off the configuration of products with their according categories:

'categories' => [
   'label' => 'Categories',
   'config' => [
      'type' => 'inline',
      'foreign_table' => 'tx_myext_domain_model_category',
      'MM' => 'tx_myext_product_category_mm'
   ],
],

This second option deserves some additional kudos because it does not need a TCA-configuration for the temporary table tx_myext_product_category_mm because you don't need to show up or edit the whole table or parts of it in the Backend; the SQL definition is sufficiently.

Those are the summarized configuration possibilities within the TCA. As you see, the huge count of options can be overwhelming for the novice. But in future, they can be auto-generated by the Extension Builder (refer to Ch. 10).

As already mentioned, the TCA is stored in a file with the database table name as filename suffixed with .php in the directory Configuration/TCA/Overrides

tx_sjroffers_domain_model_organization.php
<?php
return [
   'ctrl' => [
      'title' => 'LLL:EXT:sjr_offers/Resources/Private/Language/locallang_db.xlf:tx_sjroffers_domain_model_organization',
      'label' => 'name',
      'tstamp' => 'tstamp',
      'crdate' => 'crdate',
      'languageField' => 'sys_language_uid',
      'transOrigPointerField' => 'l18n_parent',
      'transOrigDiffSourceField'  => 'l18n_diffsource',
      'prependAtCopy' => 'LLL:EXT:lang/locallang_general.xlf:LGL.prependAtCopy',
      'copyAfterDuplFields' => 'sys_language_uid',
      'useColumnsForDefaultValues' => 'sys_language_uid',
      'delete' => 'deleted',
      'enablecolumns' => [
         'disabled' => 'hidden'
      ],
      'iconfile' => 'EXT:sj_roffers/Resources/Public/Icons/Icon_tx_sjroffers_domain_model_organization.svg'
   ],
   'interface' => [
      'showRecordFieldList' => 'status,name,address,telephone_number,telefax_number,url,email_address,description,contacts,offers,administrator'
   ],
   'types' => [
      '0' => ['hidden,status,name,address;;1;;,description, contacts,offers,administrator']
   ],
   'palettes' => [
      '1' => ['showitem' => 'telephone_number,telefax_number,url,email_address']
   ],
   'columns' => [
      'sys_language_uid' => [],
      'l18n_parent' => [],
      'l18n_diffsource' => [],
      'hidden' => [],
      'status' => [],
      'name' => [],
      'address' => [],
      'telephone_number' => [],
      'telefax_number' => [],
      'url' => [],
      'email_address' => [],
      'description' => [],
      'contacts' => [],
      'offers' => [],
      'administrator' => [],
   ],
];

The tables of all the Domain objects are defined like this.

Now we can create a directory (SysDirectory) which will contain all the data sets. Let's create our first organization (see Fig. 6-12).

_images/figure-6-12.png

Figure 6-12: The input form for creating an organization with all its offers.

Now you can set up the whole data structure. In our project this allows the offer-provider to set up some example data and thus we could do some early integration tests. However, we can not access the given data because we still miss the Repositories that will be defined in the following section.

Creating the Repositories

We have already introduced the Repositories in Chapter 3. They serve with capabilities to save and reaccess our objects. We set up such a Repository object for every Aggregate-Root object which are, then again, used for accessing all the Aggregate-Root's corresponding objects. In our concrete example \MyVendor\SjrOffers\Domain\Model\Organization is such an Aggregate-Root object. The Repository's class name is derived from the class name of the Aggregate-Root object concatenated with the suffix Repository. The Repository needs to extend the class \TYPO3\CMS\Extbase\Persistence\Repository. The class file \MyVendor\ SjrOffers\Domain\Repository\OrganizationRepository will be saved in the directory EXT:sjr_ offers/Classes/Domain/Repository/. Thus the directory Repository is on the same hierarchy-level as the directory Model. In our case, the class body remains empty because all the important functionalities are already generically implemented in the super-class \TYPO3\CMS\Extbase\Persistence\Repository.

<?php

namespace MyVendor\SjrOffers\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\Repository;

class OrganizationRepository extends Repository
{

}

We create a \MyVendor\SjrOffers\Domain\Repository\OfferRepository exactly the same way but we will later extend it with own methods for accessing offers. It's very likely that we have to access the other objects for categories, regions and update data of contact information of certain persons independent of the offers or their organizations. Thus we define some additional Repositories for those objects for easier access from the Frontend.

Note

You have to resist the urge to define Repositories for each object and limit yourself to a minimal number of Repositories. Instead, you should define the access methods within the Aggregate-Root objects as find methods.

\TYPO3\CMS\Extbase\Persistence\Repository serves with the following methods which are of course accessible and overridable in the extending child derivations:

add($object)

Adds an object to the Repository which is then persistent in the sense of Domain-Driven Design. But be careful, it will not written (and enhanced with an UID) to the database before finishing the loop through the Extension, to be precise after the call of the method persistAll() of the PersistenceManager.

remove($object) and removeAll()

The opponent of add(). An object will be removed from the Repository and is gonna be deleted from the database after finishing the Extension's loop. The method removeAll() empties the whole Repository.

update($modifiedObject)

An existing object in the Repository will be updated with the properties of the given object. Extbase finds the to-be-updated object by the uid of the given object and throws an exception if it does not exist.

findAll() and countAll()

Returns all the Repository's objects that are currently persisted in the database. However, this slightly confusing behaviour is intended. Whereas findAll() returns an Array of objects the method countAll() only counts the currently persisted objects (if the database backend is of type SQL it just executes the query SELECT COUNT) and returns an Integer number.

findByProperty($value), findOneByProperty($value) and countByProperty($value)

Those three methods help by finding one or several objects and by counting all the objects that correspond to the given value. The substring Property must be replaced by the uppercase-written property name of the class that is managed by the Repository. The methods then only return the objects as well count the objects whose properties Property correspond to the given value. Whereas the method findByProperty() returns an Array of all the matching objects, the method findOneByProperty() only returns the first object that was found. That is, assuming that no certain sorting order was given, the order in which the objects were created in the Backend. Last but not least, the method countByProperty() returns the count of the objects that would be returned if findByProperty() was given the same value and is, of course, an Integer number.

createQuery()

In opposite to the methods above, this function does not manage objects in the Repository. Instead, it returns a Query object which can be helpful to assemble own queries to the Storage-Backend. The details for this procedure will be given in the following chapter.

Before accessing the defined objects on the Repository you need to tell Extbase on which pages on TYPO3's page tree (see below for TYPO3's concept of the page tree) it should seek and file the objects. Without any further definitions Extbase will use the page tree's root (the globe).

Generally there are three cases which need to be distinguished: Persisting a newly created object, reaccessing an existing object and updating the properties of an existing object. When creating a new object Extbase determines the destination pages in the following rule hierarchy:

<procedure>

  • If, as already described in Chapter 4, the option source is checked then the objects will be searched in the corresponding pages
  • If the TypoScript-Setup of the page contains the definition of plugin.tx_*extensionname*.persistence.storagePid with a comma-separated list of PIDs then those pages will be consulted.
  • If the TypoScript-Setup of the page contains the definition of config.tx_extbase.persistence.storagePid with a comma-separated list of PIDs then those pages will be consulted.
  • If none of the cases from above applies, then the root page will be consulted for the objects.

</procedure>

When insertion of new Domain objects happens, then the procedure will be as follows:

<procedure>

  • If there's a TypoScript setup at plugin.tx_extensionname.persistence.classes.*FullClassName*.newRecordStoragePid with a single page value, then this is gonna be used.
  • If there's a TypoScript setup at config. tx_extbase.persistence.classes.*FullClassName*.newRecordStoragePid with a single page value, the this is gonna be used.
  • If none of the cases above apply, then the object will be inserted at the first item in the list of search pages. So to say, in the end the root page (the one with the globe) is gonna be used for insertion.

</procedure>

When updating the Domain objects their PID is not changed. However, you can implement the property pid in your domain object with its corresponding set- and get-methods. Then a domain object may be moved from one page to another by setting a new pid.

Note

Most occurring mistake for seemingly empty Repositories is a mis-configured Storage-PID. Thus, you should firstly evaluate the Template Module whether it is set correctly.

Besides of the options for setting the Page UID there exist two other possibilities for configuring the Persistence Layer: enableAutomaticCacheClearing and updateReferenceIndex. The option config.tx_extbase.persistence.enableAutomaticCacheClearing = 1 within the TypoScript setup leads to a deletion of the cache whenever the data is rewritten. This option is normally activated.

Note

Usually, data sets will be saved into Folders in the Page Tree though the pages using those data sets will be somewhere else. If their cache should be cleared as well then you should set up their PIDs in the field TSConfig of the page's preferences of the directory. For example, out Offers will be shown on the pages with the PIDs 23 and 26 (let's say for a Single and a List View). Then we will configure the variable TCEMAIN.clearCacheCmd = 23,26 in the page preferences of the SysFolder. Then the cache of these pages will be cleared as well and changes of an offer will show up immediately.

Internally, TYPO3 manages an index of all relationships between two data sets the so-called RefIndex. Due to this index it's possible to show the number of associated data sets in the list module's column [Ref.]. By clicking on the number you get further information about the incoming and outgoing references of the dataset. This index is automatically updated when any data sets get edited. The configuration config.tx_extbase.persistence.updateReferenceIndex = 1 effects an update when data sets get edited in the Frontend though it is normally deactivated due to its huge effects on performance.

Before calling a Repository's methods they need to be instantiated at first with use of the ObjectManager or via dependency injection.

Dependency injection example from chapter 2
 /**
  * @var ProductRepository
  */
 private $productRepository;

 /**
  * Inject the product repository
  *
  * @param \MyVendor\StoreInventory\Domain\Repository\ProductRepository $productRepository
  */
 public function injectProductRepository(ProductRepository $productRepository)
 {
     $this->productRepository = $productRepository;
 }

Warning

Repositories are Singletons therefore there may only exist one instance of each class at one time of script-execution. If a new instance is requested, the system will prove whether an instance of the requested object exists and will instead of creating a new object return the existing one. This is ensured by using the dependency injection. Thus, never ever use the PHP syntax keyword new for creating a repository object because the objects that are placed there will not be automatically persisted.

Now you know all the basic tools for durable persistation and recovering of your objects. Extbase offers a lot more sophisticated functionalities for special needs because it happens quite frequently that the standard methods of saving and seeking data in a repository are not sufficient for the individual case. Thus Extbase let's you define individual requests without losing the existing abstractions of the existing persistence backend. Additionally, Extbase let's you use "foreign" data sources which are most often data tables of the same database.

Individual Database Queries

The previous descriptions about generic methods of queries to a Repository are sufficient for simple use-cases. However, there are many cases where they are not adequate and require more flexible solutions. On the requirements list of our application is the functionality to print a list of all the offers. In natural language this would sound as follows:

  • "Find all the offers for a certain region."
  • "Find all the offers corresponding to a certain category."
  • "Find all the offers containing a certain word."
  • "Find offers that are associated to a selected set of organizations."

There are principally two ways of implementing such methods. On the one hand, you could request all the offers from the Backend and filter them manually. This is flexible and easy to implement. On the other hand, you could write a request matching your criteria exactly and execute it. Contrary to the first case, this method would only build the objects that are really needed, which positively affects the performance of your application.

Note

You may start developing your application using the first method and then, seeing your application growing, veer to the second method. Luckily, all the changes are encapsulated in the Repository, so you don't have to change any code out of the Persistence Backend.

You can use Extbase's Query-object for implementing individual queries by giving it all the essential information needed for a qualified request to the database backend. This information contains:

  • The request's class (Type) to which the request applies.
  • An (optional) Constraint which restricts the result set.
  • (Optional) Parameters which configure a section of the result set by a limit or an offset.
  • (Optional) Parameters concerning the Orderings of the result set.

Within a Repository you can create a Query object by using the command $this->createQuery(). The Query object is already customized to the class which is managed by the Repository. Thus, the result set only consists of objects of that class, i.e. it consists of Offer objects within the OfferRepository. After giving all needed information to the Query object (detailed information will be given later on) you execute the request by using execute() which returns a sorted Array with the properly instantiated objects (or a via limit and offset customized section of it). For example, the generic Repository method findAll() looks as follows:

/**
 * Returns all objects of this repository.
 *
 * @return QueryResultInterface|array
 * @api
 */
public function findAll()
{
    return $this->createQuery()->execute();
}

More Repository search methods are available:

/**
 * Finds an object matching the given unique id.
 *
 * @param int $uid The unique id of the object to find
 * @return object The matching object if found, otherwise NULL
 * @api
 */
public function findByUid($uid)
{
    return $this->findByIdentifier($uid);
}
/**
 * Finds an object matching the given identifier.
 *
 * @param int $uid The identifier of the object to find
 * @return object The matching object if found, otherwise NULL
 * @api
 */
public function findByIdentifier($identifier)
{
    return $this->persistenceManager->getObjectByIdentifier($identifier, $this->objectType);
}

You must set the storagePid to the allowed pages before a query will find any records. By default a query only searches on the root page with id=0

Typoscript example of an extension for the page ids 12 and 22

plugin.tx_[lowercasedextensionname] {
  persistence {
    storagePid = 12,22
  }
}

In the first simple use-case we don't apply any constraining parameter to the Query object. However, we have to define such a parameter to implement the first specified request, "Find all the offers for a certain region". Thus, the corresponding method looks as follows:

public function findInRegion(\MyVendor\SjrOffers\Domain\Model\Region $region)
{
    $query = $this->createQuery();
    $query->matching($query->contains('regions', $region));
    return $query->execute();
}

Using the method matching() we give the Query the following condition: The property regions of the object Offer (which is managed by the Repository) should contain the region that is referenced by the variable $region. The method contains() returns a Constraint object. The Query object has some other methods each of which returns a Constraint object. Those methods may be roughly split into two groups: Comparing operations and Boolean operations. The first group leads to a comparison between the value of a given property and another operand. The latter mentioned operations connect two conditions to one condition by the rules of Boolean Algebra and may respectively negate a result. The following Comparing operations are acceptable:

equals($propertyName, $operand, $caseSensitive = TRUE)
in($propertyName, $operand)
contains($propertyName, $operand)
like($propertyName, $operand)
lessThan($propertyName, $operand)
lessThanOrEqual($propertyName, $operand)
greaterThan($propertyName, $operand)
greaterThanOrEqual($propertyName, $operand)

The method equals() executes a simple comparison between the property's value and the operand which may be a simple PHP data type or a Domain object.

Contrarily, the methods in() and contains() accept multi-value data types as arguments (e.g. Array, ObjectStorage). As in() checks if a single-valued property exists in a multi-value operand, the latter method contains() checks if a multi-valued property contains a single-valued operand. The opposite of the introduced method findInRegion() is findOfferedBy() which accepts a multi-valued operand ($organizations).

public function findOfferedBy(array $organizations)
{
    $query = $this->createQuery();
    $query->matching($query->in('organization', $organizations));
    return $query->execute();
}

Note

The methods in() and contains() were introduced in Extbase version 1.1. (TYPO3 4.3) If you pass an empty multi-valued property value or an empty multi-valued operand (e.g. an empty Array) to them you always get a false as return value for the test. Thus you have to prove if the operand $organizations of the method call $query->in('organization', $organizations) contains sane values or if it is just an empty Array. This is dependent on your domain logic. In the last example the method findOfferedBy() would return an empty set of values.

It's possible to use comparison operators that are reaching deep into the object tree hierarchy. Let's assume you want to filter the organizations by whether they have offers for youngsters older than 16. You may define the request in the OrganizationRepository as follows:

$query->lessThanOrEqual('offers.ageRange.minimalValue', 16)

Extbase solves the path offers.ageRange.minimalValue by seeking every organization having offers whose age values have a minimum less than or equal to 16. Assuming that a Relational Database System is used in the Persistence Backend, this is internally solved by a so-called INNER JOIN. All relational types (1:1, 1:n, m:n) and all comparison operators are covered by this feature.

Note

The path notation was introduced in Extbase 1.1 (TYPO3 4.3) and is derived from the Object-Accessor notation of Fluid (see Ch. 8). In Fluid you may access object properties with the notation {organization.administrator.name}. However, Fluid does not support the notation {organization.offers.categories.title}, so that in $query->equals('offers.categories.title', 'foo') it is possible to die, due to Fluid's limitation that property access is not possible in a "concatenated way".

Besides comparison operators, the Query object supports Boolean Operators such as:

logicalAnd($constraint1, $constraint2)
logicalOr($constraint1, $constraint2)
logicalNot($constraint)

The methods above return a Constraint object. The resulting Constraint object of logicalAnd() is true if both given parameters $constraint1 and $constraint2 are true. It's sufficient when using logicalOr() to be true if only one of the given parameters is true. Since Extbase 1.1 (TYPO3 4.3), both methods accept an Array of constraints. Last, but not least, the function logicalNot() inverts the given $constraint to its opposite, i.e. true yields false and false yields true. Given this information, you can create complex queries such as:

public function findMatchingOrganizationAndRegion(\MyVendor\SjrOffers\Domain\Model\Organization $organization, \MyVendor\SjrOffers\Domain\Model\Region $region)
{
    $query = $this->createQuery();
    $query->matching(
        $query->logicalAnd(
            [
                $query->equals('organization', $organization),
                $query->contains('regions', $region)
            ]
        )
    );
    return $query->execute();
}

The method findMatchingOrganizationAndRegion() returns those offers that match both the given organization and the given region.

For our example extension we have the complex specification to find all offers that comply with the requirements of the user. The requirements are given via information about the age, the organization, the city district, and the category, as well as a freely defined search term in the front end. We encapsulate the requirements in their own Demand object that basically consists of the properties age, organisation, region, category and searchWord, plus their getters and setters. In addition to the restrictions for the needs of the user, there comes the request to show the current offers. This example request denotes a date constraint at most one week ago. In the method findDemanded() of the offerRepository, the request is implemented:

public function findDemanded(\MyVendor\SjrOffers\Domain\Model\Demand $demand)
{
    $query = $this->createQuery();
    $constraints = [];
    if ($demand->getRegion() !== null) {
        $constraints[] = $query->contains('regions', $demand->getRegion());
    }
    if ($demand->getCategory() !== null) {
        $constraints[] = $query->contains('categories', $demand->getCategory());
    }
    if ($demand->getOrganization() !== null) {
        $constraints[] = $query->contains('organization', $demand->getOrganization());
    }
    if (is_string($demand->getSearchWord()) && strlen($demand->getSearchWord()) > 0) {
        $constraints[] = $query->like($propertyName, '%' . $demand->getSearchWord . '%');
    }
    if ($demand->getAge() !== null) {
        $constraints[] = $query->logicalAnd(
            [
                $query->logicalOr(
                    [
                        $query->equals('ageRange.minimumValue', null),
                        $query->lessThanOrEqual('ageRange.minimumValue', $demand->getAge())
                    ]
                ),
                $query->logicalOr(
                    [
                        $query->equals('ageRange.maximumValue', null),
                        $query->greaterThanOrEqual('ageRange.maximumValue', $demand->getAge())
                    ]
                ),
            ]
        );
    }
    $constraints[] = $query->logicalOr(
        [
            $query->equals('dateRange.minimumValue', null),
            $query->equals('dateRange.minimumValue', 0),
            $query->greaterThan('dateRange.maximumValue', (time() - 60*60*24*7))
        ]
    );
    $query->matching($query->logicalAnd($constraints));
    return $query->execute();
}

The Demand object is passed as an argument. In the first line, the Query object is created. All single constraint terms are then collected in the array $constraints. The $query->logicalAnd($constraints) instruction brings together these constraint terms, and they are assigned to the Query object via matching(). With return $query->execute();, the query is executed and the located Offer objects are returned to the caller.

The example's offer age range requirement is interesting.

$constraints[] = $query->logicalAnd(
    [
        $query->logicalOr(
            [
                $query->equals('ageRange.minimumValue', null),
                $query->lessThanOrEqual('ageRange.minimumValue', $demand->getAge())
            ]
        ),
        $query->logicalOr(
            [
                $query->equals('ageRange.maximumValue', null),
                $query->greaterThanOrEqual('ageRange.maximumValue', $demand->getAge())
            ]
        ),
    ]
);

This requirement is fulfilled using multiple levels of nested query constraints. Each logicalOr() condition allows either an unset age (value equals() null) or a boundary age value. (Here, the minimum age is more recent in the past than the maximum age, on a timeline.) The logicalAnd() constraint then joins the two logicalOr() constraints, making a single constraint, overall.

You can sort the result of a query by assigning one or more rules $query->setOrderings($orderings); to the Query object. These rules are collected in an associative array. Each array element has the property name on which the sort is based as its key, and the search order constant as its value. There are two constants for the search order: \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING for an ascending order, and \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING for a descending order. A complete sample for specifying a sort order looks like this:

$query->setOrderings(
    [
        'organization.name' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
        'title' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
    ]
);

Multiple orderings are processed in the specified order. In our sample the offers are ordered first by the name of the organization, then inside the organization by the title of the offers, both in ascending order (thus from A to Z). Since Extbase 1.1 (TYPO3 4.3), you can use TypoScript-style point notation for specifying the property names.

If you need only an extract of the result set, you can do this with the two parameters, Limit and Offset. Assuming you want to get the tenth up to thirtieth offers from the overall query result from the repository, you can use the following lines:

$query->setOffset(10);
$query->setLimit(20);

Both methods expect an integer value. With the method setOffset(), you set the pointer to the object you will start with. With the method setLimit(), you set the maximum count of objects you will get.

At first sight the usage of a Query object with Constraint objects instead of directly written SQL statements may look inefficient. But doing so here with Extbase makes complete abstraction of the storage backend possible.

Note

The Query object leans against the Java Specification Request (JSR) 283. JSR 283 describes a standardised content repository for Java, The FLOW team ported this idea to PHP. You can find more information about this at http://jcp.org/en/jsr/detail?id=283.

Even so, using the method statement() of the Query object, you can send a native SQL statement to the database.

$result = $query->statement('SELECT * FROM tx_sjroffers_domain_model_offer
    WHERE title LIKE ? AND organization IN ?', ['%climbing%', [33,47]]);

is translated by Extbase to the following query:

SELECT * FROM tx_sjroffers_domain_model_offer WHERE title LIKE '%climbing%' AND
    organization IN ('33','47')

Warning

You should always avoid making queries to the persistence layer outside of the domain model. Encapsulate these queries always in a repository.

Inside of the repositories, you can access the database using a database connection:

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$connection = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tx_sjroffers_domain_model_offer');

$queryBuilder = $connection->createQueryBuilder();
$query = $queryBuilder
    ->select('*')
    ->from('tx_sjroffers_domain_model_offer')
    ->where(...)

$rows = $query->execute()->fetchAll();

You have to handle the creation and maintenance of the objects by yourself.

The method execute() per default returns a ready built object and the related objects - the complete Aggregate. In some cases, though, it is convenient to preserve the "raw data" of the objects, e.g. if you want to manipulate them before you build objects out of them. For this, you have to change the settings of the Query object.

$query->getQuerySettings()->setReturnRawQueryResult(TRUE);

Since Extbase 1.2 (TYPO3 4.4), the method execute() returns a multidimensional array with the object data. Inside an object, one finds single value properties, multi value properties and NULL values. Let's have a look at an object with a single value property.

[
    'identifier' => '<identifier>',
    'classname' => '<classname>',
    'properties' => [
        '<name>' => [
            'type' => '<type>',
            'multivalue' => FALSE,
            'value' => <value>
        ],
        ...
    ],
],

In Extbase, the value for <identifier> is always the UID of the data record. The class name <classname> and the identifier together make the element unique across the whole database. The properties are stored in an own associative array. The name of the property is the key and the corresponding information of the properties are the value. The property is signed with the property type <type> and the property value <value> itself. The property type could be string, integer, DateTime, or a class name like \MyVendor\SjrOffers\Domain\Model\Organization, for example. The property is declared as single value per default ('multivalue' => FALSE).

The array of an object with a multivalue property is basically composed the same way. The actual value of the property is not a simple data type (like a string or a single object), but an array of data types. This array could also be empty, and instead of the array, a NULL value is possible. The property type for multivalue properties is always \TYPO3\CMS\Extbase\Persistence\ObjectStorage. In the future, other containers like array or splObjectStorage may be supported. The property is per definition declared as multivalue ('multivalue' => TRUE).

[
    'identifier' => '<identifier>',
    'classname' => '<classname>',
    'properties' => [
        '<name>' => [
            'type' => '<type>',  // always '\TYPO3\CMS\Extbase\Persistence\ObjectStorage'
            'multivalue' => TRUE,
            'value' => [
                [
                    'type' => '<type>',
                    'index' => <index>,
                    'value' => <value>
                ],
                ...
            ],
        ],
    ],
],

If a property has a NULL value, it is stored in the object array like this:

[
    'identifier' => '<identifier>',
    'classname' => '<classname>',
    'properties' => [
        '<name>' => [
            'type' => '<type>',
            'multivalue' => <boolean>,
            'value' => NULL
        ],
        ...
    ],
],

The debug output of the return value looks like figure 6-13.

_images/figure-6-13.png

Figure 6-13: Debug output of "raw" object data

Maybe in figure 6-13 you have noticed the empty array (EMPTY!) of the properties of the organization. In the domain model the property organization of the offer is annotated with @TYPO3\CMS\Extbase\Annotation\ORM\Lazy. This annotation instructs Extbase to load the properties of the object only when they are really needed (so called lazy loading).

Beside setReturnRawQueryResult(), there are three additional settings for the execution of a query. All settings are occupied with default values that are set when the Query object was created by $this->createQuery(). The settings are enclosed in an own QuerySettings object that you can get from the Query object with getQuerySettings(). In table 6-3 you find all settings in summary.

Table 6-3: Settings for the execution of a query (QuerySetting)

Setting If this attribute is set (=true), ... Default
setReturnRawQueryResult() ... instead of the ready built object graphs, the database tuples are returned as an array false
setRespectStoragePage() ... the result set is limited to these tuples/objects that are assigned to a given page or directory in the backend (e.g. pid IN (42,99)) true
setRespectSysLanguage() ... the result set for localized data is limited to these tuples/objects valid for either the default language or for all languages (e.g. sys_language_uid IN (-1,0)) This setting is mostly used for internal purposes. true
setRespectEnableFields() ... the result set is limited to these tuples/objects that at the present moment can be viewed by the current user (e.g. deleted=0 AND hidden=0) true

While the setting setReturnRawQueryResult() is active by matching() and statement(), the remaining three settings are only effective by matching().

Beside the method execute(), the Query object provides the method count() for disposal. It returns only the number of elements of the result set, as an integer value, and can only be used in conjunction with the method matching(). In a backend SQL database, a statement of the form SELECT COUNT(*) FROM ... would be sent, which has significantly more performance than SELECT * FROM ....

In any backend storage case, the call

$offersInRegion = $query->matching($query->contains('regions', $region))->count();

thus returns the count of offers of a given region.

Implicit relation cardinality handling

Extbase supports several types of cardinalities that describe the relationship between entities - among these are RELATION_HAS_ONE (1:1), RELATION_HAS_MANY (1:n) and RELATION_HAS_AND_BELONGS_TO_MANY (m:n).

Using these types in individual queries will result in invoking an implicit LEFT JOIN on the database layer. The following sections are using the Blog Example to explain what happens under the hood in terms of database queries. The used entities are the following:

  • Blog.posts having 1:n relation to Post
  • Post.author having 1:1 relation to Person
  • Person.tags having m:n relation to Tag
  • Person.tagsSpecial having m:n relation to Tag

Note

The table names in the following SQL-like examples have been shortened for better readability. Instead of tx_blogexample_post the real table name used in the Blog Example would be tx_blogexample_domain_model_post. Besides that, only the relevant query parts as mentioned, not all of them.

1:1 (RELATION_HAS_ONE)
$query = $postRepository->createQuery();
$query->matching(
   $query->equals('author.firstname', 'Dave')
);
$posts = $query->execute();
SELECT    tx_blogexample_post.*
FROM      tx_blogexample_post
LEFT JOIN tx_blogexample_person
ON        tx_blogexample_post.author = tx_blogexample_person.uid
WHERE     tx_blogexample_person.firstname = 'Dave';

Even if the SQL-like query contains a LEFT JOIN, due to the 1:1 cardinality this won't lead to duplicate results for Post entities.

1:n (RELATION_HAS_MANY)
$query = $blogRepository->createQuery();
$query->matching(
   $query->greaterThanOrEqual('posts.date', 1501234567)
);
$blogs = $query->execute();
SELECT    DISTINCT tx_blogexample_blog.*
FROM      tx_blogexample_blog
LEFT JOIN tx_blogexample_post
ON        tx_blogexample_blog.uid = tx_blogexample_post.blog
WHERE     tx_blogexample_post.date >= 1501234567;

Since there might be more Post entities belonging to a single Blog entity it could happen that the LEFT JOIN results in having many duplicate Blog entities in the result set.

m:n (RELATION_HAS_AND_BELONGS_TO_MANY)
$query = $postRepository->createQuery();
$query->matching(
   $query->logicalOr([
      $query->equals('author.tags.name', 'typo3'),
      $query->equals('author.tagsSpecial.name', 'typo3')
    ])
);
$posts = $query->execute();
SELECT    DISTINCT tx_blogexample_post.*
FROM      tx_blogexample_post
LEFT JOIN tx_blogexample_person
ON        tx_blogexample_post.author = tx_blogexample_person.uid
LEFT JOIN tx_blogexample_tag_mm tx_blogexample_tag_mm_1
ON        tx_blogexample_tag_mm_1.uid_local = tx_blogexample_person.uid
AND       tx_blogexample_tag_mm_1.fieldname = 'tags'
LEFT JOIN tx_blogexample_tag_mm tx_blogexample_tag_mm_2
ON        tx_blogexample_tag_mm_2.uid_local = tx_blogexample_person.uid
AND       tx_blogexample_tag_mm_2.fieldname = 'tags_special'
LEFT JOIN tx_blogexample_tag tx_blogexample_tag_1
ON        tx_blogexample_tag_mm_1.uid_foreign = tx_blogexample_tag_1.uid
LEFT JOIN tx_blogexample_tag tx_blogexample_tag_2
ON        tx_blogexample_tag_mm_2.uid_foreign = tx_blogexample_tag_2.uid
WHERE     tx_blogexample_tag_1.name = 'typo3'
OR        tx_blogexample_tag_2.name = 'typo3';

Since the nature of a many-to-many relation is to be used by various entities, this will also lead to lots of duplicated Post entities in the result set in this rather complex query example.

Distinct entity handling in query result set
Cardinality distinct entity handling suggested
1:1 (RELATION_HAS_ONE) no since for each left-sided entity there is always just one right-sided entity
1:n (RELATION_HAS_MANY) yes since having more than one right-sided entity will lead to left-sided duplicates
m:n (RELATION_HAS_AND_BELONGS_TO_MANY) yes since having more than one right-sided entity will lead to left-sided duplicates

For each of the above mentioned scenarios, when having distinct entity handling is suggested, an implicit SELECT DISTINCT statement is used instead of the regular plain SELECT statement. This does also apply to counting result sets where COUNT(DISTINCT <table-name>.uid) is used instead of a plain COUNT(*) statement.

Using Foreign Data Sources

In real projects many times data from different sources has to be ordered. One target of Extbase is to normalize the access to this data sources and to abstract it from the concrete technical solution. These "foreign" data sources could be tables from the same TYPO3 database or a web service.

Extbase building-up strongly of the rule "Convention over Configuration" (see also the appendix A for this). Foreign database tables rarely correspond with the conventions of Extbase. Therefore the assignment of the class to a given table as well as the assignment of field names to property names of the classes must be configured via TypoScript. This assignment is also called mapping. The following configuration enables the storage of the object data of a class \MyVendor\MyExtension\Domain\Model\Person in the table tt_address, which is available in most TYPO3 installations.

plugin.tx_myextension {
    persistence {
        classes {
            MyVendor\MyExtension\Domain\Model\Person {
                mapping {
                    tableName = tt_address
                    recordType = \MyVendor\MyExtension\Domain\Model\Person
                    columns {
                        birthday.mapOnProperty = dateOfBirth
                        street.mapOnProperty = thoroughfare
                    }
                }
            }
        }
    }
}

The options for the persistence can be set beneath the TypoScript path plugin.tx_myextension.persistence for the extension or beneath config.tx_extbase.persistence globally for all extensions. Inside of classes for every class the mapping is defined. With tableName the name of the table is given in which the object data is to be stored. The option recordType is used in a table, that is used for the storage of different classes, to assign the record of "his" class. With columns the separate assignments of the field names (left) to the corresponding properties (right) is done.

Note

Regard in each case that the field type fits the data type of your property. Additional information you will find in "Preparing the tables of the Domain Objects" above in this chapter.

This configuration causes Extbase to use the table tt_address when reconstructing or persisting of objects of the class \MyVendor\MyExtension\Domain\Model\Person. Thereby the values of the properties dateOfBirth and thoroughfare are stored in the fields birthday and street. If the configuration option tableName is not set, Extbase searches for a table that corresponds to the lower cased cased class name, in our case: tx_myextension_domain_model_person. If for a property none rule for the mapping is defined, the property name, translated in lower case with underscores, is expected as field name. The property name dateOfBirth would result in a field name date_of_birth.

Modeling the Class Hierarchy

In chapter 5 in "Use inheritance in class hierarchies" we have already used class hierarchies. A relational database doesn't know about some concepts of object oriented programming - also not the concept of class hierarchies. Therefore there is a mismatch between the object oriented domain model and the relational model of the database (object-relational impedance mismatch). However there are some technical options to represent the class hierarchies. Let us look for these with a simplified class hierarchy detached from our sample extension (see figure 6-14).

_images/figure-6-14.png

Figure 6-14: A simple class hierarchy

The classes Organization and Person are specializations of the class Party (not a jollification, but a party in not political manner). The class Organization is again a generalization of the sub classes Company and ScientificInstitution. Now assume that in our extension data from instances of the classes Organization, Company, ScientificInstitution and Person (the so called concrete classes) have to be stored. The class Party is initially used as a container for properties and behaviour that should be available in the concrete classes without mention them also there (to avoid redundancies is the whole purpose of a class hierarchy). For saving the data there a some options available:

  • For every concrete class an own database table will be created (Concrete Table Inheritance). The table contains fields for all own and inherited properties. For the class Company in this case a table company is created, which contains the fields name, number_of_employees and type_of_business. The table person only contains the fields name and date_of_birth.
  • For all classes of a class hierarchy only one table is created (Single Table Inheritance). This table is assigned to the class which contains all sub classes which data has to be stored inside. In our case this would be the table party with fields for all properties of all sub classes: name, number_of_employees, type_of_business, research_focus and date_of_birth.
  • For every class of the class hierarchy an own table is created in which only the properties are stored which are defined in the class (Class Table Inheritance). The table party in this case contains the field name, the table organization only the field number_of_employees and the table company accordingly the field type_of_business:

This time Extbase and the backend of TYPO3 are supporting the first two options. For the first option Concrete Table Inheritance you have only to create the tables and configure them as described in the TCA. No additional configuration for the class hierarchy is needed.

For the second case of Single Table Inheritance beside creation of the table there is an additional configuration effort needed. Furthermore the table must have an additional field, that contains the type of the stored database tuple. The table definition schematically looks like this:

CREATE TABLE tx_myextension_domain_model_party {
   uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment,
   pid int(11) DEFAULT '0' NOT NULL,
   record_type varchar(255) DEFAULT '' NOT NULL,

   name varchar(255) DEFAULT '' NOT NULL,
   number_of_employees int(11) unsigned DEFAULT '0' NOT NULL,
   date_of_birth int(11) unsigned DEFAULT '0' NOT NULL,
   type_of_business varchar(255) DEFAULT '' NOT NULL,
   research_focus varchar(255) DEFAULT '' NOT NULL,

   PRIMARY KEY (uid),
   KEY parent (pid)
}

The name of the field that contains the type can be chosen freely. In our case it is the field record_type. The field name must be specified in the ctrl section of the TCA as type:

Configuration/TCA/tx_myextension_domain_model_party.php
// …
'ctrl' => [
   'title' => 'Party',
   'label' => 'name',
   'type' => 'record_type',
   // …
],
// …

In your TypoScript template you have to tell Extbase for every concrete class in which table the data of the instances are stored and with which type they should be stored.

config.tx_extbase.persistence.classes {
    MyVendor\MyExtension\Domain\Model\Organization {
        mapping {
            tableName = tx_myextension_domain_model_party
            recordType = MyVendor\MyExtension\Domain\Model\Organization
        }
        subclasses {
            \MyVendor\MyExtension\Domain\Model\Company = MyVendor\MyExtension\Domain\Model\Company
            \MyVendor\MyExtension\Domain\Model\ScientificInstitution = MyVendor\MyExtension\Domain\Model\ScientificInstitution
        }
    }
    MyVendor\MyExtension\Domain\Model\Person {
        mapping {
            tableName = tx_myextension_domain_model_party
            recordType = \MyVendor\MyExtension\Domain\Model\Person
        }
    }
    MyVendor\MyExtension\Domain\Model\Company {
        mapping {
            tableName = tx_myextension_domain_model_party
            recordType = \MyVendor\MyExtension\Domain\Model\Company
        }
    }
    MyVendor\MyExtension\Domain\Model\ScientificInstitution {
        mapping {
            tableName = tx_myextension_domain_model_party
            recordType = \MyVendor\MyExtension\Domain\Model\ScientificInstitution
        }
    }
}

Every class is assigned with tableName = tx_myextension_domain_model_party to this table. In recordType inside the table an unique identifier is expected (even the Record Type). It is advisable to use the class name for this. For every super class additional all subclasses have to be declared under subclasses. In our example Party and Organization are super classes, but only the class Organization should could be instantiated. For this it is enough to configure these class. The two subclasses Company and ScientificInstitution are specified. First it looks weird that at both sides of the equation sign the same class name stands. On the right side the really the name of the sub class must be given. On the left side only an unique identifier inside TYPO3 is expected, so that this configuration can be extended by other extensions without any risk if necessary. Once again it is recommend to use the class name.

You can create new objects of the classes Person, Organization, Company or ScientificInstitution in the frontend as normal. Extbase will store them in the table party and put the class name in the type field. During reconstruction, when the object is transported from the database to the memory, Extbase identifies the class on the basis of the type field and instantiated a corresponding object.

Normally objects also should be created and edited in the backend. But the backend doesn't know the concept of classes. For this you must provide a select field in the form with this the backend user can choose the class in terms of the Record Type. This can be done with including the following configuration to your TCA:

Configuration/TCA/tx_myextension_domain_model_party.php
 // …
'types' => [
   '0' => [
      'showitem' => 'record_type, name'
   ],
   '\MyVendor\MyExtension\Domain\Model\Organization' => [
      'showitem' => 'record_type, name, numberOfEmployees'
   ],
   '\MyVendor\MyExtension\Domain\Model\Person' => [
      'showitem' => 'record_type, name, dateOfBirth'
   ],
   '\MyVendor\MyExtension\Domain\Model\Company' => [
      'showitem' => 'record_type, name, numberOfEmployees, typeOfBusiness'
   ],
   '\MyVendor\MyExtension\Domain\Model\ScientificInstitution' => [
      'showitem' => 'record_type, name, numberOfEmployees, researchFocus'
   ]
],
'columns' => [
   // …
   'record_type' => [
      'label' => 'Domain Object',
      'config' => [
         'type' => 'select',
         'items' => [
            ['undefined', '0'],
            ['Organization', '\MyVendor\MyExtension\Domain\Model\Organization'],
            ['Person', '\MyVendor\MyExtension\Domain\Model\Person'],
            ['Company', '\MyVendor\MyExtension\Domain\Model\Company'],
            ['ScientificInstitution', '\MyVendor\MyExtension\Domain\Model\ScientificInstitution']
         ],
         'default' => '\MyVendor\MyExtension\Domain\Model\Person'
      ],
   ],
   // …
],
// …

In the section ctrl the type field record_type is configured as selection list. With this the desired domain object respectively the class name can be chosen. This impacts the display of the form fields. In the section types for every Record Type (in our case the class name) the fields to be displayed are defined; when the Record Type changes the new set of form fields are displayed after a confirmation by TYPO3.

You can access the objects via repositories as normal. In your controller the corresponding lines can look like this:

<?php
declare(strict_types = 1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\CompanyRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CompanyController extends ActionController
{
    /**
     * @var CompanyRepository
     */
    private $companyRepository;

    /**
     * Inject the company repository
     *
     * @param CompanyRepository $companyRepository
     */
    public function injectCompanyRepository(CompanyRepository $companyRepository)
    {
        $this->companyRepository = $companyRepository;
    }

    /**
     * List Action
     *
     * @return void
     */
    public function listAction()
    {
        $companies = $this->companyRepository->findAll();
        $this->view->assign('companies', $companies);
    }
}

You can also find straightforward all concrete classes of a super class:

<?php
declare(strict_types = 1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\OrganizationRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class OrganizationController extends ActionController
{
    /**
     * @var OrganizationRepository
     */
    private $organizationRepository;

    /**
     * @param OrganizationRepository $organizationRepository
     */
    public function injectOrganizationRepository(OrganizationRepository $organizationRepository)
    {
        $this->organizationRepository = $organizationRepository;
    }

    public function listAction()
    {
        $organizations = $this->organizationRepository->findAll();
        // ...
    }
}

In the result set $organizationRepository there are domain objects of the class \MyVendor\MyExtension\Domain\Model\Organization and all configured subclasses \MyVendor\MyExtension\Domain\Model\Company and \MyVendor\MyExtension\Domain\Model\ScientificInstitution are included. The query of a super class is only possible for Single Table Inheritance this time. In the future this should also be possible for Concrete Table Inheritance.

Note

A prominent example for the Single Table Inheritance is the table tt_content, in which all types of content elements are stored. Every extension can enhance the table with own fields. Accordingly big is the amount of columns of this table. The type of the content elements is stored in the field CType.

With this chapter we close the work on the domain model and whose storage firstly. During the process of real development projects this process naturally is not linear. Again and again we come back to the domain model for enhance something or to optimize. In the following chapters we will dedicate to the "Flow" through the extension. In the so called Controllers we define the sequences inside the extension.

Controlling the flow with controllers

In the previous chapters we already transcribed the Domain of our example extension SJROffers to a software based Domain Model. This lead to multiple files with class definitions, to be found in the extension subfolder sjr_offers/Classes/Domain/Model/. Furthermore we set up the persistence layer. As a result we are already able to deposit the data of our Domain in the form of Domain Objects and to retrieve it again.

In this chapter you'll see how to control the flow inside of your extension. The bottom line is to evaluate requests of the website user, in order to trigger the appropriate action. Regarding our example extension SJROffers, it may make sense to show a list of all offers or to give out all relevant information to one offer. Further examples of actions are:

  • Deleting a specific offer
  • Deleting all offers of one organization
  • Displaying a form to change the data of an offer
  • Updating an offer
  • Listing the newest offers

The code for receiving the request and for executing the appropriate action is combined in Controllers. A Controller is a component of the Model-View-Controller architecture, of which the basics are described in chapter 2, section "Model-View-Controller in Extbase". The operation of a Controller interconnected with the other components was described in chapter 3.

A Controller is an object of an extension, which is instantiated and called inside of extbase by the Dispatcher object. The controller takes care of the complete flow inside of the extension. It is the link between the Request, the Domain Model and the reaction in form of the Response. Inside of the Controller, the data necessary for the flow is fetched from the respective Repositories, prepared according to the demand from outside and passed to the code responsible for the output (View). Besides this main task, a Controller is responsible for:

  • accepting the Request and Response object, respectively rejecting them, in case they can not be processed.
  • inducing a check of the data coming in from the URL (especially from links) or forms of the Frontend. This data has to be checked for type and validity.
  • checking which method (Action) of the Controller shall be called for further processing.
  • preparing the incoming data, so it can be passed to the method in charge (Argument Mapping)
  • initiating the rendering process.
  • passing the output of the rendering process to the Response object.

In the following section, we'll create the necessary Controller Classes of our extension and and therein implement the adequate Action methods. For this, we'll first have to decide, which Actions have to be implemented. For an extension usually needs multiple different Actions, we'll group them in different Controller Classes. A very "natural" way of grouping would be: Every Aggregate Root Class, containing objects on which an Action shall be applied, is administered by a proper Controller. In our case the two classes Organization and Offer are indicated. Now let's start with our first Controller.

Creating Controllers and Actions

The Controller classes are stored in the folder EXT:sjr_offer/Classes/Controller/. The name of the Controller is composed by the name of the Domain Model and the Suffix Controller. So the Controller \MyVendor\SjrOffers\Controller\OfferController is assigned to the Aggregate Root Object \MyVendor\SjrOffers\Domain\Model\Offer. And the name of the Class file is OfferController.php.

The Controller class must extend the class \TYPO3\CMS\Extbase\Mvc\Controller\ActionController which is part of Extbase. The individual Actions are combined in separate methods. The method names have to end in Action. The body of OfferController thus looks like this:

OfferController.php
<?php

namespace MyVendor\SjrOffers\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class OfferController extends ActionController
{
   // Action methods will be following here
}

When realizing the desired tasks through Action methods you will often stumble upon very similar flows and patterns. Each task will be carried out by a single Action or a chain of Actions:

  1. A list of Domain Objects is to be displayed.
  2. A single Domain Object is to be displayed.
  3. A new Domain Object is to be created.
  4. An existing Domain Object is to be edited.
  5. A Domain Object is to be deleted.

We will shed some light on these recurring patterns in the following sections. Together with the schedule model you will learn the background to generate your own flows.

Tip

Note that you are free to choose the method names for your Actions as you like. Nevertheless we recommend to stick to the names presented here, to help other developers to find their way through your code.

Flow Pattern "display a list of Domain Objects"

The first pattern in our example fits the action "display a list of all offers". One action method usually will be enough for implementing this. We choose indexAction as name of the method:

// …
/**
 * @var OfferRepository
 */
private $offerRepository;

/**
 * Inject the offer repository
 *
 * @param \MyVendor\SjrOffers\Domain\Repository\OfferRepository $offerRepository
 */
public function injectOfferRepository(OfferRepository $offerRepository)
{
   $this->offerRepository = $offerRepository;
}

/**
 * Index Action
 *
 * @return string
 */
public function indexAction()
{
    $offers = $this->offerRepository->findAll();
    $this->view->assign('offers', $offers);
    return $this->view->render();
}

This can be simplified even more. As described in chapter 4 in section "controlling the flow", it is not necessary to return the rendered content. Furthermore we avoid initializing the variable $offers, which we only use once. So we get:

/**
 * Index Action
 *
 * @return void
 */
public function indexAction()
{
    $this->view->assign('offers', $this->offerRepository->findAll());
}

initializeAction

In old TYPO3 Versions the initializeAction() was used to get the repository instance. Later we can use this action, to modify the Request, before the property mapper is executed or integrate JavaScript libraries.

The ActionController not only calls the method initializeAction(), which is executed before any Action in the Controller, but also a method in the Form of initialize*Foo*Action(), which is called only before the method *foo*Action().

Tip

The trick of implementing an empty method body in the super class, which is the "filled" in the subclass is called Template Pattern.

Flow Pattern "display a single Domain Object"

The second pattern is best put into action by a single method as well. We call it showAction(). In contrast to indexAction we have to to tell this method from outside which Domain Object is to be displayed. In our case, the offer to be shown is passed to the method as Argument:

/**
 * Show action
 *
 * @param \MyVendor\SjrOffers\Domain\Model\Offer $offer The offer to be shown
 * @return string The rendered HTML string
 */
public function showAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
   $this->view->assign('offer', $offer);
}

Usually the display of a single Object is called by a link in the frontend. In our example extension it connects the list view by something like the following URL:

http://localhost/index.php?id=123&amp;tx_sjroffers_pi1[offer]=3&amp;tx_sjroffers_pi1[action]=show&amp;tx_sjroffers_pi1[controller]=Offer

Due to the 2 Arguments tx_sjroffers_pi1[controller]=Offer and tx_sjroffers_pi1[action]=show, the dispatcher of Extbase passes the request to the OfferController. In the request we find the information that the Action show is to be called. Before passing on the further processing to the method showAction(), the Controller tries to map the Arguments received by the URL on the arguments of the method. Extbase maps the arguments by their names. In our example Extbase detects, that the GET Argument tx_sjroffers_pi1[offer]=3 corresponds to the method argument $offer: showAction(\MyVendor\SjrOffers\Domain\Model\Offer *$offer*). The type of this Argument is fetched by Extbase from the method signature: showAction(*\MyVendor\SjrOffers\Domain\Model\Offer* $offer). In case this so called Type Hint should not be present, Extbase reads the type from the annotation written above the method: @param *\MyVendor\SjrOffers\Domain\Model\Offer* $offer.

After successful assigning, the value of the incoming argument has to be casted in the target type as well as checked for validity (read more about validation in chapter 9 in section "Validating Domain Objects"). In our case the incoming value is "3". Target type is the class \MyVendor\SjrOffers\Domain\Model\Offer. So Extbase interprets the incoming value as uid of the object to be created and sends a request to the Storage Backend to find an Object with this uid. If the object can be reconstructed fully valid it is passed to the method as argument. Inside of the method showAction() the newly created object is passed on to the view, which is taking care of the HTML output as usual.

Tip

Inside of the template you can access all properties of the Domain Object, including all existing child objects. Thus this Flow Pattern does not only cover single domain objects but, in the event, also a complex aggregate.

If an Argument is identified as invalid, the already implemented method errorAction() of ActionController is called instead of the method showAction(). The method then generates a message for the frontend user and passes the processing to the previous Action, in case it is given. The latter is especially useful with invalid form field input as you'll see in the following.

Flow Pattern "creating a new Domain Object"

For the third Flow Pattern, the one for creating a new Domain Object, two steps are required: First, a form for inputting the Domain Data has to be shown in Frontend. Second, a new Domain Object has to be created (using the incoming form data) and put in the appropriate Repository. We're going to implement these two steps in the methods newAction() `and :php:`createAction().

Tip

We already described these steps in chapter 3 in section "Alternative route: creating a new posting". We now shortly revise this Flow using our example extension and focus on some further aspects.

First the method newAction() is called by a Link in frontend with the following URL:

http://localhost/index.php?id=123&amp;tx_sjroffers_pi1[organization]=5&amp;tx_sjroffers_pi1[action]=new&amp;tx_sjroffers_pi1[controller]=Offer

Extbase instantiates the Organization `Object which is mapped to the Argument :php:`$organization, just as it was the case with the Offer object in the method showAction(). In the URL is no information (yet) though, which value the Argument $newOffer shall have. So the default value (=null) set in the method signature is used. With these Arguments, the controller passes the further processing to the method newAction().

<?php
declare(strict_types = 1);

namespace MyVendor\SjrOffers\Controller;

use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class OfferController extends ActionController
{
    // ...

    /**
     * @param \MyVendor\SjrOffers\Domain\Model\Organization $organization The organization
     * @param \MyVendor\SjrOffers\Domain\Model\Offer $offer The new offer object
     * @return string An HTML form for creating a new offer
     * @Extbase\IgnoreValidation("newOffer")
     */
    public function newAction(\MyVendor\SjrOffers\Domain\Model\Organization $organization, \MyVendor\SjrOffers\Domain\Model\Offer $newOffer = null)
    {
        $this->view->assign('organization', $organization);
        $this->view->assign('newOffer', $newOffer);
        $this->view->assign('regions', $this->regionRepository->findAll());
    }

    // ...
}

This action passes to the view in organization the Organization object, in newOffer null (to begin with) the and in region all Region Objects contained in the RegionRepository. The view creates the output of the form in frontend, using a template, which we focus on in chapter 8 in section "Template Creation by example". After the user filled in the data of the offer and submitted the form, the Method createAction() shall be called. It expects as Arguments an Organization Object and an Object of the class \MyVendor\SjrOffers\Domain\Model\Offer. Therefore Extbase instantiates the Object and "fills" its Properties with the appropriate Form data. If all Arguments are valid, the Action createAction() is called.

/**
 * @param \MyVendor\SjrOffers\Domain\Model\Organization $organization The organization the offer belongs to
 * @param \MyVendor\SjrOffers\Domain\Model\Offer $newOffer A fresh Offer object which has not yet been added to the repository
 * @return void
 */
public function createAction(\MyVendor\SjrOffers\Domain\Model\Organization $organization, \MyVendor\SjrOffers\Domain\Model\Offer $newOffer)
{
   $organization->addOffer($newOffer);
   $newOffer->setOrganization($organization);
   $this->redirect('show', 'Organization', NULL, ['organization' => $organization]);
}

The new offer is allocated to the organization and inversely the organization is allocated to the offer. Thanks to this allocation Extbase will cause the persistence of the new offer in the dispatcher before returning to TYPO3.

After creating the new offer, the appropriate organization is to be displayed with all of its offers. We therefore start a new request (request-response-cycle) by redirecting to showAction() of the OrganizationController using the Method redirect(). The actual organization is hereby passed on as an argument. Inside the ActionController you have the following Methods for redirecting to other Action controllers at your disposal:

redirect($actionName, $controllerName = NULL, $extensionName = NULL,
   array $arguments = NULL, $pageUid = NULL, $delay = 0, $statusCode = 303)
redirectToURI($uri, $delay = 0, $statusCode = 303)
forward($actionName, $controllerName = NULL, $extensionName = NULL,array $arguments = NULL)

Using the redirect() Method, you can start a new request-response-cycle on the spot, similar to clicking on a link: The given Action (specified in $actionName) of the appropriate controller (specified in $controllerName) in the given extension (specified in $extensionName) is called. If you did not specify a controller or extension, Extbase assumes, that you stay in the same context. In the fourth parameter $arguments you can pass an Array of arguments. In our example ['organization' => $organization] would look like this in the URL: tx_sjroffers_pi1[organization]=5. The Array key is transcribed to the parameter name, while the organization object in $organization is transformed into the number 5, which is the appropriate UID. If you want to link to another page inside the TYPO3 installation, you can pass its uid in the 5th parameter ($pageUid). A delay before redirecting can be achieved by using the 6th parameter ($delay). By default the reason for redirecting is set to status code 303 (which means See Other).You can use the 7th parameter ($statusCode) to override this (for example with 301, which means Moved Permanently).

In our example, the following code is sent to the Browser. It provokes the immediate reload of the page with the given URL:

<html><head><meta http-equiv="refresh" content="0;url=http://localhost/
index.php?id=123&amp;tx_sjroffers_pi1[organization]=5&amp;tx_sjroffers_
pi1[action]=show&amp;tx_sjroffers_pi1[controller]=Organization"/></head></html>

The Method redirectToURI() corresponds to the Method redirect(), but you can directly set a URL respectively URI as string, e.g. <html><head><meta http-equiv= "refresh" content="0;url=http://example.com/foo/bar.html"/></head></html>. With this, you have all the freedom to do what you need. The Method forward(), at last, does a redirect of the request to another Action on the spot, just as the two redirect Methods. In contrast to them, no request-response-cycle ist started, though. The request Object is only updated with the details concerning Action, Controller and Extension, and then passed back to the dispatcher for processing. The dispatcher then passes on the actual Request- and Response- Objects to the appropriate Controller. Here, too, applies: If no Controller or Extension is set, the actual context is kept.

This procedure can be done multiple times when calling a page. There is the risk, though, that the process runs into an infinite loop (A redirects to B, B redirects to A again). In this case, Extbase stops the processing after some steps.

There is another important difference to the redirect Methods. When redirecting using the Method forward(), new objects will not (yet) be persisted to database. This is not done until at the end of a request-response-cycle. Therefore no UID has yet been assigned to a new Object and the transcription to a URL parameter fails. You can manually trigger the action of persisting before you execute the redirection, by using $this->objectManager->get(PersistenceManager::class)->persistAll(), though.

When calling the Method createAction(), we already described the case of all Arguments being valid. But what happens, if a Frontend user inserts invalid data - or even manipulates the form to deliberately attack the website?

Tip

You find detailed information about validation and security in chapter 9

Fluid adds multiple hidden fields to the form generated by the Method newAction(). These contain information about the origin of the form (__referrer) as well as, in encrypted form (__trustedProperties), the structure of the form (shorted in the example below).

<input type="hidden" name="tx_sjroffers_list[__referrer][extensionName]"
      value="SjrOffers" />
<input type="hidden" name="tx_sjroffers_list[__referrer][controllerName]" value="Offer" />
<input type="hidden" name="tx_sjroffers_list[__referrer][actionName]" value="edit" />
<input type="hidden" name="tx_sjroffers_list[__trustedProperties]"
      value="a:4:{s:5:\"offer\";a:12:
      ...
      s:10:\"__identity\";i:1;}s:12:\"organization\";i:1;
      s:6:\"action\";i:1;s:10:\"controller\";
      i:1;}8888b05fbf35fc96d0e3aadd370a8856a9edad20" />

If now a validation error occurs when calling the Method createAction(), an error message is saved and the processing is passed back to the previous Action, including all already inserted form data. Extbase reads the necessary information from the hidden fields __referrer. In our case the Method newAction() is called again. In contrast to the first call, Extbase now tries to create an (invalid) Offer Object from the form data, and to pass it to the Method in $newOffer. Due to the annotation @Extbase\IgnoreValidation("newOffer") Extbase this time accepts the invalid object and displays the form once more. Formerly filled in data is put in the fields again and the previously saved error message is displayed if the template is intending so.

_images/figure-7-1.png

Figure 7-1: Wrong input in the form of an offer leads to an error message (in this case a modal JavaScript window)

Tip

Standard error messages of Extbase are not yet localized in Version 1.2 (TYPO3 4.4). In section "Localize error messages" in chapter 8, we describe a possibility to translate them too, though.

Using the hidden field __trustedProperties, the Extbase property mapper compares the incoming property data with the one that are allowed. If the request contains data for non whitelisted properties, the property mapper throws an exception.

Using the \TYPO3\CMS\Extbase\Annotation\IgnoreValidation("parameterName") annotation, you tell Extbase that the argument is not to be validated. If the argument is an Object, the validation of its properties is also bypassed.

Flow Pattern "Editing an existing Domain Object"

The flow pattern we now present you, is quite similar to the previous one. We again need two action Methods, which this time we call editAction() and updateAction(). The Method editAction() provides the form for editing, while updateAction() updates the Object in the Repository. In contrast to newAction() it is not necessary to pass an organization to the Method editAction(). It is sufficient to pass the offer to be edited as an Argument.

<?php
declare(strict_types = 1);

namespace MyVendor\SjrOffers\Controller;

use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class OfferController extends ActionController
{
    // ...

    /**
     * @param \MyVendor\SjrOffers\Domain\Model\Offer $offer The existing, unmodified offer
     * @return string Form for editing the existing organization
     * @Extbase\IgnoreValidation("offer")
     */
    public function editAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
    {
       $this->view->assign('offer', $offer);
       $this->view->assign('regions', $this->regionRepository->findAll());
    }

    // ...
}

Note once again the annotation @Extbase\IgnoreValidation("offer"). The Method updateAction() receives the changed offer and updates it in the repository. Afterwards a new request is started and the organization is shown with its updated offers.

/**
 * @param \MyVendor\SjrOffers\Domain\Model\Offer $offer The modified offer
 * @return void
 */
public function updateAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
   $this->offerRepository->update($offer);
   $this->redirect('show', 'Organization', NULL, ['organization' => $offer->getOrganization()]);
}

Warning

Do not forget to explicitly update the changed Domain Object using update(). Extbase will not do this automatically for you, for doing so could lead to unexpected results. For example if you have to manipulate the incoming Domain Object inside your Action Method.

At this point we have to ask ourselves how to prevent unauthorized changes of our Domain data. The organization and offer data are not to be changed by all visitors after all. So an administrator is allocated to each organization, authorized to change the data of that organization. The administrator can change the contact data of the organization, create and delete offers and contact persons as well as edit existing offers. Securing against unauthorized access can be done on different levels:

  • On the level of TYPO3, access to the page and/or plugin is prohibited.
  • Inside the Action, it is checked, if access is authorized. In our case it has to be checked if the administrator of the organization is logged in.
  • In the template, links to Actions, to which the frontend user has no access are blinded out.

Of these three levels, only the first two offer reliable protection. We do not take a closer look on the first level in this book. You can find detailed information for setting up permissions in your TYPO3 system in the Core API. The second level, we are going to implement in all "critical" Actions. Let's look at an example with the Method updateAction().

use TYPO3\CMS\Core\Utility\GeneralUtility;
use \MyVendor\SjrOffers\Service\AccessControlService;

public function initializeAction()
{
   $this->accessControlService = GeneralUtility::makeInstance(AccessControlService::class);
}

public function updateAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
   $administrator = $offer->getOrganization()->getAdministrator();
   if ($this->accessControlService->isLoggedIn($administrator)) {
      $this->offerRepository->update($offer);
   } else {
      $this->flashMessages->add('Please sign in.');
   }
   $this->redirect('show', 'Organization', NULL, ['organization' => $offer->getOrganization()]);
}

We ask a previously instantiated AccessControlService if the administrator of the organization responsible for the offer is logged in the frontend. If yes, we do update the offer. If no, an error message is generated, which is displayed in the subsequently called organization overview.

Extbase does not yet offer an API for access control. We therefore implement an AccessControlService on ourselves. The description of the class is to be found in the file EXT:sjr_offers/Classes/Service/AccessControlService.php.

<?php

namespace MyVendor\SjrOffers\Service;

use TYPO3\CMS\Core\SingletonInterface;

class AccessControlService implements SingletonInterface
{

   public function isLoggedIn($person = NULL)
   {
      if (is_object($person)) {
         if ($person->getUid() === $this->getFrontendUserUid()) {
             return TRUE;
         }
      }
      return FALSE;
   }

   public function getFrontendUserUid()
   {
      if($this->hasLoggedInFrontendUser() && !empty($GLOBALS['TSFE']->fe_user->
             user['uid'])) {
         return intval($GLOBALS['TSFE']->fe_user->user['uid']);
      }
      return NULL;
   }

   public function hasLoggedInFrontendUser()
   {
      return $GLOBALS['TSFE']->loginUser === 1 ? TRUE : FALSE;
   }

}

The third level can easily be bypassed by manually typing the link or the form data. It therefore only reduces the confusion for honest visitors and the stimulus for the bad ones. Let's take a short look on this snippet from a template:

{namespace sjr=MyVendor\SjrOffers\ViewHelpers}
<!-- ... -->
<sjr:security.ifAuthenticated person="{organization.administrator}">
   <f:link.action controller="Offer" action="edit" arguments="{...}">
      <f:image src="EXT:sjr_offers/Resources/Public/Icons/edit.gif" alt="edit" />
   </f:link.action>
   <!-- ... -->
</sjr:security.ifAuthenticated>

Tip

A Service is often used to implement functionalities that are needed on multiple places in your extensions and are not related to one Domain Object.

Services are often stateless. In this context that means that their function does not depend on previous access. This does not rule out dependency to the "environment". In our example you can be sure, that a verification by isLoggedIn() always leads to the same result, regardless of any earlier verification - given that the "environment" has not changed (considerably), e.g. by the Administrator logging out or even losing his access rights.

Services usually can be built as Singleton (implements t3lib_Singleton). You can find detailed information to Singleton in chapter 2 in section "Singleton".

The AccessControlService is not Part of the Domain of our extension. It "belongs" to the Domain of the Content Management System. There are Domain Services also of course, like a Service creating a continuous invoice number. They are usually located in EXT:my_ext/Classes/Domain/Service/.

We make use of an IfAuthenticatedViewHelper to access the AccessControlService. The class file IfAuthenticatedViewHelper.php is in our case located in EXT:sjr_offers/Classes/ViewHelpers/Security/.

namespace MyVendor\SjrOffers\ViewHelper\Security;

use MyVendor\SjrOffers\Service\AccessControlService;
use TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class IfAuthenticatedViewHelper extends IfViewHelper
{
   /**
    * @param mixed $person The person to be tested for login
    * @return string The output
    */
   public function render($person = NULL)
   {
      $accessControlService = GeneralUtility::makeInstance(AccessControlService::class);
      if ($accessControlService->isLoggedIn($person)) {
         return $this->renderThenChild();
      } else {
         return $this->renderElseChild();
      }
   }

}

The IfAuthenticatedViewHelper extends the If-ViewHelper of fluid and therefore provides the opportunity to use if-else branches. It delegates the access check to the AccessControlService. If the check gives a positive result, in our case a link with an edit icon is generated, which leads to the method editAction() of the OfferController.

Flow Pattern "Deleting a Domain Object"

The last Flow pattern realizes the deletion of an existing Domain Object in one single Action. The appropriate Method deleteAction() is kind of straightforward:

/**
 * @param \MyVendor\SjrOffers\Domain\Model\Offer $offer The offer to be deleted
 * @return void
 */
public function deleteAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
   $administrator = $offer->getOrganization()->getAdministrator();
   if ($this->accessControlService->isLoggedIn($administrator) {
      $this->offerRepository->remove($offer);
   } else {
      $this->flashMessages->add('Please sign in.');
   }
   $this->redirect('show', 'Organization', NULL, ['organization' => $offer->getOrganization()]);
}

The important thing here is that you delete the given Offer from the Repository using the method remove(). After running through your extension, Extbase will delete the associated record from the Database by marking it as deleted.

Tip

In principle it doesn't matter how you generate the result (usually HTML code) inside the Action. You can even decide to use the traditional way of building extensions in your Action - with SQL Queries and maker-based Templating. We invite you to pursue the path we chose up till now, though.

The flow patterns we present here are meant to be blueprints for your own flows. In real life projects they may get way more complex. The Method indexAction() of the OfferController looks like this in it's "final stage":

/**
 * @param \MyVendor\SjrOffers\Domain\Model\Demand $demand A demand (filter)
 * @return string The rendered HTML string
 */
public function indexAction(\MyVendor\SjrOffers\Domain\Model\Demand $demand = NULL)
{
   $allowedStates = (strlen($this->settings['allowedStates']) > 0) ?
         t3lib_div::intExplode(',', $this->settings['allowedStates']) : [];
   $listCategories = (strlen($this->settings['listCategories']) > 0) ?
         t3lib_div::intExplode(',', $this->settings['listCategories']) : [];
   $selectableCategories = (strlen($this->settings['selectableCategories']) > 0) ?
         t3lib_div::intExplode(',', $this->settings['selectableCategories']) : [];
   $propertiesToSearch = (strlen($this->settings['propertiesToSearch']) > 0) ?
         t3lib_div::trimExplode(',', $this->settings['propertiesToSearch']) : [];

   $this->view->assign('offers',
      $this->offerRepository->findDemanded(
         $demand,
         $propertiesToSearch,
         $listCategories,
         $allowedStates
      )
   );
   $this->view->assign('demand', $demand);
   $this->view->assign('organizations',
      array_merge(
         [0 => 'All Organisations'],
         $this->organizationRepository->findByStates($allowedStates)
      )
   );
   $this->view->assign('categories',
      array_merge(
         [0 => 'All Categories'],
         $this->categoryRepository->findSelectableCategories($selectableCategories)
      )
   );
   $this->view->assign('regions',
      array_merge(
         [0 => 'All Districts'],
         $this->regionRepository->findAll()
      )
   );
}

In the first few lines of the script, configuration options, set in the TypoScript template as comma separated list, are transcribed to arrays. Then this information is passed to the View piece by piece.

One requirement our extension has to realize, is that a visitor of the website can define a special demand, which is then used to filter the range of offers. We already implemented an appropriate Method findDemanded() (see chapter 6). To define his demand, the visitor chooses the accordant options in a form (see pic. 7-2).

_images/figure-7-2.png

Figure 7-2: The buildup of the "demand" in a form above the offer list.

Warning

Watch out, that you do not implement logic, which actually belongs in the domain, inside of the Controller. Concentrate on the mere Flow.

Tip

In real life you will often need similar functionality in some or even all Controllers. The previously mentioned access control is a simple example. In our example extension we sourced it out to a service object. Another possibility is to create a basis Controller which extends the ActionController of Extbase. Inside you implement the shared functionality. Then the concrete controllers with your Actions extend this Basis Controller again.

The Flow inside of a Controller is triggered from outside by TYPO3. For extensions which generate content for the frontend, this is usually done by a plugin, placed on the appropriate page. How to configure such a plugin you'll see in the following section.

Configuring and embedding Frontend Plugins

The action should be called on by a frontend-plugin. We've already addressed the configuration of a simple frontend-plugin in chapter 4 in the section Configuring the plugin. For the purpose of our example a rudimentary plugin is enough. To place a plugin from the backend on a page, two steps are necessary: The plugin must be registered as a content type (plugin) and its behavior must be configured. Both steps are resolved by two Extbase API-methods. These calls are located in two different files.

In the file EXT:sjr_offers/ext_tables.php you have to register every plugin as a content element with TYPO3 using the static method registerPlugin().

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
     $_EXTKEY,
     'List',
     'The Inventory List'
);

The method registerPlugin() expects three arguments. The first argument is the extension key (sjr_offers in our example). This key is the same as the directory name of the extension. The second parameter is a freely selectable name of the plugin (a short, meaningful name in UpperCamelCase). The plugin name plays a significant role in the allocation of GET- and POST parameters to the appropriate plugin: http://localhost/index.php?id=123&ts_sjroffers_list[offer]=3. The third argument is the label by which the plugin appears in the list of plugin in the backend. Now the plugin is made available in the backend, we can add a plugin with the name _Offers_ to our example.

For the second step we have to configure the behaviour of the plugin in the file EXT:sjr_offers/ext_localconf.php with the static method configurePlugin(). Beside the actions that have to be called on by the plugin, you also have to specify which content will be stored in cache.

<?php
if (!defined ('TYPO3_MODE')) die ('Access denied.');

\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
     $_EXTKEY,
     'List',
     ['Inventory' => 'list']
);

The method expects 4 arguments. The first argument is, just like the one used in the registration process, the extension key. With the second argument, the plugin name, Extbase can assign the configuration to the appropriate plugin.

The third argument is an array which contains all controller-action combinations which the plugin is authorized to execute. The specification ['Offer' => 'index'] authorizes the plugin to perform the method indexAction()

in

\MyVendor\SjrOffers\Controller\OfferController. Be aware that the name of the controller is written without the suffix Controller and the name of the action method without the suffix Action.

The fourth, optional argument is also an array which is composed as the previous one. This one however contains the Controller-Action combinations which are _not_ stored in cache. These are especially those actions that issue a form. The methods createAction() or the updateAction() shouldn't be explicitly used here, because they don't produce any result code that must be stored in cache.

Configuring the behavior of the extension

Not all organizations are to be displayed in our example extensions, but just the ones belonging to a certain status (like e.g. internal, external, non-member). In the TypoScript template of our page we therefore establish an option allowedStates under the path tx_sjroffers.settings:

plugin.tx_sjroffers {
    settings {
        allowedStates = 1,2
    }
}

Extbase makes the settings inside of the path plugin.tx_sjroffers.settings available as an array in the class variable $this->settings. Our Action thus looks like this:

public function indexAction()
{
    $this->view->assign(
        'organizations',
        $this->organizationRepository->findByStates(
            \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->settings['allowedStates'])
        )
    );

    // ...
}

In the OrganizationRepository, we implemented a Method findByStates(), which we do not further investigate here (see more in chapter 6, section "Implement individual database queries"). The Method expects an array containing the allowed states. We generate it from the comma separated list using the TYPO3 API function \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(). We then pass on the returned choice of organizations to the view, just as we are used to do.

Tip

Of course we could also have passed the comma separated list directly to the Method findByStates(). We do recommend, though, to prepare all parameter coming from outside (settings, form input) before passing them on to the two other components Model and View. </tip>In this chapter you've learned how to set the Objects of your domain in motion and how to control the flow of a page visit. You now are able to realize the two components Model and Controller of the MVC paradigm inside your extension. In the following chapter, we will address the third component, the View. We'll present the substantial scope of the template engine Fluid.

Styling the output with Fluid

Up to now you have learned how to represent the business logic of an application with a model and how to manipulate the model with the use of controllers. So far we have covered the output of data for the user only marginally. We want to take a closer look at this now. We will show you how you can create output for the user simple and fast. To make this work most efficient, specially for TYPO3 and FLOW, a new templating engine named Fluid was developed, which is flexible and extensible, but easy to learn.

In this chapter you will start with learning the basic concepts of Fluid. After that we will show you some functions which are helpful in templating with the help of various examples. Beside this we show you the development of a ViewHelper using an example. Finally we show you how you can apply the learned technologies with the help of a practical sample.

Basic Concepts

Fluid is a template engine which lets you display content on a website very easily. A specific file (the template) will be processed and the containing placeholders will be replaced with the current content. This is the basic concept of template engines - as well as Fluid's.

Fluid is based on three conceptual pillars which build the backbone of the template engine and provide for scalability and flexibility:

  • Object Accessors output the content of variables which were assigned to the View to be displayed.
  • ViewHelpers are special tags in the template which provide more complex functionality such as loops or generating links.
  • Arrays make it possible to assign hierarchical values to ViewHelpers.

Outputting Data with Object Accessors

A template engine uses a placeholder to fill content in specified areas in a template and the result is then returned to the user. In Fluid, these placeholders are called Object Accessors.

Tip

The markers used in the classic marker based templates of TYPO3 v4 are also placeholders which are replaced later on by the desired data. You will notice though, that the placeholders used in Fluid are clearly more flexible and versatile.

Object Accessors are written in curly brackets. For example, {blogTitle} will output the content of the variable blogTitle. The variables have to be assigned in the controller with $this->view->assign(variableName, object). Let us look at this in an example of a list of blog posts. In the controller, we assign some data to the template with the following code:

namespace ExtbaseTeam\BlogExample\Controller;

class PostController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
    // ...
    public function indexAction(\ExtbaseTeam\BlogExample\Domain\Model\Blog $blog)
    {
        $this->view->assign('blogTitle', 'Webdesign-Blog');
        $this->view->assign('blogPosts', $blog->getPosts());
    }
}

Now we can insert the string »Webdesign-Blog« into the template with the Object Accessor {blogTitle}. Let us take a look at the associated template:

<h1>{blogTitle}</h1>

<f:for each="{blogPosts}" as="post">
    <b>{post.title}</b><br />
</f:for>

Upon generation of the output, the Object Accessor {blogTitle} will be replaced by the title of the blog »Webdesign-Blog«. To output the individual blog posts, the tag <f:for> is used, which you can also see in the template above. Depending on the title of each blog post, the complete output looks like this:

<h1>Webdesign-Blog</h1>

<b>Fluid as template-engine</b><br />
<b>TypoScript to configure TYPO3</b><br />

Tip

If you want to output an object instead of a String, the object needs to have a __toString()-method which returns the textual representation of the object.

In the example above, you will also find the Object Accessor {post.title} which is used to output the title of a blog post. This hierarchical notation is a syntax that makes it possible to walk through associations in the object graph - you can literally move from object to object. Often, a complex object is assigned to the View, but only parts of it will be displayed. In the example above, we used {post.title} to display the property title of the object. Generally, Fluid tries to handle such hierarchical properties in the following order:

  • If post is an array or an object which implements the interface ArrayAccess, the corresponding property will be returned as long as it exists.
  • If it is an object, and a method getTitle() exists, the method will be called. This is the most common use case of an Object Accessor, since by convention all public properties have a corresponding get-method.
  • The property will be returned if it exists in the object and it is public. We discourage the ability to utilize this though, since it violates the Uniform Access Principle (see box)

You can navigate through more complex objects, because Object Accessors can be nested multiple times. For example, to output the email address of an author of a blog post, you can use {post.author.emailAddress}. That's almost equivalent to the expression $post->getAuthor()->getEmailAddress() in PHP, but focused on the essential.

Only the get-method, and not just any method, of an object can be called with Object Accessors. This ensures that there is no PHP code in the template. It is better to place PHP code in your own ViewHelper if needed. The following describes how to do this.

Implementing more complex functionalities with ViewHelpers

Functionalities that exceed the simple output of values have to be implemented with ViewHelpers. Every ViewHelper has its own PHP class. Now, we're going to see how we can use ViewHelpers. Later, you'll also learn how to write your own ViewHelper.

To use an existing ViewHelper, you have to import the Namespace and assign a shortcut to it. You can do this with the declaration {namespace ...=...}.

All Namespaces used in your template must always be registered. This might seem redundant, but because all important information is embedded in the template, readability increases immensely for other template editors who work on the same templates.

The standard ViewHelper of Fluid will be imported and assigned to the shortcut f with the following declaration:

{namespace f=TYPO3\CMS\Fluid\ViewHelpers}

This Namespace will be imported automatically by Fluid. All ViewHelpers that come with Fluid are prefixed with f. Your own Namespaces have to be imported into the template like previously mentioned.

All tags, which begin with a registered prefix, will be evaluated. Here's a small example:

<ul>
    <f:for each="{blogPosts}" as="post">
        <li>{post.title}</li>
    </f:for>
</ul>

Tags without a registered prefix (in this example <ul> and <li>) will be treated as text. The tag <f:for> will be interpreted as a ViewHelper since it starts with the prefix f:. This is implemented in the class \TYPO3\CMS\Fluid\ViewHelpers\ForViewHelper.

The first part of the class name is the complete Namespace like it was defined earlier with {namespace f=TYPO3\CMS\Fluid\ViewHelpers}. Followed by the name of the ViewHelper and the ending ViewHelper.

Every argument of a ViewHelper will be interpreted by Fluid. The ViewHelper <f:for> from the previous example therefore receives the array of all blog posts with the argument each.

Tip

If the name of the ViewHelper contains a single or multiple periods, it will be resolved as a sub package. For example, the ViewHelper f:form.textfield is implemented in the class \TYPO3\CMS\Fluid\ViewHelpers\Form\TextfieldViewHelper. Therefore ViewHelpers can be divided further and structured even more.

ViewHelpers are the main tools of template editors. They make it possible to have a clear separation of template and embedded functionality.

Tip

All control structures like if/else or for are individual ViewHelpers in Fluid and not a core language feature. This is one of the main reasons for the flexibility of Fluid. You'll find a detailed reference of the ViewHelpers in Appendix C.

Inline Notation for View Helpers

It is intuitive and natural for most of the ViewHelpers to be called with the tag based syntax. Especially with control structures or form elements, this syntax is easily understood. But there are also ViewHelpers which can lead to difficult to understand and invalid template code when used as a tag. An example of this is the f:uri.resource ViewHelper, which returns the path to a resource in the Public/ folder of an Extension. It is being used inside of <link rel="stylesheet" href="..." /> for example. Using the normal, tag based syntax it looks like this:

<link rel="stylesheet" href="<f:uri.resource path='myCss.css' />" />

That is very difficult to read and doesn't communicate adequately the meaning of the ViewHelper. Also, the above code is not valid XHTML and therefore most text editors can't display the code with correct syntax highlighting anymore.

For that reason, it is also possible to call the ViewHelper differently, with the help of the inline notation. The inline notation is function-oriented, which is more suitable for this ViewHelper: Instead of <f:uri.resource /> you can also write {f:uri.resource()}.

So the example above can be changed to:

<link rel="stylesheet" href="{f:uri.resource(path: 'myCss.css')}" />

The purpose of the ViewHelper is easily understandable and visible - it is a helper function that returns a resource. It is well formed XHTML code as well and the syntax highlighting of your editor will work correctly again.

We'll illustrate some details of Fluid's syntax, based on formatting a date.

Lets assume we have a blog post object with the name post in the template. It has, among others, a property date which contains the date of the creation of the post in a DateTime object.

DateTime objects, that can be used in PHP to represent dates, have no __toString()-method and can therefore not be outputted with Object Accessors in the template. You'll trigger a PHP error message, if you simple write {post.date} in your template.

In Fluid there is a ViewHelper f:format.date to output DateTime objects, which (as you can see on the prefix f:) is already part of Fluid:

<f:format.date format="Y-m-d">{post.date}</f:format.date>

This ViewHelper formats the date as defined in the format property. In this case, it's very important that there are no whitespaces or newlines before or after {post.date}. If there is, Fluid tries to chain the whitespace and the string representation of {post.date} together as string. Because the DateTime object has no method __toString(), a PHP error message will be thrown again.

Tip

To avoid this problem, all f:format-ViewHelpers have a property to specify the object to be formatted.

Instead of writing <f:format.date>{post.date}</f:format.date> you can write: <f:format.date date="{post.date}" /> to bypass the problem. But again, there can't be any characters before or after {post.date}. </tip>You can pretty much see, that in this case the tag based syntax is prone to errors: We have to know, that {post.date} is an object so we don't add whitespaces inside of <f:format.date>...</f:format.date>.

An alternative would be to use the following syntax:

{post.date -> f:format.date(format: 'Y-m-d')}

Inside the Object Accessor we can use a ViewHelper to process the value. The above example is easily readable, intuitive and less error prone as the tag based variation.

Tip

This might look familiar, if you happen to know the UNIX shell: There is a pipe operator (|) which has the same functionality as our chaining operator. The arrow shows the direction of the data flow better though.

You can also chain multiple ViewHelpers together. Lets assume we want to pad the processed string to the length of 40 characters (e.g. because we output code). This can be simply written as:

{post.date -> f:format.date(format: 'Y-m-d') -> f:format.padding(padLength: 40)}

Which is functionally equal to:

<f:format.padding padLength="40"><f:format.date format="Y-m-d">{post.date}</f:format.date></f:format.padding>

The data flow is also easier to read with an inline syntax like this, and it is easier to see on which values the ViewHelper is working on. We can thus confirm that you can process the value of every Object Accessor by inserting it into the ViewHelper with the help of the chaining operator (->) . This can also be done multiple times.

Flexible Arrays Data Structures

Arrays round off the concept of Fluid and build another core concept of the template engine. Arrays in Fluid can be somewhat compared to associative arrays in PHP. Every value in a Fluid array needs a key.

Arrays are used to pass a variable number of arguments to View Helpers. The best example is the link.action-ViewHelper. With this you can create a link to other Controllers and Actions in your Extension. The following link refers to the index Action of the Post Controller:

<f:link.action controller="Post" action="index">Show list of all posts</f:link.action>

Many links in your application though need parameters, which can be passed with the arguments attribute. We can already see that we need arrays to do so: It's unpredictable how many parameters you want to pass. By using an array we can pass an indefinite amount of parameters. The following example adds the parameter post to the link:

<f:link.action controller="Post" action="show" arguments="{post: currentPost}">Show current post</f:link.action>

The array {post: currentPost} consists of a single element with the name post. The value of the element is the object currentPost. Multiple elements are separated by a comma: {post: currentPost, blogTitle: 'Webdesign-Blog'}.

Fluid only supports named arrays, which means, that you always have to specify the key of the array element. Lets look at what options you have when creating an array:

{
    key1: 'Hello',
    key2: "World",
    key3: 20,
    key4: blog,
    key5: blog.title,
    key6: '{firstname} {lastname}'
}

The array can contain strings as values as in key1 and key2. It can also have numbers as values as in key3. More interesting are key4 and key5: Object Accessors are being specified as array values. You can also access sub-objects like you are used to with Object Accessors. All strings in arrays are interpreted as Fluid markup as well. So that you can combine strings from individual strings for example. This way, it is also possible to call ViewHelpers with the inline notation.

These are the basic concepts of Fluid. Now we move on to more advanced concepts, which increase the effectiveness of template creation. The following chapter will explain how to use different output formats to achieve different views of data.

Using Different Output Formats

The Model-View-Controller-Paradigm (MVC), as described in chapter 2, has many decisive advantages: It separates the model from the user interaction and it allows different output formats for the same data. We want to discuss the later.

Often different output formats are useful when generating content for CSV files, RSS feeds or print views. On the example of the blog we will show you, how you can extend your Extension with a print view.

Lets assume you have programed a HTML view for a list of blog posts. The Fluid template of this view is Resources/Private/Templates/Post/list.html. Now you want to add a print view, which is formatted differently. Create a new template Resources/Private/Templates/Post/list.print and write the appropriate Fluid markup to generate the print view. You can use the format attribute of the link ViewHelper to generate a link to the print view:

<f:link.action action="list" format="print">Print View</f:link.action>

The same list action is being called that was used for the HTML view. However, Fluid doesn't choose the file list.html but list.print, because the format attribute of the link.action ViewHelper changed the format to print, our print view. You notice: The format is being reflected in the file ending of the template.

Tip

In the example above we have given the print view the name print. All format names are treated equally. There are no technical limitations for format names. Therefore you should choose a semantically, meaningful name.

Output other formats with Fluid

If you want to output JSON, RSS or similar data with Fluid, you have to write the appropriate TypoScript which passes the page rendering to Extbase and Fluid respectively. Otherwise, TYPO3 will always generate the <head>- and <body>-section.

You can use the following TypoScript:

rss = PAGE
rss {
   typeNum = 100
   10 =< tt_content.list.20.*[ExtensionKey]*_*[PluginName]*

   config {
   disableAllHeaderCode = 1
   additionalHeaders = Content-type:application/xml
   xhtml_cleaning = 0
   admPanel = 0
   }
}

You still have to exchange [ExtensionKey] and [PluginName] with the name of the Extension and Plugin. We recommend to search for the path of your Plugin in the TypoScript Object Browser to avoid misspelling. Further on you have to explicitly set plugin.tx_*[ExtensionKey]*.persistence.storagePid to the number of the page containing the data to tell Extbase from which page the data should be read.

Moving Repeating Snippets To Partials

Some parts within different Templates might be the same. In order to not repeat this part in multiple templates, Fluid offers so called Partials. Partials are small pieces of Fluid template within a separate file that can be included in multiple Templates.

For example, an extension might display tags inside an Resources/Private/Templates/RecordType/Index.html template and also in Resources/Private/Templates/RecordType/Show.html. The snippet to display these tags might look like:

<b>Tags</b>: <f:for each="{tags}" as="tag">{tag}</f:for>

If this is to be changed, e.g. an ul is preferred some day, the modifications would have to be made in both templates.

That's where Partials are used. Partials are stored, by default, within Resources/Private/Partials/. One might create a new Partial Resources/Private/Partials/Tags.html with the snippet:

<b>Tags</b>: <f:for each="{tags}" as="tag">{tag}</f:for>

Inside the existing Template the snippet can now be replaced with a ViewHelper to render the Partial:

{f:render(partial: 'Tags', arguments: {
    tags: post.tags
})}

Fluid will replace the ViewHelper call with the result of the rendered Partial. The argument "partial" receives the full path within the configured Partial folder to the file, excluding the file extension.

It's also possible to create further folders, e.g.: Resources/Private/Partials/Blogpost/Tags.html and to call the Partial:

{f:render(partial: 'Blogpost/Tags', arguments: {
    tags: post.tags
})}

ViewHelper Namespace import

Like within Fluid Templates, custom ViewHelpers can be used within Partials. Because these ViewHelpers are not in the default namespace, their namespace needs to be imported. For information about how to import a namespace, see Importing namespaces.

It's possible to use a Global namespace import, or imports the namespace within the Partial, or within the Template.

Note

Up to CMS v8 this namespace import has to be within each Partial where such a ViewHelper was used. Since CMS v8 there is no need anymore. The namespace has to be imported within the Partial or the Templates. It still can be imported in both, but the template is enough.

How to treat Partials

Partials should best be treated as reusable block of Fluid, with no dependencies. Therefore the namespace imports should happen within the Partial. Also the Partial should be self contained.

This way the Partial can be reused within different Templates. As each Template has do call the Partial, each Template can map existing variables to match the used variables within the Partial.

Let's assume the following Partial: Resources/Private/Partials/Tags.html again:

<b>Tags</b>: <f:for each="{tags}" as="tag">{tag}</f:for>

This Partial only requires the variable {tags}.

Let's assume the following Template: Resources/Private/Templates/BlogPosts/Index.html:

<f:for each="{blogPosts}" as="blogPost">
    <h2>{blogPost.title}</h2>
    {blogPost.content -> f:format.html()}

    {f:render(partial: 'Tags', arguments: {
       tags: blogPost.tags
    })}
</f:for>

Within the Template, no variable {tags} exists. Instead, the variable {blogPost.tags} is mapped to {tags} for the Partial.

This way, it can also be reused for the following Template:

Resources/Private/Templates/Blogs/Index.html:

<f:for each="{blogs}" as="blog">
    <h2>{blog.title}</h2>
    {blog.description -> f:format.html()}

    {f:render(partial: 'Tags', arguments: {
       tags: blog.tags
    })}
</f:for>

Again, there is no variable {tags}. This time, an index of Blogs is displayed instead of blog posts of a single Blog. Both have relations to tags which should be displayed the same way. With a single Partial and the arguments-Argument this is possible.

Creating A Consistent Look And Feel With Layouts

While partials are suitable for small recurring elements, layouts build the frame of the templates (see Figure 8-1). They should create a consistent look and feel of a web application and decorate an existing template. In a layout, areas are marked as variable and replaced with the current template. Note that the template has the focus and controls the output. You also have to determine which layout is being used in the template.

_images/figure-8-1.png

Figure 8-1: Layouts build the outer frame for a template, whereas recurring elements can be implemented in a template with partials.

Now we look at how to create and use a layout. A layout is a Fluid file in the folder Resources/Private/Layouts/. It contains placeholders which should be replaced by content of the corresponding template within the layout. In the following example you see a use case of the ViewHelper <f:render section="..." /> as placeholder.

<html>
   <!-- ... -->
   <body>
      <h1>Blogging with Extbase:</h1>
      <f:render section="main" />
      <h6>This is the footer section</h6>
   </body>
</html>

Tip

Layouts in Extbase usually don't contain the basic structure of a HTML document (<html>, <head> etc.), since this is usually generated with TYPO3. For the purpose of illustration though, we show a complete HTML page.

A template looks like this:

<f:layout name="default" />

<f:section name="main">
   <h2>Blog List</h2>
   <!-- ... -->
</f:section>

The first line in the template defines which layout should be wrapped around the template. With specifying name="default", Fluid will use the file Resources/Private/Layouts/default.html as layout.

The template must also contain <f:section name="...">...</f:section> for every placeholder in the layout. So by defining the placeholder <f:render section="main">, like in the example layout above, a template, which uses this layout, must define the section <f:section name="main">...</f:section>, whose content then is being inserted in the layout. Layouts can reference any number of sections. Different sections are often used for multi-column layouts. Besides, you can use all features of Fluid in layouts, which you'll get to know in the course of this chapter, for building templates. So layouts offer various possibilities for efficiently templating a web application.

Tip

You'll find a practical example for building layouts in the section "Template Creation by example" later on in this chapter.

Now that you got to know how you can structure templates with layouts and partials, we want to explore some options ViewHelpers offer. In the following segment we'll introduce a powerful tool for template building. A ViewHelper which combines the possibilities of Fluid and the classic TYPO3-templating.

Using TypoScript For Rendering: The cObject-ViewHelper

The cObject-ViewHelper is a very powerful ViewHelper. It connects Fluid with the options that TypoScript offers. The following line in the HTML template will be replaced with the referenced TypoScript object.

<f:cObject typoscriptObjectPath="lib.title"/>

Now we only have to define lib.title in the TypoScript Setup:

lib.title = TEXT
lib.title.value = Extbase and Fluid

»Extbase and Fluid« will be outputted in the template. Now we can output an image (e.g. headlines with unusual fonts) by changing the TypoScript to:

lib.title = IMAGE
lib.title {
   file = GIFBUILDER
   file {
      10 = TEXT
      10.value = Extbase and Fluid
   }
}

So far it's not really a "real world" example, because no data is being passed from Fluid to the TypoScript. We'll demonstrate now how to pass a parameter to the TypoScript with the example of a user counter. The value of our user counter should come from the Blog-Post. (Every Blog-Post should count how many times it's been viewed in this example).

In the Fluid template we add:

<f:cObject typoscriptObjectPath="lib.myCounter">{post.viewCount}</f:cObject>

Alternatively we can use a self closing tag. The data is being passed with the help of the data attribute.

<f:cObject typoscriptObjectPath="lib.myCounter" data="{post.viewCount}" />

Also advisable for this example is the inline notation, because you can easily read it from left to right:

{post.viewCount -> f:cObject(typoscriptObjectPath: 'lib.myCounter')}

Now we still have to evaluate the passed value in our TypoScript template. We can use the stdWrap attribute current to achieve this. It works like a switch: If set to 1, the value, which we passed to the TypoScript object in the Fluid template, will be used. In our example it looks like this:

lib.myCounter = TEXT
lib.myCounter {
   current = 1
   wrap = <strong>|</strong>
}

This TypoScript snippet outputs the current number of visits written in bold.

Now for example we can output the user counter as image instead of text without modifying the Fluid template. We simply have to use the following TypoScript:

lib.myCounter = IMAGE
lib.myCounter {
   file = GIFBUILDER
   file {
      10 = TEXT
      10.text.current = 1
   }
}

At the moment we're only passing a single value to the TypoScript. It's more versatile though to pass multiple values to the TypoScript object, because then you can select which value to use in the TypoScript and the values can be concatenated. You can also pass whole objects to the ViewHelper in the template:

{post -> f:cObject(typoscriptObjectPath: 'lib.myCounter')}

Now, how do you access individual properties of the object in the TypoScript-Setup? You can use the property field of stdWrap:

lib.myCounter = COA
lib.myCounter {
   10 = TEXT
   10.field = title
   20 = TEXT
   20.field = viewCount
   wrap = (<strong>|</strong>)
}

Now we always output the title of the blog, followed by the amount of page visits in parenthesis in the example above.

You can also combine the field based approach with current: If you set the property currentValueKey in the cObject ViewHelper, then this value will be available in the TypoScript template with current. This is especially useful when you want to emphasize that the value is very important for the TypoScript template. For example, the amount of visits is very important in our view counter:

{post -> f:cObject(typoscriptObjectPath: 'lib.myCounter', currentValueKey: 'viewCount')}

In the TypoScript template you can now use both, current and field, and have therefor the maximum flexibility with the greatest readability. The following TypoScript snippet outputs the same information as the previous example:

lib.myCounter = COA
lib.myCounter {
   10 = TEXT
   10.field = title
   20 = TEXT
   20.current = 1
   wrap = (<strong>|</strong>)
}

The cObject ViewHelper is a powerful option to use the best advantages of both worlds by making it possible to embed TypoScript expressions in Fluid templates

In the next chapter, we'll turn our attention to a function which most ViewHelper have. This function makes it possible to modify the HTML output of a ViewHelper by adding your own tag attributes.

Adding additional tag attributes with additionalAttributes

At the moment most of the ViewHelpers can be split into two types: One group of ViewHelpers is rather functional oriented. That applies for example to the format ViewHelpers, which can format data or currencies. The other group is mostly output oriented, because these ViewHelpers output mostly a HTML tag. Samples of this are the form ViewHelpers, all of them start with f:form. Every form ViewHelper generates a HTML tag, like e.g. <form> or <input>.

A Fluid ViewHelper supports most attributes that are also available in HTML. There are for example the attributes class and id which exists in all tag based ViewHelper. You find a listing of all universal properties in the appendix C.

However, sometimes attributes are needed that are not provided by the ViewHelper - maybe a special JavaScript event handler or proprietary attributes (which are used by JavaScript frameworks like Dojo for example). To output them as well without changing the ViewHelper, the attribute additionalAttributes is available. This is an associative array by which additional tag attributes can be defined, like the following example shows:

<f:form.textbox id="myTextbox" additionalAttributes="{onclick: 'alert(\'Hello World\')'}" />

Another common use case is the data-attribute, which can be used in JavaScript functions:

<f:form.textbox additionalAttributes="{data-anything: 'some info'}" />

The HTML tag generated by the ViewHelper now also supports the onclick attribute:

<input type="text" onClick="alert('Hello World')" id="myTextbox" />

The additionalAttributes is especially helpful if only a few of this additional attributes are needed. Otherwise it is often reasonable to write an own ViewHelper which extends the corresponding ViewHelper.

additionalAttributes is provided by the TagBasedViewHelper, the base class for tag based ViewHelpers (see appendix C) and it allows the adding of optional attributes for the HTML tag output.

Using boolean conditions

Boolean conditions are queries that compare two values with each other (e.g. with == or >=) and then returns the value true or false. Which values are interpreted as true or false by Fluid depends on the data type. A number for example is evaluated as true if it is greater than 0.

Tip

You find a complete list of all evaluating possibilities in appendix C in the section "Boolean expressions".

With the help of conditions you can hide or show certain parts of a template. A typical scenario is the display of search results: If search results are found, they were displayed; if none results are found an appropriate message should be displayed. In Fluid the IfViewHelper enables such case-by-case analysis.

Simple if queries (without an else term) looks like this:

<f:if condition="{blog.posts}">
This is only shown if blog posts are available.
</f:if>

Tip

If none comparison operator like == is given, per default empty lists are interpreted as false and list with at least one element as true.

Using the inline notation it looks like this:

<div class="{f:if(condition: blog.posts, then: 'blogPostsAvailable')}">
This div has the CSS class 'BlogPostAvailable', if blog posts are available.
</div>

Also if-then-else structures are possible. In that case the then tag is required:

<f:if condition="{blog.posts}">
<f:then>
This is only shown if blog posts are available.
</f:then>
<f:else>
No blog posts available.
</f:else>
</f:if>

This is also possible in the inline notation:

<div class="{f:if(condition: blog.posts, then: 'blogPostsAvailable', else: 'noPosts')}">
This div has the CSS class 'BlogPostAvailable', if blog posts are available.
If no posts are available this div container gets the CSS class 'noPosts' assigned.
</div>

Realize complex comparisons

Until now we have employed with simplest boolean evaluations. With the syntax you have learned until now, no comparisons or modulo operations are possible. Fluid supports these conditions as well. Here is a short example:

<f:if condition="{posts.viewCount} % 2">
viewCount is an even number.
</f:if>

Note the enhanced syntax inside the condition. The compare operators >, >=, <, <=, ==, != and % are available. The parameter left and right of the operators could be numbers, object accessors, arrays and ViewHelpers in inline notation, but not strings.

Tip

Comparisons with strings, like <f:if condition="{gender} == 'male'">....</f:if>, are not possible with Fluid yet because of the complex implementation. If you need such a condition, you have to write a ViewHelper that returns the needed string. Then you can compare the object accessor with the output of the ViewHelper:

<f:if condition="{gender} == {my:male()}">...</f:if>

The just shown detailed notation for comparisons and modulo operations is not only available for the if ViewHelper but for all ViewHelpers which have a parameter of the type boolean.

Tip

Once you develop an own ViewHelper - like described in the section "<xref linkend="Fluid_custom_viewHelper" />" later on in this chapter - you can use boolean expressions as arguments. Therefore the ViewHelper has to mark all arguments where those expressions are to be used as boolean. The just explained functionality is not only available in the if ViewHelper, but rather available in self developed ViewHelper.<remark> ??? Sentence is not very clear</remark>

Upon engaged with many functionalities for the author of templates in this section, we would show you now how to develop your own ViewHelper.<remark>??? Does anyone understand the first part of the sentence?</remark>

Developing a custom ViewHelper

Developing a custom ViewHelper is often necessary in extension development. This chapter will demonstrate how to write a custom ViewHelper for the blog example extension.

The official documentation of Fluid for writing custom ViewHelpers can be found within Fluid documentation at https://github.com/TYPO3/Fluid/blob/master/doc/FLUID_CREATING_VIEWHELPERS.md.

The Example ViewHelper

"Avatar" images are pictures or icons that for example are dedicated to the author of an article in blogs or on forums. The photos of blog authors and forum moderators are mostly stored on the appropriate server. With users that only want to ask a question or to comment a blog post, this is not the case. To allow them to supply their article with an icon, a service called gravatar.com is available. This online service makes sure that an email address is assigned to a certain avatar picture.

A web application that wants to check if an avatar picture exists for a given email address has to send a checksum (with the hash function md5()) of the email address to the service and receives the picture for display.

This section explains how to write a ViewHelper that uses an email address as parameter and shows the picture from gravatar.com if it exists.

The custom ViewHelper is not part of the default distribution, therefore an own namespace import is necessary to use this ViewHelper. In the following example the namespace \MyVendor\BlogExample\ViewHelpers is imported with the prefix blog. Now, all tags starting with blog: are interpreted as ViewHelper from within this namespace:

{namespace blog=MyVendor\BlogExample\ViewHelpers}

For further information about namespace import, see Importing namespaces.

The ViewHelper should be given the name "gravatar" and only take an email address as parameter. The ViewHelper is called in the template as follows:

<blog:gravatar emailAddress="username@example.com" />

See Global namespace import for information how to import namespaces globally.

The implementation

Every ViewHelper is a PHP class. For the Gravatar ViewHelper the name of the class is \MyVendor\BlogExample\ViewHelpers\GravatarViewHelper.

Following the naming conventions for Extbase extensions the ViewHelper skeleton is created in the PHP file EXT:blog_example/Classes/ViewHelpers/GravatarViewHelper.php:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class GravatarViewHelper extends AbstractViewHelper
{
   public static function renderStatic(
       array $arguments,
       \Closure $renderChildrenClosure,
       RenderingContextInterface $renderingContext
   ) {
       // Implementation ...
   }
}

Note

Prior to Fluid 2.4, the method renderStatic() required a use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; at the beginning of the ViewHelper class file and additionally a use CompileWithRenderStatic; inside of the ViewHelper class. Otherwise the method render() would not be found. If you use the latest TYPO3 9 or higher, this should no longer be necessary.

Every ViewHelper must inherit from the class \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper.

A ViewHelper can also inherit from subclasses of AbstractViewHelper, e.g. from \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper. Several subclasses are offering additional functionality. The TagBasedViewHelper will be explained later on in this chapter in detail.

In addition every ViewHelper needs a method renderStatic(), which is called once the ViewHelper will be displayed in the template. The return value of the method is copied directly into the complete output. If the above example is extended like the following:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class GravatarViewHelper extends AbstractViewHelper
{
   use CompileWithRenderStatic;

   public static function renderStatic(
       array $arguments,
       \Closure $renderChildrenClosure,
       RenderingContextInterface $renderingContext
   ) {
       return 'World';
   }
}

And called like this in the template:

{namespace blog=MyVendor\BlogExample\ViewHelpers}

Hello <blog:gravatar />

The displayed result will be Hello World.

Register arguments of ViewHelpers

The Gravatar ViewHelper must hand over the email address on which identifies the Gravatar. This is the last remaining piece before the implementation can be completed.

All arguments of a ViewHelper must be registered. Every ViewHelper has to declare explicitly which parameters are accepted. The registration happens inside initializeArguments():

public function initializeArguments()
{
    $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
}

While this works for most use cases, there are some when arguments with a given prefix should be allowed, e.g. data- arguments. Therefore it's possible to handle additional arguments. For further information see Handle additional arguments.

This way the ViewHelper receives the argument emailAddress of type string. The type is given by the second argument of the registerTagAttribute(). The arguments are registered via $this->registerArgument($name, $type, $description, $required, $defaultValue). These arguments can be accessed through the array $arguments, which is passed into the method.

Tip

Sometimes arguments can take various types. In this case the type mixed should be used.

Finally the output of the img tag needs to be implemented:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class GravatarViewHelper extends AbstractViewHelper
{
   use CompileWithRenderStatic;

   public function initializeArguments()
   {
       $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
   }

   public static function renderStatic(
       array $arguments,
       \Closure $renderChildrenClosure,
       RenderingContextInterface $renderingContext
   ) {
      return '<img src="http://www.gravatar.com/avatar/' .
         md5($arguments['emailAddress']) .
         '" />';
   }
}

Escaping of Output

The above implementation still does not provide the expected result. The output for the following usage:

<blog:gravatar emailAddress="username@example.com" />

Does not result in:

<img src="http://www.gravatar.com/avatar/5f0efb20de5ecfedbe0bf5e7c12353fe" />

Instead the result is:

&lt;img src=&quot;http://www.gravatar.com/avatar/5f0efb20de5ecfedbe0bf5e7c12353fe&quot; /&gt;

By default all output is escaped by htmlspecialchars to prevent cross site scripting.

To allow unescaped HTML output, escaping has to be explicitly disabled. This is done by setting the class property $escapeOutput to false:

protected $escapeOutput = false;

To make the above implementation work, the property is set to false. The output is no longer escaped and the <img>-Tag is now rendered as expected.

If escaping of children is disabled, no nodes that are passed with inline syntax or values used as tag content will be escaped. Note that $escapeOutput takes priority: if it is disabled, escaping of child nodes is also disabled unless explicitly enabled.

protected $escapeChildren = false;

Passing in children is explained in Prepare ViewHelper for inline syntax.

Creating HTML/XML tags using TagBasedViewHelper

For ViewHelpers which create HTML/XML tags, Fluid provides an enhanced base class: \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper. This base class provides an instance of \TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder that can be used to create HTML-tags. It takes care of the syntactically correct creation and for example escapes single and double quotes in attribute values.

Attention

Correctly escaping the attributes is mandatory as it affects security and prevents cross site scripting attacks.

The GravatarViewHelper will now make use of the TagBasedViewHelper. Because the Gravatar ViewHelper creates an img tag the use of the \TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder is advised:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

class GravatarViewHelper extends AbstractTagBasedViewHelper
{
    protected $tagName = 'img';

    public function initializeArguments()
    {
        parent::initializeArguments();
        $this->registerUniversalTagAttributes();
        $this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
        $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
    }

    public function render()
    {
        $this->tag->addAttribute(
            'src',
            'http://www.gravatar.com/avatar/' . md5($this->arguments['emailAddress'])
        );
        return $this->tag->render();
    }
}

What is different in this code?

First of all, the ViewHelper does not inherit directly from AbstractViewHelper but from TagBasedViewHelper, which provides and initializes the tag builder. Beyond that there is a class property $tagName which stores the name of the tag to be created. Furthermore the tag builder is available at $this->tag. It offers the method addAttribute() to add new tag attributes. In our example the attribute src is added to the tag, with the value assigned one line above it. Finally, the GravatarViewHelper creates an img tag builder, which has a method named render(). After configuring the tag builder instance, the rendered tag markup is returned.

Note

As $this->tag is an instance variable, render() is used to generate the output. renderStatic() would have no access. For further information take a look at The different render methods.

Notice that the attribute $escapeOutput is no longer necessary.

Furthermore the TagBasedViewHelper offers assistance for ViewHelper arguments that should recur directly and unchanged as tag attributes. These should be output directly must be registered in initializeArguments() with the method $this->registerTagAttribute($name, $type, $description, $required = false). If support for the <img> attribute alt should be provided in the ViewHelper, this can be done by initializing this in initializeArguments() in the following way:

public function initializeArguments()
{
    $this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}

For registering the universal attributes id, class, dir, style, lang, title, accesskey and tabindex there is a helper method registerUniversalTagAttributes() available.

If support for universal attributes should be provided and in addition to the alt attribute in the Gravatar ViewHelper the following initializeArguments() method will be necessary:

public function initializeArguments()
{
    parent::initializeArguments();
    $this->registerUniversalTagAttributes();
    $this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}

Insert optional arguments

An optional size for the image can be provided to the Gravatar ViewHelper. This size parameter will be used to determine the height and width in pixels of the image and can range from 1 to 512. When no size is given, an image of 80px is generated.

The render() method can be improved like this:

public function initializeArguments()
{
    $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
    $this->registerArgument('size', 'integer', The size of the gravatar, ranging from 1 to 512', false, 80);
}

public function render()
{
    $this->tag->addAttribute(
       'src',
       'http://www.gravatar.com/avatar/' .
           md5($this->arguments['emailAddress']) .
           '?s=' . urlencode($this->arguments['size'])
    );
    return $this->tag->render();
}

With this setting of a default value and setting the fourth argument to false, the size attribute becomes optional.

Prepare ViewHelper for inline syntax

So far the Gravatar ViewHelper has focused on the tag structure of the ViewHelper. The call to render the ViewHelper was written with tag syntax which seemed obvious because it itself returns a tag:

<blog:gravatar emailAddress="{post.author.emailAddress}" />

Alternatively this expression can be written using inline notation:

{blog:gravatar(emailAddress: post.author.emailAddress)}

One should see the Gravatar ViewHelper as a kind of post processor for an email address and would allow the following syntax:

{post.author.emailAddress -> blog:gravatar()}

This syntax places focus on the variable that is passed to the ViewHelper as it comes first.

The syntax {post.author.emailAddress -> blog:gravatar()} is an alternative syntax for <blog:gravatar>{post.author.emailAddress}</blog:gravatar>. To support this the email address comes either from the argument emailAddress or, if it is empty, the content of the tag should be interpreted as email address.

This was in particular used with formatting ViewHelpers. These ViewHelpers all support both tag mode and inline syntax.

Depending on the implemented method for rendering, the implementation is different:

With renderStatic()

To fetch the content of the ViewHelper the argument $renderChildrenClosure is available. This returns the evaluated object between the opening and closing tag.

Lets have a look at the new code of the renderStatic() method:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class GravatarViewHelper extends AbstractViewHelper
{
    use CompileWithContentArgumentAndRenderStatic;

    protected $escapeOutput = false;

    public function initializeArguments()
    {
        $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for');
    }

    public static function renderStatic(
        array $arguments,
        \Closure $renderChildrenClosure,
        RenderingContextInterface $renderingContext
    ) {
        $emailAddress = $renderChildrenClosure();

        return '<img src="http://www.gravatar.com/avatar/' .
            md5($emailAddress) .
            '" />';
    }
}
With render()

To fetch the content of the ViewHelper the method renderChildren() is available in the AbstractViewHelper. This returns the evaluated object between the opening and closing tag.

Lets have a look at the new code of the render() method:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

class GravatarViewHelper extends AbstractTagBasedViewHelper
{
    protected $tagName = 'img';

    public function initializeArguments()
    {
        $this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', false, null);
    }

    public function render()
    {
        $emailAddress = $this->arguments['emailAddress'] ?? $this->renderChildren();

        $this->tag->addAttribute(
            'src',
            'http://www.gravatar.com/avatar/' . md5($emailAddress)
        );

        return $this->tag->render();
    }
}

Importing namespaces

Three different ways exist to import a ViewHelper namespace into Fluid. These three ways are explained in the following section.

Local namespace import via <>-Syntax

To import a ViewHelper namespace into Fluid, the following Syntax can be used:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:blog="http://typo3.org/ns/MyVendor/BlogExample/ViewHelpers"
      data-namespace-typo3-fluid="true"
>
    <!-- Content of Fluid Template -->
</html>

In above example blog is the namespace available within the Fluid template and MyVendor\BlogExample\ViewHelpers is the PHP namespace to import into Fluid.

All ViewHelper which start with blog: will be looked up within the PHP namespace.

The <html>-Tags will not be part of the output, due to data-namespace-typo3-fluid="true" attribute.

One benefit of this approach is auto completion within modern IDEs and the possibility to lint the Fluid files.

Local namespace import via {}-Syntax

To import a ViewHelper namespace into Fluid, the following Syntax can be used:

{namespace blog=MyVendor\BlogExample\ViewHelpers}

In above example blog is the namespace available within the Fluid template and MyVendor\BlogExample\ViewHelpers is the PHP namespace to import into Fluid.

All ViewHelper which start with blog: will be looked up within the PHP namespace.

Each of the rows will result in an blank line. Multiple import statements can go into a single, or multiple lines.

Global namespace import

Fluid allows for registering namespaces. This is already done for typo3/cms-fluid and typo3fluid/fluid ViewHelpers. Therefore they are always available via the f namespace.

Custom ViewHelpers, e.g. for a site package, can be registered the same way. Namespaces are registered within $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces'] in the form of:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['blog'] = [
    'MyVendor\BlogExample\ViewHelpers',
];

In above example blog is the namespace within Fluid templates, which is resolved to the PHP namespace \MyVendor\BlogExample\ViewHelpers.

Handle additional arguments

If a ViewHelper allows further arguments then the configured one, see Register arguments of ViewHelpers, the handleAdditionalArguments() method can be implemented.

The \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper makes use of this, to allow setting any data- argument for tag based ViewHelpers.

The method will receive an array of all arguments, which are passed in addition to the registered arguments. The array uses the argument name as key and the argument value as the value. Within the method these arguments can be handled.

E.g. the AbstractTagBasedViewHelper implements the following behaviour:

public function handleAdditionalArguments(array $arguments)
{
    $unassigned = [];
    foreach ($arguments as $argumentName => $argumentValue) {
        if (strpos($argumentName, 'data-') === 0) {
            $this->tag->addAttribute($argumentName, $argumentValue);
        } else {
            $unassigned[$argumentName] = $argumentValue;
        }
    }
    parent::handleAdditionalArguments($unassigned);
}

To keep the default behaviour, all unwanted arguments should be passed to the parent method call parent::handleAdditionalArguments($unassigned);, to throw accordingly exceptions.

The different render methods

ViewHelper can have one or multiple of the following three methods for implementing the rendering. The following section will describe the differences between all three implementations.

compile()-Method

This method can be overwritten to define how the ViewHelper should be compiled. That can make sense if the ViewHelper itself is a wrapper for another native PHP function or TYPO3 function. In that case the method can return the call to this function and remove the need to call the ViewHelper as a wrapper at all.

The compile() has to return the compiled PHP code for the ViewHelper. Also the argument $initializationPhpCode can be used to add further PHP code before the execution.

Note

The renderStatic() method has still to be implemented for the non compiled version of the ViewHelper. In the future, this should no longer be necessary.

Example implementation:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class StrtolowerViewHelper extends AbstractViewHelper
{
    use CompileWithRenderStatic;

    public function initializeArguments()
    {
        $this->registerArgument('string', 'string', 'The string to lowercase.', true);
    }

    public static function renderStatic(
        array $arguments,
        \Closure $renderChildrenClosure,
        RenderingContextInterface $renderingContext
    ) {
        return strtolower($arguments['string']);
    }

    public function compile(
        $argumentsName,
        $closureName,
        &$initializationPhpCode,
        ViewHelperNode $node,
        TemplateCompiler $compiler
    ) {
        return 'strtolower(' . $argumentsName. '[\'string\'])';
    }
}
renderStatic()-Method

Most of the time this method is implemented. It's the one that is called by default from within the compiled Fluid.

It is however not called on AbstractTagBasedViewHelper implementations - on such classes you still need to use the render() method since that is the only way you can access $this->tag which contains the tag builder that generates the actual XML tag.

As this method has to be static, there is no access to instance attributes, e.g. $this->tag within an subclass of AbstractTagBasedViewHelper.

Note

This method can not be used when access to child nodes is necessary. This is the case for ViewHelpers like if or switch which need to access their children like then or else. In that case, render() has to be used.

render()-Method

This method is the slowest one. As the compiled version always calls the renderStatic() method, this will call further PHP code which, in the end, will call the original render() method.

By using this method, the surrounding logic does not get compiled.

Note

This way is planned to be removed in the future.

Using PHP based views

So far we have used Fluid as template engine. Most textual output formats are well representable with Fluid. For some use cases it is reasonable to use pure PHP for the output. An example of such an use case is the creation of JSON files.

For this reason, Extbase also supports PHP based views. Assume we want create a JSON based output for the list action in the post controller of the BlogExample. To be able to do so we need a PHP based view.

When no Fluid template is found for a controller/action/format combination, a PHP based view will be used. This PHP class is resolved against a naming convention which is defined in the ActionController in the class variable $viewObjectNamePattern. The default naming convention is following:

\MyVendor\@extension\View\@controller\@action@format

All parts beginning with @ will be replaced accordingly. When no class with this name can be found, the @format will be removed from the naming convention and a matching class again searched for.

Our PHP based view for the list view of the post controller should have the class name \MyVendor\BlogExample\View\Post\ListJSON, because it applies only for the format JSON. So that the class according to the naming convention must be implemented in the file EXT:blog_example/Classes/View/Post/ListJSON.php.

Each view must implement the interface \TYPO3\CMS\Extbase\Mvc\ViewViewInterface. This consists off some initializing methods and the render() method, which is called by the controller for displaying the view.

It is often helpful to inherit directly from \TYPO3\CMS\Extbase\Mvc\View\AbstractView which provides default initializing methods and you only have to implement the render() method. A minimal view would like this:

<?php
namespace MyVendor\BlogExample\View\Post;

class ListJSON extends \TYPO3\CMS\Extbase\Mvc\View\AbstractView
{
   public function render()
   {
      return 'Hello World';
   }
}

Now we have the full expression power of PHP available and we can implement our own output logic. For example our JSON view could look like this:

<?php
namespace MyVendor\BlogExample\View\Post;

class ListJSON extends \TYPO3\CMS\Extbase\Mvc\View\AbstractView
{
   public function render()
   {
      $postList = $this->viewData['posts'];
      return json_encode($postList);
   }
}

Here we can see that the data that is passed to the view is available in the array $this->viewData. These are converted to JSON data using the function json_encode and then returned.

Tip

PHP based views are also helpful for specially complex kind of output like the rendering of PDF files.

View configuration options in the controller

You have some methods in the controller that you can overwrite to control the resolution of the view. In the most cases the customization of $viewObjectNamePattern should be flexible enough, but sometimes you have to put more logic into it.

For example you might have to initialize your view in a special manner before it can be used. For this there is the template method initializeView($view) inside the ActionContoller, which gets the view as parameter. In this method you should write your own initializing routine for your view.

If you want to control the resolving and initializing of the view completely, you have to rewrite the method resolveView(). This method has to return a view that implements \TYPO3\CMS\Extbase\Mvc\ViewViewInterface. Sometimes it is enough to just overwrite the resolution of the view object name. Therefore you must overwrite the method resolveViewObjectName(). This method returns the name of the PHP class which should be used as view.

Tip

If you have a look at the source code of Extbase at these points, in the comment blocks of the above mentioned methods you see an @api annotation. These methods are part of the official API of Extbase and could be overwritten for personal use.

Methods without an API annotation should never be overwritten (although it is technically possible), because they could be directly changed in feature versions of Extbase.

Now you have learned about the most helpful functions of Fluid. In the following section we would show the interaction of these functions during the creation of a real template, to give you a better feeling for the work with Fluid.

Template Creation by example

In this section we will show you some of the techniques you got to know in the course so far, in the interaction with our sample extension sjr_offers. We will focus on practical solutions for repeating problems. The directory structure of the extension is shown in Figure 8-2. We are using both layouts and partials, to avoid double code. Inside Scripts we put JavaScript code that we use for animations and for a Date picker in the frontend.

_images/figure-8-2.png

Figure 8-2: Folder structure of layouts, templates and partials inside the extension sjr_offers

The extension sjr_offers has an OfferController and an OrganizationController. Using the OfferController, offers can be displayed as a list using the method indexAction() or as single view using the method showAction() method. Also offers can be created using the method newAction() and available offers can be edited using the method editAction(). The OrganizationController incorporates the same actions for organizations, with exception of the creation of organizations. Within the folder EXT:sjr_offers/Resources/Private/Templates we have created a folder for each controller, without the suffix Controller in the name. Each action method has its own HTML template. There is also no suffix Action allowed in the name.

Setting up the HTML basic framework

The various templates have many common elements. First we define the basic framework by a common layout (see the section Creating A Consistent Look And Feel With Layouts earlier in this chapter) and store repeating code in partials (see the section "Moving Repeating Snippets To Partials" earlier in this chapter). The basic framework of our templates looks as follows:

<f:layout name="default" />
   <f:section name="content">
   <!-- ... -->
</f:section>

In most templates we are referencing the layout default, that should build the "frame" of our plugin output. The actual template resides in a section with the name content. The layout definition is stored in the HTML file EXT:sjr_offers/Resources/Private/Layouts/default.html

<div class="tx-sjroffers">
<f:render section="content" />
<f:flashMessages id="dialog" title="Notice!"/>
</div>

A section content of the respective template is rendered and after this a message to the frontend user is shown if necessary. The complete content of the plugin is then "packed" in a div container. The message - a so called flash message - will be created inside our sample extension in the controller, e.g. at unauthorized access (see also the sections for edit and delete controller actions in chapter 7):

public function updateAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
   $administrator = $offer->getOrganization()->getAdministrator();
   if ($this->accessControlService->isLoggedIn($administrator)) {
      // ...
   } else {
      $this->flashMessages->add('Please log in.');
   }
   // ...
}

Store functions in ViewHelper

With this the base framework of our plugin output is ready. In the templates of our sample extension there still exists some repeating jobs, which can be stored in ViewHelper classes.

One requirement for the extension is, that the organizations can edit their (and only their) offers in the frontend. We have to control the access at different levels, so that not every website user can change the data. We have discussed the different level of the access control already in chapter 7. One of those levels are the templates. Elements for editing the data, like forms, links and icons, should only displayed when an authorized administrator of the organization is logged in as frontend user (see figure 8-3). In chapter 7 we suggested the IfAuthenticatedViewHelper and the AccessControlService, that we had implemented for this purpose.

_images/figure-8-3.png

Figure 8-3: Single view of an organization with its offers (left) and the same view with shown editing symbols (right)

Another repeating job is the formatting of numbers and date intervals, For example how the date is displayed for the Offerperiod (Angebotszeitraum) in Figure 8-3. An offer can have a minimum and/or a maximum amount of attendees for example. If none of this is given, nothing should be displayed. If only one of these values is given the value should be prefixed with from respectively to. We store these jobs in a NumericalRangeViewHelper and call it in our template like this:

<sjr:format.numericRange>{offer.ageRange}</sjr:format.numericRange>

Alternatively you can use the inline notation of Fluid (therefore see the box Inline Notation Versus Tag Based Notation earlier in this chapter):

{offer.ageRange->sjr:format.numericRange()}

The NumericRangeViewHelper is implemented as follows:

namespace MyVendor\SjrOffers\ViewHelpers\Format;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class NumericRangeViewHelper extends AbstractViewHelper
{
  /**
   * @param \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface $range The range
   * @return string Formatted range
   */
  public function render(\MyVendor\SjrOffers\Domain\Model\NumericRangeInterface $range = NULL)
  {
    $output = '';
    if ($range === NULL) {
      $range = $this->renderChildren();
    }
    if ($range instanceof \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface) {
      $minimumValue = $range->getMinimumValue();
      $maximumValue = $range->getMaximumValue();
      if (empty($minimumValue) && !empty($maximumValue)) {
        $output = 'bis&nbsp;' . $maximumValue;
      } elseif (!empty($minimumValue) && empty($maximumValue)) {
        $output = 'ab&nbsp;' . $minimumValue;
      } else {
        if ($minimumValue === $maximumValue) {
          $output = $minimumValue;
        } else {
          $output = $minimumValue . '&nbsp;-&nbsp;' . $maximumValue;
        }
      }
    }
    return $output;
  }
}

The method render() has the optional argument $range. This is important for the inline notation. When this argument is not set (also NULL), the code between the starting and ending tag is processed ("normal" notation) by calling the method renderChildren(). Is the result an object that implements the NumericRangeInterface then the described use cases are checked step by step and the resulting string is returned. In a similar manner the DateRangeViewHelper was implemented.

Design a form

At the end we show you another sample for designing a form for editing the basic data of an organization. You find the associated template edit.html in the folder EXT:sjr_offers/Resources/Private/Templates/Organization/.

{namespace sjr=MyVendor\SjrOffers\ViewHelpers}
<f:layout name="default" />
<f:section name="content">
  <sjr:security.ifAuthenticated person="{organization.administrator}">
    <f:then>
      <f:render partial="formErrors" arguments="{formName: 'organization'}" />
      <f:form class="tx-sjroffers-form" method="post" action="update" name="organization"
            object="{organization}">
        <label for="name">Name</label>
        <f:form.textbox property="name" size="46" /><br />
        <label for="address">Address</label>
        <f:form.textarea property="address" rows="6" cols="46" /><br />
        <label for="telephoneNumber">Telephone</label>
        <f:form.textbox property="telephoneNumber" size="46" /><br />
        <label for="telefaxNumber">Telefax</label>
        <f:form.textbox property="telefaxNumber" size="46" /><br />
        <label for="emailAddress">E-Mail</label>
        <f:form.textbox property="emailAddress" size="46" /><br />
        <label for="url">Homepage</label>
        <f:form.textbox property="url" size="46" /><br />
        <label for="description">Description</label>
        <f:form.textarea property="description" rows="8" cols="46" /><br />
        <f:form.submit value="Save"/>
      </f:form>
    </f:then>
    <f:else>
      <f:render partial="accessError" />
    </f:else>
  </sjr:security.ifAuthenticated>
</f:section>

The form is enclosed in the tags of the IfAuthenticatedViewHelper. If the access is granted than the form is displayed, otherwise the content of the partial accessError is displayed.

<div id="dialog" title="Notice!">
You are not authorized to execute this action.
Please first log in with your username and password.
</div>

With the declaration of object="{organization}" the proper form is bound to the assigned Organization object in the editAction().<remark>TODO: Rewrite sentence</remark> The form consists of input fields that are created by Fluid with the form.textbox ViewHelper respectively the form.textarea ViewHelper. Each form field is bound to their specific property of the Organization object using property="telefaxNumber". The attribute value of the concrete object is inserted in the form fields during rendering of the page. When submitting the form, the data is send as POST parameters to the method updateAction().

When the entered data is not valid, the method editActon() is called again and an error message is displayed. We have stored the HTML code for the error message in a partial formErrors (see EXT:sjr_offers/Resources/Private/Partials/formErrors.html). In this partial, the name of the form that relates to the error message is given as formName:

<f:form.errors for="formName">
   <div id="dialog" title="{error.propertyName}">
      <p>
         <f:for each="{error.errors}" as="errorDetail">
            {errorDetail.message}
         </f:for>
      </p>
   </div>
</f:form.errors>

Conclusion

In this chapter you first learned the basic concepts of Fluid, followed by the use partials and layouts which help a lot with structuring your templates. You have learned about the cObject ViewHelper that makes use of TypoScript in Fluid templates possible. In addition you have seen how additional tag attributes are included in the output of a ViewHelper. This chapter was rounded up by the presentation of the if ViewHelper and the possibility to formulate complex bool terms. Finally we have shown how you develop your own ViewHelper. And at last we have present all concepts again with the help of an extensive practical sample - the sjr_offers extension.

By now you should have a good overview about the different levels of the MVC design pattern. The following chapter will focus on functions that touch all 3 levels. We will suggest the localization of an extension, the validating of data as well as the security of an extension.

Internationalization, Validation and Security

In the previous chapters we examined all levels of the MVC pattern, exploring each aspect in detail. This will change in this chapter: Here we will talk about functions on extension programming, where the interaction of the different levels is important. First we show how to program Extbase based extensions which can create output in different languages. Besides the localisation of static text we also provide the translation of domain objects. In the second section of the chapter we explain how the consistency terms of the domain model are to be implemented and checked. This chapter will end with some aspects which enhance the security of the extension.

Localizing and internationalizing an extension

Particularly in business relationships there is often the need to build a website in more than one language. Therefore not only does the translation of the websites content need to be completed, but also the extensions which are used must also be available in multiple languages.

The configuration options for localization inside TYPO3 are versatile. You will find a comprehensive description of all concepts and options in the Frontend Localization Guide (https://docs.typo3.org/typo3cms/FrontendLocalizationGuide/). For the following sections we assume a correct configuration of the localization, which is normally done in the TypoScript root template and looks like this:

config {
  linkVars = L
  uniqueLinkVars = 1
  sys_language_uid = 0
  language = default
  locale_all = en_GB
  htmlTag_langKey = en
}

[globalVar = GP:L = 1]
config {
  sys_language_uid = 1
  language = de
  locale_all = de_DE.utf8
  htmlTag_langKey = de
}
[global]

The selection of the frontend language is carried out with a parameter in the URL (linkVars = L). Important is the definition of the UID of the language (sys_language_uid = 0) and the language key of the language (language = default). When the URL of the website contains the parameter L=1 the output occurs in german, if the parameter is not set the output of the website occurs in the default language (in our example in english).

In the next section, we start with the translation of static text like captions of links which appear in templates. After this we go to translate the content of extensions, thus the domain objects. Finally we explain how you can adjust the date formats in accordance with the date conventions in the particular country.

Multi language Templates

When you style the output of your extension using Fluid, you often have to localize particular terms or maybe short text in the templates. In the following sample template of the blog example which displays a single blog post with its comments there are some constant terms:

<h3>{post.title}</h3>
<p>By: {post.author.fullName}</p>
<p>{post.content -> f:format.nl2br()}</p>

<h3>Comments</h3>
<f:for each="{post.comments}" as="comment">
  {comment.content -> f:format.nl2br()}
  <hr>
</f:for>

Tip

The template is a little bit simplified and reduced to the basic.

First of all the text "By:" in front of the author of the post is hard coded in the template, as well as the caption "Comments". For the use of the extension on an English website this is no problem but if you want to use it on a German website, the texts "By" and "Comments" would be displayed instead of "Von" and "Kommentare". To make such text exchangeable it has to be removed from the template and inserted in a language file. Every text which is to be translated is given an identifier that can be inserted in the template later. Table 9-1 shows the identifier used in the sample and their translations into german and english.

Table 9-1: The texts how we want to translate them

Identifier English German
author_prefix By: Von:
comment_header Comments Kommentare

In TYPO3 (and also in Extbase) the language file, in which the translated terms are stored, is named locallang.xlf. It should contain all terms that have to be translated, in our example "By:" and "Comments", and their translations. Using Extbase the the file locallang.xlf must reside in the folder Resources/Private/Language/. To localize the above terms we create the locallang.xlf file the following way:

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.0" xmlns="urn:oasis:names:tc:xliff:document:1.1">
   <file source-language="en" datatype="plaintext" original="messages" date="2011-10-18T18:20:51Z" product-name="my-ext">
       <header/>
       <body>
           <trans-unit id="author_prefix">
               <source>By:</source>
           </trans-unit>
           <trans-unit id="comment_header">
               <source>Comments</source>
           </trans-unit>
       </body>
   </file>
</xliff>

Tip

The TYPO3 Core API describes in detail the construction of the locallang.xlf file (https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Internationalization/XliffFormat.html).

Now the placeholder for the translated terms must be inserted into the template. To do this, Fluid offers the ViewHelper f:translate. In this ViewHelper you give the identifier of the term to be inserted as argument key and the ViewHelper inserts either the german or the english translation according to the current language selection

<f:translate key="comment_header" />
<!-- or -->
{f:translate(key: 'comment_header')}

Tip

The used language is defined in the TypoScript template of the website. By default the english texts are used; but when with setting of the TypoScript setting config.language = de you can set the used language to german for example.

To implement a language selection normally TypoScript conditions are used. These are comparable with an if/else block

[globalVar = GP:L = 1]
config.language = de
[else]
config.language = default
[end]

When the URL of the website contains a parameter L=1, then the output is in German; if the parameter is not set the output is in the default language English.

With the use of complex TypoScript conditions the language selection could be set to depend on the forwarded language of the browser.

By replacing all terms of the template with the translate ViewHelper we could fit the output of the extension to the currently selected language. Here we have a look at the Fluid template for the output of the blog posts, now without the hardcoded english terms:

<h3>{post.title}</h3>
<p><f:translate key="author_prefix"> {post.author.fullName}</p>
<p>{post.content -> f:format.nl2br()}</p>
<h3><f:translate key="comment_header"></h3>
<f:for each="{post.comments}" as="comment">
   {comment.content -> f:format.nl2br()}
   <hr>
</f:for>

Tip

Sometimes you have to localize a string in the PHP code, for example inside of a controller or a ViewHelper. In that case you can use the static method \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($key, $extensionName). This method requires the localization key as the first and the name of the extension as the second parameter. Then the corresponding text in the current language will be loaded from this extension's locallang.xlf file .

Output localized strings using sprintf

In the above example we have outputted the name of the blog post author simply by using {blog.author.fullName}. Many languages have special rules on how names are to be used - especially in Thailand it is common to only show the first name and place the word "Khan" in front of it (which is a polite form). We want to enhance our template now as far as it can to output the name of the blog author according to the current language. In German and English this is the form "first name last name" and in Thai "Khan first name".

Also for this use cases the translate ViewHelper can be used. With the aid of the array arguments, values can be embedded into the translated string. To do this, the syntax of the PHP function sprintf is used.

If we want to implement the above example, we must assign the first name and the last name of the blog author separate to the translate ViewHelper:

<f:translate key="name" arguments="{1:post.author.firstName, 2: post.author.lastName}" />

How should the corresponding string in the locallang.xml file looks like? It describes on which position the placeholder are to be inserted. For English and German it looks like this:

<label index="name">%1$s %2$s</label>

Important are the placeholder strings %1$s and %2$s. These will be replaced with the assigned parameters. Every placeholder starts with the % sign, followed by the position number inside the arguments array, starting with 1, followed by the $ sign. After that the usual formatting specifications follows - in the example it is the data type string (s). Now we can define for Thai, that "Khan" followed by the first name should be output:

<label index="name">Khan %1$s</label>

Tip

The keys in the arguments array of the ViewHelper have no relevance. We recommend to give them numbers like the positions (starting with 1), because it is easy understandable.

Tip

For a full reference of the formatting options for sprintf you should have a look at the PHP documentation: http://php.net/manual/de/function.sprintf.php.

Changing localized terms using TypoScript

If you use an existing extension for a customer project, you sometimes find out that the extension is insufficient translated or that the translations have to be adjusted. TYPO3 offers the possibility to overwrite the localization of a term by TypoScript. Fluid also support this.

If, for example, you want use the text "Remarks" instead of the text "Comments", you have to overwrite the identifier comment_header for the English language. For this you can add following line to your TypoScript template:

plugin.tx_blogexample._LOCAL_LANG.default.comment_header = Remarks

With this you will overwrite the localization of the term comment_header for the default language in the blog example. So you can adjust the translation of the texts like you wish, without changing the locallang.xml file.

Until now we have shown how to translate static text of templates. Of course it is important that also the data of an extension is translated according to the national language. We will show this in the next section.

Multi language domain objects

With TYPO3 you can localize the data sets in the backend. This also applies to domain data, because they are treated like "normal" data sets in the TYPO3 backend. To make your domain objects translatable you have to create additional fields in the database and tell TYPO3 about them. The class definitions must not be changed. Lets have a look at the required steps based on the blog class of the blog example. TYPO3 needs three additional database fields which you should insert in the ext_tables.sql file:

CREATE TABLE tx_blogexample_domain_model_blog {
    // ...
    sys_language_uid int(11) DEFAULT '0' NOT NULL,
    l10n_parent int(11) DEFAULT '0' NOT NULL,
    l10n_diffsource mediumblob NOT NULL,
    // ...
};

You are free to choose the names of the database fields, but the names we use here are common in the world of TYPO3. In any case you have to tell TYPO3 which name you have chosen. This is done in the ctrl section of the TCA configuration file Configuration/TCA/tx_blogexample_domain_model_blog.php

<?php

return [
    'ctrl' => [
        // ...
        'languageField' => 'sys_language_uid',
        'transOrigPointerField' => 'l10n_parent',
        'transOrigDiffSourceField' => 'l10n_diffsource',
        // ...
    ]
];

The field sys_language_uid is used for storing the UID of the language in which the blog is written. Based on this UID Extbase choose the right translation depending on the current TypoScript setting in config.sys_language_uid. In the field l10n_parent the UID of the original blog created in the default language, which the current blog is a translation of. The third field l10n_diffsource contains a snapshot of the source of the translation. This snapshot is used in the backend for creation of a differential view and is not used by Extbase.

In the section columns of the TCA you have to configure the fields accordingly. The following configuration adds two fields to the backend form of the blog: one field for the editor to define the language of a data record and one field to select the data record the translation relates to.

<?php

return [
    // ...
    'types' => [
        '1' => ['showitem' => 'l18n_parent , sys_language_uid, hidden, title,
                    description, logo, posts, administrator'],
    ],
    'columns' => [
        'sys_language_uid' => [
            'exclude' => 1,
            'label' => 'LLL:EXT:lang/locallang_general.php:LGL.language',
            'config' => [
                'type' => 'select',
                'foreign_table' => 'sys_language',
                'foreign_table_where' => 'ORDER BY sys_language.title',
                'items' => [
                    ['LLL:EXT:lang/locallang_general.php:LGL.allLanguages',-1],
                    ['LLL:EXT:lang/locallang_general.php:LGL.default_value',0]
                ],
            ],
        ],
        'l18n_parent' => [
            'displayCond' => 'FIELD:sys_language_uid:>:0',
            'exclude' => 1,
            'label' => 'LLL:EXT:lang/locallang_general.php:LGL.l18n_parent',
            'config' => [
              'type' => 'select',
              'items' => [
                  ['', 0],
              ],
              'foreign_table' => 'tx_blogexample_domain_model_blog',
              'foreign_table_where' => 'AND tx_blogexample_domain_model_blog.uid=###REC_FIELD_
                    l18n_parent### AND tx_blogexample_domain_model_blog.
                    sys_language_uid IN (-1,0)',
            ],
        ],
        'l18n_diffsource' => [
            'config' => [
              'type' =>'passthrough'
            ],
        ],
        // ...
    ],
];

With it, the localization of the domain object is already configured. By adding &amp;L=1 to the URL, the language of the frontend will be changed to german. If there is an existing translation of a blog, it will be shown. Otherwise the blog is output in the default language.

Tip

You can control this behavior. If you set the option config.sys_language_mode to strict in the TypoScript configuration, then only these objects are shown which really have content in the frontend language. More information for this you will find in the Frontend Localization Guide of the Core Documentation.

How TYPO3 handles the localization of content offers two important specific features: The first is that all translations of a data record respectively a data record that is valid for all languages (UID of the language is 0 or -1) will be "added" to the data record in the default language. The second special feature is that always the UID of the record in the default language is bound for identification although the translated data record in the database table has another UID. This conception has a serious disadvantage: If you want to create a data record for a language that has no data record in the default language, you have to create the latter before. But with what content?

Lets have an example for illustration: You create a blog in the default language English (=default). It is stored in the database like this:

uid:              7 (given by the database)
title:            "My first Blog"
sys_language_uid: 0 (selected in backend)
l10n_parent:      0 (no translation original exists)

After a while you create a German translation in the backend. In the database the following record is stored:

uid:              42 (given by the database)
title:            "Mein erster Blog"
sys_language_uid: 1 (selected in backend)
l10n_parent:      7 (selected in backend respectively given automatically)

A link that references the single view of a blog looks like this:

http://www.example.com/index.php?id=99&amp;tx_blogexample_pi1[controller]=Blog&amp;tx_blogexample_pi1[action]=show&amp;tx_blogexample_pi1[blog]=7

By adding &amp;L=1 we referencing now the German version:

http://www.example.com/index.php?id=99&amp;tx_blogexample_pi1[controller]=Blog&amp;tx_blogexample_pi1[action]=show&amp;tx_blogexample_pi1[blog]=7&amp;L=1

Notice that the given UID in tx_blogexample_pi1[blog]=7 is not changed. There is not UID of the data record of the german translation (42). This has the advantage that only the parameter for the language selection is enough. Concurrently it has the disadvantage of a higher administration effort during persistence. Extbase will do this for you by carrying the UID of the language of the domain model and the UID of the data record in which the domain data is effectively stored as "hidden" properties of the AbstractDomainObject internally. In Table 9-2 you find for different actions in the frontend the behavior of Extbase for localized domain objects.

Table 9-2: Behavior of Extbase for localized domain objects in the frontend.

  No parameter L given, or L=0 L=x (x>0)
Display (index, list, show) Objects in the default language (sys_language_uid=0) respectively object for all languages (sys_language_uid=-1) are shown The objects are shown in the selected language x. If an object doesn't exist in the selected language the object of the default language is shown (except by sys_language_mode=strict)
Editing (edit, update) Like displaying an object. The domain data is stored in the "translated" data record, in the above example in the record with the UID 42.
Creation (new, create) Independent of the selected frontend language the data is stored in a new record in whose field sys_language_uid the number 0 is inserted.

Extbase also supports all default functions of the localization of domain objects. It has its limits when a domain object should be created exclusively in a target language. Especially when no data record exists in the default language.

Localization of date output

It often occurs that a date or time must be displayed in a template. Every language area has its own convention on how the date is to be displayed: While in Germany the date is displayed in the form Day.Month.Year, in the USA the form Month/Day/Year is used. Depending on the language the date must be formatted different.

Generally the date or time is formatted by the format.date ViewHelper:

<f:format.date date="{dateObject}" format="d.m.Y" />
<!-- or -->
{dateObject -> f:format.date(format: 'd.m.Y')}

The date object {dateObject} is displayed with the date format given in the parameter format. This format string must be in a format which is readable by the PHP function date() and declares the format of the output. Table 9-3 shows the some important placeholders.

Table 9-3: Some place holder of date.

Format character Description Example
d Day of the month as number, double-digit, with leading zero 01 ... 31
m Month as number, with leading zero 01 ... 12
Y Year as number, with 4 digits 2011
y Year as number, with 2 digits 11
H Hour in 24 hour format 00 ... 23
i Minutes, with leading zero 00 ... 59

But the ViewHelper has to be configured different. Depending on the language area, which is controlled by the language of the user, an other format string should be used. Here we combine the format.date ViewHelper with the translate ViewHelper which you got to know in the section "Multilanguage templates"

<f:format.date date="{dateObject}" format="{f:translate(key: 'date_format')}" />

Than you can store an other format string for every language in the locallang.xml file and you can change the format string via TypoScript if needed. This method to translate content you got to know in the section "Multilanguage templates".

Tip

There are other formatting ViewHelpers for adjusting the output of currencies or big numbers. These ViewHelpers all starts with format. You can find an overview of these ViewHelpers in Appendix C. These ViewHelpers can be used like the f:format.date ViewHelper you have just learned.

In this section you have learned how you can translate and localize an extension. First we have worked on the localization of single terms in the template, after this we had a look at the content of the extension. Finally the customization of date information for country-specific formats where explained. In the next section you will see how constraints of the domain objects can be preserved.

Validating domain objects

We have learned about Extbase and Fluid in detail, but considered terms of consistence of the domain only marginally. Often we estimate that domain objects at all times retain consistent according to certain rules. This is not done automatically, so it is rather important to define these rules explicitly. In the blog example for example we can make the following rules:

  • The field username and password of the user object must have at least 5 characters. Furthermore the username must not contain special characters.
  • The field email of the user object must contain a valid email address.

These rules must apply at every point in time for the user object; on the other way a user object is only valid if it complies to these rules. These rules are called invariants, because they must be valid during the entire lifetime of the object.

In a first step you have to consider which invariants your domain objects have to offer. The next point is to put these invariants to Extbase in an appropriate form. Extbase provides validators for checking the invariants - these are PHP classes in which the invariants are implemented in code.

We will show you in the following how you can use a validator for the checking of invariants, and how you can give the user the possibility to correct an error when an error occurs.

Validators for checking of Invariants

A validator is a PHP class that has to check a certain invariant. All validators that are used in Extbase extensions have to implement the interface \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface. The interface requires validators to implement two methods:

  • validate($value)
  • getOptions()

Obviously, the main method is validate, which is called by the framework. The value which is to be validated is passed along said method and it's the validator's job to check if that value is valid.

Note

Although the interface states, that the method validate should return a \TYPO3\CMS\Extbase\Error\Result object, it's not common practice to do so because most people who create custom validators extend the class \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator.

This enables you to call the addError()` method and let the abstract validator take care of returning a proper result object to the validation framework.

If the logic of your validator allows for loose/variable validation checks, validator options might come in handy. Extbase ships a StringLength validator for instance which offers the options minimum and maximum that let you define the string length the validator should use to check the incoming value against.

Tip

You will find the complete reference of the ValidatorInterface in Appendix B.

For example, a validator which checks whether the passed string is an email address looks like this:

public function validate($value)
{
    if (!is_string($value) || !$this->validEmail($value)) {
        $this->addError(
            $this->translateErrorMessage(
                'validator.emailaddress.notvalid',
                'extbase'
            ), 1221559976);
    }
}

protected function validEmail($emailAddress)
{
    return \TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($emailAddress);
}

If $value is neither a string nor a valid email address, the validator adds an error by calling $this->addError().

Tip

The method addError() expects an error message and an error code. The latter should be unique, therefore we recommend to use the UNIX timestamp of the creation time of the source code. With the help of the error code the error can be definitely identified, for example in bug reports.

As default, extbase will not call your validator if the value to validate is empty. This is configured through the property $acceptsEmptyValues which is set to true as default.

In the package \TYPO3\CMS\Extbase\Validation\Validator\* Extbase offers many validators for default requirements like the validation of emails, numbers or strings.

When does validation take place?

Domain objects in Extbase are validated only at one point in time: When they get inserted into a controller action. With the help of figure 9-1 we can show what happens before the action is called.

_images/figure-9-1.png

Figure 9-1: Data flow of a request before the action is called

When a user sends a request, Extbase first determines which action respectively controller is responsible for this request. As Extbase knows the names and types of the arguments of the action it can create objects from the incoming data. This operation will be described in detail in the section "Argument mapping" later on. Now the main step for us is as follows: The created objects are to be validated, that is the invariants are to be checked. If all arguments are successfully validated, the requested action of the extension is called and it can continue processing the given objects for example give it to the view for displaying.

Tip

Certainly it would be helpful if the validation is also be done during the persisting of the objects to the database. At the moment it is not done since the data is stored in the database after sending the answer back to the browser. Therefore the user could not be informed in case of validating errors. In the meantime a second validating when persisting the objects is built into FLOW, so this will be expected in Extbase in the medium term.

When an error occurs during validation, the method errorAction() of the current controller is called. The provided default errorAction() redirects the user to the last used form when possible, in order to give him a chance to correct the errors.

Tip

You may ask how the errorAction() knows which form was the last displayed one. This information is created by the form ViewHelper. He adds automatically the property __referrer to every generated form, which contains information about the current extension, controller and action combination. This data can be used by the errorAction() to display the erroneous form again.

Registering validators

Now we know how validators are working and when they are called. However we have to connect our domain model with the validators to define which part of the model is has to be checked by which validator. Therefore there are three possibilities which we define in the following:

  • validating in the domain model with annotations
  • validating in the domain model with an own validator class
  • validating of controller arguments

Validating in the domain model with annotations

In most cases it is sufficient to validate the properties of a domain object separately. When all properties are validated with success the complete domain object is also successful validated; when a property can not be validated the validation of the complete domain object fails.

To define how a property of our domain object should be validated we use so called annotations of our source code. Annotations are machine readable "annotations" in the source code that are placed in comment blocks and start with the character @.

For the validation the @TYPO3\CMS\Extbase\Annotation\Validate annotation is available. With it we can specify which validator is to be used for checking the annotated property. Let us take a look at this using a part of the domain model Post of the blog example:

<?php
namespace MyVendor\BlogExample\Domain\Model;

class Post extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
    /**
     * @var string
     * @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"minimum": 3, "maximum": 50})
     */
    protected $title;

    /**
     * @var string
     */
    protected $content;
}

With the line @TYPO3\CMS\Extbase\Annotation\Validate("StringLength", options={"minimum": 3, "maximum": 50}) the validator for the property $title is specified. In parenthesis the parameter for the validator are specified. In our case we make sure that a title of a blog post is never shorter than three characters and will never be longer than 50 characters.

Which validator class is to be used? Extbase looks for a validator class using \TYPO3\CMS\Extbase\Validation\Validator\*ValidatorName*Validator. Using the above given annotation @TYPO3\CMS\Extbase\Annotation\Validate("StringLength") the validator \TYPO3\CMS\Extbase\Validation\Validator\StringLengthValidator is used.

When you have created your own validator to check the invariants you can use it in the @TYPO3\CMS\Extbase\Annotation\Validate annotation using the full class name, like shown in the following example:

<?php
namespace MyVendor\BlogExample\Domain\Model;

class Post extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
    /**
     * @var string
     * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\BlogExample\Domain\Validator\TitleValidator")
     */
    protected $title;

    /**
     * @var string
     */
    protected $content;
}

Here we validate the property $title with the \MyVendor\BlogExample\Domain\Validator\TitleValidator. This validator class now can check any invariants. For example, the validator shown in the following listing checks whether the title of a blog post is always build-on the scheme Maintopic: Title:

<?php

namespace MyVendor\BlogExample\Domain\Validator;

use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;

class TitleValidator extends AbstractValidator
{
   protected function isValid($value)
   {
      // $value is the title string
      if (count(explode(':', $value)) >= 2) {
         return;
      }
      $this->addError('The title was not of the type [Topic]:[Title].', 1221563773);
   }
}

Now you have seen how you can validate particular properties of the domain model. The next section shows to you, how complex domain objects are to be validated.

Validating in the domain model with an own validator class

The just introduced possibilities to register validators in the model is specially practical when individual properties of the model are to be validated. Sometimes it is necessary to validate the relationship between two or more properties of a model class. For example for a user registration it is reasonable that in the user object the property $password and $passwordConfirmed exists which should be identical. Therefore the individual validators for $password respectively $passwordConfirmation can not help, because they have no access to each other. You need an option to validate a domain object as a whole.

For this you can implement an own validator class for every object in the domain model which validates the object as a whole and with it access to all object properties is possible.

Important hereby is the correct naming convention. If you need a validator for the class \MyVendor\ExtbaseExample\Domain\Model\User it must be implemented in the class \MyVendor\ExtbaseExample\Domain\Validator\UserValidator. The name of the validator for a model object is incidental by replacing the namespace Model with Validator and also append Validator. When following the naming convention the validator is automatically called when it exists.

Equipped with this knowledge we can implement the UserValidator which compares $password with $passwordConfirmation. At first we must check if the given object is of the type user - after all the validator can be called with any object and has to add an error in such case:

<?php
namespace MyVendor\ExtbaseExample\Domain\Validator;

class UserValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
{
    public function validate($user)
    {
        if (! $user instanceof \MyVendor\ExtbaseExample\Domain\Model\User) {
            $this->addError('The given Object is not a User.', 1262341470);
        }
    }
}

So, if $user is not an instance of the user object an error message is directly created with addError(). The validator does not validate the object any further.

Tip

The method addError() gets two parameters - the first is an error message string while the second is an error number. The Extbase developers always uses the current UNIX timestamp when calling addError(). By this it is secured that the validation errors can be unique identified.

Now we have created the foundation of our validator and can start with the proper implementation - the check for equality of the passwords. This is made quickly:

<?php
namespace MyVendor\ExtbaseExample\Domain\Validator;

class UserValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
{
    public function validate($user)
    {
        if (! $user instanceof \MyVendor\ExtbaseExample\Domain\Model\User) {
            $this->addError('The given Object is not a User.', 1262341470);
            return;
        }
        if ($user->getPassword() !== $user->getPasswordConfirmation()) {
            $this->addError('The passwords do not match.', 1262341707);
        }
    }
}

Because we have access to the complete object the checking for equality of $password and $passwordConfirmation is very simple now.

Now we have got to know two possibilities how validators can be registered for our domain objects: directly in the model via @TYPO3\CMS\Extbase\Annotation\Validate annotation for single properties and for complete domain objects with an own validator class.

The illustrated validators until now are always executed when a domain model is given as parameter to a controller action - that is for all actions. Sometimes it is desired to initiate the validation only when calling special actions. How this can be done we will see in the next section.

Validating of controller arguments

If you want to validate a domain object only when calling a special action you have to define validators for individual arguments. Therefore a slightly modified form of the @TYPO3\CMS\Extbase\Annotation\Validate annotation can be used which is set in the comment block of the controller action. It has the format @TYPO3\CMS\Extbase\Annotation\Validate *[variablename] [validators]*, in the example below it is $pageName \MyVendor\MyExtension\Domain\Validator\PagenameValidator:

/**
 * Creates a new page with a given name.
 *
 * @param string $pageName THe name of the page which should be created.
 * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\MyExtension\Domain\Validator\PageNameValidator", param="pageName")
 */
public function createPageAction($pageName)
{
    // ...
}

Here the parameter $pageName is checked with an own validator.

Interaction of validators

Now you know three possibilities how validators are to be registered. For an argument of an action the following validators are called:

  • The data types of the (primitive) arguments are checked. When a parameter is defined with @param float as a floating number then the validator checks this. When you want to disable the type validation for an argument, you have to declare the type as mixed.
  • All @TYPO3\CMS\Extbase\Annotation\Validate annotations of the domain model are evaluated.
  • The validator class of the domain object is called when it exists.
  • More validators that are defined in the action with @TYPO3\CMS\Extbase\Annotation\Validate are called.

Lets have a look at the interaction once more with an example:

/**
 * Creates a website user for the given page name.
 *
 * @param string $pageName The name of the page where the user should be created.
 * @param \MyVendor\ExtbaseExample\Domain\Model\User $user The user which should be created.
 * @TYPO3\CMS\Extbase\Annotation\Validate("MyVendor\BlogExample\Domain\Validator\CustomUserValidator", param="user")
 */
public function createUserAction($pageName, \MyVendor\ExtbaseExample\Domain\Model\User $user)
{
    // ...
}

Here the following things are validated: $pageName must be a string. The data type of the @param annotation is validated. For $user all @TYPO3\CMS\Extbase\Annotation\Validate annotations of the model are validated. Also the \MyVendor\BlogExample\Domain\Validator\UserValidator is called if it exists. Beyond that the validator \MyVendor\BlogExample\Domain\Validator\CustomUserValidator is used to validate $user.

In some use cases it is reasonable that inconsistent domain objects are given as arguments. That can be the case for multi page forms, because after filling the first page the domain object is not complete. In this case you can use the annotation @TYPO3\CMS\Extbase\Annotation\IgnoreValidation("parameter"). This prevents the processing of the @TYPO3\CMS\Extbase\Annotation\Validate annotations in the domain model and calling the validator class of the domain object.

Case study: Edit an existing object

Now you know all building blocks you need to edit a blog object with a form. Hereby the edit form should be displayed again in case of a validation error. Two actions are involved at editing the blog: The editAction shows the form with the blog to be edited and the updateAction saves the changes.

Tip

If you want to implement edit forms for the domain objects of your extension you should implement it according to the schema displayed here.

The editAction for the blog looks like this:

public function editAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
{
    $this->view->assign('blog', $blog);
}

The blog object that we want to edit is passed and given to the view. The Fluid template than looks like this (slightly shortened and reduced to the important):

<f:form name="blog" object="{blog}" action="update">
    <f:form.textbox property="title" />
    <f:form.textbox property="description" />
    <f:form.submit />
</f:form>

Note that the blog object to be edited is bound to the form with object="{blog}". With this you can reference a property of the linked object with help of the property attribute of the form elements.

Also the name of the form (name="blog") is important because it is used as variable name for the object to be send. When submitting the form the updateAction is called with the blog object as parameter.

public function updateAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
{
    $this->blogRepository->update($blog);
}

So the name of the argument is $blog because the form has the name blog. When no validating errors occur, the blog object will be persisted with its changes.

Now have a look what happens when the user inserts erroneous data in the form. In this case an error occurs when validating the $blog arguments. Therefore instead of the updateAction, the errorAction is called. These action routes the request with forward() to the last used action because in case of an error the form should be displayed again. Additional an error message is generated and given to the controller. Ergo: In case of a validation error the editAction is displayed again.

As we want to display the erroneous object again it is important that the updateAction and editAction use the same argument names. In our example the argument is called $blog in both cases, so we are on the safe side.

Now we get an other problem: Also the editAction validates all parameter, but our blog object is not valid - we are captured in an endless loop. Therefore we have to suppress the argument validation for the editAction. For this we need the annotation @TYPO3\CMS\Extbase\Annotation\IgnoreValidation – the comment block of the editAction must be changed like this:

<?php
declare(strict_types = 1);

namespace MyVendor\BlogExample\Controller;

use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
    /**
     * @param \MyVendor\BlogExample\Domain\Model\Blog $blog The blog object
     * @Extbase\IgnoreValidation("blog")
     */
    public function editAction(\MyVendor\BlogExample\Domain\Model\Blog $blog)
    {
        $this->view->assign('blog', $blog);
    }
}

Now the blog object is not validated in the editAction. So also a non valid blog object is displayed correct.

Tip

If Extbase throws the exception TYPO3CMSExtbaseMvcExceptionInfiniteLoopException it signs that the @TYPO3\CMS\Extbase\Annotation\IgnoreValidation annotation is missing.

Fluid automatically adds the CSS class f3-form-error to all erroneous fields - so you can frame them in red for example using CSS. There is also a flashMessages ViewHelper which outputs the error messages of the validation.

Case study: Create an object

In the last section you have seen how to edit a blog object with a form. Now we will show you how to create a new blog object with a form. Also for creating a blog object two actions are involved. The newAction shows a form for creating an object and the createAction finally stores the object.

The only difference to the editing of an object is that the newAction is not always given an argument: when first displaying the form it is logical that there is no object available to be displayed. Therefore the argument must be marked as optional.

Here you will see all that we need. At first the controller code:

<?php
declare(strict_types = 1);

use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
    /**
     * This action shows the 'new' form for the blog.
     *
     * @param \MyVendor\BlogExample\Domain\Model\Blog $newBlog The optional default values
     * @Extbase\IgnoreValidation("newBlog")
     */
    public function newAction(\MyVendor\BlogExample\Domain\Model\Blog $newBlog = NULL)
    {
        $this->view->assign('newBlog', $newBlog);
    }

    /**
     * This action creates the blog and stores it.
     *
     * @param \MyVendor\BlogExample\Domain\Model\Blog $newBlog
     */
    public function createAction(\MyVendor\BlogExample\Domain\Model\Blog $newBlog)
    {
        $this->blogRepository->add($newBlog);
    }
}

The Fluid template for the newAction looks like this (in short form):

<f:flashMessages />
<f:form name="newBlog" object="{newBlog}" action="create">
    <f:form.textbox property="title" />
    <f:form.textbox property="description" />
    <f:form.submit />
</f:form>

What is the summary of what we have we done? Again it is important that the newAction and the createAction have the same argument name. This has also to conform with the name of the Fluid template (newBlog in the example). Also the parameter for the newAction must be marked as optional and the validation of the parameter must be suppressed with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation. Finally you can output validation errors in the template using the flashMessages ViewHelper when saving the data.

In figure 9-2 you find an overview of the behavior of Extbase when displaying, editing respectively creating of domain objects in the frontend.

_images/figure-9-2.png

Figure 9-2: Data flow of the form display and saving. When a validating error occurs it is displayed again.

Mapping arguments

In this section we would describe in detail what happens during a request before the accordingly action is called. Particular interesting is this process when sending a form. Because the HTTP protocol (and PHP) only can transfer arrays and strings, a big array with data is transferred when sending a form. In the action, domain objects are often expected as input parameter, so somehow the array must become an object. That is done by Extbase during the so called Argument Mappings. It makes it possible that as an user of Extbase you not only work with arrays, but you can change objects in forms or give over a complete object as parameter in links.

Lets have a look at all of this in a concrete example: We pick up the blog example extension and edit a blog object, like you got to know in the last section ("Case study: Edit an existing object"). When you edit a blog you see a form in which you can change the properties of the blog, in our case title and description.

The Fluid form looks like this (shortened to the essential):

<f:form method="post" action="update" name="blog" object="{blog}">
    <f:form.textbox property="title" />
    <f:form.textbox property="description" />
</f:form>

If the form is submitted the data will be sent in the following manner to the server:

tx_blogexample_pi1[blog][__identity] = 5
tx_blogexample_pi1[blog][title] = My title
tx_blogexample_pi1[blog][description] = Description

First of all the data is tagged with a prefix that contains the name of the extension and the plugin (tx_blogexample_pi1). This makes sure that two extensions have no impact on each other. Furthermore all changed properties of the blog object are transferred in an array, in our case title and description. As we want to change a blog object, we also need the identity of the blog object. In order to do this, Fluid automatically adds the __identity property for the blog object and fills it with the UID of the blog.

Now on the server side a blog object must be created out of this information. This is the job of the property mapper. His operation method is shown in figure 9-3.

For every argument it must be decided first whether a new object has to be created or if the work is based on an existing object. This will be decided based on the identity property __identity. If this is not in the input data a new object is created. Otherwise the framework knows the object identity and can go on work with it.

Tip

When you take a look at what is transferred to the server by the new action of the blog example, you will find that no identity properties are transferred - in this case a new object is created as desired.

In the blog example from above the __identity property is available, therefore the object with the corresponding UID is fetched from the repository and used for further modification.

When no properties should be changed the object is given as argument to the action. So that is always persistent, that is changes to this object are saved automatically. <remark>!!!Sentence not clear</remark>

_images/figure-9-3.png

Figure 9-3: The internal control flow of the property mapper.

In our case not only the __identity property is sent, but also a new title and description for our blog. For safety reasons a copy of the persistent object is applied. The properties of the copy are changed as given in the request, in our case title and description are set new. The generated copy is yet a transient object (see section "live cycle of objects" in chapter 2), that is changes on the object are not automatically persisted. The changed copy is given to the action as argument.

Now we have to code in our controller explicit that we want to replace the existing persistent blog object with our changed blog object. For this the repository offers a method update():

$this->blogRepository->update($blog);

With this the changed object will be made into the persistent object: The changes are stored permanent now.

We want to assume a refinement of the argument mapping: When a link to an action is generated and the link contains an object as parameter the identity of the object is transferred automatically. In the following example the UID is transferred instead of the blog object:

<f:link.action action='show' arguments='{blog: blog}'>Show Blog</f:link.action>

The generated URL contains the identity of the blog object: tx_blogexample_pi1[blog]=47. That is a short form of tx_blogexample_pi1[blog][__Identity]=47. Therefore the property mapper gets the blog object with the identity 47 from the repository and returns it directly without copying before.

Now you know the argument mapping in detail an can use it in specific in your own projects.

After you have learned how you can make sure any invariants of domain objects, the focus will be directed to the secure programming of the complete extension.

programming secure extensions

While mostly the functionality of an extension is set of great value, the safety aspect of the programmed code is clearly less respected. In this section we will make you sensible for safety relevant aspects you should take care of during extension development. In addition we will show you some concepts implemented by extbase that increase the safety of an extension.

A basic principle that you don't have to disregard when programming extensions is, that you should never trust the user input. All input data your extension gets from the user can be potentially malicious. That applies for all data that are transferred via GET and POST over from a form. But also cookies should be classified as malicious, because they can be manipulated by the user.

In the daily programming, all the data that comes from the user should be treated with carefulness - check always if the format of the data corresponds with the format you expected. For example you should check for a field that contains an email address, that a valid email address was entered and not any other text. Here is the validating framework of extbase, you have learned about in the past section, much helpful.

Especially critical are the positions where directly communicated with the database, e.g. with the SQL query language. In the next section we will show what is to care of with it. After this we present some concepts that extbase and fluid uses internally to increase the security of an extension. We will show you how queries that changes data are to be secured by extbase. Next we addict to the Cross Site Scripting and illustrate how to secure your own extensions.

create own database queries

Even though you will mostly use the query language of extbase (see section "implementing custom queries" in chapter 6) to formulate database queries, there is an option to directly formulate SQL queries. That is very helpful for example when you need performance optimization. Always create your own SQL queries in repository classes, to have the potential unsafe code at a defined place.

If you create own SQL queries you always have to convert the input data to the desired format, for example to a number with the use of intval().

Tip

More hints for safety programming with PHP you find also in the PHP handbook at http://php.net/security .

Now we want to present some concepts that are used by extbase and fluid to increase the security of an extension. First we explain how requests that changes data are verified by extbase. After that we explain Cross Site Scripting in order that you can secure your extension for that effect.

Trusted Properties

In the section "mapping arguments" above in this chapter we have explained the transparent argument mapping. For this all properties that are to be send, were changed transparent on the object. Certainly this implies a safety risk, that we will explain with an example: Assume we have a form to edit a user object. This object has the properties username, email, password and description. We want to provide the user a form to change all properties, except the username (because the username should not be changed in our system).

The form looks (shortened) like this:

<f:form name="user" object="{user}" action="update">
   <f:form.textbox property="email" />
   <f:form.textbox property="password" />
   <f:form.textbox property="description" />
</f:form>

If the form is sent, the argument mapping for the user object gets this array:

[
   __identity => ...
   email =>  ...
   password => ...
   description => ...
],

Because the __identity property and further properties are set, the argument mapper gets the object from the persistence layer, makes a copy and then applies the changed properties to the object. After this normally we call the method update($user) for the corresponding repository to make the changes persistent.

What happened if an attacker manipulates the form data and transfers an additional field username to the server? In this case the argument mapping would also change the $username property of the cloned object - although we actual said that this property should not be changed by the user itself.

To avoid this problem fluid creates a hidden form field __trustedProperties which contains information about what properties are to be trusted. Once a request reaches the server, the property mapper of Extbase compares the incoming fields with the property names, defined by the __trustedProperties argument.

As the content of said field could also be manipulated by the client, the field does not only contain a serialized array of trusted properties but also a hash of that array. On the server side, the hash is also compared to ensure the data has not been tampered with on the client side.

So only the form fields that are generated by Fluid with the appropriate ViewHelpers are transferred to the server. If an attacker tries, like described above, to add a field on the client side, this is detected by the property mapper and an exception will be thrown.

In general __trustedProperties should work completely transparent for you, you don't have to know how it works in detail. You have to know this background knowledge only if you want to change data via JavaScript or webservices.

Prevent Cross Site Scripting

Fluid contains some integrated techniques to secure web applications per default. One of the important parts for this is the automatic prevention against cross site scripting, that counts to the most used attack against web applications. In this section we give you a problem description and show how you can avoid cross site scripting (XSS).

Assume you have programmed a forum. An "evil" user will get access to the admin account. For this he posted following harmful looking message in the forum to try to embed JavaScript code:

<script type="text/javascript">alert("XSS");</script>

When he let display the forum post he gets, if the programmer of the forum has made no additional preventions, a JavaScript popup "XSS". The attacker now knows that every JavaScript he write in a post, is executed when displaying the post - the forum is vulnerable for cross site scripting. Now the attacker can replace the code with a more complex JavaScript program, that for example can read the cookies of the visitors of the forum and send them to a certain URL.

If an administrator retrieve this prepared forum post, his session ID (that is stored in a cookie) is transferred to the attacker. By setting the cookie at the attacker himself, in the worst case he can get administrator privileges.

How can we prevent this now? The forum post don't have to put out unchanged - before we have to mask out all special characters with a call of htmlspecialchars(). With this instead of <script>..</script> the safe result is delivered to the browser: &amp;lt;script&amp;gt;...&amp;lt;/script&amp;gt;. So the content of the script tag is no longer executed as JavaScript, but only displayed.

But there is a problem with this: If you miss only at one place the clean masking of the data, a XSS hole exists in the system.

In Fluid the output of every object accessor that occurs in a template is automatically processed by htmlspecialchars(). But Fluid uses htmlspecialchars() only for templates with the extension .html, e.g. if the output format is set to HTML. If you use other output formats it is disabled and you have to make sure to mask the special characters correct. Also deactivated is is it for object accessors that are used in arguments of a ViewHelper. A short example for this:

{variable1}
<f:format.crop append="{variable2}">a very long text</f:format.crop>

The content of {variable1} is send thru htmlspecialchars(), instead the content of {variable2} is not changed. The ViewHelper must get the unchanged data because ewe can not foresee what he will be done with the data. For this reason ViewHelper that output parameter directly have to mask them correct.

Conclusion

In this chapter we have considered tasks that touch several layer of the MVC architecture. First we have seen how to cover extensions for multi languages by adjust static text in the templates and also country-specific formats like date or time are adjusted. In addition we have shown that also the domain model itself can be translated. After this we show the check of invariants in the model, inclusive the display of error messages in the template and the possibility to correct wrong entries by the user. At last we have shown concepts that apply to the security of the extension. Important by this is tha you have understand what are request hashes and how to prevent against cross site scripting.

Creating an Extension With the Extension Builder

When creating an extension you have to write quite a bit of recurrent code. Usually, getter and setter methods are required for every property of the domain model. Additionally, corresponding entries in the TCA definition and in the SQL table definition are necessary. Setting up these things by hand can be very exhausting.

The Extension Builder reduces this effort. You can create the model of your extension with a graphical user interface. The correct domain models, the TCA definition, SQL files, etc. will be automatically created.

Command controllers

Command controllers make logic available at the command line and in the scheduler backend module.

They can provide functionality for recurring tasks like mail queues, cleanups, imports and more, which is then available for administrators and regular backend users.

Creating command controllers

A CommandController needs to be located at Classes/Command/. This following simple example meets the minimum requirements.

Classes/Command/SimpleCommandController.php:

<?php
namespace Vendor\Example\Command;

use \TYPO3\CMS\Extbase\Mvc\Controller\CommandController;

class SimpleCommandController extends CommandController
{

    public function simpleCommand()
    {

    }
}

Requirements are:

  1. Classname must match file name.
  2. Class must extend \TYPO3\CMS\Extbase\Mvc\Controller\CommandController.
  3. Method names must end with Command.

After creation of the controller you need to register it. Add the following line to ext_localconf.php to let TYPO3 know about the controller.

/ext_localconf.php:

<?php

if (TYPO3_MODE === 'BE') {
    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ExtensionName-MeaningFullName'] =
        \Vendor\ExtKey\Command\SimpleCommandController::class;
}

Calling commands

Clear the backend-cache to complete the registration. Once a command is registered and available, you can check the availability by calling ` /cli_dispatch.phpsh extbase help`.

The output should look like:

EXTENSION "EXAMPLE":
-------------------------------------------------------------------------------
  simple:simple

To execute your command call typo3/cli_dispatch.phpsh extbase simple:simple.

Note

Commands are running in TYPO3 Backend context. Therefore you cannot directly access TypoScript of frontend pages for example.

Command arguments

Some commands need to be flexible and therefore need some arguments which may be optional or required.

As with ActionController you can define them the same way:

/**
 * @param int $required
 * @param bool $optional
 */
public function argumentsCommand($required, $optional = false)
{

}

As soon as you have parameter for your command, you must document them, to enable TYPO3 to detect there types for mapping, and whether they are required or not. To make an argument optional, provide a default value.

TYPO3 will map the incoming values to the documented type.

You can check whether the documentation is correct, by calling typo3/cli_dispatch.phpsh extbase help simple:arguments. The result will be something like:

COMMAND:
  example:simple:arguments

USAGE:
  /typo3/cli_dispatch.phpsh typo3/cli_dispatch.phpsh extbase simple:arguments [<options>] <required>

ARGUMENTS:
  --required

OPTIONS:
  --optional

Command documentation

So far you have provided information on what the command and its arguments. To help others, you may want to provide further information within the PHPDoc that is to be displayed on the commandline:

/**
 * This is a short description.
 *
 * This will be further information available to everyone asking for it
 * from the cli.
 *
 * @param int $required This is an required argument.
 * @param bool $optional And this is an optional argument.
 */
public function argumentsCommand($required, $optional = false)
{

}

The information is shown when calling typo3/cli_dispatch.phpsh extbase help simple:arguments:

This is a short description.

COMMAND:
  example:simple:arguments

USAGE:
  /typo3/cli_dispatch.phpsh typo3/cli_dispatch.phpsh extbase simple:arguments [<options>] <required>

ARGUMENTS:
  --required           This is an required argument.

OPTIONS:
  --optional           And this is an optional argument.

DESCRIPTION:
  This will be further information available to everyone asking for it
  from the cli.

Last Update 01 Feb 2016.

Property Mapper

Extbase provides a Property Mapper to convert different values, like integers or arrays, to other types, like strings or object. In this example, we provide a string that will be converted to an integer:

$output = $this->objectManager->get(\TYPO3\CMS\Extbase\Property\PropertyMapper::class)
    ->convert('10', 'integer');

Conversion is done by using the TYPO3\CMS\Extbase\Property\PropertyMapper::convert() method.

How to use property mapper

The above example was a really simple one. Most of the time you will convert from an array to an where some points must be considered. This example will show a simple conversation:

$input = [
    'username' => 'This is the user name',
];

$output = $this->objectManager->get(\TYPO3\CMS\Extbase\Property\PropertyMapper::class)
    ->convert(
        $input,
        'TYPO3\CMS\Extbase\Domain\Model\FrontendUser'
    );

The result will be a new instance of TYPO3\CMS\Extbase\Domain\Model\FrontendUser with defined property username.

Note

The property mapper won't check validation rules. The result will be whatever the input is.

Allow mapping of sub-properties

It's also possible to map to subtypes. In the above example, the FrontendUser has a sub-property of type TYPO3\CMS\Extbase\Domain\Model\FrontendUserGroup. If you wanna map an incoming id, you have to configure the mapper as per default he won't map sub properties for security reasons:

$input = [
    'username' => 'This is the user name',
    'usergroup' => [
        1,
    ],
];

// Get property mapping configuration
$mappingConfiguration = $this->objectManager
    ->get('TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder')
    ->build();
// Adjust configuration to allow mapping of sub property 'usergroup'
$mappingConfiguration->forProperty('usergroup')
    ->allowAllProperties();

$output = $this->objectManager->get(\TYPO3\CMS\Extbase\Property\PropertyMapper::class)
    ->convert(
        $input,
        'TYPO3\CMS\Extbase\Domain\Model\FrontendUser',
        $mappingConfiguration
    );

Coding Guidelines

Extbase and Fluid follow the principle of Convention over Configuration. That means that many tasks will work automatically if you respect certain naming conventions for classes, files and method names. Furthermore, this principle improves the consistency and readability of your code. Thus it is much easier for extension developers to understand how unknown extensions (based on Extbase) work, because the structure is always the same und common tasks are solved the same way.

With Extbase and Fluid we set quite an emphasis on the intuitive and logical naming scheme of classes, methods and variables. Our goal is to provide the source code as readable as possible, because the names are already reflecting what the code does. It even happens that we change a name or identifier several times during development for having it intuitive for as many developers as possible.

You will notice that this is a big difference to the previous TYPO3 v4 (where the names are often abbreviated and inconclusive) making the daily work and understanding of the code much easier.

Generally, classes are written in UpperCamelCase and methods and variables are written in lowerCamelCase. Underscores (_) are only used in class names for the separation of the namespace, in methods and variables they are generally not used. In addition, the name must be detailed and meaningful; abbreviations are to avoid.

Folder structure

Every extension based on Extbase contains certain folders in the main directory:

Classes
Here resides the complete source code for the extension. Only PHP files are allowed, each one containing exactly one class or interface. All classes (or interfaces) are loaded via the autoloader mechanism when needed.
Configuration
Here is the configuration of the extension located, that means flexform configuration, TCA definitions and TypoScript files. Subfolder can be created when they are needed or helpful.
Documentation
Contains the documentation of the extension. The subfolders are named according to the following schema: [name of the document]/[format]/[language]. Therefore you will find the extension manual normally either in the folder Manual/DocBook/en or in Manual/OpenOffice/en/manual.sxw.
Resources

Here there are the static resources of the extension. This means all files which are not PHP files but are necessary for the conduction of the extension. This might be code from libraries, template files, images, css files and so on.

It is distinguished between public (Public/) and private (Private/) resources. In the folder Private/ there is a .htaccess file, which is blocking direct access to non public files.

Resources/Private
Contains non public resources of the extension.
Resources/Public

Contains public resources of the extension.

Within these two folder the authors of the extension can choose the structure freely, but we recommend the following structure:

Resources/Public/Media
This is a good place for images, CSS files or media files, which are delivered directly to the client.
Resources/Private/Templates
Here are the default Fluid templates for the extension (see also chapter 8).
Resources/Private/PHP

Contains PHP code, which is not compatible to the naming conventions like external PHP libraries, procedural code and so on. If you run TYPO3 in composer mode, you should define the autoloading for said folder in the composer.json of your extension.

If you don't run TYPO3 in composer mode, the autoloader of TYPO3 will automatically search for php files in all extensions and you don't need to act yourself.

Tests
All unit tests are found here. The structure should be the same as in Classes. All test classes should end with Test.
ext_emconf.php
Contains the configuration for the extension manager of TYPO3 like metadata as the name, the description and the author of the extension.
ext_icon.gif
The icon of the extension, which will be visible in the Extension Manager. It should have a size of 18 x 16 px.
ext_localconf.php
In this file there is the configuration of the Frontend Plugins, which are offered by the extension (see appendix B, "Configuration of Frontend Plugins").
ext_tables.php
In this file you will put configuration regarding the backend. For more see appendix B, "Configuration of Frontend Plugins".
ext_tables.sql
This is a file with SQL commands for the definition of the database tables.

File and class names

Class names in Extbase are composed with the following parts:

  1. the vendor prefix. For example, if your name is Example, then this part could be Ex.
  2. the name of the extension in UpperCamelCase. For example, if the extension-key is blog_example, then this part of the classname is BlogExample.
  3. the path within the Classes/ folder down to the folder, where the file containing the class resides.

In table A-1 you see some naming examples for file and class names.

Table A-1: Examples for class names

classname extension key folder
TYPO3\CMS\Extbase\Exception extbase extbase/Classes/Exception.php
TYPO3\CMS\Extbase\Mvc\Controller\ActionController extbase extbase/Classes/Mvc/Controller/ActionController.php
Ex\BlogExample\Domain\Model\Post blog_example blog_example/Classes/Domain/Model/Post.php

Interfaces end with Interface, for example TYPO3\CMS\Extbase\Mvc\RequestInterface. With abstract classes the last part of the name always begins with Abstract, for example TYPO3\CMSExtbase\Mvc\Controller\AbstractController.

Extbase Reference

In this appendix, you can look up how Extbase interacts with the TYPO3 installation. This includes the registration of plugins and the configuration of Extbase extensions.

Note

Under https://docs.typo3.org/typo3cms/CheatSheets.html you find a useful Cheat Sheet for Extbase and Fluid.

Registration of frontend plugins

In classical TYPO3 extensions the frontend functionality is divided into several frontend plugins. Normally each has a separate code base. In contrast, there is only one code base in Extbase (a series of controllers and actions). Nevertheless, it is possible to group controllers and actions to make it possible to have multiple frontend plugins.

For the definition of a plugin, the files ext_localconf.php and Configuration/TCA/Overrides/tt_content.php have to be adjusted.

In ext_localconf.php resides the definition of permitted controller action Combinations. Also here you have to define which actions should not be cached. In Configuration/TCA/Overrides/tt_content.php there is only the configuration of the plugin selector for the backend. Let