From 15c812c0627ae177abdafcb5c65203e4decc6833 Mon Sep 17 00:00:00 2001 From: fab1en Date: Tue, 5 Jul 2011 19:56:27 +0000 Subject: [PATCH] First commit of the current work status git-svn-id: https://svn.typo3.org/TYPO3v4/Documentation/book_extbase_fluid/trunk@871 709f56b5-9817-0410-a4d7-c38de5d9e867 --- 0-introduction/1-short-history.xml | 134 +++ 0-introduction/2-target-audience.xml | 37 + 0-introduction/3-structure.xml | 79 ++ 0-introduction/4-typographic-conventions.xml | 35 + 0-introduction/5-thanks.xml | 50 ++ 0-introduction/index.xml | 96 +++ 1-installation/1-configuring-the-server.xml | 47 ++ .../2-install-extbase-and-fluid.xml | 90 ++ 1-installation/3-configuring-your-ide.xml | 69 ++ .../4-extbase-and-fluid-autocompletion.xml | 36 + 1-installation/5-debugging-with-xdebug.xml | 12 + 1-installation/6-more-helpful-extensions.xml | 12 + 1-installation/index.xml | 36 + 10-outlook/1-foo.xml | 12 + 10-outlook/2-bar.xml | 12 + 10-outlook/index.xml | 24 + .../1-Object-oriented Programming in PHP.xml | 637 +++++++++++++++ 2-basic_principles/2-Domain-Driven Design.xml | 12 + .../3-Model-View-Controller in Extbase.xml | 12 + .../4-Test-Driven Development.xml | 12 + 2-basic_principles/5-Conclusion.xml | 12 + 2-basic_principles/index.xml | 84 ++ 3-blog_example/1-first-orientation.xml | 72 ++ .../2-the-stations-of-the-journey.xml | 50 ++ 3-blog_example/3-calling-the-extension.xml | 105 +++ 3-blog_example/4-and-action.xml | 87 ++ 3-blog_example/index.xml | 49 ++ 4-first-extension/1 the example extension.xml | 71 ++ ...lder structure and configuration files.xml | 103 +++ .../3 create the domain model.xml | 125 +++ .../4 make products persistent.xml | 151 ++++ 4-first-extension/5 controlling the flow.xml | 12 + 4-first-extension/6 adding the template.xml | 12 + .../7 configuring the plugin.xml | 12 + 4-first-extension/index.xml | 31 + 5-domain/1-the-application-domain.xml | 203 +++++ 5-domain/2-implementing-the-domain-model.xml | 12 + 5-domain/index.xml | 64 ++ 6-persistence/1-prepare-the-database.xml | 771 ++++++++++++++++++ .../2-configure-the-backends-inputforms.xml | 668 +++++++++++++++ ...-implement-individual-database-queries.xml | 136 +++ 6-persistence/4-use-foreign-data-sources.xml | 12 + .../5-modeling-the-class-hierarchy.xml | 12 + 6-persistence/index.xml | 29 + .../1-Creating Controllers and Actions.xml | 317 +++++++ ...iguring and embedding Frontend Plugins.xml | 13 + ...figuring the behavior of the extension.xml | 12 + 7-controllers/index.xml | 110 +++ 8-fluid/1-basic-concepts.xml | 456 +++++++++++ 8-fluid/10-template-creation-by-example.xml | 211 +++++ 8-fluid/11-conclusion.xml | 26 + 8-fluid/2-using-different-output-formats.xml | 79 ++ ...-moving-repeating-snippets-to-partials.xml | 57 ++ ...-consistent-look-and-feel-with-layouts.xml | 78 ++ ...t-for-rendering-the-cobject-viewhelper.xml | 160 ++++ ...g-attributes-with-additionalattributes.xml | 48 ++ 8-fluid/7-using-boolean-conditions.xml | 115 +++ 8-fluid/8-developing-a-custom-viewhelper.xml | 405 +++++++++ 8-fluid/9-using-php-based-views.xml | 119 +++ 8-fluid/index.xml | 48 ++ ...ng-and-internationalizing-an-extension.xml | 567 +++++++++++++ .../2-validating-domain-objects.xml | 731 +++++++++++++++++ .../3-programming-secure-extensions.xml | 202 +++++ 9-crosscutting-concerns/4-conclusion.xml | 26 + 9-crosscutting-concerns/index.xml | 29 + a-coding_guidelines/1-foo.xml | 12 + a-coding_guidelines/2-bar.xml | 12 + a-coding_guidelines/index.xml | 16 + b-extbase_reference/1-foo.xml | 12 + b-extbase_reference/2-bar.xml | 12 + b-extbase_reference/index.xml | 16 + glossary.txt | 72 ++ index.xml | 59 ++ 73 files changed, 8357 insertions(+) create mode 100644 0-introduction/1-short-history.xml create mode 100644 0-introduction/2-target-audience.xml create mode 100644 0-introduction/3-structure.xml create mode 100644 0-introduction/4-typographic-conventions.xml create mode 100644 0-introduction/5-thanks.xml create mode 100644 0-introduction/index.xml create mode 100644 1-installation/1-configuring-the-server.xml create mode 100644 1-installation/2-install-extbase-and-fluid.xml create mode 100644 1-installation/3-configuring-your-ide.xml create mode 100644 1-installation/4-extbase-and-fluid-autocompletion.xml create mode 100644 1-installation/5-debugging-with-xdebug.xml create mode 100644 1-installation/6-more-helpful-extensions.xml create mode 100644 1-installation/index.xml create mode 100644 10-outlook/1-foo.xml create mode 100644 10-outlook/2-bar.xml create mode 100644 10-outlook/index.xml create mode 100644 2-basic_principles/1-Object-oriented Programming in PHP.xml create mode 100644 2-basic_principles/2-Domain-Driven Design.xml create mode 100644 2-basic_principles/3-Model-View-Controller in Extbase.xml create mode 100644 2-basic_principles/4-Test-Driven Development.xml create mode 100644 2-basic_principles/5-Conclusion.xml create mode 100644 2-basic_principles/index.xml create mode 100644 3-blog_example/1-first-orientation.xml create mode 100644 3-blog_example/2-the-stations-of-the-journey.xml create mode 100644 3-blog_example/3-calling-the-extension.xml create mode 100644 3-blog_example/4-and-action.xml create mode 100644 3-blog_example/index.xml create mode 100644 4-first-extension/1 the example extension.xml create mode 100644 4-first-extension/2 create folder structure and configuration files.xml create mode 100644 4-first-extension/3 create the domain model.xml create mode 100644 4-first-extension/4 make products persistent.xml create mode 100644 4-first-extension/5 controlling the flow.xml create mode 100644 4-first-extension/6 adding the template.xml create mode 100644 4-first-extension/7 configuring the plugin.xml create mode 100644 4-first-extension/index.xml create mode 100644 5-domain/1-the-application-domain.xml create mode 100644 5-domain/2-implementing-the-domain-model.xml create mode 100644 5-domain/index.xml create mode 100644 6-persistence/1-prepare-the-database.xml create mode 100644 6-persistence/2-configure-the-backends-inputforms.xml create mode 100644 6-persistence/3-implement-individual-database-queries.xml create mode 100644 6-persistence/4-use-foreign-data-sources.xml create mode 100644 6-persistence/5-modeling-the-class-hierarchy.xml create mode 100644 6-persistence/index.xml create mode 100644 7-controllers/1-Creating Controllers and Actions.xml create mode 100644 7-controllers/2-Configuring and embedding Frontend Plugins.xml create mode 100644 7-controllers/3-Configuring the behavior of the extension.xml create mode 100644 7-controllers/index.xml create mode 100644 8-fluid/1-basic-concepts.xml create mode 100644 8-fluid/10-template-creation-by-example.xml create mode 100644 8-fluid/11-conclusion.xml create mode 100644 8-fluid/2-using-different-output-formats.xml create mode 100644 8-fluid/3-moving-repeating-snippets-to-partials.xml create mode 100644 8-fluid/4-creating-a-consistent-look-and-feel-with-layouts.xml create mode 100644 8-fluid/5-using-typoscript-for-rendering-the-cobject-viewhelper.xml create mode 100644 8-fluid/6-adding-additional-tag-attributes-with-additionalattributes.xml create mode 100644 8-fluid/7-using-boolean-conditions.xml create mode 100644 8-fluid/8-developing-a-custom-viewhelper.xml create mode 100644 8-fluid/9-using-php-based-views.xml create mode 100644 8-fluid/index.xml create mode 100644 9-crosscutting-concerns/1-localizing-and-internationalizing-an-extension.xml create mode 100644 9-crosscutting-concerns/2-validating-domain-objects.xml create mode 100644 9-crosscutting-concerns/3-programming-secure-extensions.xml create mode 100644 9-crosscutting-concerns/4-conclusion.xml create mode 100644 9-crosscutting-concerns/index.xml create mode 100644 a-coding_guidelines/1-foo.xml create mode 100644 a-coding_guidelines/2-bar.xml create mode 100644 a-coding_guidelines/index.xml create mode 100644 b-extbase_reference/1-foo.xml create mode 100644 b-extbase_reference/2-bar.xml create mode 100644 b-extbase_reference/index.xml create mode 100644 glossary.txt create mode 100644 index.xml diff --git a/0-introduction/1-short-history.xml b/0-introduction/1-short-history.xml new file mode 100644 index 00000000..742c9abb --- /dev/null +++ b/0-introduction/1-short-history.xml @@ -0,0 +1,134 @@ + +
+ A short history of Extbase and Fluid + + After the implementation of TYPO3 v5 and FLOW3 as the basis framework + started, the development of TYPO3 v4 and TYPO3 v5 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 the current 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"). + + + The Berlin Manifesto + + We, the participants of the TYPO3 Transition Days 2008 state that + … + + + + TYPO3 v4 continues to be actively developed + + + + v4 development will continue after the release of v5 + + + + Future releases of v4 will see its features converge with those + in TYPO3 v5 + + + + TYPO3 v5 will be the successor of TYPO3 v4 + + + + Migrtion of content from TYPO3 v4 to TYPO3 v5 will be easily + possible + + + + TYPO3 v5 will introduce many new concepts and ideas. Learning + never stops and we'll help with adequate resources to ensure a smooth + transition. + + + + Signed by: + + Patrick Broens, Karsten Dambekalns, Dmitry Dulepov, + Andreas Förthner, Oliver Hader, Martin Herr, Christian Jul Jensen, + Thorsten Kahler, Steffen Kamper, Christian Kuhn, Sebastian Kurfürst, + Martin Kutschker, Robert Lemke, Tobias Liebig, Benjamin Mack, Peter + Niederlag, Jochen Rau, Ingo Renner, Ingmar Schlecht, Jeff Segars, Michael + Stucki, Bastian Waidelich + + + 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 FLOW3. + + 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 stimulated discussionTODO: sounds + strange, the last open topics like naming conventions or 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 + FLOW3, and transform it to code for TYPO3 v4. That's how the first working + version of Fluid vor TYPO3 v4 came into being at the end of the T3BOARD09. + Additionally, it was now pretty easy to keep Fluid for FLOW3 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 of 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. +
diff --git a/0-introduction/2-target-audience.xml b/0-introduction/2-target-audience.xml new file mode 100644 index 00000000..b17b8ad9 --- /dev/null +++ b/0-introduction/2-target-audience.xml @@ -0,0 +1,37 @@ + +
+ Target Audience + + This book is for TYPO3 extension developers who have basic knowledge + in PHP programming and in working with and administering TYPO3. It gives a + compact introduction into the Extbase framework and the Fluid template + engine. We are striving for the following target audiences: + + + + Beginners, who want to use Extbase and Fluid right from start as a + basis for own extensions + + + + Experienced developers, who want to learn Extbase and Fluid before + a new project + + + + Developers, who want to port their existing extensions early to + FLOW3 + + + + Deciders, who want to gain a technical overview over the new + framework. + + +
diff --git a/0-introduction/3-structure.xml b/0-introduction/3-structure.xml new file mode 100644 index 00000000..433679ee --- /dev/null +++ b/0-introduction/3-structure.xml @@ -0,0 +1,79 @@ + +
+ Structure of this book + + This chapter is structured into ten chapters and three appendixes. The + chapters discuss the following topics: + + Chapter 1, Installation, leads you through the + installation of Extbase and Fluid. To make extension development most + effective, we give suggestions for development environments and tips and + tricks for debugging. + + Chapter 2, Basic principles, begins with an + overview about the concepts of object oriented programming, because these + are essential for working with Extbase. After that, we dive into + Domain-Driven Design, a programming paradigm which is supported optimally by + 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 were implemented + in Extbase. Based on a given example extension we explain how a blog posting + is created and goes through the different system stages until it is + shown. + + 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, Modelling the Domain, shows + Domain-Driven Design with a practical example. It shows how a model can be + planned and implemented. + + If 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 + now focus on the application flow of the extension in chapter 7, + controlling the flow with controllers. These are + implemented in the controller layer. + + Now, only the output layer of the extension is missing, the so-called + view. In chapter 8, styling the output with + Fluid, Fluid is explained in detail and its functionalities are + shown with several examples. At the end of the chapter, the explained + functionalities are combined and demonstrated at 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. Additionally, this chapter shows + you parts of FLOW3 and explains how Extbase Extensions can be ported to + TYPO3 v5. + + Extbase mostly uses the conventions of FLOW3. In Appendix A, + Coding Guidelines, they are summarized. + + Appendix B Reference of Extbase contains an + overview about 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 the API which is needed to + create own ViewHelpers. +
diff --git a/0-introduction/4-typographic-conventions.xml b/0-introduction/4-typographic-conventions.xml new file mode 100644 index 00000000..b23e2f7e --- /dev/null +++ b/0-introduction/4-typographic-conventions.xml @@ -0,0 +1,35 @@ + +
+ Typographic conventions + + This book uses the following typographic conventions: + + File names and directories + + emphasiszed content + + class names, method + names, inline code + + cursive non-proportional fonts + are used if the user should replace certain code parts. + + + This stands for a general advice or hint. + + + + This stands for a tip or a suggestion. + + + + With this symbol, certain special behavior is explained, which could + lead to problems or impose a risk. + +
diff --git a/0-introduction/5-thanks.xml b/0-introduction/5-thanks.xml new file mode 100644 index 00000000..032ae4c1 --- /dev/null +++ b/0-introduction/5-thanks.xml @@ -0,0 +1,50 @@ + +
+ Thanks + +
+ from Jochen and Sebastian + + Above all, we want to thank our families and partners for the + understanding and the support, when we spent our afternoons and evenings + with the book instead of them. Also our customers had to be patient the + one or another time, when we developed Extbase or wrote the book, instead + of continuing a customer project. + + TYPO3 would not exist without the dedication and vision of Kasper + Skårhøy 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 v5. + Extbase would not be possible without this inspiration and + guideline. + + Also when creating the book, we had generous support: A thank you + goes to Patrick Lobacher, as he has written 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! +
+ +
+ from the translators + + +
+
diff --git a/0-introduction/index.xml b/0-introduction/index.xml new file mode 100644 index 00000000..2b65ae6b --- /dev/null +++ b/0-introduction/index.xml @@ -0,0 +1,96 @@ + + + Introduction + + TYPO3 is a powerful and mature content management system, which has a + lot of features and a large degree of flexibility. However, the architecture + of the currently used 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 FLOW3 is approaching production quality, and + TYPO3 v5 is taking shape. 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 easy-to-maintain TYPO3 extensions, which are also future proof, as + the porting to TYPO3 v5 is eased a lot. The template engine Fluid makes sure + that the user interface of the extension can easily be created + individually. + + Extbase makes sure that there is a clear separation between different + concerns, which make maintenance a lot more simple, because the code is + structured modularily. Because of this modular design, the development time + for initial development, but also for adjustments, drops, and thus also the + costs associated with that. Extbase also lessens the burden of the developer + when it comes to security-relevant and repeating tasks, for example the + validation of arguments, the persistence of data, and the reading of + TypoScript and FlexForm settings. Because of that, developers can focus on + solving the problems of their clients. + + Because of the modern architeture 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, and plan and model the extension + more thoroughly before the implementation starts. In return, the source code + of the extension becomes a lot better readable, more flexible and more + extensible. Moreover, the used concepts are also applicable to other + programming languages and frameworks, as the development paradigms like + Domain-Driven Design or Model-View-Controller architecture can be used + universally. + + Extensions which build on Extbase can be ported to TYPO3 v5 and FLOW3 + in a manageable fashion, as the structure of the extension, the naming + conventions and the used APIs are almost the same. Hence, Extbase eases the + transition to FLOW3 and TYPO3 v5 -- if you use Extbase, you can later easily + switch to FLOW3 and TYPO3 v5. + + + TYPO3 v4 and TYPO3 v5 + + In the TYPO3 community, some products are developed in parallel + right now. Here, they should be presented shortly. TYPO3 v4 is the + established branch of TYPO3, which powers hundreds of thousands of + installations world-wide. Right now, version 4.4 is the current version, + and version 4.5 is in development. + + Because the internal structures of TYPO3 v4 were growing + organically, and thus can be quite confusing, the TYPO3 team decided to + begin a new development branch parallel to TYPO3 v4: TYPO3 v5 should be + developed from scratch. While doing that, special attention has been on + clean code and an easy and powerful infrastructure.The last + sentence is strange -> REWORK. It quickly emerged that we + first needed to write a web application framework, before the CMS itself + should be developed. This web application framework is FLOW3, and on that + basis, TYPO3 v5 is now developed. + + + We hope that Extbase and Fluid are the introduction to a completely + new world of programming for you, such that you can now start learning the + new concepts of FLOW3. At the beginning, you will learn a lot of new + concepts, but over time, you will notice that Extbase and Fluid makes you a + lot more productive than before. With a bit of luck, you might get into the + "flow" which drove us while we developed Extbase and Fluid. + + Not only you as developer can profit from Extbase: Also the FLOW3 + development team can test many concepts for their real-world applicability, + and remove bugs and inconsistencies, which occur in Extbase and + Fluid. + + + + + + + + + + + diff --git a/1-installation/1-configuring-the-server.xml b/1-installation/1-configuring-the-server.xml new file mode 100644 index 00000000..3deee0da --- /dev/null +++ b/1-installation/1-configuring-the-server.xml @@ -0,0 +1,47 @@ + +
+ Configuring the Server + + Since TYPO3 is written in the PHP scrpting language, you will need a + webserver like Apache with PHP (version 5.2 or 5.3) support, for your TYPO3 + development. Additionally TYPO3 requires a MySQL 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 use a PHP Opcode Cache like + eAccelerator (http://eaccelerator.net), as it caches + the compiled PHP code often reducing script loading times by more than half. + By default eAccelerator will not cache PHP comments. However, as Extbase + uses these comments to retrieve important information, eAccelerator must not + omit them. To achieve this you need to configure eAccelerator with the + option --with-eaccelerator-doc-comment-inclusion. A complete + installation of eAccelerator will then work as follows: First download the + eAccelerator source code and navigate to the source directory using the + console. You have to adjust the eAccelerator source to your installed PHP + version by running the command phpize. After this you can + configure the eAccelerator source code not to remove the PHP comments by + running ./configure --with-eaccelerator-doc-comment-inclusion. + Now compile eAccelerator with the command make. To finish the + installation run make install as the root + user. + + It might be neccessary to adjust your PHP configuration to load + eAccelerator. Now you can check if your source code comments are preserved + by using a TYPO3 instance. Install the extbase + Extension in the TYPO3 backend by using the Extension Manager and then open + the Reports module. Select the Status + Report submodule and have a look at the + extbase section, which will tell you whether the PHP + comments are preserved or not. + + TODO: Add picture 1-1 +
diff --git a/1-installation/2-install-extbase-and-fluid.xml b/1-installation/2-install-extbase-and-fluid.xml new file mode 100644 index 00000000..19fad9ec --- /dev/null +++ b/1-installation/2-install-extbase-and-fluid.xml @@ -0,0 +1,90 @@ + +
+ Install Extbase and Fluid + + Since TYPO3 version 4.3 Extbase and Fluid are included into the TYPO3 + core as so called System Extensions. To install these, + go to the Extension Manager and select Install + Extensions. Click the little plus icon at the beginnings of the + entries for the Extensions extbase and + fluid. Afterwards -if neccessary- there will by a + dialogue, adjusting the TYPO3 database a little, because Extbase will need a + new database table to store cache informations. + + Now you have installed Extbase and Fluid and can use them for + Extension development. To check if Extbase and Fluid are working, we will + install a Blog Example Extension, which was created for testing Extbase and + Fluid. We will have a closer look at this Extension in chapter 3. + + Again open the Extension Manager and open the submodule + Import extensions this time. Click + Retrieve/Update to download the current list of + available Extensions. Now you can search for the Extension + blog_example. With a click on the red arrow next to the + result, you will start to download the Extension to your TYPO3 instance. + After that you can click Install extension right away, + to have it installed. + + After completing that, you'll find a new submodule + Blogs inside the Web module. If + you can't see it, you should reload the TYPO3 backend in your browser. With + a click on Create example data, some dummy records will + be created. The Blog Example is now working and can go on with Extbase and + Fluid. + + The development of Extbase and Fluid proceeds quite fast. If you miss + certain functionalities or experiencing an issue, you should install the + development versions of Extbase and Fluid from the software versionining + system Subversion. Open the typo3conf/ext/ directory of + your TYPO3 instance on the command line and get the development versions + (called trunk) of Extbase and Fluid with the command line tool + Subversion: + + svn checkout + https://svn.typo3.org/TYPO3v4/CoreProjects/MVC/extbase/trunk + extbase + + svn checkout + https://svn.typo3.org/TYPO3v4/CoreProjects/MVC/fluid/trunk + fluid + + TODO: Please check the markup of the code + example. + + + Report issues or help at development + + If you find an issue in Extbase or Fluid, you should have a look + at the development platform forge.typo3.org. These projects are + developed there by the community and you will find a bug tracker and a + wiki for the projects: + + + + Development of Extbase: + http://forge.typo3.org/projects/show/typo3v4-mvc + + + + Development of Fluid: + http://forge.typo3.org/projects/show/package-fluid + + + + Additionally there is a special newsgroup and mailing list, which + you can use for your Extbase and Fluid related questions. You'll find + the mailing list called TYPO3-project-typo3v4mvc on + http://lists.typo3.org. The according newsgroup is + also found on lists.typo3.org by searching for + typo3.projects.typo3v4mvc. Questions posted to the + mailing list and newsgroup normally are answered by committed members of + the community. + + +
diff --git a/1-installation/3-configuring-your-ide.xml b/1-installation/3-configuring-your-ide.xml new file mode 100644 index 00000000..9276a1c7 --- /dev/null +++ b/1-installation/3-configuring-your-ide.xml @@ -0,0 +1,69 @@ + +
+ 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. Among + syntax highlighting an IDE offers code completion in first place 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. + + + Sources + + The NetBeans IDE is offered for Windows, Linux and Mac OS X. We use + NetBeans PHP, which you can download from + http://netbeans.org. + + Eclipse is also available for all important Operating Systems. The + PHP IDE is called Eclipse PDT, which you can download from + http://eclipse.org/pdt. + + + In general you should create projects in NetBeans repectively (????) + Eclipse for Extbase and Fluid. 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/). + + + By default NetBeans uses space chars for code indentation. But as + the TYPO3 Coding Guidelines demand Tabs for indentation, you should + configure NetBeans accordingly. Open the preferences dialogue of NetBeans + and choose the entry Editor. Now, in the section + Formatting, deactivate the option Expand + Tabs to Spaces 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. + + When developing an Extension by yourself you should also create a + dedicated project for it in NetBeans or Eclipse. +
diff --git a/1-installation/4-extbase-and-fluid-autocompletion.xml b/1-installation/4-extbase-and-fluid-autocompletion.xml new file mode 100644 index 00000000..dfd36fb7 --- /dev/null +++ b/1-installation/4-extbase-and-fluid-autocompletion.xml @@ -0,0 +1,36 @@ + +
+ 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. + + TODO: Insert figure 1-2 with caption: 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. +
diff --git a/1-installation/5-debugging-with-xdebug.xml b/1-installation/5-debugging-with-xdebug.xml new file mode 100644 index 00000000..b935ad4b --- /dev/null +++ b/1-installation/5-debugging-with-xdebug.xml @@ -0,0 +1,12 @@ + +
+ Debugging with Xdebug + + Blah Blah Blah Blah +
\ No newline at end of file diff --git a/1-installation/6-more-helpful-extensions.xml b/1-installation/6-more-helpful-extensions.xml new file mode 100644 index 00000000..e572dba5 --- /dev/null +++ b/1-installation/6-more-helpful-extensions.xml @@ -0,0 +1,12 @@ + +
+ More helpful Extensions + + Blah Blah Blah Blah +
\ No newline at end of file diff --git a/1-installation/index.xml b/1-installation/index.xml new file mode 100644 index 00000000..b9b3ec80 --- /dev/null +++ b/1-installation/index.xml @@ -0,0 +1,36 @@ + + + Installation + + In this chapter we want to help you to set up an effecient 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 und 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. + + 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. + + + + + + + + + + + + + diff --git a/10-outlook/1-foo.xml b/10-outlook/1-foo.xml new file mode 100644 index 00000000..0c9e359e --- /dev/null +++ b/10-outlook/1-foo.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
diff --git a/10-outlook/2-bar.xml b/10-outlook/2-bar.xml new file mode 100644 index 00000000..73791ced --- /dev/null +++ b/10-outlook/2-bar.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
\ No newline at end of file diff --git a/10-outlook/index.xml b/10-outlook/index.xml new file mode 100644 index 00000000..b2802bdf --- /dev/null +++ b/10-outlook/index.xml @@ -0,0 +1,24 @@ + + + Outlook + + Even if Extbase and Fluid are very powerful tools, there is an advance + in development. In this chapter we would like to show an outlook of the + current development. In the first section we will introduce the new + Kickstarter for Extbase-Extensions, which will speed up the initial setup of + an extension massivly. In the second section we will show you, not only how + to write Fronted-Plugins with Extbase, but also how to write Backend-Modules + with it. You can now make use of your accumulated knowlegde on Extbase and + Fluid immediately. The chapter will be closed with an outlook on FLOW3, + since Extbase is a bridging technology to FLOW3. + + + + + diff --git a/2-basic_principles/1-Object-oriented Programming in PHP.xml b/2-basic_principles/1-Object-oriented Programming in PHP.xml new file mode 100644 index 00000000..f47facff --- /dev/null +++ b/2-basic_principles/1-Object-oriented Programming in PHP.xml @@ -0,0 +1,637 @@ + +
+ 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 to much and finally decided to go on vacation) and a + program, promising recuperation by booking a coach 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 accout 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 ammount of coaches, 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, for + 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 + ca not be united. This methodology, called + Encapsulation in Object Orientation, is + necessary, if only because of 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 coaches, a motor and other useful stuff. + Furthermore, there will be funtions, 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 sattelite 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 is necessary for successfull 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'd be a blueprint for + ships in general. We now focus not the ship but this blueprint. It is + called Class, in this case is is the Class + ship. In PHP this is written as follows; + + <?php + + class ship { + + ... + + } + + ?> + + + In this piece of code we kept noting 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 wirtten. we'll now + add these Properties and Methods: + + class ship { + + + + public $name; + + public $coaches; + + public $engineStatus; + + public $speed; + + + + function startEngine() {} + + function stopEngine() {} + + function moveTo($location) {} + + + + } + + Our ship now has a name ($name), a number of + coaches ($coaches) 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 our abstraction these few + will be sufficient though. We'll focus on why every Property is marked + with the key word public further down. + + + 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 (as well as FLOW3). + We can also swith on the engine + (startEngine()), travel with the ship to the + desired destination (moveTo($location)) and switch + off the engine again (stopEnginge()). 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 containint Method name ad (if + avallabe) parameters is called Method signature or method head. Everything + contained by the Method ist called method body accordingly. + + Now we'll 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 Objext from the Class. + This Object is also called 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"] => NUL + + ["coaches"] => 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!' + + } esle { + + echo 'Objects are not identical!' + + } + + In this examle the output ist 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 to do, 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, how an object to be instantiated later will be called. So we need + a mechanism to do this indepentent 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 acces the Property + "speed" in the acual 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 coaches 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 coaches 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 + §coaches respectively + $name. +
+
+ +
+ 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 behid 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 $luxuryCoaches; + + 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 or example + Ship) is called parent class + or superclass. The class formed by + Extension (in our example LuxuryLiner) is called + 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 Porperties + (like the ammount of luxury coaches in + $luxuryCoaches) 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: + + TODO: Enter Code + + 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 unconvenient. 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() TODO: "methodname" + should be "emphasis" in addition to "classname". I did not get it, + sorry! the Method of the parent class can be accessed + comfortably - so our former example can be re-written in a smarter + way: + + TODO: Enter Code +
+ +
+ Abstact 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 setupCoaches(). 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 seperately for each ship type. + + TODO: Enter Code + + In the parent class we have defined only the body of the Method + setupCoaches(). 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 sattelite 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: + + TODO: Enter Code + + 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 + SatteliteTV. It is also possible to implement not + only one interface class but multiple, seperated 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 behaviour 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: + + TODO: Enter Code + + The LuxuryLiner may alter the property + coaches, 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 denfine all Properties as + protected. Like that, they can not be altered any + more from outside and you should use special Methods (called getter ans + 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. setupCoaches() 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. + + TODO: Enter Code + + We now have a Method setCoaches() which + sets the number of coaches. Furthermore it changes - depending on the + number of coaches - 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 + $coaches 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. + + + 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: + + TODO: Enter Code + + 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 ist + uses the static method + setShipyardSupportTelephoneNumber($newNumber). For + the Method is static, it is called through the scheme + classname::methodname() TODO: "methodname" should be + "emphasis" in addition to "classname". I did not get it, + sorry!, 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 acess 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 + + + Further information about design patterns can e.g. be found on + http://sourcemaking.com/ 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 & + Prototype. + +
+ Singleton + + This design pattern makes sure, that only one insatance of a class + can exist at a time. In TYPO3 you can mark a class + as singleton by letting it implement the interface + t3lib_Singleton. 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: + + TODO: Enter Code + + In order to have the singletons correctly created you have to use + the static TYPO3 Method + t3lib_div::makeInstance(). This method gives back + - as seen in the example above - always the same object, if you request + a singleton. +
+ +
+ 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 + t3lib_Singleton automatically is of the type + Prototype. + + + 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 refresehd 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. +
+
+
diff --git a/2-basic_principles/2-Domain-Driven Design.xml b/2-basic_principles/2-Domain-Driven Design.xml new file mode 100644 index 00000000..444c9abe --- /dev/null +++ b/2-basic_principles/2-Domain-Driven Design.xml @@ -0,0 +1,12 @@ + +
+ Domain-Driven Design + + Blah Blah Blah Blah +
diff --git a/2-basic_principles/3-Model-View-Controller in Extbase.xml b/2-basic_principles/3-Model-View-Controller in Extbase.xml new file mode 100644 index 00000000..d85c25bf --- /dev/null +++ b/2-basic_principles/3-Model-View-Controller in Extbase.xml @@ -0,0 +1,12 @@ + +
+ Model-View-Controller in Extbase + + Blah Blah Blah Blah +
diff --git a/2-basic_principles/4-Test-Driven Development.xml b/2-basic_principles/4-Test-Driven Development.xml new file mode 100644 index 00000000..3191dfa5 --- /dev/null +++ b/2-basic_principles/4-Test-Driven Development.xml @@ -0,0 +1,12 @@ + +
+ Test-Driven Development + + Blah Blah Blah Blah +
diff --git a/2-basic_principles/5-Conclusion.xml b/2-basic_principles/5-Conclusion.xml new file mode 100644 index 00000000..17e11d6c --- /dev/null +++ b/2-basic_principles/5-Conclusion.xml @@ -0,0 +1,12 @@ + +
+ Conclusion + + Blah Blah Blah Blah +
diff --git a/2-basic_principles/index.xml b/2-basic_principles/index.xml new file mode 100644 index 00000000..07f4ec8f --- /dev/null +++ b/2-basic_principles/index.xml @@ -0,0 +1,84 @@ + + + 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 clubs or even in private, others are developed in + big teams in the context of major projects. While a newbie, writing his + first extension, may hassle with some first-time problems concerning TYPO3, + many big projects are based on homemade Frameworks. So style and + architecture of today's extensions are quite inhomogeneous. Hence it is + often very difficult to extend or modify existing Extensions for own + projects: Before being able to do so, you have to intensively dig into the + way of thinking and programming 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. + Based on 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 like to present in this chapter. You'll find 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): Goal of this Approach of developing + 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 being 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 usage (and by-passing) of the application + programming interface (API) of TYPO3. Extbase in contrast will demand + additional knowledge and skills in domains that are useful and effective far + beyond the TYPO3 Community. Thus you can access an extensive accumulation of + experience in 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 like to warmly recommend this technique of + developing. + + + + + + + + + + + diff --git a/3-blog_example/1-first-orientation.xml b/3-blog_example/1-first-orientation.xml new file mode 100644 index 00000000..58c621fb --- /dev/null +++ b/3-blog_example/1-first-orientation.xml @@ -0,0 +1,72 @@ + +
+ First orientation + + The so called blog example is an example + extension, which mainly focuses on showing the process of the extension + development and shows the possibilities of an extension based on Extbase. + This extension is an usual blog, which either can be administrated on 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 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". + + TODO: Insert figure 3-1: "Domain of the blog example" + + + The blog example has been created originally by the FLOW3 team and + has been backported to TYPO3 version 4.x. When you will work more with + FLOW3 and TYPO3 5.x in the future, you will find this example there in a + near identical form. You will find some notes about the relationship + between Extbase and FLOW3 in the section "Migration to FLOW3 and TYPO3 v5" + in chapter 10. + + + The complete source code can be found in a folder, which has the same + name like 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. + + TODO: Insert 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. + + + 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. Those are in + particular 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. +
diff --git a/3-blog_example/2-the-stations-of-the-journey.xml b/3-blog_example/2-the-stations-of-the-journey.xml new file mode 100644 index 00000000..81142612 --- /dev/null +++ b/3-blog_example/2-the-stations-of-the-journey.xml @@ -0,0 +1,50 @@ + +
+ 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 now able + to start. Figure 3-3 gives you an overview of the stations on the journey, + which you will get to know more detailed in the upcoming sections. + + TODO: Insert 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).TODO: Add callouts + + The Dispatcher bundles all information of the + request in a Request and sends it to the responsible + part of the extension, which takes over the flow control - the so called + Controller (2). + + Within the controller the responsible storage room, which is in charge + of the blogs, - the Repository - is instructed to + return all the stored blogs using the method findAll() + (3). + + The Repository returns a collection of the + already made Blog objects with all of its 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 on its turn returns the HTML code to + the calling TYPO3 process (6). +
diff --git a/3-blog_example/3-calling-the-extension.xml b/3-blog_example/3-calling-the-extension.xml new file mode 100644 index 00000000..df4eb9c9 --- /dev/null +++ b/3-blog_example/3-calling-the-extension.xml @@ -0,0 +1,105 @@ + +
+ 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 the outputted 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 inclosed 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. + + TODO: Insert figure 3-3: "Output of the plugin of our example + extension + + The process of processing is within of TYPO3 forwarded to the + dispatcher of Extbase at first. The dispatcher completes several preliminary + tasks before it hands the further processing on to the according position + within the code of our blog example:TODO: Rewrite + sentences. + + + + 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 object. + + + + It prepares the cache in which content is stored for faster + reuse. + + + + It instantiates and configures the controller of our extension + which controls the further processing within the extension. + + + + When these preparations are full-filled by the Extbase dispatcher, we + are able to travel to the first stop of our destination: the controller. In + our example the 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 + Tx_BlogExample_Controller_BlogController. At first + this might seem long-winded but the syntax follows a very strict convention + (please see box "Be careful, conventions!"). + + + 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 + art 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. + +
diff --git a/3-blog_example/4-and-action.xml b/3-blog_example/4-and-action.xml new file mode 100644 index 00000000..bf75625d --- /dev/null +++ b/3-blog_example/4-and-action.xml @@ -0,0 +1,87 @@ + +
+ 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: + + TODO: Insert code + + 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. + + + Who already dealed with the model-view-controller-pattern will + notice, that the controller has only a little amount of code. Extbase (and + FLOW3) aim to the approach to have a slim controller. The controller is + exclusively responsible for the control of the process flow. Additional + logic (especially business or domain logic) needs to be seperated into + classes in the subfolder Domain. + + + + 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 + Tx_Extbase_MVC_Controller_ActionController. The + BlogController "inherits" all methods from it, by deriving it form this + class: + + class Tx_BlogExample_Controller_BlogController extends + Tx_Extbase_MVC_Controller_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 detailled like this: + + TODO: Insert code + + In the first line a repository is instantiated, which "contains" all + 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 + 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(). +
diff --git a/3-blog_example/index.xml b/3-blog_example/index.xml new file mode 100644 index 00000000..c921c8db --- /dev/null +++ b/3-blog_example/index.xml @@ -0,0 +1,49 @@ + + + A journey through the Blog Example + + In this chapter we accompany you on a journey through a simple TYPO3 + extension. While traveling on this round trip you get to know more about the + extension development with Extbase and Fluid and learn the most important + stations and coordinates of the extension development with the help of a + example extension. You first familiarize with the geography and the typical + characteristics of a extension and find out, which processes run in the + background. This knowledge will then help you in the creation of an own + extension + + If you search for a specific manual for the creation of an extension, + chapter 4 will show you the right set of tools. However we recommend you to + build the fundamentals for that 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. + + It is a benefit, if you look into the original sourcecode while + reading the text so the orientation in your own extension later will be much + easier. + + + If you use a debugger, it can be interesting to follow a full cycle + in the single step modus. For that you have to set a breakpoint in the + file Dispatcher.php. You will find this class - like + every other class of Extbase also - 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. + + + + + + + + + diff --git a/4-first-extension/1 the example extension.xml b/4-first-extension/1 the example extension.xml new file mode 100644 index 00000000..fc270870 --- /dev/null +++ b/4-first-extension/1 the example extension.xml @@ -0,0 +1,71 @@ + +
+ 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: + + + + Create directory tree and the minimal configuration files + + + + Translate the problem domain in an abstract model domain + (model) + + + + Configuration of persistence layer + + + + Definition of database tables + + + + Configure the display of backend forms + + + + Create repositories for product objects + + + + + + Define the application flow inside the extension (create + controllers and action methods) + + + + Realize design with HTML-templates + + + + Configure the plugin for list display + + + + Install and test the extension + + + + + 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 kickstarter, a convenient + tool to create the base of an extension which is outlined in chapter + 10. + +
diff --git a/4-first-extension/2 create folder structure and configuration files.xml b/4-first-extension/2 create folder structure and configuration files.xml new file mode 100644 index 00000000..35241176 --- /dev/null +++ b/4-first-extension/2 create folder structure and configuration files.xml @@ -0,0 +1,103 @@ + +
+ 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 inventory, and thus + we specify at the same time the name of the extension as + Inventory. + + + 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/. Globaly installed extensions are + available to all websites using the same installation. They are stored in + typo3/ext/. System extensions are delivered with the + TYPO3-distribution and are in the folder typo3/sysext/. + Extbase or Fluid are examples of system extensions. All three 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 + 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 all other files + that are also processed by our extension (e.g. HTML templates) or delivered + directly to the front end (e.g. icons,javascript). 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 + inventory/ should look as in image 4-1. + + TODO: Image 4-1 + + So that the extension can be loaded by TYPO3, we require two + configuration files. These are discarded into the extension folder + inventory/ on the uppermost level. You can copy and + adapt these files from an existing extension. Later you will let them be + created by the kickstarter. + + The file ext_emconf.php contains the meta + information for the extension, e.g. the title, a description, the status, + the name of the author etc.. It does not differ to conventional extensions. + It is recommended to indicate the dependence to Extbase and Fluid (if + appropriate, also a certain version). + + <?php +$EM_CONF[$_EXTKEY] = array( + 'title' => 'Inventory List', + 'description' => 'An extension to manage a stock.', + 'category' => 'plugin', + 'author' => 'Jochen Rau', + 'author_company' => '', + 'author_email' => '', + 'dependencies' => 'extbase,fluid' + 'state' => 'alpha', + 'clearCacheOnLoad' => '1', + 'version' => '0.0.0', + 'constraints' => array( + 'depends' => array( + 'typo3' => '4.3.0-4.3.99', + 'extbase' => '1.0.0-0.0.0', + 'fluid' => '1.0.0-0.0.0', + ) + ) + ); +?> + + The file ext_icon.gif contains the icon of the + extension. For this you can use any graphic stored in GIF format. It should + not exceed a width of 18 pixels and a height of 16 pixels. The icon appears + in the extension manager and in the extension repository (TER). + + 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. +
diff --git a/4-first-extension/3 create the domain model.xml b/4-first-extension/3 create the domain model.xml new file mode 100644 index 00000000..2d651085 --- /dev/null +++ b/4-first-extension/3 create the domain model.xml @@ -0,0 +1,125 @@ + +
+ 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 + Tx_Inventory_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 supplements of .php + at the last word, to count after the last underscore, of the class name. + This class file is stored in the folder + EXT:inventory/Classes/Domain/Model/. + + + The labels of the classes always must reflect the folder structure. + For example extbase expects the class + Tx_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 + Tx_Inventory_Domain_Model_Product must be derivated + from the extbase class + Tx_Extbase_DomainObject_AbstractEntity. + + <?php +class Tx_Inventory_Domain_Model_Product extends Tx_Extbase_DomainObject_AbstractEntity { + /** + * @var string + **/ + protected $name = ''; + + /** + * @var string + **/ + protected $description = ''; + + /** + * @var int + **/ + protected $quantity = 0; + + public function __construct($name = '', $description = '', $quantity = 0) { + $this->setName($name); + $this->setDescription($description); + $this->setQuantity($quantity); + } + + public function setName($name) { + $this->name = (string)$name; + } + + public function getName() { + return $this->name; + } + + public function setDescription($description) { + $this->description = (string)$description; + } + + public function getDescription() { + return $this->description; + } + + public function setQuantity($quantity) { + $this->quantity = (int)$quantity; + } + + 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. + + + 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 + Kickstarter. 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. + + + 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 better is done + with so-called validators (see the section "validating domain objects" in + chapter 9). + + + +
diff --git a/4-first-extension/4 make products persistent.xml b/4-first-extension/4 make products persistent.xml new file mode 100644 index 00000000..4983df0a --- /dev/null +++ b/4-first-extension/4 make products persistent.xml @@ -0,0 +1,151 @@ + +
+ Make Products Persistent + + From the class + Tx_Inventory_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 after the side was produced completely by TYPO3, + by PHP again. So that the products are available over a longer time, we must + make it "durable". Usually this happens in that one stores it into a + database. Therefore we design first of all the database table necessary for + that. + + + The designing of the database tables can be done by the Kickstarter. + In the TYPO3 V5, these steps completely are omitted. + + + TYPO3 will do this for us if we register the corresponding SQL + proclamation in the file + EXT:inventory/ext_tables.sql: + + CREATE TABLE tx_inventory_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 emerge as columns again. + + + On the entries in the database take action then over that baking by + TYPO3. The forms of the Backends are produced on the basis of a + configuration, that is discarded in a PHP Array, the so-called + Table-Configuration-Array (shortly TCA). + + Within the Extension take action then over Repositories transparently + on these data. "Transparently" means that one must make himself in the + access to Repositories around the type of the storage of the data no + thoughts. + + So that that knows baking now, how it should indicate the product data + in a form, we must configure this for the table in the file + EXT:inventory/ext_tables.php. There is discarded in the Array $TCA under the + table name as a key the configuration. This comprises several sections. In + the section ctrl, basic characteristics are how the table name or the + statement which table column the Label is supposed to be taken for the + entries. In the section columns is described for each table column how this + in that is supposed to be indicated baking. The section type defines, in + which sequence the table columns are indicated, and like this where + appropriate. Arranged become. + + + The possibilities to influence with the TCA the edition in that + baking, are immense. In the frame these beeches we only can tear this. You + find a fully permanent listing of all option Online under + http://typo3.org/documentation/document-library/core-documentation/doc_core_api/4.3.0/view/4/1/. + In more extensive Extensions, one would evacuate the sections columns and + type out of Performance reasons also into an own file tca. php. In our + example, this minimal configuration should suffice however. + + + <?php +if (!defined ('TYPO3_MODE')) die ('Access denied.'); + +$TCA['tx_inventory_domain_model_product'] = array ( + 'ctrl' => array ( + 'title' => 'Inventory', + 'label' => 'name', + ), + 'columns' => array( + 'name' => array( + 'label' => 'Item Label', + 'config' => array( + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim,required' + ) + ), + 'description' => array( + 'label' => 'Item Description', + 'config' => array( + 'type' => 'text', + 'eval' => 'trim' + ) + ), + 'quantity' => array( + 'label' => 'Stock Quantity', + 'config' => array( + 'type' => 'input', + 'size' => '4', + 'eval'=> 'int' + ) + ), + ), + 'types' => array( + '0' => array('showitem' => 'name, description, quantity') + ) +); +?> + + After we installed the Extension, we can design our first in that + baking per dukte. How in image 4-2 shown, let's produce receive becomes in + addition a system file, that the products 1: In this, we put some few new + Bestandsdaten at 2. + + + + + + ToDo: insert image 4-2 with replaced english version screenshot + + + We created a copy (or a model) in this section reality in that we + translated only a cut at characteristics of the real products in software, + that play a role in our domain. This of the real world abstracted therewith + completely is designed model. + + In order take action on that in the baking designed object, design we + a Repository for products. The + Tx_Inventory_Domain_Repository_ProductRepository is "discarded" are an + object, in that the products. We can request a Repository to find all (or + certain) products and to deliver at us. The Repository class is very short + in our case: + + <?php +class Tx_Inventory_Domain_Repository_ProductRepository + extends Tx_Extbase_Persistence_Repository {} +?> + + Our ProductRepository must be diverted by + Tx_Extbase_Persistence_Repository and inherits by this all methods. It can + remain therefore in our simple example empty. We discard the class file + ProductRepository. php into the file + EXT:inventory/Classes/Domain/Repository/. +
diff --git a/4-first-extension/5 controlling the flow.xml b/4-first-extension/5 controlling the flow.xml new file mode 100644 index 00000000..58b2e519 --- /dev/null +++ b/4-first-extension/5 controlling the flow.xml @@ -0,0 +1,12 @@ + +
+ Controlling The Flow + + not yet +
diff --git a/4-first-extension/6 adding the template.xml b/4-first-extension/6 adding the template.xml new file mode 100644 index 00000000..dd7d7fb2 --- /dev/null +++ b/4-first-extension/6 adding the template.xml @@ -0,0 +1,12 @@ + +
+ Adding The Template + + not yet +
diff --git a/4-first-extension/7 configuring the plugin.xml b/4-first-extension/7 configuring the plugin.xml new file mode 100644 index 00000000..833e7b5d --- /dev/null +++ b/4-first-extension/7 configuring the plugin.xml @@ -0,0 +1,12 @@ + +
+ Configuring The Plugin + + not yet +
diff --git a/4-first-extension/index.xml b/4-first-extension/index.xml new file mode 100644 index 00000000..f63db059 --- /dev/null +++ b/4-first-extension/index.xml @@ -0,0 +1,31 @@ + + + Creating a first extension + + 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. + + + + + + + + + + + + + + + diff --git a/5-domain/1-the-application-domain.xml b/5-domain/1-the-application-domain.xml new file mode 100644 index 00000000..907002d9 --- /dev/null +++ b/5-domain/1-the-application-domain.xml @@ -0,0 +1,203 @@ + +
+ 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 are 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: + + TODO: check for a translation of + Stadtjugendring + + + + 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 searched by the users and filterd 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. + + TODO: insert figure 5-1 here + + 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 choosen 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. + + TODO: check if vendor is the right term + + + 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 Ubiquitos 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 Ubiquitos 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 offfer 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, wich you can see in + figure 5-2. Every rectangle emblematize an objects 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. + + TODO: Insert figure 5-2 here + + Figure 5-2: First draft of the domain. + + + 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. + + + + 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 itselfby 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). + + TODO: Insert figure 5-3 here + + 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). + + TODO: Insert figure 5-4 here + + 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, whichwill be irrelevant in a later revision. We suggest that + you first implement a basic model and than - with additional knowledge of + the yet unknown details of the model - improve it step by step. Lets start + with our first lines of code. +
diff --git a/5-domain/2-implementing-the-domain-model.xml b/5-domain/2-implementing-the-domain-model.xml new file mode 100644 index 00000000..25aae980 --- /dev/null +++ b/5-domain/2-implementing-the-domain-model.xml @@ -0,0 +1,12 @@ + +
+ implementing the domain model + + +
\ No newline at end of file diff --git a/5-domain/index.xml b/5-domain/index.xml new file mode 100644 index 00000000..7412a427 --- /dev/null +++ b/5-domain/index.xml @@ -0,0 +1,64 @@ + + + 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 flayer. In the future those offers will be managed by + the youth organizations via the Internet. At the same time parents, children + and teenager 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. + + + Please note that we, the authors are also in a proceeding learning process. With our todays 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. + + + + + diff --git a/6-persistence/1-prepare-the-database.xml b/6-persistence/1-prepare-the-database.xml new file mode 100644 index 00000000..0a3d51e9 --- /dev/null +++ b/6-persistence/1-prepare-the-database.xml @@ -0,0 +1,771 @@ + +
+ 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. + + + One of the main purposes of Extbase and FLOW3 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 + Tx_SjrOffers_Domain_Model_Organization: + +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. + + + 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 informations. + + + The definition of the database table fields name, + address etc. follow in round brackets. Some of them should + sould 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 fieldname 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. + + + More information about Localization and Multilingualism can be + found in Chapter 9. + + + + 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 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. + + + 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 datatypes for the values true and false. + + + As with fieldnames 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 + JOINs. 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Comparison of different field types
What should be saved?Field type>Field range>
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).
Character strings, textschar(n)max. 255 Bytes **
(names, addresses, product descriptions etc.;varchar(n)max. n Bytes (up to max. n = 65.553)
images that are managed by TYPO3)textmax. 65.553 Bytes
mediumtext*max. 16.777.215 Bytes
longtext*max. 4.294.967.295 Bytes
Integer typestinyint[(n)] *8 Bit
(item counts, ages etc.;-128 to +128 (signed; n=4)
in TYPO3 as well as dates and0 to 255 (unsigned; n=3)
boolean properties)
smallint[(n)]16 Bit
-32.768 to +32.767 (signed; n=6)
0 to 65535 (unsigned; n=5)
mediumint[(n)] *24 Bit
-8.388.608 to +8.388.607 (signed; n=9)
0 to 16.777.215 (unsigned; n=8)
int[(n)]32 Bit
-2.147.483.648 to +2.147.483.647 (signed; n=11)
0 to 4.294.967.295 (unsigned; n=10)
bigint[(n)]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-pointdecimal(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)
+
+
+ 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 memoriam 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. + + 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. + + + <code>NULL</code> or <code>NOT NULL</code>? + + All common Relational Database Management Systems (RDBMS) allow + NULL as a special value for a field. This usually means that this value + is kind of "not defined". However, be clear about the semantical + differences of the values NULL, 0 and + "" (i.e. the NULL value, the number 0 and the empty + string). The difference gets clear with the value of the participation + fees of the SjrOffers example. If the field + attendance_fee contains the value NULL then + the participation fee is not defined and NOT that the fee is 0 Euro. + However, in this concrete example this may due in the same + Frontend-output ("free of charge") but that has to be reasoned depending + on the use-case. + + One cannot make calculations with NULL values. + The functions AVG, SUM, etc. ignore the + NULL value. + + + + One cannot do comparing instructions on NULL + values. For example, the comparison of NULL = NULL + always leads to false due to the vagueness of + NULL. Thus, it does not make sense to write a + statement like uid = NULL and there is an own + operator introduced for that IS which leads to + expressions like uid IS NULL. However, Extbase + automagically figures out the right way for you. + + + + NULL values in queries like + DISTINCT, ORDER BY and GROUP + BY are seen the same way and are thus grouped + together. + + + + Fields permitting NULL values take more memory, + because it is harder to improve the database engine for those SQL + queries. + + A general rule of thumb is to avoid NULL + values as far as your Domain Semantic allows that. + + + 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 + informations 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 informations about + assorting, the visibility and the access control informations. They + concern the relationship of the related objects and not the objects + themself. + + + Do not store data in the Intermediate Table that concern the + Domain. Though TYPO3v4 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 1) possible and useful, 2) technically possible + but rarely sensible, 3) either technically impossible or not + supported. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Combination of reference type and technical storage
+ + 1:11:nn:1m:n
Comma-separated list2)2)3)2)
Foreign Keys1)1)1)3)
Intermediate Table3)3)1)1)
+ + 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( + ... + contact int(11) NOT NULL, + ... +); + + + NULL values are explicitly allowed and stands for + "The contact partner has not yet been assigned." + Later on, Extbase computes the Contact-object out of the + UUID. + + 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 disencourage that solution because of its drawbacks: For + example, comma-separated fields complicate the search and hinder the + indexation in the databse. 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 methode 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 + Tx_Sjr_Offers_Domain_Model_Organization. This will later be + filled with instances of the class + Tx_Sjr_Offers_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, + ... +); + + + + 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 + Tx_Sjr_Offers_Domain_Model_Offer. Consequently, it will be + filled with an instance of the class + Tx_Sjr_Offers_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( + ... + contact int(11) NOT NULL, + ... +); + + + 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. + + + The name of the Intermediate Table can be chosen freely. However, + the following convention is recommended: + tx_myext_linkesobjekt_rechtesobjekt_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. + +
+
diff --git a/6-persistence/2-configure-the-backends-inputforms.xml b/6-persistence/2-configure-the-backends-inputforms.xml new file mode 100644 index 00000000..e3cb3426 --- /dev/null +++ b/6-persistence/2-configure-the-backends-inputforms.xml @@ -0,0 +1,668 @@ + +
+ 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). In bigger extensions, as ours is, this Array is usually separated into two different files. The file EXT:sjr_offers/ext_tables.php contains the basic configuration of the table and refers to the second file which is, in our case, called EXT:sjr_offers/Configuration/TCA/tca.php. The latter file holds a copy of the basic properties of the referring configuration file as well as the configuration of the representation of all the table fields. Whereas the first file is loaded on every page load, the latter file is cached and loaded just on demand. That improves the performance. + + + + 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 of the TYPO3-API which you can find at http://typo3.org/documenta- tion/document-library/core-documentation/doc_core_api/current/. + + + + Firstly, you should dip into the top layer of the TCA hierarchy. The file EXT:sjr_offers/ext_tables.php contains the following entries: + + + $TCA['tx_sjroffers_domain_model_organization'] = array( + ... + ); + + $TCA['tx_sjroffers_domain_model_offer'] = array( + ... + ); + + $TCA['tx_sjroffers_domain_model_person'] = array( + ... + ); + + + + + The associative Array contains all informations 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. + + + + $TCA['tx_sjroffers_domain_model_organization'] = array( + 'ctrl' => array( + ... + ), + 'interface' => array( + ... + ), + 'types' => array( + ... + ), + 'palettes' => array( + ... + ), + 'columns' => array( + 'first_fieldname' => array( + ... + ), + 'second_fieldname' => array( + ... + ), + ), + ); + + + + 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 informations 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 Datasets (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 rowset choosing the Info option. Altering the option maxDBListItems you can set how many rowsets will be shown in the List Module without switching to the detail view of the database table. Then again, the number of rowsets 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 rowset. 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 fieldnames which should emerge at the Input Form. An example of the table tx_sjroffers_domain_model_organization is: + + 'types' => array( + '1' => array('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 informations about the appearance (e.g. color and strucure). The information at the fourth place allow the use of the Richt Text Editore. 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' => array( + '1' => array('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' => array( + 'exclude' => 0, + 'label' => 'LLL:EXT:sjr_offers/Resources/Private/Language/locallang_db.xml:tx_sjroffers_domain_model_organization.name', + 'config' => array( + '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 fieldlabel (label), the conditions for the visibility of the field (exclude, displayCond) as well as informations 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' => array( + 'label' => 'Organization's name', + 'config' => array( + '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). + + + + + The field type input may be used for date and time inputs: + + 'minimum_value' => array( + 'label' => 'valid since', + 'config' => array( + '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: + + + + + + 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) specifys the area of the text input field. + + + 'address' => array( + 'label' => 'Address:', + 'config' => array( + 'type' => 'text', + 'cols' => 20, + 'rows' => 3 + ) + ), + + + + + + + + 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' => array( + 'label' => 'Hide:', + 'config' => array( + 'type' => 'check' + ) + ), + + + + + + 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' => array( + 'exclude' => 1, + 'label' => 'Property for', + 'config' => array( + 'type' => 'check', + 'eval' => 'required,unique', + 'cols' => 5, + 'default' => 31, + 'items' => array( + array('Level 1',''), + array('Level 2',''), + array('Level 3',''), + array('Level 4',''), + array('Level 5',''), + ) + ) + ), + + + 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. + + + + + + + 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' => array( + 'label' => 'Sex', + 'config' => array( + 'type' => 'radio', + 'default' => 'm', + 'items' => array( + array("male", 'm'), array('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. + + + + + + + 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' => array( + 'exclude' => 0, + 'label' => 'Status', + 'config' => array( + '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. + + + + + 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' => array( + 'exclude' => 1, + 'label' => 'Categories', + 'config' => array( + '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. + + + + + + + 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' => array( + 'label' => 'Bilder', + 'config' => array( + '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. + + + 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) + + + + + + + + Field type "none" + Fields of this type show up the raw data values which cannot be edited (see Fig. 6-10). + + + 'date' => array( + 'label' => 'Datum (Timestamp)', + 'config' => array( + '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. + + + + + + + Field type "passthrough" + + The field type "passthrough" is for data which are processed internly but cannot be edited or viewed in the Form. An example for that would be informations to references (Foreign Keys). + + + 'organization' => array( + 'config' => array( + '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 formular 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 in the TYPO3-Core API (refer to http://typo3.org/documentation/ document-library/core-documentation/doc_core_api/4.3.0/view/4/2/). + + + 'offers' => array( + 'label' => 'Offers', + 'config' => array( + '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. + + + + + + + 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' => array( + 'label' => 'Track Title', + 'config' => array( + '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' => array( + 'label' => 'Track', + 'config' => array( + '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' => array( + 'label' => 'Categories', + config' => array( + '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 Kickstarter (refer to Ch. 10). + + + + As already mentioned, the TCA is normally split into two different files due to performance reasons: The first part contains the general configurations of the table and is loaded with every page load compared to the second part which contains configurations for the table columns and are only loaded as necessary. In our example Extension the first part which is saved in ext_tables.php contains the following stuff: + + $TCA['tx_sjroffers_domain_model_organization'] = array( + 'ctrl' => array( + 'title' => 'LLL:EXT:sjr_offers/Resources/Private/Language/ locallang_db.xml: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.xml:LGL.prependAtCopy', + 'copyAfterDuplFields' => 'sys_language_uid', + 'useColumnsForDefaultValues' => 'sys_language_uid', + 'delete' => 'deleted', + 'enablecolumns' => array( + 'disabled' => 'hidden' + ), + 'dynamicConfigFile' => t3lib_extMgm::extPath($_EXTKEY) . 'Configuration/TCA/tca.php', + 'iconfile' => t3lib_extMgm::extRelPath($_EXTKEY) . 'Resources/Public/Icons/icon_tx_sjroffers_domain_model_organization.gif' + ) + ); + + + + + This file only contains the essential ctrl section. The value corresponding to the key dynamicConfigFile holds the filepath to the file which contains the second part of the TCA; whereby the filepath and the filename can be chosen freely. However, the file should resist in the directory Configuration (or any subdirectory). The corresponding second part of the file TCA.php is as follows: + + + $TCA['tx_sjroffers_domain_model_organization'] = array( + 'ctrl' => $TCA['tx_sjroffers_domain_model_organization']['ctrl'], + 'interface' => array( + 'showRecordFieldList' => 'status,name,address,telephone_number,telefax_number,url,email_address,description,contacts,offers,administrator'), + 'types' => array( + '1' => array('showitem' => 'hidden,status,name,address;;1;;,description, contacts,offers,administrator') + ), + 'palettes' => array( + '1' => array('showitem' => 'telephone_number,telefax_number,url,email_address') + ), + 'columns' => array( + 'sys_language_uid' => array(...), + 'l18n_parent' => array(...), + 'l18n_diffsource' => array(...), + 'hidden' => array(...), + 'status' => array(...), + 'name' => array(...), + 'address' => array(...), + 'telephone_number' => array(...), + 'telefax_number' => array(...), + 'url' => array(...), + 'email_address' => array(...), + 'description' => array(...), + 'contacts' => array(...), + 'offers' => array(...), + 'administrator' => array(...), + ) + ); + + On the top we can see the backreference to the TCA's first part ctrl and below all the residual parts of the configuration. 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). + + + + + + 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 Tx_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 suffic Repository. The Repository needs to extend the class Tx_Extbase_Persistence_Repository. The class file Tx_ 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 direcory Model. In our case, the class body remains empty because all the important functionalities are already generically implemented in the super-class Tx_Extbase_Persistence_Repository. + class Tx_SjrOffers_Domain_Repository_OrganizationRepository extends Tx_Extbase_Persistence_Repository {} + + + + We create a Tx_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 informations 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. + + + + 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. + + + + Tx_Extbase_Persistence_Repository serves with the following methods which are of course accessable and overwritable in the extending child derivations: + + + + <code>add($object)</code> + 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. + + + + <code>remove($object)</code> and <code>removeAll()</code> + 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. + + + + <code>replace($existingObject, $newObject)</code> + Replaces an existing object with a new object. Instead of the combination of add() and remove() this method keeps the existing object in the database. + + + + <code>update($modifiedObject)</code> + 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. + + + + <code>findAll()</code> and <code>countAll()</code> + 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. + + + + <code>findByProperty($value)</code>, <code>findOneByProperty($value)</code> and <code>countByProperty($value)</code> + 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 methode 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. + + + + <code>createQuery()</code> + 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: + + + + 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. + + + + When insertion of new Domain objects happens, then the procedure will be as follows: + + + + If there's a TypoScript setup at plugin.tx_extensionname.persistence.classes.FullClassName.newRecordStoragePid with a single page value, then this is gonne 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. + + + + + + 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. + + + + Most occuring 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. + + + + TYPO3 v4's Page Tree + In TYPO3 each Content Element and Dataset which should be rendered in the Backend corresponds to a certain Page. Technically, a page is nothing more than a a Node Element or a leaf in the virtual Page Tree. Every page is associated with a unique Page ID (PID). Some of the pages are reachable via a URL and TYPO3 renders and delivers them (usually in HTML). For example, the URL http://www.example.com/index.php?id=123 requests the Page with the PID 123. In this case, the term Page has the meaning of being a Webpage. But there are other cases, e.g. a directory (SysFolder) or a separator which are used to save data in a clear and structured way. A special already existing PID is 0 which is used to refer to the root page (the one with the shiny globe). TYPO3 v5 will use the concept of the Page Tree, too. But it will do many things much better such as the clear separation of Contents and Structure. While v4 interlaces the structure of a Page Tree into every aspect of data persistence, v5 treats alternative structurization principles such as trees of categories or timelines equally well. + + + + Usually, datasets will be saved into Folders in the Page Tree though the pages using those datasets 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. Alternatively, you can use the extension nc_beclearcachehelper for managing your cache preferences. + + + + Internally, TYPO3 manages an index of all relationships between two datasets the so-called RefIndex. Due to this index it's possible to show the number of associated datasets in the list module's column [Ref.]. By clicking on the number you get further informations about the incoming and outgoing references of the dataset. This index is automatically updated when any datasets get edited. The configuration config.tx_extbase.persistence.updateReferenceIndex = 1 effects an update when datasets 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 the TYPO3-API method makeInstance(): + + $offerRepository = t3lib_div::makeInstance('Tx_SjrOffers_Domain_Repository_ OfferRepository'); + + + + + 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 makeInstance(). 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. With Extbase version 1.2 you may even persist whole class hierarchies in a database table so that you don't have to define a special table for each Domain object. The following Sections will describe the possibilities of sophisticated data persistation. + +
\ No newline at end of file diff --git a/6-persistence/3-implement-individual-database-queries.xml b/6-persistence/3-implement-individual-database-queries.xml new file mode 100644 index 00000000..053f21c2 --- /dev/null +++ b/6-persistence/3-implement-individual-database-queries.xml @@ -0,0 +1,136 @@ + +
+ 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. On the other hand you could use a certain request matching exactly your criteria and just execute it. Contrary to the first case, the latter method would only build the objects that are really needed which positively affects the performance of your application. Therefore, the first method is more flexible and easier to implement. + + + + You may start developing your application using the first method and then, seeing your application growing, veering to the second method. Luckily, all the changes are encapsulated in the Repository and you therefore 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. Those information contain: + + 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 method findAll() looks as follows: + + public function findAll() { + return $this->createQuery()->execute(); + } + + + In this simple first use-case we don't 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(Tx_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 splitted into two groups: Comparing operations and Boolean operations. The first mentioned lead 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 negate a result respectively. 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. + + + + Contrary, 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(); + } + + + + The methods in() and contains() were introduced in Extbase version 1.1. 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 tree of object hierarchy. Let's assume you want to filter the organizations 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 which has offers whose age values have a minimum which is less or equal 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. + + + + The path notation was introduced in Extbase 1.1 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} whereas $query->equals('offers.categories.title', 'foo') is possible die to the limitation in Fluid that the access of properties is not possible in a "concatenated way". + + + + Besides of the comparison operators the Query object supports Boolean Operators like: + + 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 params $constraint1 and $constraint2 are true whereas it's sufficient when using logicalOr() to be true if only one of the given params is true. Since Extbase 1.1 both methods accept an Array of constraints. Last but not least, the function logicalNot() inverts the given $constraing to it's opposite, i.e. true gets false and false gets true. Given this information, you can create complex queries like: + + + public function findMatchingOrganizationAndRegion(Tx_SjrOffers_Domain_Model_Organization $organization, Tx_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 need the + + +
\ No newline at end of file diff --git a/6-persistence/4-use-foreign-data-sources.xml b/6-persistence/4-use-foreign-data-sources.xml new file mode 100644 index 00000000..51aef48d --- /dev/null +++ b/6-persistence/4-use-foreign-data-sources.xml @@ -0,0 +1,12 @@ + +
+ Using Foreign Data Sources + + not yet +
diff --git a/6-persistence/5-modeling-the-class-hierarchy.xml b/6-persistence/5-modeling-the-class-hierarchy.xml new file mode 100644 index 00000000..a9d47943 --- /dev/null +++ b/6-persistence/5-modeling-the-class-hierarchy.xml @@ -0,0 +1,12 @@ + +
+ Modelling the Class Hierarchy + + not yet +
diff --git a/6-persistence/index.xml b/6-persistence/index.xml new file mode 100644 index 00000000..bddcae6c --- /dev/null +++ b/6-persistence/index.xml @@ -0,0 +1,29 @@ + + + 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. + + 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 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. + + + + + + + + \ No newline at end of file diff --git a/7-controllers/1-Creating Controllers and Actions.xml b/7-controllers/1-Creating Controllers and Actions.xml new file mode 100644 index 00000000..847d3f3c --- /dev/null +++ b/7-controllers/1-Creating Controllers and Actions.xml @@ -0,0 +1,317 @@ + +
+ 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 + Tx_SjrOffers_Controller_OfferController is assigned + to the Aggegate Root Object + Tx_SjrOffers_Domain_Model_Offer. And the Name of the + Class file is OfferController.php. + + The Controller class must extend the class + Tx_Extbase_MVC_Controller_ActionController which is + part of Extbase. The individual Actions are combined in seperate methods. + The method names have to end in Action. The body of + OfferController thus looks like this: + + class Tx_SjrOffers_Controller_OfferController extends + Tx_Extbase_MVC_Controller_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 transacted by a single Action or a chain + of Actions: + + + + A list of Domain Objects is to be displayed. + + + + A single Domain Object is to be displayed. + + + + A new Domain Object is to be created. + + + + An existing Domain Object is to be edited. + + + + 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. + + + 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: + + public function indexAction() { + + $offerRepository = + t3lib_div_makeInstance('Tx_SjrOffers_Domain_Repository_OfferRepository'); + + + $offers = $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: + + public function indexAction() { + + $offerRepository = + t3lib_div_makeInstance('Tx_SjrOffers_Domain_Repository_OfferRepository'); + + + $this->view->assign('offers', + $offerRepository->findAll()); + + } + + This Flow is prototypic for a task which merely has to give out + data. Domain Objects are fetched from a Repository previously instatiated + and passed to the View for future processing. Inside of our + OfferController we have to make use of + OfferRepository in the different Actions again and + again. For that we don't have to intantiate the Repository in each and + every Action, extbase offers the Method + initializeAction(). It can be used for tasks + concerning multiple Actions and is called before any Action is executed. + In the class ActionController the body of this + Method is empty. You can overwrite it in your own Controller though. In + our case we assign the instance of our Repository to the Class Variable + $offerRepository. Our Controller thus looks like + this: + + protected $offerRepository; + + + + pubilc function initializeActio() { + + $this->offerRepository = + t3lib_div::makeInstance('Tx_JjrOffers_Domain_Repository_OfferRepository'); + + } + + + + public function indexAction() { + + $this->view->assign('offers', + $offerRepository->findAll()); + + } + + ActionController not only calls hte Method + initializeAction(), which is executed before any + Action in the Controller, but also a Method in the Form of + initializeFooAction(), which is called only before + the Method fooAction(). The Method for the + initializing of Action is of course not only useful for preparing + Repositories. You can also use them for integrating JavaScript libraries + or to check if a specific FE user is logged in. + + + 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 Mehod 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 th offer to be + shown is passed to the Method as Argument: + + /** + + * @param Tx_SjrOffers_Domain_Model_Offer $offer The offer to + be shown + + * @return string The rendered HTML string + + */ + + public function showAction(Tx_SjrOffers_Domain_Model_Offer + $offer) { + + $this->view->assign('offer', $offer); + + } + + Ususally the display of a single Object is called by a link in + Forntend. In our example extension it connects the list view by something + like the following URL: + + http://localhost/index.php?id=123&tx_sjroffers_pi1[offer]=3&tx_sjroffers_pi1[action]=show&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 + the GET Argument tx_sjroffers_pi1[offer]=3 + corresponds to the Method Argument + $offer: + showAction(Tx_SjrOffers_Domain_Model_Offer $offer). + The type of this Argument is fetched by extbase from the Method signature: + showAction(Tx_SjrOffers_Domain_Model_Offer $offer). + In case this so called Type Hint should not be + present, or (e.g. for the types string or + int in PHP) not possible, extbase reads the type from + the commentary written above the Method: @param + Tx_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 cahpter 9 in section "Validating Domain + Objects"). In our case the incoming value is "3". Target type is the class + Tx_SjrOffers_Domain_Model_Offer. So extbase + interprets the 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. + + + 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 fpor vreating a new Domain + Object, two steps are required: First, a form for inputting the FDomain + Data has to be shown in Frontend. Second, a new Domain Object hast to be + created (using the incomin form data) and put in the appropriate + Repository. We're going to implement these two steps in the Methods + newAction() and + createAction(). + + + We already described these steps in cahpter 3 in section + "TODO: Insert correct section name." We 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 withe the following URL: + + http://localhost/index.php?id=123&tx_sjroffers_pi1[oranization]=5&tx_sjroffers_pi1[action]=new&tx_sjroffers_pi1[controller]=Offer + + Extbase instantiates the Organization Object + which is mapped to the Argument $organization, just + as it was the case with the Offer Object in the + Method showAction(). In the URL are 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(). + + /** + + * @param Tx_SjrOffers_Domain_Model_Organization $organization + The organization + + * @param Tx_SjrOffers_Domain_Model_Offer $offer The new offer + object + + * @return string An HTML form for creating a new + offer + + * @dontvalidate $newOffer + + */ + + public function + newAction(Tx_SjrOffers_Domain_Model_Organization $organization, + Tx_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 "TODO: Insert correct section name". +
+ +
+ with Ablaufmuster + + Text +
+ +
+ Ablaufmuster + + Text +
+
diff --git a/7-controllers/2-Configuring and embedding Frontend Plugins.xml b/7-controllers/2-Configuring and embedding Frontend Plugins.xml new file mode 100644 index 00000000..f580e477 --- /dev/null +++ b/7-controllers/2-Configuring and embedding Frontend Plugins.xml @@ -0,0 +1,13 @@ + +
+ Configuring and embedding Frontend Plugins + + The Controller classes are stored in the folder EXT:sjr_offer/Classes/Controller/. +
diff --git a/7-controllers/3-Configuring the behavior of the extension.xml b/7-controllers/3-Configuring the behavior of the extension.xml new file mode 100644 index 00000000..4eb1dc48 --- /dev/null +++ b/7-controllers/3-Configuring the behavior of the extension.xml @@ -0,0 +1,12 @@ + +
+ Configuring the behavior of the extension + + Blah Blah Blah Blah +
diff --git a/7-controllers/index.xml b/7-controllers/index.xml new file mode 100644 index 00000000..e4114efb --- /dev/null +++ b/7-controllers/index.xml @@ -0,0 +1,110 @@ + + + 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 subfolder EXT:sjr_offers/Classes/Domain/Model/. Furthermore we + set up the persistence layer. As a result we are alredy 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 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 such actions are: + + + + Deleting a specific offer + + + + Deleting all offers of one organizatin linkion + + + + Displaying a form to change the data of an offer + + + + Updating an offer + + + + Listing the newest offers + + The code receiving the request and 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 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. + + + + + + diff --git a/8-fluid/1-basic-concepts.xml b/8-fluid/1-basic-concepts.xml new file mode 100644 index 00000000..7cbdcdc2 --- /dev/null +++ b/8-fluid/1-basic-concepts.xml @@ -0,0 +1,456 @@ + +
+ 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. + + + 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:class Tx_BlogExample_Controller_PostController extends Tx_Extbase_MVC_Controller_ActionController { + ... + public function indexAction(Tx_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>{blog.Title}</h1> + +<f:for each="{blogPost}" 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 /> + + + 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) + + + + + The Uniform Access Principle + + The Uniform Access Principle says, all services offered by a + module should be available through an uniform notation which does not + betray whether they are implemented through storage or through + computation. Explanation on Wiki + + Stored objects are being accessed directly using public class + variables in PHP - and it is visible on the outside that the object + isn't being computed. For this reason, we often use get and + set-methods in our models. Therefore, all options of the class are + accessible through method calls and are uniformly addressed - it is + not visible on the outside whether the class computed or stored the + value directly. + + + 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=Tx_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: + + <u> +<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 + Tx_Fluid_ViewHelpers_ForViewHelper. + + The first part of the class name is the complete Namespace like it + was defined earlier with {namespace f=Tx_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. + 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.textbox is implemented in the class + Tx_Fluid_ViewHelpers_Form_TextboxViewHelper. + 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. + + + 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 Notification 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 formating + 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. + + + 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}. + 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. + + + 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. + + + Inline Notation vs. Tag Based Notation + + Once again a comparison between inline notation and tag based + syntax: + + Tags have an advantage, if: + + + + Control structures are being displayed: + + <f:for each="{posts}" as="post">...</f:for> + + + + The ViewHelper returns a tag: + + <f:form.textbox /> + + + + The hierarchical structure of ViewHelpers is + important: + + <f:form> + + <f:form.textbox /> + + </f:form> + + + + The ViewHelper contains a lot of content: + + <f:section name="main"> + + .... + + </f:section> + + + + Inline notation should be used, if: + + + + The focus is on the data flow: + + {post.date -> f:format.date(format: 'Y-m-d') -> + f:format.padding(padLength: 40)} + + + + The ViewHelper is being used inside of XML tags: + + <link rel="stylesheet" href="{f:uri.resource(path: + 'styles.css')}" /> + + + + The nature of the ViewHelper is rather a helper + function + + {f:translate(key: '...')} + + + +
+ +
+ 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. +
+
diff --git a/8-fluid/10-template-creation-by-example.xml b/8-fluid/10-template-creation-by-example.xml new file mode 100644 index 00000000..856973c1 --- /dev/null +++ b/8-fluid/10-template-creation-by-example.xml @@ -0,0 +1,211 @@ + +
+ 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. + + TODO: insert 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 "" earlier in this chapter) and store + repeating code in partials (see the section "" earlier in this chapter). The basic + framework of our templates looks as follows: + + {namespace sjr=Tx_SjrOffers_ViewHelpers} +<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 conent 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 section + "TODO:insert section name" in chapter 7): + + public function updateAction(Tx_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 implemeted for this + purpose. + + TODO: insert 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 TODO Change this to the correct + term as also used in the screenshot 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 "" earlier in this + chapter): + + {offer.ageRange->sjr:format.numericRange()} + + The NumericRangeViewHelper is implemented as follows: + + TODO: insert code here + + 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 similiar 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/. + + TODO: insert code here + + 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().TODO: Rewrite sentence 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 propety 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> + + + <emphasis role="bold">Localize error messages</emphasis> + + The error messages of the default validators that are delivered + with Extbase are not localized in version 1.2. You can translate the + messages yourself by replacing the before described partial + formErrors with the following code: + + TODO: insert code here + + In the file + EXT:sjr_offers/Resources/Private/Language/locallang.xml + you have to write for example: + + <label index="newOffer.title">Title of the offer</label> +<label index="newOffer.title.1238108067">The length of the title must between 3 an 50 character.</label> + + This solution is only an agreement. The default localization of + the error messages is planned for a future version of + Extbase.TODO: rework for current Extbase version + +
+
diff --git a/8-fluid/11-conclusion.xml b/8-fluid/11-conclusion.xml new file mode 100644 index 00000000..dd1af5e1 --- /dev/null +++ b/8-fluid/11-conclusion.xml @@ -0,0 +1,26 @@ + +
+ 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. +
diff --git a/8-fluid/2-using-different-output-formats.xml b/8-fluid/2-using-different-output-formats.xml new file mode 100644 index 00000000..ce71da6c --- /dev/null +++ b/8-fluid/2-using-different-output-formats.xml @@ -0,0 +1,79 @@ + +
+ 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. + + + 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 respectivly. Otherwise, TYPO3 will always generate + the <head>- and + <body>-section. + + You can use the following TypoScript: + + rss = PAGE +rss ( + typeNum = 100 + 10 =< tt_content.lost.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. Futher on you have to + implicitley set + plugin.[ExtensionKey].persistence.storagePid + to the ID of the page containg the data, to tell Extbase from which page + the data should be read. + +
diff --git a/8-fluid/3-moving-repeating-snippets-to-partials.xml b/8-fluid/3-moving-repeating-snippets-to-partials.xml new file mode 100644 index 00000000..cc307fde --- /dev/null +++ b/8-fluid/3-moving-repeating-snippets-to-partials.xml @@ -0,0 +1,57 @@ + +
+ Moving Repeating Snippets To Partials + + During developing of templates, you often reach a point, where you + need a template snippet that you wrote already before somewhere else. To + avoid duplicating code and to guarantee a consistent look with such + recurring elements, Fluid supports so called Partials. + You can think of partials as of a small code snippets that you can include + anywhere within a template. In our blog example there is a list of tags, + which are used in different views. In the list view of all posts and in the + detail view of a single blog post. In this case it makes sense to move the + view of the tag list to a partial. + + All Partials are Fluid templates and are located in + Resources/Private/Partials/. For example we could save + our tag-view-partial in + Resources/Private/Partials/tags.html and it could + contain the following code to display the list of the tags: + + <b>Tags</b>: <f:for each="{tags}" + as="tag">{tag}</f:for> + + We use the ViewHelper f:render partial="..." to call a Partial + in a template: + + <ul> + <f:for each="{blogPosts}" as="post"> + <li><b>{post.title}</b><br /> + <f:render partial="tags" arguments="{tags: post.tags}" /> + </li> + </f:for> +</ul>With partial="tags", the ViewHelper + is being told to render the Partial called tags. Further on, + the list of the tags is being passed to the ViewHelper as parameter. All + variables needed in the Partial, have to be passed to it explicitely with + arguments="...". + + + We referenced the Partial with the ending .html. In doing so, the + current format is being used. Therefore a different format of the output + is applied automatically when changing the format. If you specify the + ending of the Partial explicitely, then this Partial is always used, no + matter in which format the data is being outputted. More information + about formats can be found in »Using Different Output Formats« in the + previous chapter. + + + +
diff --git a/8-fluid/4-creating-a-consistent-look-and-feel-with-layouts.xml b/8-fluid/4-creating-a-consistent-look-and-feel-with-layouts.xml new file mode 100644 index 00000000..a2a484e9 --- /dev/null +++ b/8-fluid/4-creating-a-consistent-look-and-feel-with-layouts.xml @@ -0,0 +1,78 @@ + +
+ 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. + + TODO: insert 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> + 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 whose content will be inserted. So by defining the placeholder + <f:section name="main">, like in the example 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. (TODO: does anybody + understand this sentence?) 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. + + + You'll find a practical example for building layouts in the + section "" 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. +
diff --git a/8-fluid/5-using-typoscript-for-rendering-the-cobject-viewhelper.xml b/8-fluid/5-using-typoscript-for-rendering-the-cobject-viewhelper.xml new file mode 100644 index 00000000..489c47fd --- /dev/null +++ b/8-fluid/5-using-typoscript-for-rendering-the-cobject-viewhelper.xml @@ -0,0 +1,160 @@ + +
+ 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 + ??? What is the tip + »Extbase and Fluid«TODO: Add formatting for output + 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 + } +} + + + TypoScript + + TypoScript is a flexible configuration language, which can control + the rendering of a page very detailed. It consists of TypoScript objects + (also known as Content object or cObject) and + their configuration options. + + The simplest Content object is TEXT + which outputs unmodified text. The TypoScript object IMAGE + can be used to generate images and database entries can be outputted + with CONTENT. + + + 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 notaion, 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.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 emphazise 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. +
diff --git a/8-fluid/6-adding-additional-tag-attributes-with-additionalattributes.xml b/8-fluid/6-adding-additional-tag-attributes-with-additionalattributes.xml new file mode 100644 index 00000000..9793dc2d --- /dev/null +++ b/8-fluid/6-adding-additional-tag-attributes-with-additionalattributes.xml @@ -0,0 +1,48 @@ + +
+ 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\')'}" /> + + 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. +
diff --git a/8-fluid/7-using-boolean-conditions.xml b/8-fluid/7-using-boolean-conditions.xml new file mode 100644 index 00000000..526b4209 --- /dev/null +++ b/8-fluid/7-using-boolean-conditions.xml @@ -0,0 +1,115 @@ + +
+ 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. + + + 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> + + + 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. + + + 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. + + + Once you develop an own ViewHelper - like described in the section + "" 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. ??? Sentence is not very + clear + + + Upon engaged with many functionalities for the author of templates + in this section, we would show you now how to develop your own + ViewHelper.??? Does anyone understand the first part of the + sentence? +
+
diff --git a/8-fluid/8-developing-a-custom-viewhelper.xml b/8-fluid/8-developing-a-custom-viewhelper.xml new file mode 100644 index 00000000..8d961815 --- /dev/null +++ b/8-fluid/8-developing-a-custom-viewhelper.xml @@ -0,0 +1,405 @@ + +
+ Developing a custom ViewHelper + + The development of an own ViewHelper is much asked for in practice and + is part of the base repertoire of the extension development. We will guide + you step by step through a simple example from the blog example and describe + enhanced techniques afterwards. + +
+ The Gravatar-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 to display. Therefore the use of + gravatar.com introduces no security risk because the + user of the blog only see the checksums of the email address and not the + email address itself. This is possible as no efficient possibility is + known to get the original data reconstructed from the checksum. + + In this section we show you how to write your own ViewHelper that + uses an email address as parameter and shows the picture from gravatar.com + if it exists. +
+ +
+ Preliminary considerations + + The first step should be thinking about how to use the ViewHelper + later on in the template, in order to get a clear view about the arguments + of the ViewHelper. We take the point of view of a template author who + wants to use our ViewHelper later on, without knowledge of the internal + operations. + + First of all, think about how the ViewHelper should be called inside + the template: The ViewHelper is not part of the default distribution, + therefore we need an own namespace import to use the ViewHelper. We import + the namespace Tx_BlogExample_ViewHelpers with the token + blog. Now, all tags starting with blog: are + interpreted as ViewHelper: + + {namespace blog=Tx_BlogExample_ViewHelpers} + + Our ViewHelper should get the name gravatar and only get an email + address as parameter. We will call the ViewHelper in the template as + follows: + + <blog:gravatar emailAddress="sebastian@typo3.org" /> + + After this preliminary considerations we will start with the + implementation. +
+ +
+ Now implementing! + + Every ViewHelper is a PHP class whose name is derived from the + namespace import and the name of the XML element. The classname consists + of the following three parts: + + + + full namespace (in our example + Tx_BlogExample_ViewHelpers) + + + + the name of the ViewHelper in UpperCamelCase writing (in our + example Gravatar) + + + + the ending ViewHelper + + + + For the Gravatar ViewHelper the name of the class is + Tx_BlogExample_ViewHelpers_GravatarViewHelper. + + Following the naming conventions for Extbase extensions we create + the ViewHelper skeleton in the PHP file + EXT:blog_example/Classes/ViewHelpers/GravatarViewHelper.php: + + class Tx_BlogExample_ViewHelpers_GravatarViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { + public function render() { + } +} + + Every ViewHelper must inherit from the class + Tx_Fluid_Core_ViewHelper_AbstractViewHelper. + + + A ViewHelper can also inherit from subclasses of + AbstractViewHelper, e.g. from + Tx_Fluid_Core_ViewHelper_TagBasedViewHelper. Several + subclasses are offering additional functionality. We will talk about the + just addressed TagBasedViewHelper later on in this chapter in detail in + "Creating XML tags using TagBasedViewHelper". + + + In addition every ViewHelper needs a method render(), which is + called once the ViewHelper is to be displayed in the template. The return + value of the method is copied directly into the complete output. If we + enhanced our ViewHelper from above as follows + + class Tx_BlogExample_ViewHelpers_GravatarViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { + public function render() { + return 'World'; + } +}and we insert it in the template like this + + {namespace blog=Tx_BlogExample_ViewHelpers} +Hello <blog:gravatar />Hello World + should be displayed. +
+ +
+ Register arguments of ViewHelpers + + Our Gravatar ViewHelper must hand over the email + address it should work on. This is the last needed building block, before + we can implement our needed functionality. + + All arguments of a ViewHelper must be registerd. Every ViewHelper + has to declare explicit which parameters are accepted. + + The easiest alternative to register these arguments is to enhance + the render() method. All method arguments of the + render() method are automatically arguments of the + ViewHelpers. In our example it looks like this: + + /** + * @param string $emailAddress + */ + public function render($emailAddress) { + }With this the ViewHelper gets the argument + emailAddress, which is of the type string. You + see that the annotation of the method in the PHPDoc block is important, + because the type of the parameter is based on this by Fluid. + + + If you forget to specify the type of a parameter, an error message + will be displayed. Check at all times that the PHPDoc block is complete + and syntactical correct. For example, if you forget the @ + in front of the param, the type of the parameter is not + identified. + + + + Sometimes arguments should get different + types. In this case you should use the type mixed in the PHPDoc. With + the line @param mixed $emailAddress any type of object can + be given as parameter emailAddress, e.g. arrays, strings or + integer values. + + + At the end we implement the output as img tag: + + class Tx_BlogExample_ViewHelpers_GravatarViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { + /** + * @param string $emailAddress The email address to resolve the gravatar for + * @return string the HTML <img>-Tag of the gravatar + */ + public function render() { + return '<img src="http://www.gravatar.com/avatar/' . md5($emailAddress) . '" />'; + } +}Congratulation on creating your first ViewHelper! In the + following sections we will show you some enhancements and tricks for + implementing ViewHelpers. +
+ +
+ Register Arguments with initializeArguments() + + Initializing the ViewHelper arguments directly at the + render() method is extreme handy, when you don't have to much + arguments. But sometimes you'll build a complex inheritance hierarchy with + the ViewHelper, where different level of the inheritance structure should + register additional arguments. Fluid itself does this for example with the + form ViewHelpers. + + Because method parameter and annotations are not inheritable, there + must be an additional way to register the arguments of a ViewHelper. Fluid + provides the method initializeArguments for this. In this + method you can register additional arguments by calling + $this->registerArgument($name, $type, $description, $required, + $defaultValue). You can access these arguments through the array + $this->arguments. + + The above example could be changed in the following way and would + function identical: + + class Tx_BlogExample_ViewHelpers_GravatarViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { + /** + * Arguments Initialization + */ + protected function initializeArguments() { + $this->registerArgument('emailAddress', 'string', + 'The email address to resolve the gravatar for', TRUE); + } + + /** + * @return string the HTML <img>-Tag of the gravatar + */ + public function render() { + return '<img src="http://www.gravatar.com/avatar/' . + md5($this->arguments['emailAddress']) . '" />'; + } +}In this example the usage of + initializeArguments is not particular meaningful, because the + method only requires one parameter. When working with complex ViewHelpers + which have a multilevel inheritance hierarchy, it is sometimes more + readable to register the arguments with + initializeArguments(). +
+ +
+ Creating XML tags using TagBasedViewHelper + + For ViewHelper that create XML tags Fluid provides an enhanced + baseclass: the Tx_Fluid_Core_TagBasedViewHelper. This + ViewHelper provides a Tag-Builder that can be used to + create tags in a simple way. It takes care about the syntactical correct + creation of the tag and escapes for example single and double quote in + attributes. + + + With the correct escaping of the attributes the system security is + enhanced, because it prevents cross site scripting + attacks that would break out of the attributes of XML tags. + + + In the next step we modify the just created + GravatarViewHelper a bit and use the + TagBasedViewHelper. Because the + Gravatar-ViewHelper creates an img tag the use + of the Tag-Builder is advised. + + Lets have a look how we change the ViewHelper: + + TODO:code + + What has changed? First of all, the ViewHelper inherits not directly + from AbstractViewHelper but from + TagBasedViewHelper, which provides and initializes the + Tag-Builder. Beyond that there is a class variable $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 (Attribute, Value) + to add new tag attributes. In our example we add the attribute + src to the tag, with the value assigned one line above it. + Finally the Tag-Builder offers a method render() which + generates and returns the tag which than is given back, because we want to + insert it in the template. + + + You may ask why this code is better even though it is much longer. + It communicates the meaning much better and therefore it is preferred to + the first example, where the gravatar URL and the creating of the + img tag was mixed. + + + The base class TagBasedViewHelper allows you to + implement ViewHelpers which returns a XML tag easier and cleaner and help + to concentrate at the essential. + + Furthermore the TagBasedViewHelper offers assistance for ViewHelper + arguments that should recur direct and unchanged as tag attributes. These + could be registerd in initializeArguments() with the method + $this->registerTagAttribute($name, $type, $description, $required + = FALSE). If we want to support the <img> + attribure alt in our ViewHelper, we can initialize 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 we want to support the universal attributes and the + alt attribute in our Gravatar ViewHelper we need + the following initializeArguments() method: + + public function initializeArguments() { + parent::initializeArguments(); + $this->registerUniversalTagAttributes(); + $this->registerTagAttribute('alt', 'string', 'Alternative Text for the image'); +} +
+ +
+ Insert optional arguments + + All ViewHelper arguments we have registered so far were required. By + setting a default value for an argument in the method signature, the + argument is automatically optional. When registering + the arguments through initializeArguments() the according + parameter has to be set to FALSE. + + Back to our example: We can add a size parameter for the picture in + the Gravatar ViewHelper. This size parameter will be used to determine the + height and width of the image in pixels and can range from 1 to 512. When + no size is given, an image of 80px is generated. + + We can enhance the render() method like this: + + /** + * @param string $emailAddress The email address to resolve the gravatar for + * @param string $size The size of the gravatar, ranging from 1 to 512 + * @return string the HTML <img>-Tag of the gravatar + */ + public function render($emailAddress, $size = '80') { + $gravatarUri = 'http://www.gravatar.com/avatar/' . md5($emailAddress) . '?s=' . urlencode($size); + $this->tag->addAttribute('src', $gravatarUri); + return $this->tag->render(); + } +}With this setting of a default value we have made the + size attribute optional. +
+ +
+ Prepare ViewHelper for inline syntax + + So far with our gravatar ViewHelper we have focussed on the tag + structure of the ViewHelper. We have used the ViewHelper only with the tag + syntax (because it returns a tag as well): + + <blog:gravatar emailAddress="{post.author.emailAddress}" + /> + + Alternatively we can rewrite this sample in the inline + notation: + + {blog:gravatar(emailAddress: + post.author.emailAddress)} + + With this, the tag concept of the ViewHelper is mostly gone. 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()} + + Here the email address has the focus and we see the gravatar + ViewHelper as a converting step based on the email address. + + We want to show you now what a ViewHelper has to do, to support this + syntax. The syntax {post.author.emailAddress -> + blog:gravatar()} is an alternative writing for + <blog:gravatar>{post.author.emailAddress}</blog:gravatar>. + To support this we have to use the email address either from the argument + emailAddress or, if it is empty, we should interpret the + content of the tag as email address. + + How did we get the content of a ViewHelper tag? For this a helper + 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: + + /** + * @param string $emailAddress The email address to resolve the gravatar for + * @param string $size The size of the gravatar, ranging from 1 to 512 + * @return string the HTML <img>-Tag of the gravatar + */ + public function render($emailAddress = NULL, $size = '80') { + if ($emailAddress === NULL) { + $emailAddress = $this->renderChildren(); + } + + $gravatarUri = 'http://www.gravatar.com/avatar/' . md5($emailAddress) . '?s=' . urlencode($size); + $this->tag->addAttribute('src', $gravatarUri); + return $this->tag->render(); + } +}This code section has the following effect: First we have + made the ViewHelper attribute emailAddress optional. If no + emailAddress attribuite is given, we interpret the content of + the tag as email address. The rest of the code in unchanged. + + + This trick was specially used at the format ViewHelpers. Every + ViewHelper supports both writings there. + +
+
diff --git a/8-fluid/9-using-php-based-views.xml b/8-fluid/9-using-php-based-views.xml new file mode 100644 index 00000000..32d73bca --- /dev/null +++ b/8-fluid/9-using-php-based-views.xml @@ -0,0 +1,119 @@ + +
+ 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: + + Tx_@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 Tx_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.??? + Check for correctness.. This does not match with the format mentioned above. + One would expect the class to be + Tx_BlogExample_view_Post_List_JSON + + Each view must implement the interface + Tx_Extbase_MVC_View_ViewInterface. 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 + Tx_Extbase_MVC_View_AbstractView which provides default + initializing methods and you only have to implement the + render() method. A minimal view would like this: + + class Tx_BlogExample_View_Post_ListJSON extends Tx_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: + + class Tx_BlogExample_View_Post_ListJSON extends Tx_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. + + + 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 + Tx_Extbase_MVC_ViewInterface. 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. + + + 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. +
+
diff --git a/8-fluid/index.xml b/8-fluid/index.xml new file mode 100644 index 00000000..b86daa1d --- /dev/null +++ b/8-fluid/index.xml @@ -0,0 +1,48 @@ + + + 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 FLOW3 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. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/9-crosscutting-concerns/1-localizing-and-internationalizing-an-extension.xml b/9-crosscutting-concerns/1-localizing-and-internationalizing-an-extension.xml new file mode 100644 index 00000000..bee6614f --- /dev/null +++ b/9-crosscutting-concerns/1-localizing-and-internationalizing-an-extension.xml @@ -0,0 +1,567 @@ + +
+ Localizing and internationalizing an extension + + Particularly in business relationships there often is the need to + provide a website in one or multiple languages. Therefore not only the + translation of the website content has to be done but also the extensions + which are used must be available in multiple languages. During extension + development you should think about a possible translation later on and set + the course according to this. + + 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 + (http://typo3.org/documentation/document-library/core-documentation/doc_l10nguide/current/). + 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: + + TODO: insert code here + + 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: + + TODO: insert code here + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierEnglishGerman
author_prefixBy:Von:
comment_headerCommentsKommentare
+ + In TYPO3 (and also in Extbase) the language file, in which the + translated terms are stored, is named locallang.xml. + 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.xml must reside in the folder + Resources/Private/Language/. To localize the above + terms we create the locallang.xml file the following + way: + + TODO: insert code here + + The framework of the file includes the XML header, the root element + of the XML file (<T3locallang>) and the description + (<description>). The translations of the strings are in + the <data type="array"> section. For every language + there is a section <languageKey + index="[language]" type="array">. For every + identifier is defined how it should be given out in a particular language + in this section. In the above file the texts are stored in two languages: + default, which equals to English and + de for German. + + + The TYPO3 Core API describes in detail the construction of the + locallang.xml file + (http://typo3.org/documentation/document-library/core-documentation/doc_core_api/4.2.0/view/7/2/). + + + 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')} + + + 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: + + TODO: insert code here + + + Sometimes you have to localize a string in the PHP code, for + example in the controller or inside of a ViewHelper. In that case you + can use the static method + Tx_Extbase_Utility_Localization::translate($key, + $extensionName). In addition to the key inside the + locallang file also the name of the extension must be given as + parameter, in order to load the correct locallang file. + + +
+ Output localized strings using <code>sprintf</code> + + 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> + + + 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. + + + + For a full reference of the formatting options for + sprintf you should have a look at the PHP documantation: + 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 translateable 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 3 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, + l18n_parent int(11) DEFAULT '0' NOT NULL, + l18n_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 file + ext_tables.php in the section ctrl of + the corresponding database table. + + $TCA['tx_blogexample_domain_model_blog'] = array ( + 'ctrl' => array ( + ... + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l18n_parent', + 'transOrigDiffSourceField' => 'l18n_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 dependending on the current + TypoScript setting in config.sys_language.uid. In the field + l18n_parent the UID of the original blog created in the + default language, which the current blog is a translation of. The third + field l18n_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. + + TODO: insert code here + + With it, the localization of the domain object is already + configured. By adding &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. + + + 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 v4 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 anoher 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? + + + In FLOW3 this is solved better. There only a "structure node" + exists to which the content element is added with its different language + parts. A default language in this spirit does not exist. + + + 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) +l18n_parent: 0 (no tranlation 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) +l18n_parent: 7 (selected in backend respectively given automaticly) + + A link that references the single view of a blog looks like + this: + + http://www.example.com/index.php?id=99&tx_blogexample_pi1[controller]=Blog&tx_blogexample_pi1[action]=show&tx_blogexample_pi1[blog]=7 + + By adding &L=1 we referencing now the German + version: + + http://www.example.com/index.php?id=99&tx_blogexample_pi1[controller]=Blog&tx_blogexample_pi1[action]=show&tx_blogexample_pi1[blog]=7&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 persistance. 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=0L=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 shownThe 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. + TODO: combine the columns 2 and 3 +
Creation (new, create)Independent of the selected frontend language the domain + object is first marked valid for all languages. The data is stored + in a new record in whose field sys_language_uid the + number -1 is inserted. + TODO: combine the columns 2 and 3 +
+ + 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 funtion 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 characterDescriptionExample
dDay of the month as number, double-digit, with leading + zero01 ... 31
mMonth as number, with leading zero01 ... 12
YYear as number, with 4 digits2011
yYear as number, with 2 digits11
HHour in 24 hour format00 ... 23
iMinutes, with leading zero00 ... 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". + + + 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. +
+
diff --git a/9-crosscutting-concerns/2-validating-domain-objects.xml b/9-crosscutting-concerns/2-validating-domain-objects.xml new file mode 100644 index 00000000..57da7e7b --- /dev/null +++ b/9-crosscutting-concerns/2-validating-domain-objects.xml @@ -0,0 +1,731 @@ + +
+ 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 automaticly, so it is rather important to define these + rules explicitly. In the blog example for example we can make the folowing + 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. If + the invariant is fulfilled than the validator returns true + otherwise false. In Extbase all validators have to implement + the interface + Tx_Extbase_Validation_Validator_ValidatorInterface. + In this interface some are methods defined. The most important is called + isValid($object). An object or value is passed over to it and + it must return true when the object or value is valid, + otherwise it returns false. There are some more methods in + the ValidatorInterface to make it possible to pass + settings and poll error messages. We recommend to inherit all validators + from the + Tx_Extbase_Validation_Validator_ValidatorInterface, + because you get a default implemetation of the helper methods and you only + have to implement the IsValid() method. + + + 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: + + TODO: insert code here + + When $value is a string that compares to a (complex) + regular expression, the validator returns true. Otherwise an + error message is generated using addError() and then it + returns false. + + + 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. + + + In the package + Tx_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 at what happens before the action is called. + + TODO: insert figure here + + 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 descibed 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. + + + 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 FLOW3, 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. + + + You may ask how the errorAction() knows + which form was the last displayed one. This information is created by + the form ViewHelper. He adds automaticly 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 valiator. 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 validaton the @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: + + class Tx_BlogExample_Domain_Model_Post extends Tx_Extbase_DomainObject_AbstractEntity { + /** + * @var string + * @validate StringLength(minimum=3, maximum=50) + */ + protected $title; + + /** + * @var string + */ + protected $content; + ... +} + + With the line @validate StringLength(minimum=3, + maximum=50) the validator for the property $title is + specified. In paranthesis the parameter for the validator are specified. + In our case we make shure 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 + Tx_Extbase_Validation_Validator_ValidatorNameValidator. + Using the above given annotation @validate StringLength the + validator + Tx_Extbase_Validation_Validator_StringLengthValidator + is used. + + When you have created your own validator to check the invariants + you can use it in the @validate annotation using the full + class name, like shown in the following example: + + class Tx_BlogExample_Domain_Model_Post extends Tx_Extbase_DomainObject_AbstractEntity { + /** + * @var string + * @validate Tx_BlogExample_Domain_Validator_TitleValidator + */ + protected $title; + + /** + * @var string + */ + protected $content; + ... +}Here we validate the property $title with the + Tx_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: + + TODO: insert code here + + 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 sepecially 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 a 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 + Tx_ExtbaseExample_Domain_Model_User it must be + implemented in the class + Tx_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 return false in such + case: + + class Tx_ExtbaseExample_Domain_Validator_UserValidator extends Tx_Extbase_Validation_Validator_AbstractValidator { + public function isValid($user) { + if (! $user instanceof Tx_ExtbaseExample_Domain_Model_User) { + $this->addError('The given Object is not a User.', 1262341470); + return FALSE; + } + return TRUE; + } +} + + 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 but returns + false. + + + 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: + + class Tx_ExtbaseExample_Domain_Validator_UserValidator extends Tx_Extbase_Validation_Validator_AbstractValidator { + public function isValid($user) { + if (! $user instanceof Tx_ExtbaseExample_Domain_Model_User) { + $this->addError('The given Object is not a User.', 1262341470); + return FALSE; + } + if ($user->getPassword() !== $user->getPasswordConfirmation()) { + $this->addError('The passwords do not match.', 1262341707); + return FALSE; + } + return TRUE; + } +}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 + @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 @validate + annotation can be used which is set in the comment block of the + controller action. It has the format @validate + [variablename] [validators], in the example + below it is $pageName + Tx_MyExtension_Domain_Validator_PagenameValidator: + + /** + * Creates a new page with a given name. + * + * @param string $pageName THe name of the page which should be created. + * @validate $pageName Tx_MyExtension_Domain_Validator_PageNameValidator + */ +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 @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 + @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 Tx_ExtbaseExample_Domain_Model_User $user The user which should be created. + * @validate $user Tx_BlogExample_Domain_Validator_CustomUserValidator + */ +public function createUserAction($pageName, Tx_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 + @validate annotations of the model are validated. Also the + Tx_BlogExample_Domain_Validator_UserValidator is called if + it exists. Beyond that the validator + Tx_BlogExample_Domain_Validator_CustomUserValidator is used + to validate $user. + + In some use cases it is reasonable that inconsistent + domain objects are gives 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 + @dontvalidate $parameter. This + prevents the processing of the @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. + + + 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(Tx_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((Tx_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 @dontvalidate - the comment block of the + editAction must be changed like this: + + /** + * @param Tx_BlogExample_Domain_Model_Blog $blog The blog object + * @dontvalidate $blog + */ +public function editAction(Tx_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. + + + If Extbase thows the exception + Tx_Extbase_MVC_Exception_InfiniteLoop it signs that the + @dontvalidate 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: + + /** + * This action shows the 'new' form for the blog. + * + * @param Tx_BlogExample_Domain_Model_Blog $newBlog The optional default values + * @dontvalidate $newBlog + */ +public function newAction(Tx_BlogExample_Domain_Model_Blog $newBlog = NULL) { + $this->view->assign('newBlog', $newBlog); +} + +/** + * This action creates the blog and stores it. + * + * @param Tx_BlogExample_Domain_Model_Blog $newBlog + */ +public function createAction(Tx_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 + @dontvalidate. 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. + + TODO: insert figure 9-2 here + + 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. + + + 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. !!!Sentence not + clear + + TODO: insert figure 9-3 + + 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. + + + Copies of objects + + Why a copy of an object is created when it is to be changed? Lets + have assume that the persistent object would be directly changed. In + this case an empty controller would be updating persistent + objects: + + public function updateAction(Tx_BlogExample_Domain_Model_Blog $blog) { + // object will be automaticly persisted +} + + At first this is very in transparent and difficult to understand. + Besides of that, this procedure implies a big safety issue: When the + original object is changed it would be impossible to cancel the + persisting of the changes. For this reason a copy of the object is + returned for changed objects, so the developer of the extension has to + decide explicit whether or not the changes are to be made + persistent. + + + 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. +
+
diff --git a/9-crosscutting-concerns/3-programming-secure-extensions.xml b/9-crosscutting-concerns/3-programming-secure-extensions.xml new file mode 100644 index 00000000..1e7aa365 --- /dev/null +++ b/9-crosscutting-concerns/3-programming-secure-extensions.xml @@ -0,0 +1,202 @@ + +
+ 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 coocies should be classified as malicious, because they can be + manipuated 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 databaase 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(). + + + 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 used by extbase and fluid + to increase the safeness 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. +
+ +
+ Request hashes (HMAC) + + 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: + + array( + __idetity => ... + 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 happend 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 so called Request + Hash. This is a check field to secure the transmission of the + data to the server. The request hash consist of the names of all form + fields that are allowed to transferred to the server at maximum. + Additional these information is signed with the TYPO3 encryption key, so + the client can't manipulate the request hash undetected. The request hash + is stored in the form in a hidden field with the name + __hmac. + + 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 via the request hash and the request is stopped with an + exception. + + + If you write an API with extbase to change data with other + webservices you have to disable the request hash, bacause without the + knowledge of the private key of the other server you can not generate a + valid request hash. + + The checking of the request hash can be deactivated with the + following annotation: @dontverifyrequesthash + + + In general the request hash should be 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 technics to secure web applications + per default. One of the importand 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 prevetions, a JavaScript popup "XSS". The + attacke 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 worsest case he can get + adminitrator priviledges. + + How can we prevent this now? The forum post don't have to put out + unchanged - before we have to mask out all special charaters with a call + of htmlspecialchars(). With this instead of + <script>..</script> the safe result is delivered + to the browser: + &lt;script&gt;...&lt;/script&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 occures in a + template is automaicly 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 caracters 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 becaus 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. +
+
diff --git a/9-crosscutting-concerns/4-conclusion.xml b/9-crosscutting-concerns/4-conclusion.xml new file mode 100644 index 00000000..3bc96adb --- /dev/null +++ b/9-crosscutting-concerns/4-conclusion.xml @@ -0,0 +1,26 @@ + +
+ Conclusion + + In this chapter we have considered tasks that touch several layer of + the MVC achitecture. 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 thedisplay 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. + Importand by this is tha you have understand what are request hashes and how + to prevent against cross site scripting. + + Now in the progrss of the book you have get to know Extbase and Fluid + intensively. We should not get out of view that these technologies also are + bridges to FLOW3 and TYPO3 version 5. The following chapter contains an + outlook to FLOW3 and the ongoing development. +
diff --git a/9-crosscutting-concerns/index.xml b/9-crosscutting-concerns/index.xml new file mode 100644 index 00000000..26f04396 --- /dev/null +++ b/9-crosscutting-concerns/index.xml @@ -0,0 +1,29 @@ + + + Internationalization, Validation and Security + + In the previous chapters we have examined all levels of the MVC + pattern. Thereby we have cosidered every level seperately. 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 programm 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. + + + + + + + + + diff --git a/a-coding_guidelines/1-foo.xml b/a-coding_guidelines/1-foo.xml new file mode 100644 index 00000000..0c9e359e --- /dev/null +++ b/a-coding_guidelines/1-foo.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
diff --git a/a-coding_guidelines/2-bar.xml b/a-coding_guidelines/2-bar.xml new file mode 100644 index 00000000..73791ced --- /dev/null +++ b/a-coding_guidelines/2-bar.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
\ No newline at end of file diff --git a/a-coding_guidelines/index.xml b/a-coding_guidelines/index.xml new file mode 100644 index 00000000..4db865e8 --- /dev/null +++ b/a-coding_guidelines/index.xml @@ -0,0 +1,16 @@ + + + Coding Guidelines + + Blah Blah Blah Blah + + + + + diff --git a/b-extbase_reference/1-foo.xml b/b-extbase_reference/1-foo.xml new file mode 100644 index 00000000..0c9e359e --- /dev/null +++ b/b-extbase_reference/1-foo.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
diff --git a/b-extbase_reference/2-bar.xml b/b-extbase_reference/2-bar.xml new file mode 100644 index 00000000..73791ced --- /dev/null +++ b/b-extbase_reference/2-bar.xml @@ -0,0 +1,12 @@ + +
+ Section foo + + Blah Blah Blah Blah +
\ No newline at end of file diff --git a/b-extbase_reference/index.xml b/b-extbase_reference/index.xml new file mode 100644 index 00000000..d446281a --- /dev/null +++ b/b-extbase_reference/index.xml @@ -0,0 +1,16 @@ + + + Extbase Reference + + Blah Blah Blah Blah + + + + + diff --git a/glossary.txt b/glossary.txt new file mode 100644 index 00000000..501a2fca --- /dev/null +++ b/glossary.txt @@ -0,0 +1,72 @@ +FORMATTING +========== + +ViewHelpers: + * ViewHelpers in continuous text should be formatted like that: + ... the cObject ViewHelper + ... there exists a ViewHelper format.date + +Code Snippets: + * Code Snippets are indented with a single TAB character + +GLOSSARY +======== + +The glossary should provide information on how certain german words should be translated. Please update the glossary if you come over any special words. + +German = English + +Ablaufmuster = schedule model +Action = Action + Eine Methode als Teil eines Controllers +Aggregate = Aggregate + im Sinne von Domain-Driven Design +Allgegenwärtige Sprache = Ubiquitous Language + Feststehender Begriff in Domain Driven Design +Anmerkung = Annotation +Annotation = Ein konfigurations-relevanter "Doc Comment", z.B. @scope +Ausnahme = Exception +Controller = Controller +Domänenmodell = Domain Model + Variante eines Models das dem Domain-Driven Design Prinzipien entspricht +Domain-Driven Design (DDD) = Domain-Driven Design + Programmier-Paradigma zur Modellierung von Domänen mit Fokus auf die Geschäftslogik +Entität = Entity + Individuelles Objekt nach DDD +Entwurfsmuster = Design Pattern + Formale Beschreibung einer Lösung für ein gängiges Design-Problem +Exception = Exception + Eine Ausnahmesituation, technisch umgesetzt durch PHPs Exception Handler +Exception werfen = ... throw Exception ... +Fehler = Error +Fehlerbehandlung = Error Handling +Fabrik = Factory + spezielles Design Pattern +Interface = Interface + Im Sinne von PHP "interface" +Schnittstelle = Interface +Lazy Loading = Lazy Loading +Modell = Model + Komponente aus dem MVC Pattern +Namensraum = Namespace + Namentliche Abgrenzung von Klassen, Interfaces aber auch Parametern. +Paket = Package + Ein FLOW3 Paket +Persistenz = Persistence +Prototyp = Prototype + (Design Pattern) +Reflection = Reflection +Repository = Repository + Teil des DDD, allgemeines Konstrukt zur Aufbewahrung von Entities +Anfrage = Request + Anfrage an das Framework z.B. über einen Web Browser oder die Kommandozeile +Request Handler +Service = Service + im Sinne von Domain-Driven Design +Singleton (Design Pattern) +Type Hint + Klassen- oder Interfacename vor einem Methoden- oder Funktionsargument. foo(TypeHint $bar) +Value Object (VO) + ein Design Pattern, verwendet in DDD +View +View Helper diff --git a/index.xml b/index.xml new file mode 100644 index 00000000..39c8cd47 --- /dev/null +++ b/index.xml @@ -0,0 +1,59 @@ + + + Developing TYPO3 Extensions with Extbase and Fluid + + + + + + TYPO3 is a free enterprise web content management system licensed + under the GPL. + + + + + Sebastian + Kurfürst + + sebastian@typo3.org + + + + + Jochen + Rau + + jochen.rau@typoplanet.de + + + + + 2010 + Sebastian Kurfürst + Jochen Rau + + + + + + + + + + + + + + + + + + + +