TYPO3 Flow Render View Template from Database

In einem kürzlichen Anwendungsfall benötigte ich in einem Projekt die Möglichkeit, das View Template dynamisch aus der Datenbank anstatt aus dem Resource/Private/Template Verzeichnis aus einer HTML View zu laden. Dies könnte zum Beispiel dann sinnvoll sein, wenn ein Backend/Admin Benutzer (wie in meinem Fall) in der Lage sein soll den View Code zu editieren.

Ein klassischer Anwendungsfall könnte das Editieren eines kleinen Partials bzw. dem Inhalt eines HTML Newsletter sein.
Leider bietet TYPO3 Flow von Hause aus keine Möglichkeit, den View Code dynamisch zu injecten anstatt aus einer HTML Datei zu laden.

Eine Möglichkeit soll dieser Artikel mit Hilfe einer eigenen View Klasse aufzeigen:

Die View Klasse

Wir erstellen in unserem Package eine eigene View Klasse. Dazu ist es wichtig, dass die Datei innerhalb einer Ordnerstruktur Classes/Vendor/Package/View/MyController/MyActionName.php angelegt wird.
Zur Erklärung: Die View Klasse muss den Namen der Controller Action tragen, in welcher sie automatisch verwendet werden soll. Damit das Funktioniert muss die View Klasse zusätzlich in einem Ordner mit dem Namen des entsprechenden Controllers abgelegt werden. Daraus ergibt sich die Verzeichnisstruktur View/MyController/MyActionName.php

Da wir innerhalb des View Template Codes in der Lage sein wollen Fluid Code zu rendern, müssen wir unsere eigene View Klasse von \TYPO3\Fluid\View\TemplateView ableiten.
Im Großen und Ganzen besteht der gesamte Code unserer eigenen View Klasse aus den wichtigsten Methoden der abgeleiteten Basis Klasse mit ein paar wenigen aber tiefgreifenden Eingriffen in den Code

class MyActionName extends \TYPO3\Fluid\View\TemplateView {

    /**
     * @var string
     */
    protected $xmlTemplateCode = "";

    /**
     * @param $xmlTemplateCode
     */
    public function setXmlTemplateCode($xmlTemplateCode) {
        $this->xmlTemplateCode = $xmlTemplateCode;
    }

    /**
     * Resolve the template path and filename for the given action. If $actionName
     * is NULL, looks into the current request.
     *
     * @param string $actionName Name of the action. If NULL, will be taken from request.
     * @throws Exception\InvalidTemplateResourceException
     * @throws \TYPO3\Fluid\View\Exception\InvalidTemplateResourceException
     * @return string Full path to template
     */
    protected function getTemplateSource($actionName = NULL) {
        // This is the modified Part
        if ($this->xmlTemplateCode != "") {
            return $this->xmlTemplateCode;
        }

        // This can be deleted
        $templatePathAndFilename = $this->getTemplatePathAndFilename($actionName);
        $templateSource = Files::getFileContents($templatePathAndFilename, FILE_TEXT);
        if ($templateSource === FALSE) {
            throw new Exception\InvalidTemplateResourceException('"' . $templatePathAndFilename . '" is not a valid template resource URI.', 1257246929);
        }

        return $templateSource;
    }


    /**
     * Original Render Code with a little adjustment!!!!!
     * isCompilable is always set to FALSE
     * This enables to load a different Fluid Template on each request without cache
     *
     *
     * Loads the template source and render the template.
     * If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout.
     *
     * @param string $actionName If set, the view of the specified action will be rendered instead. Default is the action specified in the Request object
     * @return string Rendered Template
     * @api
     */
    public function render($actionName = NULL) {
        $this->baseRenderingContext->setControllerContext($this->controllerContext);
        $this->templateParser->setConfiguration($this->buildParserConfiguration());

        $templateIdentifier = $this->getTemplateIdentifier($actionName);
        if ($this->templateCompiler->has($templateIdentifier)) {
            $parsedTemplate = $this->templateCompiler->get($templateIdentifier);
        } else {
            $parsedTemplate = $this->templateParser->parse($this->getTemplateSource($actionName));

            // Set isCompilable to FALSE
            $parsedTemplate->setCompilable(false);

            if ($parsedTemplate->isCompilable()) {
                $this->templateCompiler->store($templateIdentifier, $parsedTemplate);
            }
        }

        if ($parsedTemplate->hasLayout()) {
            $layoutName = $parsedTemplate->getLayoutName($this->baseRenderingContext);
            $layoutIdentifier = $this->getLayoutIdentifier($layoutName);
            if ($this->templateCompiler->has($layoutIdentifier)) {
                $parsedLayout = $this->templateCompiler->get($layoutIdentifier);
            } else {
                $parsedLayout = $this->templateParser->parse($this->getLayoutSource($layoutName));
                if ($parsedLayout->isCompilable()) {
                    $this->templateCompiler->store($layoutIdentifier, $parsedLayout);
                }
            }
            $this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext);
            $output = $parsedLayout->render($this->baseRenderingContext);
            $this->stopRendering();
        } else {
            $this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext);
            $output = $parsedTemplate->render($this->baseRenderingContext);
            $this->stopRendering();
        }

        return $output;
    }
}

Anwendung / Im Controller

Um nun innerhalb unseres Controllers bzw. genauer gesagt unserer myActionName Action den View Template Code dynamisch zu laden ist lediglich eine Zeile Code notwendig:

/**
 * MyActionName Action
 */
public function myActionName() {

    $myTemplateCode = "
….
"; // Load this from Database or WebService

    $this->view->setXmlTemplateCode($myTemplateCode); // This allows to set a XMLTemplate which is stored in Database
}

In meinem Beispiel arbeite ich mit XML Template Code welcher aus einer Datenbank injected wird, daher der Name der Methode setXmlTemplateCode.

TYPO3 Flow Model Revisions / Doctrine Versionable

Es gibt Anwendungsfälle, bei denen jede Änderung an einem oder mehrerer Models festgehalten bzw. archiviert werden sollte.

Dies ist mittels des Doctrine Behaviors “Versionable” relativ einfach und vor allem transparent umzusetzen (Doctrine Behaviors).

Mein Versuch war es, dies in TYPO3 Flow einzubauen. Jedoch scheiterte mein Versuch da ich kein umfassendes Doctrine Wissen hatte um dies einzubauen.

Nun möchte ich hier einen Alternativen Weg vorstellen, welcher sich einem Symfony2 Bundle von SimpleThings bedient (GitHub: simplethings/entityaudit).

Um dieses SymfonyBundle in TYPO3 Flow lauffähig und schön zu integrieren, werden wir im folgenden eine Bridge zwischen Flow und dem Symfony Bundle erstellen.

SimpleThings EntityAudit installieren

Die Installation des SimpleThings/EntityAudit Bundles in unserem Flow Framework gestaltet sich dank Composer als relativ einfach.
Wir führen den Composer Befehl einfach im Root Verzeichnis der TYPO3 Flow Anwendung aus:

composer require "simplethings/entity-audit-bundle"

Hiermit wird das Symfony Bundle im Packages/Library Verzeichnis abgelegt.

Um dieses Bundle von Flows Object Management auszuschließen, fügenden wir folgende Einstellung in unserer Settings.yaml hinzu:

TYPO3:
  Flow:
    object:
      excludeClasses:
        'simplethings.entityauditbundle': ['SimpleThings\\EntityAudit\\.*']

Flow AOP Bridge

Als nächstes bedienen wir uns dem mächtigen AOP Features von TYPO3 Flow um unser Symfony Bundle zu integrieren.
Dazu legen wir einen neuen AOP Aspect in unserem Package an:

buildEventManager())")
     * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint
     * @return \Doctrine\Common\EventManager
     */
    public function registerSimpleThingsAuditManager(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) {
        $emInstance = $joinPoint->getAdviceChain()->proceed($joinPoint);

        // Hier festlegen, welche Entities versioniert werden sollen
        $auditEntitiesArray = array('Vendor\Package\Domain\Model\MyEntity');

        $entityAuditConf = new \SimpleThings\EntityAudit\AuditConfiguration();
        $entityAuditConf->setAuditedEntityClasses($auditEntitiesArray);

        $entityAuditManagerInstance = new \SimpleThings\EntityAudit\AuditManager($entityAuditConf);
        $entityAuuditManagerInstance->registerEvents($emInstance);
        
        return $emInstance;
    }
}
?>

Als nächstes führen wir ein

# Database aktualisieren (Audit Tables erstellen)
./flow doctrine:update

auf der Shell aus und prüfen anschließend unsere Datenbank Tabellen. Wenn alles funktioniert hat, sollte es nun passend zu unserer MyEntity Tabelle eine weitere MyEntity_audit Tabelle sowie eine revision Tabelle geben.

Nun solltet ihr testen, ob bei einer Änderung von MyEntity auch gleichzeitig eine entsprechende Revision und ein Eintrag in der MyEntity_audit Tabelle angelegt wird.

Flow Annotation “Versionable”

Natürlich ist es unschön, die zu versionierenden Models innerhalb des Aspects im Array hardcoded einzutragen. Eine andere Möglichkeit könnte hier die Settings.yaml sein, in welcher die zu versionierenden Models eingetragen werden können.
Am saubersten halte ich jedoch die Möglichkeit, hierfür eine eigene TYPO3 Flow Annotation zu erstellen.

Hierzu legen wir eine neue Klasse mit dem Namen “AuditEntity” an und befüllen Sie mit dem Code für eine Annotation
Beispielnamespace: Vendor\Package\Audit\Annotations\AuditEntity

/**
 * @Annotation
 * @Target("CLASS")
 */
final class AuditEntity {
/** Nothing here, it's only a Marker Class */
}

Nun lassen sich innerhalb EntityAuditRegisterAspect mittels des TYPO3 Flow Reflection Services alle Models ermitteln, welche die neue Annotation tragen:

/**
 * @var \TYPO3\Flow\Reflection\ReflectionService
 * @Flow\Inject
 */
protected $reflectionService;

$annotatedModels = $this->reflectionService->getClassNamesByAnnotation('Vendor\Package\Audit\Annotations\AuditEntity');

Um sich die versionierten Models später nochmal auslesen lassen zu können, bringt das SimpleThings EntityAudit Bundle einen AuditReader mit GitHub: SimpleThings/EntityAudit AuditReader. Diesen können wir ebenfalls in unserem TYPO3 Flow Package verwenden.

TYPO3 Flow xDebug with DebugProxy

In PHP war das Debugging noch nie eine einfache Angelegenheit. Wenn der zu untersuchende Code auf einem entfernten Server lief, welcher sich hinter einer Firewall / NAT verbirgt, wurde es noch ein Stückchen komplizierter.

Wunderbar dass es mit TYPO3 Flow noch ein wenig komplexer wird, als es bereits sowieso schon ist. Der Grund liegt ganz einfach an der Tatsache, dass TYPO3 Flow für eure Services, Controller und anderen Klassen sogenannte Proxy Klassen generiert, welche dann zur Laufzeit ausgeführt werden.

Dies bedeutet, dass ein Breakpoint innerhalb der IDE im UserController auf der indexAction (z.B. Zeile 18) im später generierten und ausgeführten Proxy mit dem ungefähren Namen “UserController_Original” eine ganz andere Zeile ist. Das führt dazu dass z.B. PHPStorm bei einem Breakpoint entweder gar keine Reaktion zeigt oder das Debugging mit einer Fehlermeldung endet.

Lösung: DebugProxy

Ein DebugProxy schaltet sich zwischen eurer IDE und dem xDebug Protokoll und ist in der Lage, Zeilennummern und Dateipfade für TYPO3 Flow zu mappen.
Im besten Fall arbeitet der DebugProxy völlig transparent ohne das die IDE oder man selbst etwas davon mitbekommt.

Eines dieser DebugProxy Scripte war der debugproxy von sandstorm mitte 2012, welcher im April 2013 noch für TYPO3 Flow 2.0 angepasst wurde, mit den aktuellen Versionen jedoch nicht mehr zu funktionieren scheint.

https://github.com/sandstorm/debugproxy

Inzwischen gibt es jedoch vom User dfeyer auf GitHub einen neueren und deutlich schnelleren DebugProxy geschrieben in Googles Programmiersprache GO zum Download.

https://github.com/dfeyer/flow-debugproxy

Mein Setup (PHP 5.6, Debian 8, TYPO3 Flow 3.0)

Installation von GO und setzen der benötigten Go-Pfade

apt-get install golang
export $GOPATH=/usr/local/go
export $GOBIN=
export $GOROOT=/usr/lib/go

Setup des DebugProxy Scripts

cd /usr/local/go/src
# GitHub Repo klonen
git clone https://github.com/dfeyer/flow-debugproxy.git
cd /usr/local/go/src/github.com/dfeyer/flow-debugproxy

# Go Dependencies & Build
go get
go build

Nun haben wir das flow-debugproxy Go Script bereit zur Ausführung:

/usr/local/go/bin/flow-debugproxy --xdebug 127.0.0.1:9000 --ide 127.0.0.1:9010 --vv

Remote Server / Firewall / NAT ?!

Eine der wohl einfachsten Lösungen dieses Problems ist ein SSH-Tunnel von eurem lokalen Rechner auf dem PHPStorm läuft zum entsprechenden Remote-Server.

SSH-Tunnel auf OS X öffnen

ssh user@host -p 22 -R9010:127.0.0.1:9010

Hier wird eine SSH Sitzung gestartet welche den lokalen Port 9010 auf den Remote-Server Port 9010 tunnelt.
Auf dem Remote-Server lauscht auf Port 9010 nun der (vorher gestartete) DebugProxy von dfeyer, welcher die Daten an xDebug (welcher an Port 9000 lauscht) weitergibt bzw. zuvor ein Path-Mapping vornimmt.

Weitere Informationen / Links

Flow DebugProxy Script von dfeyer: GitHub: dfeyer/flow-debugproxy
Altes DebugProxy Script von sandstorm: GitHub: sandstorm/debugproxy
Hilfreiches Gist: https://gist.github.com/michaelgerdemann/7a4e2f5315c19ff6f896

Edit:
Mittlerweile gibt es auch auf discuss.neos.io einen entsprechenden Beitrag zu dem Thema:
Neos.io: how-to-debug-a-flow-application-with-xdebug-and-phpstorm

Nachtrag: PHPStorm / xDebug / DebugProxy

Folgendes Setup auf einem MacBook mit MAMP (Free)

xDebug Einstellungen in der php.ini

[xdebug]
zend_extension="/Applications/MAMP/bin/php/php5.6.10/lib/php/extensions/no-debug-non-zts-20131226/xdebug.so" ; Achtung! Pfad muss angepasst werden!
xdebug.remote_autostart=1
xdebug.remote_enable=1
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.idekey=phpstorm-xdebug

flow-debugproxy starten
Nachdem der flow-debugproxy von dfeyer in Go kompiliert wurde, können wir diesen nun starten:
./flow-debugproxy –v (Pfad: /Users/…./go/bin)

PHPStorm Einstellungen

xdebug_phpstorm_1

xdebug_phpstorm_2

xdebug_phpstorm_3

xdebug_phpstorm_4

TYPO3 Extbase: Storage Pid im Repository ändern

Die Storage Pid wird normalerweise über das TypoScript festgelegt und gilt somit für das gesamte Repository der Extension.
Möchte man – aus welchen Gründen auch immer – in einer individuellen Repository Methode eine andere Storage Pid verwenden, lässt sich dies unter anderem mit der Typo3QuerySettings lösen:

/**
 * Returns only Advert Presets
 */
public function getAllPresets() {
    $customStoragePid = "15"; // <== Custom Storage Pid

    // Get the default Settings
    $querySettings = $this->objectManager->get('\\TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
    $querySettings->setStoragePageIds(array($customStoragePid));
    $this->setDefaultQuerySettings($querySettings);

    // Now get all (only Presets)
    $queryResult = $this->findAll();
    return $queryResult;
}

PHPStorm SFTP Connect / Debian 8 Jessy – “Algorithm negotiation fail”

PHPStorm (8.x) hat beim Connect via SFTP mit einem Remote Server auf dem die neue Version von Debian 8 Jessy läuft Verbindungsprobleme.
Beim Einrichten des neuen Remote Servers wird der Verbindungsversuch mit “Algorithm negotiation fail” quittiert.

Um das Problem (quick and dirty ?!) zu beseitigen, muss in der Datei /etc/ssh/sshd_config auf dem Debian Server folgende Zeile hinzugefügt werden:

# Add this to the bottom of the /etc/ssh/sshd_config File
KexAlgorithms=diffie-hellman-group1-sha1

Anschließend muss der sshd Dienst noch neugestartet werden:

systemctl restart sshd

Sicherlich gibt es einen besseren Weg dies zu bewerkstelligen, aber für einen Test / Entwicklungsserver ist dies sicherlich noch okay.

Active Directory: Schema Version ermitteln

Um die Schema Version des AD zu ermitteln ist folgende Powershell zeile innerhalb eines Active Directory Modules notwendig:

 

Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion

 

Als Output erhaltet Ihr etwas wie z.B.

PS H:\> Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion

DistinguishedName : CN=Schema,CN=Configuration,DC=lab,DC=local
Name : Schema
ObjectClass : dMD
ObjectGUID : 94b0c7b4-d994-4eea-99cc-b8915ff31e53
objectVersion : 47

Der Wert “objectVersion” ist hier entscheiden. Die Betriebssystem Version kann anhand der folgenden Tabelle abgeleitet werden:

 

69 Windows Server 2012 R2
56 Windows Server 2012
47  Windows Server 2008 R2
44 Windows Server 2008
31 Windows Server 2003 R2
30  Windows Server 2003
13  Windows 2000

Git: Preserve specific File on Merge (Custom Merge Driver)

Erstellen einer eigenen Merge Strategie (Git Merge Driver):

Name des Merge Drivers: ours
git config –global merge.ours.driver true

echo ‘.idea/deployment.xml merge=ours’ >> .gitattributes
git add .gitattributes
git commit -m ‘Branch depended Deployment Settings’

TYPO3 Extbase: Render Fluid View in Scheduler Task

Dieser Artikel beschreibt, wie man den in TYPO3 CMS vorinstallierten Scheduler (“Planer”) dazu nutzen kann, eine Aufgabe innerhalb der eigenen Extension regelmäßig ausführen zu lassen. Der zweite Teil des Artikels beschäftigt sich dann damit, wie man innerhalb seiner “Task” Klasse eine Fluid View rendern lassen kann, um dies beispielsweise via E-Mail zu versenden.

Continue reading “TYPO3 Extbase: Render Fluid View in Scheduler Task” »

TYPO3 Flow: Render Fluid View as PDF File

Um mit TYPO3 Flow ein Fluid View so zu rendern, dass es als PDF-File ausgegeben wird, ist nicht sehr einfach.
Ob die nun gezeigte Vorgehensweise auch Best Practise ist, kann ich nicht sagen. Allerdings funktioniert es und kommt mir als Flow Anfänger relativ sauber vor.

Continue reading “TYPO3 Flow: Render Fluid View as PDF File” »

Extbase/Fluid/Flow FormConfiguration ViewHelper for __trustedProperties

Hin und wieder kommt es vor das kein Weg daran vorbei führt, innerhalb eines Fluid Formulars ein normales HTML Form Element einzufügen, welches nicht von einem Fluid Form Helper generiert wird. Die Gründe hierfür sind vielfältig, oft aber in Verbindung mit jQuery und dynamisch erstellten Feldern.

Eigentlich ist dies kein Problem, da man die nun auftretende Fehlermeldung mit Hilfe des PropertyMappers bzw. der PropertyMappingConfiguration in der initialize*Action im Controller beseitigen könnte. Allerdings kann hierbei relativ viel Configuration Code entstehen, welcher nichts mit der Business Logik ansich zu tun hat, sowie viel schlimmer noch, bei entsprechenden Änderungen im Template (der View) Anpassungen im Controller erfordern.

Um die notwendige Arbeit vom Controller in das Template (also in die View Logik) zu verschieben, ermöglicht folgender Custom ViewHelper dieses Artikels:

PS: Natürlich sollte das auch in TYPO3 Flow funktionieren!

Continue reading “Extbase/Fluid/Flow FormConfiguration ViewHelper for __trustedProperties” »

Durch das Fortsetzen der Benutzung dieser Seite, stimmst du der Benutzung von Cookies zu. Weitere Informationen

Wir verwenden Cookies, um Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf unsere Website zu analysieren. Außerdem geben wir Informationen zu Ihrer Nutzung unserer Website an unsere Partner für soziale Medien, Werbung und Analysen weiter.

Schließen