.. You may want to use the usual include line. Uncomment and adjust the path. .. include:: ../Includes.txt ===================== EXT: eim2mvc tutorial ===================== :Created: 2005-10-14T16:44:07 :Changed: 2005-10-18T17:29:43 :Classification: web application modelling :Keywords: mvc pattern modelling framework :Info 1: achim@die3.net :Info 2: Achim Eichhorn :Info 3: :Info 4: .. _EXT-eim2mvc-tutorial: EXT: eim2mvc tutorial ===================== Extension Key: **eim2mvc** Copyright 2000-2005, Achim Eichhorn, This document is published under the Open Content License available from http://www.opencontent.org/opl.shtml The content of this document is related to TYPO3 \- a GNU/GPL CMS/Framework available from www.typo3.com .. _Table-of-Contents: Table of Contents ----------------- **EXT: eim2mvc tutorial 1** **Introduction 2** What does it do? 2 Screenshots 2 **Users manual 2** **Tutorial 3** Standalone guestbook 3 Views 3 Actions 4 Datasources 5 **Connecting to typo3 6** copying the example application 9 Adapting the views 10 Adapting the datasources 12 Sources of the adapted files 14 **Known problems 16** **To-Do list 16** **Changelog 16** .. _Introduction: Introduction ------------ .. _What-does-it-do: What does it do? ^^^^^^^^^^^^^^^^ Eim2mvc is a library for model view controller like programming within typo3. Model View Controller (mvc) is a pattern developed for gui applications. It divides the data model part from the representational views and from the application logic controlling the whole application. This library is a framework which nearly follows this pattern, but not at all. The data model here consists of database tables, files, server variables,.. not only of the underlying data model of the business application itself. The view part follows the mvc view part. View does not mean, that data has to be apart from design, in the examples coming with this manual, we will see some really ugly views, which don't divide design and content. But YOU naturally can divide it, if you like (and you should, if you want to produce good readable programs). The controller part is done by a front controller, which takes all user input and an action chain, which does the business processing. In this action chain you can register actions, and actions themselves can register other actions, too. The actions also register views in the viewchain, which will then be rendered in a fifo order and the ontent of the last view will be given back. In this way, you can start with an xml representation of database content at view 1 and then use several xsl transformation in the other views to perhaps produce pdf... Maybe this framework is a little bit “heavy” for small extensions, but I think it can be useful for big extensions and it can improve the maintainability and the re usability of you applications. It is a little bit like struts in the java world and mojavi in the php world. But it does not provide any session handling, or user tracking because this is provided by typo3 itself. Other things like validators or input data filters can be implemented by using the action mechanism. This framework is far from being perfect, I hope to start an discussion, if something like this action processing is necessary for typo3 extension development, and if so, if it can be implemented somewhere more “near” the core. If you have a look in the source code, you will find a folder tests, this tests are running within eclipse with “simpletest”. Test driven development is a really great thing, have a look at it. The framework itself can be used without typo3 as well. In the folder example is a implementation of a (very poor) guestbook. In the tutorial section of this document I explain how this guestbook was implemented, there you see, how the framework itself works. In the second part of the tutorial I will show you how you then could integrate this guestbook in typo3 without too much effort. Have fun and give me feedback! .. _Screenshots: Screenshots ^^^^^^^^^^^ We don't need them here. .. _Users-manual: Users manual ------------ This extension is a library for programmers. You have to download it from TER and install it. You can use it in any extension you like. Just include it with require\_once (t3lib\_extMgm :: extPath('eim2mvc').'mvc/classes/eim2Typo3FrontController.php');You create the front controller within an extension's main method by:$application = new eim2Typo3FrontController($this,'',$aPath,$vPath,$sPath);where aPath is the path to the actions, vPath the path to the views and sPath the path to the data sources.The second parameter which is in the example above an empty String '' is called initialContent. If you perhaps want to start your rendering with some template code or some inital xml code or whatever, you can do it this way.You get the rendered content by calling:$content = $application->content;In the following tutorial section you will see, how all the things work together... .. _Tutorial: Tutorial -------- .. _Standalone-guestbook: Standalone guestbook ^^^^^^^^^^^^^^^^^^^^ If you have a look in the example folder you find the following directory structure: \- actions- sources- viewsindex.php .. _Views: Views ^^^^^ This application is a really simple guest book, it consists of a listing of all entries in the guest book which holds a link to a page with a form for a new entry. In mvc we would say, we have 2 views, one for displaying the list, and another for displaying the form. If you have a look in the views folder, you will find GBDefaultView.php (which shows the list by default) and GBFormView.php, which shows the form. In the template wou will find two really poor html template which are used for some rudimentary division of content from design. But You could do this in a better way perhaps by using a template enginge like smarty or the typo3 template mechanisms... If you have a look in the soucecode of the two views, you see, that they both have two methods, a constructor, which registers the application context by calling the parent's construtor and a method “render”, which gets content as a parameter and which produces the output of the view. All views follow this concept. How does the application know, which view to render, and which data should be used for rendering? .. _Actions: Actions ^^^^^^^ Now it is time to have a look into the actions -folder:There are two actions, the IndexAction.php and the GBNewAction.php. The IndexAction.php plays a special role in this game. The programming logics always searches IndexAction.php in the action folder, and if it is not found, the progam execution will stop with an error message. IndexAction.php looks like this: :: class IndexAction extends eim2Action{ function IndexAction(&$context){ parent::eim2Action($context); } function execute(){ //Get the requested action $action = $this->context->dataModel->getData('GBRequest','action'); if($action == 'IndexAction'){ $action = ''; } //try to register it if($this->registerAction($action)==false){ //if registration fails, use default view $this->registerView('GBDefaultView'); } //proceed with registered action, or with DefaultView... } } It consists (like all actions) of two methods, the constructor, which does the same like the constructor of the views, and the method “execute”. Execute does the processing and registers other actions and/or views.In the application above, it checks, if there was a request parameter 'action'. Then it try's to register this action. If the action can not be registered (because it does not exist,...) it switches to default view. (It also checks, if someone played with the GP Params and tried to cause an infinite loop by registering 'IndexAction' with &action=IndexAction in the url) If the action parameter was “GBNewAction”, it would have registered GBNewAction in the action chain. The logic would create an instance of GBNewAction and then it would call gb\_new\_instance->execute().Let's have a look in GBNewAction.php: :: class GBNewAction extends eim2Action{ function GBNewAction(&$context){ parent::eim2Action($context); } function execute(){ //Get the requested action $formsent = $this->context->dataModel->getData('GBRequest','formsent'); if ($formsent == 'formsent'){ $newdata = array(); $newdata['name'] = $this->context->dataModel->getData('GBRequest','name'); $newdata['mail'] = $this->context->dataModel->getData('GBRequest','mail'); $newdata['message'] = $this->context->dataModel->getData('GBRequest','message'); $newdata['date'] = date("j.n.Y"); //check the data if (($newdata['name'] != '')&&($newdata['message']!= '')){ //everything ok => update the model and display the list $this->context->dataModel->newData('GBGuestbook',$newdata); $this->registerView('GBDefaultView'); return; } //something is missing => display a message $this->context->dataModel->newData('GBSysMessage','you must enter your name and leave a message'); } //display the form $this->registerView('GBFormView'); } } If the user filled out the form and pressed submit, the variable formsent is in GBRequest. Then the single parameters are checked. It has to be entered a name and a message, without this information we display an error message ('you must enter your name and leave a message') and we register the form view again, because, the user has to enter all data.Are both provided, we update the model $this->context->dataModel->newData('GBGuestbook',$newdata); and then register the default view, for displaying the updated guest book. .. _Datasources: Datasources ^^^^^^^^^^^ Data sources have the following methods: A constructor for registering the context, and a newData, unsetData, setData and getData method.Have a look in the sources folder. There are four data sources. GBGuestbook.php contains the logic for the guest book database (which consists of a file holding the serialized information of a php array, not really a good solution...), GBRequest for get and post paramters, GBServer for server variables and GBSysmessage for accessing a string, which holds system messages.Just play around with the guestbook's frontend, and then have a look at the code in the backend. Perhaps you would want to enter some echo- debugging stuff, to get a feeling, how all works together?Remember, this is a standalone application, you have to call it by addressing it directly on you webserver, http://www.yourdomain.de/typo3conf/ext/eim2mvc/example/index.php .. _Connecting-to-typo3: Connecting to typo3 ------------------- After all of this, it is time to see, how we could bring all of this to typo3. First of all ensure, that you have installed the eim2mvc application. Then go the extension kickstarter in typo3 backend and there to “make new extension”.We want to have a new extension, and we call it “versimpleguestbook”. Which is exactly what we are implementing... Then go to database tables and enter the followning: |img-1| And for the fields: |img-2| |img-3| |img-4| |img-5| Then we need a front end plugin for displaying and adding entries: |img-6| And perhaps we want to have a back end module for maintaining the entries, if someone leaves messages we don't like... |img-7| |img-8| Press View result and the WRITE for “write lo location”.After that don't forget to install this extension! Now we have a clean new extension, and if you insert this plugin on a page and then visit this page in the frontend, you will see something like this: |img-9| .. _copying-the-example-application: copying the example application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The extension is in your typo3 installation in typo3conf/extension/verysimpleguestbook.Go to this folder, and then copy the actions, sources and views folders from the eim2mvc examples folder into this folder: |img-10| OK, what do you think, will the guest book work now? Naturally not, you would answer, I am sure. You are right, we first have to “connect” this actions, views and sources with typo3.We do this by copying the code from the index.php file of the eim2mvc examples folder to the main method of the verysimpleguestbook class. Then we have to import the FrontController class from the eim2mvc framework, and then we should have a look, what happens... |img-11| Have a look at line 32, there the FrontController class is imported.Lines 48 to 55 are nearly the same, as in the index.php of the standalone application.If you switch now to the frontend, you see: It works! But does it really work in the expected way? If you try to switch to the form you get a first hint, that there is something wrong...The problem is, that our views don't provide the urls in the typo3 typical way, with page id... The best way to do this, is to use typo3's methods for creating urls. But for this our views need a reference to the extension object. This is the right time to introduce a new class: eim2Typo3FrontController This is exactly the same as the front controller above, but it takes an additional parameter which holds a reference to our typo3 extension. Modify your code this way: In Line 32 import eim2Typo3FrontController.phpIn line 53 write $application = new eim2Typo3FrontController($this,'',$aPath,$vPath,$sPath); .. _Adapting-the-views: Adapting the views ^^^^^^^^^^^^^^^^^^ Now we have to adapt the views, so that they render urls in a more typo3 like way:Open the file sources/templates/default.htmlAdapt the line with the tag, so that it looks like this:new entryAnd now we have to change DefaultView.php.It looks like this: |img-12| The lines 14 to 18 should be replaced by the following: |img-13| This way we create a typo3 conform url. We now have to do the same for the GBFormView.php |img-14| The html template is already correct. We only have to do some changes in GBFormView: Replace the lines for creating the url by the same statement wee used for the DefaultView. The srt\_replace for {$URL} has to be adapted, too. Now you can start the application in the frontend again. And – now it works.But what about the database table we created with the kickstarter? That's a good point. Can you guess, where we have to do those changes? Yep, in the datasource section. We change the file GBGuestbook.php for working with a database table instead of the “serialized array file” we used before. .. _Adapting-the-datasources: Adapting the datasources ^^^^^^^^^^^^^^^^^^^^^^^^ Open the file “sources/GBGuestbook.php”. |img-15| The data is written to a file. We now change this, so that our database table is used. You can see in the code above, that I used the constructor to read in all data from the file. In the version with the database table I do the same, I start a sql query within the constructor. Always consider, that the instances are created by dynamic class loading and by calling factory methods. So you should not rely on a special ordering, which instance is created when. So it can happen, that you call within the constructor an object instance, which does not exist at this special moment. Therefor it is better to put application logic of views, actions and datasources not in the constructor, but in the other methods, where they belong to. If you call a method like newData on a datasource, you can be sure, that all constructors of all the other datasources in the datamodel have been called and all instances exist. So lets have a look at the code for the GBGuestbook class: :: guestbook = array(); $this->tablename = 'tx_'.$this->context->typo3Extension->extKey.'_entries'; $this->pageid = $GLOBALS["TSFE"]->id; $query = 'SELECT name,email,message,date FROM '.$this->tablename.' WHERE pid = '.$this->pageid.' '.t3lib_pageSelect::enableFields($this->tablename); $results = array(); $res = mysql( TYPO3_db, $query ); if ( mysql_error() ){ debug( array( mysql_error(), $query ) ); return $results; } while ( $row = mysql_fetch_assoc( $res ) ){ $this->guestbook[] = $row; } } function getData($key) { if ($key == 'ALL'){ return $this->guestbook; } $element = array(); if (isset($this->guestbook[$key])){ $element = $guestbook[$key]; } return $element; } function newData($data){ array_push($this->guestbook,$data); $query = $this->context->typo3Extension->cObj->DBgetInsert($this->tablename, $this->pageid, $data, implode(',',array_keys($data))); mysql( TYPO3_db, $query ); if ( mysql_error() ) { debug( array( mysql_error(), $query ) ); } } } ?> newData pushs the new entry on the guestbook array in memory and then writes it to the database. GetData only reads the guestbook array from memory and gives back the demanded data. (we only need the 'ALL' Option)The data itself is queried in the constructor which is like described above, no real good style and can perhaps cause errors). If you now change to the typo3 frontend you can start filling your guestbook with messages. We didn't touch the actions because the application logic itself is the same like in the standalone application. We only had to change the views and one datasource. That's better than implementing it completely new! I hope the concepts I introduced with this framework are clear now, so that you can use it to perhaps implement the backend module for maintaining the guestbook as an exercise by yourself :-) .. _Sources-of-the-adapted-files: Sources of the adapted files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Class.tx\_verysimpleguestbook\_pi1.php :: require_once(PATH_tslib.'class.tslib_pibase.php'); require_once (t3lib_extMgm :: extPath('eim2mvc').'mvc/classes/eim2Typo3FrontController.php'); class tx_verysimpleguestbook_pi1 extends tslib_pibase { var $prefixId = 'tx_verysimpleguestbook_pi1'; // Same as class name var $scriptRelPath = 'pi1/class.tx_verysimpleguestbook_pi1.php'; // Path to this script relative to the extension dir. var $extKey = 'verysimpleguestbook'; // The extension key. /** * [Put your description here] */ function main($content,$conf) { $this->conf=$conf; $this->pi_setPiVarDefaults(); $this->pi_loadLL(); $this->pi_USER_INT_obj=1; // Configuring so caching is not expected. This value means that no cHash params are ever set. We do this, because it's a USER_INT object! $actualPath = t3lib_extMgm :: extPath('verysimpleguestbook').'pi1/'; $aPath = $actualPath.'/actions'; $vPath = $actualPath.'/views'; $sPath = $actualPath.'/sources'; $application = new eim2Typo3FrontController($this,'',$aPath,$vPath,$sPath); $application->execute(); $content = $application->content; return $this->pi_wrapInBaseClass($content); } } if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/verysimpleguestbook/pi1/class.tx_verysimpleguestbook_pi1.php']) { include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/verysimpleguestbook/pi1/class.tx_verysimpleguestbook_pi1.php']); } views/template/default.html :: Guestbook

Guestbook

new entry

{$GUESTBOOK} views/GBFormView.php :: class GBFormView extends eim2View{ function GBFormView(&$context){ parent::eim2View($context); } function render($content){ $dataModel = & $this->context->dataModel; $url = $this->context->typo3Extension->cObj->currentPageUrl(array('action'=>'GBNewAction')); $name = $dataModel->getData('GBRequest','name'); $email = $dataModel->getData('GBRequest','email'); $message = $dataModel->getData('GBRequest','message'); $sysmessage = $dataModel->getData('GBSysMessage',''); $templateFile = dirname(__FILE__).'/templates/form.html'; $content = implode('',file($templateFile)); $content= str_replace('{$URL}',$url,$content); $content= str_replace('{$sysmessage}',$sysmessage,$content); $content= str_replace('{$name}',$name,$content); $content= str_replace('{$message}',$message,$content); $content= str_replace('{$email}',$email,$content); return $content; } } views/GBDefaultView.php :: class GBDefaultView extends eim2View{ function GBDefaultView(&$context){ parent::eim2View($context); } function render($content){ $templateFile = dirname(__FILE__).'/templates/default.html'; $template = implode('',file($templateFile)); $data = $this->context->dataModel->getData('GBGuestbook','ALL'); //t3lib_div::debug($this->context); $url = $this->context->typo3Extension->cObj->currentPageUrl(array('action'=>'GBNewAction')); $content = str_replace('{$URL}',$url,$template); $entries = ''; for($i=0;$i'; $entries.= ''.$message.''; $entries.= ''.$email.''; $entries.= '  '.$date.''; $entries.= '
'; } $content = str_replace('{$GUESTBOOK}',$entries,$content); return $content; } } sources/GBGuestbook.php :: class GBGuestbook extends eim2DataSource{ var $guestbook; var $tablename; var $pageid; function GBGuestbook(&$context){ parent::eim2DataSource($context); $this->guestbook = array(); $this->tablename = 'tx_'.$this->context->typo3Extension->extKey.'_entries'; $this->pageid = $GLOBALS["TSFE"]->id; $query = 'SELECT name,email,message,date FROM '.$this->tablename.' WHERE pid = '.$this->pageid.' '.t3lib_pageSelect::enableFields($this->tablename); $results = array(); $res = mysql( TYPO3_db, $query ); if ( mysql_error() ){ debug( array( mysql_error(), $query ) ); return $results; } while ( $row = mysql_fetch_assoc( $res ) ){ $this->guestbook[] = $row; } } function getData($key) { if ($key == 'ALL'){ return $this->guestbook; } $element = array(); if (isset($this->guestbook[$key])){ $element = $guestbook[$key]; } return $element; } function newData($data){ array_push($this->guestbook,$data); $query = $this->context->typo3Extension->cObj->DBgetInsert($this->tablename, $this->pageid, $data, implode(',',array_keys($data))); mysql( TYPO3_db, $query ); if ( mysql_error() ) { debug( array( mysql_error(), $query ) ); } } } .. _Known-problems: Known problems -------------- .. _To-Do-list: To-Do list ---------- .. _Changelog: Changelog --------- |img-16| EXT: eim2mvc tutorial - 16 .. ######CUTTER_MARK_IMAGES###### .. |img-1| image:: img-1.png .. :align: left .. :border: 0 .. :height: 210 .. :id: Grafik1 .. :name: Grafik1 .. :width: 522 .. |img-2| image:: img-2.png .. :align: left .. :border: 0 .. :height: 135 .. :id: Grafik2 .. :name: Grafik2 .. :width: 544 .. |img-3| image:: img-3.png .. :align: left .. :border: 0 .. :height: 100 .. :id: Grafik3 .. :name: Grafik3 .. :width: 532 .. |img-4| image:: img-4.png .. :align: left .. :border: 0 .. :height: 96 .. :id: Grafik4 .. :name: Grafik4 .. :width: 528 .. |img-5| image:: img-5.png .. :align: left .. :border: 0 .. :height: 140 .. :id: Grafik5 .. :name: Grafik5 .. :width: 518 .. |img-6| image:: img-6.png .. :align: left .. :border: 0 .. :height: 270 .. :id: Grafik6 .. :name: Grafik6 .. :width: 556 .. |img-7| image:: img-7.png .. :align: left .. :border: 0 .. :height: 614 .. :id: Grafik7 .. :name: Grafik7 .. :width: 633 .. |img-8| image:: img-8.png .. :align: left .. :border: 0 .. :height: 609 .. :id: Grafik8 .. :name: Grafik8 .. :width: 635 .. |img-9| image:: img-9.png .. :align: left .. :border: 0 .. :height: 286 .. :id: Grafik9 .. :name: Grafik9 .. :width: 447 .. |img-10| image:: img-10.png .. :align: left .. :border: 0 .. :height: 130 .. :id: Grafik10 .. :name: Grafik10 .. :width: 304 .. |img-11| image:: img-11.png .. :align: left .. :border: 0 .. :height: 486 .. :id: Grafik11 .. :name: Grafik11 .. :width: 669 .. |img-12| image:: img-12.png .. :align: left .. :border: 0 .. :height: 323 .. :id: Grafik12 .. :name: Grafik12 .. :width: 669 .. |img-13| image:: img-13.png .. :align: left .. :border: 0 .. :height: 54 .. :id: Grafik13 .. :name: Grafik13 .. :width: 669 .. |img-14| image:: img-14.png .. :align: left .. :border: 0 .. :height: 449 .. :id: Grafik14 .. :name: Grafik14 .. :width: 653 .. |img-15| image:: img-15.png .. :align: left .. :border: 0 .. :height: 615 .. :id: Grafik15 .. :name: Grafik15 .. :width: 669 .. |img-16| image:: img-16.png .. :align: left .. :border: 0 .. :height: 32 .. :id: Graphic1 .. :name: Graphic1 .. :width: 102