TYPO3, Extbase und Ajax

ACHTUNG: Die Informationen und der Code auf dieser Seite werden immer älter, während die Entwicklung weiter geht ...

Der aktuelle Code des AJAX Dispatchers befindet sich in pt_extbase, zu finden im TER oder auf github.

Extbase und Fluid scheinen sich mehr und mehr zum Standard in der TYPO3 Extension Entwicklung zu etablieren und das ist gut so, bieten sie doch – von ein paar kleineren Bugs abgesehen – eine solide Basis zur Entwicklung von modernen TYPO3 Erweiterungen.

Sauberer MVC-getriebener PHP-Code ist aber in vielen aktuellen Extensions nur noch die halbe Miete. Die Andere besteht aus einem dynamischen Frontend im Browser, welchem mit Hilfe von Javascript desktopähnliches Feeling beigebracht wird. Ein entscheidendes Bauteil für ein dynamisches Frontend sind asynchrone Serveraufrufe. Doch das nachladen kleiner Datenfetzen fällt mit Extbase noch nicht aus der Schachtel. Daher möchte ich zwei Möglichkeiten vorstellen, wie Extbase-AJAX-Aufrufe in Frontend und Backend realisiert werden können:

Möglichkeit 1 - Eigener Seitentyp für AJAX Aufrufe 

Für AJAX-Calls wird ein eigener Seitentyp in Typoscript definiert, welcher nur die Ausgabe eines bestimmten Plugins zurückgibt.

  • Vorteil: Nur die schlanke Pagedefinition und das gewünschte Plugin wird ausgegeben.
  • Nachteile: Für jedes Plugin mus ein eigener Pagetype definiert werden. Funktioniert nur im Frontend.

Möglichkeit 2 - Extbase Dispatcher für eId / ajax.php

Mit EId im Frontend und ajax.php im Backend bietet TYPO3 dedizierte Möglichkeiten um Prozeduren ausserhalb von Templates und Content-Elementen auszuführen. Genau das wollen wir mit einem AJAX-Call auch tun.

  • Vorteile: eID / ajax.php bootstrappen nur einen Teil der TYPO3 Umgebung und sind daher leichtgewichtiger als ein Aufruf der index.php. Mit ajax.php ist das Verfahren auch im Backend möglich. Die Registrierung des Dispatchers muss für alle Extensions nur einmal erfolgen. Keine separate Typoscript Definition nötig.
  • Nachteil: PHP Klasse für den Dispatcher nötig.

Die TYPO3 Ausgabe besteht im Regelfall aus dem Template, diversem HeaderCode und den einzelnen Contentelementen einer Seite. Zur Ausgabe des Asynchronen Aufrufs muss also ein eigener PageTyp definiert werden, welcher die Antwort auf die Rückgabe eines einzelnen Plugins beschränkt.

Dazu definieren wir uns im ersten Block zunächst einen Prototypen für AJAX Aufrufe und deaktivieren darin jegliche Header und weitere zusätzliche Ausgaben von TYPO3. Die Seite wird in Zeile 12 zusätzlich auf nicht gecached gesetzt.

Im zweiten Block wird dann von diesem Prototyp geerbt und die Konfiguration für die spezifische Extension (myextension) und das Pkugin (myplugin) vorgenommen. Die TypNummer (typenum) der Seite ist dabei beliebig – muss aber im Geltungsbereich des Typoscripts eindeutig sein. Hier ist beispielsweise der aktuelle Timestamp verwendbar.

#
# AJAX Page Prototype
#
lib.AJAXPrototype= PAGE
lib.AJAXPrototype {
  typeNum = 896571
  config {
    disableAllHeaderCode = 1
    xhtml_cleaning = 0
    admPanel = 0
          debug = 0
          no_cache = 1
    additionalHeaders = Content-type:application/json
  }
}


#
# Konkreter Pagetype 
#
AJAX_Plugintyp < lib.AJAXPrototype
AJAX_Plugintyp {
  typeNum = 89657201
  10 < tt_content.list.20.myextension_myplugin
}
Typoscript: Definition eines eigene PageTyps für AJAX Ausgaben

Ist der neue Pagetype für das Plugin im Typoscript definiert, kann eine beliebige, vorhanden Controller/Action-Kombination auf diesem Plugin aufgerufen werden. Im folgenden Beispiel mittels der jQuery.ajax() Methode.

$.ajax({
  url: "index.php"
  data: "tx_myextension_plugin[controller]=MyController&tx_myextension_plugin[action]=getData&type=89657201"
  success: function(result) {
    alert(result);
  }
});
Asynchroner Aufruf des speziellen Pagetyps mittels jQuery.ajax()

Mit ajax.php im Backend und dem eID-Aufruf im Frontend lassen sich vorher registrierte PHP-Methoden leichtgewichtig aufrufen. Um diese Methode auch für Extbase zu nutzen, habe ich für YAG einen Extbase Dispatcher geschrieben, welcher im folgenden Listing zu sehen ist.

Die nötigen Argumente für den Request bezieht der Dispatcher entweder aus GET / POST Parameterm oder einem JSON Array im Parameter „request“, womit sich ein Aufruf mittels ExtJS beispielsweise sehr sauber generieren lässt.

Werte, welche im Parameter "arguments" als assoziatives Array definiert werden, werden als Aufrufparameter direkt an die aufgerufene Action übergben.

<?php
/***************************************************************
* Copyright notice
*
*   2010 Daniel Lienert <daniel@lienert.cc>, Michael Knoll <mimi@kaktusteam.de>
* All rights reserved
*
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/

/**
* Utility to include defined frontend libraries as jQuery and related CSS
*
*
* @package Utility
* @author Daniel Lienert <daniel@lienert.cc>
*/

class Tx_PtExtbase_Utility_AjaxDispatcher {

	/**
	 * Array of all request Arguments
	 *
	 * @var array
	 */
	protected $requestArguments = array();



	/**
	 * Extbase Object Manager
	 * @var Tx_Extbase_Object_ObjectManager
	 */
	protected $objectManager;



	/**
	 * @var string
	 */
	protected $extensionName;



	/**
	 * @var string
	 */
	protected $pluginName;



	/**
	 * @var string
	 */
	protected $controllerName;



	/**
	 * @var string
	 */
	protected $actionName;



	/**
	 * @var array
	 */
	protected $arguments = array();



	/**
	 * @var integer
	 */
	protected $pageUid;



    /**
     * Initializes and dispatches actions
     *
     * Call this function if you want to use this dispatcher "standalone"
     */
    public function initAndDispatch() {
        $this->initCallArguments()->dispatch();
    }



    /**
     * Called by ajax.php / eID.php
     * Builds an extbase context and returns the response
     *
     * ATTENTION: You should not call this method without initializing the dispatcher. Use initAndDispatch() instead!
     */
    public function dispatch() {
        $configuration['extensionName'] = $this->extensionName;
        $configuration['pluginName'] = $this->pluginName;

        $bootstrap = t3lib_div::makeInstance('Tx_Extbase_Core_Bootstrap');
        $bootstrap->initialize($configuration);

        $this->objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_ObjectManager');

        $request = $this->buildRequest();
        $response = $this->objectManager->create('Tx_Extbase_MVC_Web_Response');

        $dispatcher =  $this->objectManager->get('Tx_Extbase_MVC_Dispatcher');
        $dispatcher->dispatch($request, $response);

        $response->sendHeaders();
        return $response->getContent();
    }



	/**
	 * @param null $pageUid
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function init($pageUid = NULL) {
		#define('TYPO3_MODE','FE');

		$this->pageUid = $pageUid;

		$GLOBALS['TSFE'] = t3lib_div::makeInstance('tslib_fe', $TYPO3_CONF_VARS, $pageUid, '0', 1, '', '','','');
		$GLOBALS['TSFE']->sys_page = t3lib_div::makeInstance('t3lib_pageSelect');

		#$GLOBALS['TSFE']->initFeuser();
		$GLOBALS['TSFE']->fe_user = tslib_eidtools::initFeUser();

		return $this;
	}



	/**
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function initTypoScript() {
		$GLOBALS['TSFE']->getPageAndRootline();
		$GLOBALS['TSFE']->initTemplate();
		$GLOBALS['TSFE']->getConfigArray();

		return $this;
	}



	/**
	 * @return void
	 */
    public function cleanShutDown() {
        $this->objectManager->get('Tx_Extbase_Persistence_Manager')->persistAll();
        $this->objectManager->get('Tx_Extbase_Reflection_Service')->shutdown();
    }



    /**
     * Build a request object
     *
     * @return Tx_Extbase_MVC_Web_Request $request
     */
    protected function buildRequest() {
        $request = $this->objectManager->get('Tx_Extbase_MVC_Web_Request'); /* @var $request Tx_Extbase_MVC_Request */
        $request->setControllerExtensionName($this->extensionName);
        $request->setPluginName($this->pluginName);
        $request->setControllerName($this->controllerName);
        $request->setControllerActionName($this->actionName);
        $request->setArguments($this->arguments);

        return $request;
    }



	/**
	 * Prepare the call arguments
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function initCallArguments() {
		$request = t3lib_div::_GP('request');

		if ($request) {
			$this->setRequestArgumentsFromJSON($request);
		} else {
			$this->setRequestArgumentsFromGetPost();
		}

		$this->extensionName  = $this->requestArguments['extensionName'];
		$this->pluginName = $this->requestArguments['pluginName'];
		$this->controllerName = $this->requestArguments['controllerName'];
		$this->actionName = $this->requestArguments['actionName'];

		$this->arguments = $this->requestArguments['arguments'];

		if (!is_array($this->arguments)) $this->arguments = array();

		return $this;
	}



    /**
     * Set the request array from JSON
     *
     * @param string $request
     */
    protected function setRequestArgumentsFromJSON($request) {
        $requestArray = json_decode($request, true);
        if(is_array($requestArray)) {
            $this->requestArguments = t3lib_div::array_merge_recursive_overrule($this->requestArguments, $requestArray);
        }
    }



    /**
     * Set the request array from the getPost array
     */
    protected function setRequestArgumentsFromGetPost() {
        $validArguments = array('extensionName','pluginName','controllerName','actionName','arguments');
        foreach($validArguments as $argument) {
            if(t3lib_div::_GP($argument)) $this->requestArguments[$argument] = t3lib_div::_GP($argument);
        }
    }



	/**
	 * @param $extensionName
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function setExtensionName($extensionName) {
		if(!$extensionName) throw new Exception('No extension name set for extbase request.', 1327583056);

		$this->extensionName = $extensionName;
		return $this;
	}



	/**
	 * @param $pluginName
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function setPluginName($pluginName) {
		$this->pluginName = $pluginName;
		return $this;
	}



	/**
	 * @param $controllerName
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function setControllerName($controllerName) {
		$this->controllerName = $controllerName;
		return $this;
	}



	/**
	 * @param $actionName
	 * @return Tx_PtExtbase_Utility_AjaxDispatcher
	 */
	public function setActionName($actionName) {
		$this->actionName = $actionName;
		return $this;
	}

}
?>
Ajax Extbase Dispatcher

Der Dispatcher muss in der ext_localconf.php für den Aufruf registriert werden:

$TYPO3_CONF_VARS['BE']['AJAX']['yagAjaxDispatcher'] = t3lib_extMgm::extPath('yag').'Classes/Utility/Ajax/Dispatcher.php:Tx_Yag_Utility_Ajax_Dispatcher->dispatch';
Registrieren des Dispatchers-Aufrufs über die ajax.php im Backend am Beispiel von YAG.

Mit dem obigen Skript und den Einträgen in der ext_localconf.php lassen sich nun beliebige Controller/Action Kombinationen auf beliebigen Extbase-Extensions aufrufen und Parameter an die Action übergeben.

Im folgenden ein Beispiel, wie so ein Aufruf per ExtJs an die ajax.php im Backend aussehen kann:

Ext.Ajax.request({
  url:'ajax.php',
  params: {
    ajaxID: 'yagAjaxDispatcher',
    request: Ext.encode({
      extensionName:'yag',
      pluginName:'pi1',
      controllerName:'myController',
      action:'myAction',
      arguments: {
        argument1: "Wert1"
      }
    })
  },
  success:function(response, request) {
    alert(response);
  }
});
Ajax Request per JSON-Requesr array.

Im Frontend sieht die Verwendung des Dispatchers leicht anders aus und benötigt auch ein paar zusätzliche Zeilen Code. Vielen Dank an Cornelius, für seinen Hinweis im Kommentar, den ich hier nocheinmal mit etwas mehr syling wiedergebe.

Zunächst kann über den eID Aufruf keine Methode einer Klasse sondern nur der Code einer PHP Datei ausgeführt werden. Der Dispatcher benötigt also hier einen expliziten Aufruf:

 

require_once t3lib_extMgm::extPath('pt_extbase') . 'Classes/Utility/AjaxDispatcher.php';

//Connect to database
tslib_eidtools::connectDB();


// Init TSFE for database access
$GLOBALS['TSFE'] = t3lib_div::makeInstance('tslib_fe', $TYPO3_CONF_VARS, 0, 0, true);
$GLOBALS['TSFE']->sys_page = t3lib_div::makeInstance('t3lib_pageSelect');
$GLOBALS['TSFE']->initFEuser();
$dispatcher = t3lib_div::makeInstance('Tx_PtExtbase_Utility_AjaxDispatcher'); /** @var $dispatcher Tx_PtExtbase_Utility_AjaxDispatcher */

// ATTENTION! Dispatcher first needs to be initialized here!!!
echo $dispatcher->initCallArguments()->dispatch();
$TYPO3_CONF_VARS['FE']['eID_include']['yagAjaxDispatcher'] = t3lib_extMgm::extPath('yag').'Classes/Utility/Ajax/Dispatcher.php:Tx_Yag_Utility_Ajax_Dispatcher';
Registrieren des Dispatchers-Aufrufs per Eid im Frontend am Beispiel von YAG.
Ext.Ajax.request({
  url:'index.php',
  params: {
    eID: 'yagAjaxDispatcher',
    request: Ext.encode({
      extensionName:'yag',
      pluginName:'pi1',
      controllerName:'myController',
      actionName:'myAction',
      arguments: {
        argument1: "Wert1"
      }
    })
  },
   success:function(response, request) {
    alert(response);
   }
});
Aufruf des Dispatchers über eID im Frontend

 
Inhalt © Daniel Lienert 2009-2013  •  Powered by TYPO3  •  TypoScript Blogging by Fabrizio Branca  •  Impressum