Sencha Ext JS trifft Symfony

Sencha Ext JS ist ein MVC/MVVM-JavaScript-Framework zum Erstellen plattformübergreifender Webanwendungen für Desktops, Tablets und Smartphones. Mit Version 6 (veröffentlicht im Juli 2015) hat Sencha das mobile Framework Sencha Touch und das Desktop-Pendant Ext JS zu einem einzigen Framework zusammengeführt, bei dem der View-bezogene Code getrennt auf einem vereinheitlichten Core aufsetzt, der die gemeinsame Framework-Funktionalität bereitstellt. Zusammen mit Ext JS 6 kommt eine komplette Anwendungs-Toolchain namens Sencha Cmd – ein Java- und JavaScript-basiertes Kommandozeilenwerkzeug, das als CSS-Compiler (mit SASS), JavaScript-Compiler, Paketmanager und -Kompressor sowie Ressourcen-Manager dient. Auch wenn der gesamte Build-Prozess hochgradig konfigurierbar ist (bis hin zu eigenen Ant-basierten Tasks), ist die Integration einer auf Sencha Cmd aufbauenden Anwendung in eine serverseitige Webanwendung mühsam und erfordert viel Konfiguration und Boilerplate-Code auf dem Server. Das gilt insbesondere, weil auf Sencha Cmd basierende Anwendungen zwischen einem production- und einem development-Modus unterscheiden. Während der production-Modus ein vollständig gebautes Anwendungspaket voraussetzt, kann der development-Modus direkt aus den unkompilierten und ungepackten Quelldateien laufen – ein Muss während der Entwicklung. Diese beiden Modi haben unterschiedliche Eigenschaften mit unterschiedlichen Anforderungen, insbesondere wenn nicht der in Sencha Cmd eingebaute Entwicklungsserver verwendet wird, sondern ein vollständiger serverseitiger Anwendungs-Stack (z. B. auf Basis von Apache und PHP). Um diesen Unterschied bei der Entwicklung von Ext-JS-6-basierten Anwendungen auf Grundlage einer Symfony-PHP-Webanwendung abzumildern, haben wir ein Symfony-Bundle (teqneers/ext-application-bundle) und eine dazugehörige Bibliothek (teqneers/ext-application) erstellt, die wir nun offen auf Github teilen.
Die Bibliothek teqneers/ext-application dient derzeit lediglich als Grundlage unseres Symfony-Bundles teqneers/ext-application-bundle. Wir haben nicht versucht, die Bibliothek als eigenständige Komponente oder in einem anderen Kontext als einer Symfony-Umgebung zu verwenden. Wir freuen uns über jede Hilfe und jeden Beitrag, um die Bibliothek für sich genommen nützlicher zu machen.
Es folgt ein kurzer Walkthrough zum Aufsetzen der Ext-JS-6-Demo-Anwendung (die erzeugt wird, wenn man mit Sencha Cmd die Entwicklung einer Anwendung beginnt) auf Basis einer Symfony-PHP-Anwendung.
Wir folgen der offiziellen Installationsdokumentation für Symfony, um ein funktionierendes Anwendungsgerüst aufzusetzen. Vorausgesetzt, Sie haben den Symfony-Installer gemäß der Dokumentation installiert, beginnen wir mit:
symfony new my_example
cd my_example
php app/console server:run // CTRL+C to stop
Sie haben nun eine laufende Symfony-Anwendung auf localhost Port 8000 mit dem eingebauten PHP-Webserver. Testen Sie, ob sie in Ihrem Browser korrekt lädt. Als Nächstes erstellen wir eine einfache Ext-JS-6-Anwendung. Wiederum vorausgesetzt, dass Sie Sencha Cmd gemäß der Dokumentation installiert haben, können wir Folgendes ausführen:
sencha generate app -ext -classic MyApp my-app
Dies lädt die aktuelle kostenlose Ext-JS-Testversion herunter und erstellt ein Anwendungsgerüst für eine classic-Anwendung namens MyApp im Ordner my-app. Unter „Using Sencha Cmd with Ext JS 6“ finden Sie weitere Informationen dazu, was dieser Befehl im Detail tut.
Um zu testen, ob alles wie erwartet funktioniert hat, wechseln Sie in den Anwendungsordner und starten den in Sencha Cmd eingebauten Webserver.
cd my-app
sencha app watch // CTRL+C to stop
Sie haben nun eine Ext-JS-Anwendung, die im Development-Modus auf localhost Port 1841 läuft. Testen Sie, ob sie in Ihrem Browser korrekt lädt. Nächster Halt: Installation des teqneers/ext-application-bundle. Symfony-Anwendungen nutzen die Dienste von Composer, einem PHP-Paketmanager. Um ihn entweder lokal im Projekt oder global auf Ihrem System zu installieren, folgen Sie bitte der Installationsanleitung. Nun fügen wir die benötigten Abhängigkeiten hinzu.
cd .. // don't forget to go back to our application root
composer.phar require teqneers/ext-application-bundle:dev-master teqneers/ext-application:dev-master
Bitte beachten Sie: Auch wenn teqneers/ext-application eine Abhängigkeit von teqneers/ext-application-bundle ist, müssen Sie beide explizit angeben, weil beide Pakete noch nicht in einer stabilen Version verfügbar sind. Dem üblichen Vorgehen zum Hinzufügen von Bundles zu Symfony-Anwendungen folgend, ändern wir AppKernel::registerBundles() in app/AppKernel.php:
// app/AppKernel.php
// in AppKernel::registerBundles()
$bundles = array(
// ...
new TQ\Bundle\ExtJSApplicationBundle\TQExtJSApplicationBundle(),
// ...
);
Dadurch müssen wir auch die Anwendungskonfiguration aktualisieren und die erforderlichen Einstellungen für das teqneers/ext-application-bundle hinzufügen. Das geschieht in app/config/config.yml.
// app/config/config.yml
// ...
tq_ext_js_application:
app_path: '%kernel.root_dir%/../my-app'
builds:
default:
development:
build_path: build/development/MyApp
microloader: /bootstrap.js
manifest: /bootstrap.json
app_cache: ~
production:
build_path: build/production/MyApp
microloader: microloader.js
manifest: app.json
app_cache: cache.appcache
Nun, das verdient allerdings ein paar zusätzliche Anmerkungen. Die Variable app_path zeigt auf den vollständigen Pfad der oben erstellten Ext-JS-Anwendung. Man kann Symfonys Container-Parameter nutzen, um einen Pfad relativ zum Kernel-Root-Verzeichnis zu erzeugen. Der Konfigurationsabschnitt builds enthält Informationen über die in der Ext-JS-Anwendung konfigurierten Build-Profile. In unserem Beispiel verwenden wir nur ein Build-Profil – der Name default ist beliebig, er dient lediglich dazu, einen Build in Multi-Build-Setups zu referenzieren. Jede Build-Konfiguration ist in development- bzw. production-Einstellungen aufgeteilt. Der Abschnitt development definiert die Anwendungsumgebung im development-Modus (wenn die Symfony-Anwendung über console server:run --env=dev oder app_dev.php läuft). Der Abschnitt production konfiguriert hingegen die Anwendungsumgebung für den production-Einsatz (wenn die Symfony-Anwendung über console server:run --env=prod oder app.php läuft). build_path ist der Pfad zum Verzeichnis mit den Build-Artefakten, die entweder von sencha app build (production) oder sencha app watch (development) erzeugt werden. Dieser Pfad ist relativ zu app_path. microloader, manifest und app_cache definieren die Dateien, die den Ext-JS-Micro-Loader, das Anwendungsmanifest bzw. das Application-Cache-Manifest enthalten. Pfade, die mit einem / beginnen, sind relativ zu app_path, während Pfade ohne führenden / relativ zu build_path sind. Das sollte man im Hinterkopf behalten. In der gezeigten Beispielkonfiguration bedeutet das, dass Manifest und Micro-Loader im Development-Modus direkt aus dem Anwendungsverzeichnis ausgeliefert werden, während im Production-Modus die jeweiligen Build-Artefakte verwendet werden.
Da das teqneers/ext-application-bundle einen eigenen Controller mitbringt und einige Routen benötigt, um auf die Controller-Actions zuzugreifen, müssen wir unser Routing aktualisieren, um die vom Bundle bereitgestellten Routing-Informationen einzubinden. Bearbeiten Sie einfach app/config/routing.yml:
// app/config/routing.yml
// ...
ext_app:
resource: "@TQExtJSApplicationBundle/Resources/config/routing.yml"
prefix: /
Die Anwendung sollte nun wieder genauso laufen wie zuvor – auch wenn wir noch keine Ext-JS-basierte Seite integriert haben. Dazu bearbeiten wir zunächst die indexAction im Default-Controller in src/AppBundle/Controller/DefaultController.php:
// src/AppBundle/Controller/DefaultController.php
// ...
/**
* @Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
return $this->render('default/ext.html.twig');
}
// ...
Diese Action ist so einfach wie möglich – sie rendert lediglich die View app/Resources/views/default/ext.html.twig, die wir als Nächstes erstellen.
// app/Resources/views/default/ext.html.twig
<!DOCTYPE HTML>
<html manifest="{{ extjsAppCachePath() }}" lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Welcome!</title>
<script type="text/javascript">
var Ext = Ext || {};
Ext.manifest = '{{ extjsManifestPath()|e('js') }}';
</script>
<script id="microloader" data-app="{{ extjsApplicationId() }}" type="text/javascript" src="{{ extjsBootstrapPath() }}"></script>
</head>
<body>
</body>
</html>
Das Template verwendet die Twig-Funktionen extjsAppCachePath(), extjsManifestPath(), extjsApplicationId() und extjsBootstrapPath(), um die korrekten Pfade zum Application-Cache-Manifest, zum Anwendungsmanifest und zum Micro-Loader der Anwendung sowie die Anwendungs-ID (für das clientseitige Local-Storage-Caching) auszugeben. Wenn Sie den Entwicklungs-Webserver neu starten und http://localhost:8000/ aufrufen, sehen Sie die Ext-JS-Demo-Anwendung – ausgeliefert über unser Symfony-basiertes Backend.
Es gibt allerdings ein kleines Problem, wenn man versucht, die Anwendung im production-Modus zu betreiben. Zunächst müssen wir das production-Paket unserer Anwendung bauen.
cd my-app
sencha app build
Zweitens ist unsere Anwendung aber standardmäßig so konfiguriert, dass sie im production-Modus einen sogenannten embedded Micro-Loader verwendet. Das bedeutet, dass der Micro-Loader-Code wortwörtlich in die Index-Seite unserer Anwendung kopiert wird (standardmäßig index.html). Wie das aussieht, können Sie sich in my-app/build/production/MyApp/index.html ansehen. Es gibt zwei Wege, dieses Problem zu lösen. Der erste ist, den Micro-Loader-Code aus der Datei index.html in unser Template app/Resources/views/default/ext.html.twig zu kopieren. Die zweite Option (die wir verwenden werden) ist, die Build-Konfiguration der Anwendung so anzupassen, dass Sencha Cmd ein externes Micro-Loader-Build-Artefakt erzeugt. Öffnen Sie my-app/app.json und ändern Sie die Zeilen 425 bis 430 wie folgt:
// my-app/app.json
// ...
"output": {
"base": "${workspace.build.dir}/${build.environment}/${app.name}",
"microloader": {
"embed": false
},
"appCache": {
"enable": false
}
},
// ...
Dies weist Sencha Cmd an, den embedded Micro-Loader zu deaktivieren. Führen Sie
sencha app build
erneut aus. Das erzeugt my-app/build/production/MyApp/microloader.js, das von unserem Bundle aufgegriffen und als Micro-Loader der Anwendung ausgeliefert wird. Nun können wir unsere Anwendung im production-Modus ausliefern mit:
php app/console server:run --env=prod // CTRL+C to stop
So integriert man eine Ext-JS-Anwendung in ein Symfony-basiertes Backend. Es gibt Dutzende Dinge, die sich in der Build-Konfiguration Ihrer Ext-JS-Anwendung konfigurieren, anpassen und ändern lassen. Bedenken Sie, dass einige dieser Einstellungen möglicherweise auch eine Neukonfiguration des Bundles erfordern oder derzeit sogar noch nicht unterstützt werden (z. B. das Ausliefern eines embedded Manifests).
Beteiligen Sie sich gerne – sei es durch Diskussionen mit uns, durch Testen und das Melden von Bugs, durch Feature-Wünsche oder sogar durch aktive Mitarbeit am Paket selbst. Das Paket ist auf Github verfügbar: github.com/teqneers/ext-application-bundle, die zugehörige Core-Bibliothek finden Sie ebenfalls auf Github: github.com/teqneers/ext-application.
Freuen Sie sich auf einen Folgeartikel über die Integration von Ext.direct, einem AJAX-basierten RPC-Kommunikationsframework, das Teil von Ext JS ist, in Ihre Symfony-Anwendung.
*Verwendete Versionen: Symfony 2.7.4, Sencha Cmd 6.0.1.72, Sencha Ext JS 6.0.0.640, teqneers/ext-application be2b95, teqneers/ext-application-bundle 4a0159