Organisation meiner OXID6 Themes & Module

(Lösung für Teams mit einem Entwickler)

lokale Installation

Seit OXID4 (v4.9.7) nutze ich Git als Versionsverwaltungssystem während der Entwicklung. Jedes OXID4-Kundenprojekt hatte sein eigenes Repository. Darin befanden sich die OXID-Core, alle zum Projekt gehörenden Module und das Projekt-Childtheme. Kam ein Core- oder Modul-Update wurden die neuen Dateien in das lokale Repository eingespielt und die Anpassungen zeilenweise mit einem Compare-Tool verglichen. So konnte ich alle, für die Module oder Child-Themes relevanten Anpassungen erkennen und gleichermaßen nachziehen. Lief lokal alles, wurden die betroffenen Dateien per FTP erst im Stage-System und nach erneutem Test dann ins Live-System übertragen. Schon bei zehn, sich im Support befindlichen Projekten, war der Deployment-Aufwand bei Core-Updates oder Updates an zentralen Modulen immens.

Lokal verwende ich für Projekte, an denen ich allein arbeite ein WAMP-System auf Basis von wampserver.com. Auch wenn es mit OXID6 interessante Komplett-Pakete als Virtual Machines oder Docker-Container gibt, bin ich bei WAMP geblieben. So kann ich weiterhin meine „alten“ OXID4 Projekte pflegen und parallel die neuen OXID6 Projekte starten. Ich muss lediglich das PHP von 5.6 auf 7.1 wechseln und zurück. Das geht dank dem WindowsTray-Menü von WAMP mit zwei Klicks. Alle Kundenprojekte liegen bei mir in separaten Ordnern die per Alias im WAMP eingebunden sind. Composer ist via Windows-Installer installiert und greift auf die PHP7.1 Version des WAMP-PHPs zurück.
Für den Zugriff auf GIT nutze ich das aktuelle Git. Als IDE phpStorm, aber am Ende für die tagtägliche Arbeit auch immer noch gerne UltraEdit und UltraCompare. Da ich kein command-line-Fetischist bin und die GIT-Integration im phpStorm nicht ganz so mein Ding ist, nutze ich als GIT-Gui GitExtensions.

Im Team ist die Arbeit mit WAMP nicht zu empfehlen. Hier hat sich ein stufiges System (lokal arbeiten, Deployment auf Entwicklungs-Server mit eigener DB-Instanz, Stage-Deployment mit gemeinsamer DB-Instanz und Zugriffs- und Freigabemöglichkeit durch Kunden und abschließendem Live-Deployment bewert. Hier gibt es viele Möglichkeiten der Automatisierung, die aber über Ziel dieses Beitrages hinausgehen.

Mit OXID6 erfolgte ein organisatorischer Umbruch seitens OXID eSales. Composer spielt nun eine entscheidende Rolle beim Deployment. Ich kannte Composer bereits durch die Arbeit mit Typo3 und wusste um die Rolle von composer.json-Files in den Repositories. Ich schaute mir also zuerst an, wie OXID diese Dateien bei seinen Projekten, Modulen und Themes zusammenstellte.

Nach der ersten OXID6-Test-Installation via Composer nach offizieller Anleitung, war ich vom Inhalt, der erstellten composer.json Datei im Root-Verzeichnis überrascht. Es gab nur ein require hin zur „oxideshop_metapackage_ce„. In der dortigen composer.json befanden sich dann alle weiteren Requires. Auch die Aufteilung in das vendor und das source Verzeichnis fand ich im ersten Moment ungewöhnlich. Bei näherer Betrachtung aber durchaus sinnvoll. Das vendor-Verzeichnis wird von composer kontrolliert. Im source-Verzeichnis könnten theoretisch, neben den durch composer bereitgestellten Sourcen weitere Scripte hinterlegt werden. Sicherlich könnte man noch mehr Dateien, die unter source und vendor liegen im vendor belassen, möglicherweise besteht aber so die Möglichkeit der besseren Abwärtskompatibilität zu OXID4.

In der metapackage-composer.json fiel auf, das alle Requires keine Angaben zu den Quell-Repositories hatten. Somit war klar, dass es zu jedem Require ein übergreifendes, frei zugängliches Composer-Repository bei packagist.org gibt. Interessant für mich war, dass die Themes und die Module eigene Repositories hatten. Das war sicher schon immer so, nur in dem Moment wurde mir der Sinn erst klar. Die Frage war nun, wie bekomme ich jetzt meine Module über diesen offiziellen Weg in meine OXID-Installation rein? Ich hoste meine Projekte schon immer bei bitbucket.org. Ich wusste, das ich einige kundenspezifische Module nicht als öffentliche Repositories bereitstellen kann. Andere Module waren förmlich prädestiniert dafür, auch öffentlich zugänglich zu werden. Ich legte also in bitbucket.org die ersten leeren OXID6-Modul-, -Projekt- und Child-Theme-Repositories an. Hier unterschied ich zwischen private und public Zugriff. Bei packagist.org kam ein Account hinzu. Die public-Bitbucket-Repositories bekamen auch ein Repository bei packagist.org. In Bitbucket kann man über Web-Hooks einen Push zu packagist.org ausgelöst werden. Die public-Repositories konnte ich nun analog zu den OXID-eSales-Repositories in meine composer.json, für meine erste eigene Projekt-Installation hinzufügen. Für die eigene Root-composer.json habe ich eine gemergte Version aus OXID6-Root-composer.json OXID6-metapackage-composer.json genommen und mit meinen public Repositories ergänzt. Bewusst weggelassen wurden: überflüssige Zahlungsmodule, Demo-Daten, das alte azure-Theme und Develop-OXID-Scripte. Dafür ist z.B. das OXID-PDF-Invoice-Module wieder mit dabei:

{
	"name": "therealworld/demo-oxid6",
	"type": "project",
	"description": "This file should be used as an OXID eShop project root composer.json file. Entries provided here intended to be examples and could be changed to your specific needs.",
	"license": [
		"GPL-3.0-only",
		"proprietary"
	],
	"minimum-stability": "stable",
	"repositories": [
		{"type": "vcs", "url": "git@bitbucket.org:therealworld/theme-1.git"},
		{"type": "vcs", "url": "git@bitbucket.org:therealworld/module-1.git"},
		{"type": "vcs", "url": "git@bitbucket.org:therealworld/module-2.git"},
		{"type": "vcs", "url": "git@bitbucket.org:therealworld/module-3.git"},
		{"type": "vcs", "url": "git@bitbucket.org:therealworld/module-4.git"}
	],
	"require": {
		"composer/ca-bundle": "1.1.4",
		"composer/composer": "1.8.5",
		"composer/semver": "1.5.0",
		"composer/spdx-licenses": "1.5.1",
		"doctrine/annotations": "v1.4.0",
		"doctrine/cache": "v1.6.2",
		"doctrine/collections": "v1.4.0",
		"doctrine/common": "v2.7.3",
		"doctrine/dbal": "v2.5.13",
		"doctrine/inflector": "v1.2.0",
		"doctrine/lexer": "v1.0.1",
		"doctrine/migrations": "v1.5.0",
		"justinrainbow/json-schema": "5.2.8",
		"monolog/monolog": "1.24.0",
		"ocramius/package-versions": "1.2.0",
		"ocramius/proxy-manager": "2.0.4",
		"oxid-esales/flow-theme": "v3.2.*",
		"oxid-esales/wave-theme": "v1.1.*",
		"oxid-esales/gdpr-optin-module": "v2.1.*",
		"oxid-esales/oxideshop-ce": "v6.3.*",
		"oxid-esales/oxideshop-composer-plugin": "v2.0.*",
		"oxid-esales/oxideshop-db-views-generator": "v1.2.*",
		"oxid-esales/oxideshop-doctrine-migration-wrapper": "v2.1.*",
		"oxid-esales/oxideshop-facts": "v2.3.*",
		"oxid-esales/oxideshop-unified-namespace-generator": "v2.0.*",
		"oxid-esales/paypal-module": "v5.2.*",
		"oxid-projects/pdf-invoice-module": "v2.1.*",
		"therealworld/scheduler-module": "v1.14.*",
		"therealworld/staticcache-module": "v1.1.*",
		"therealworld/tools-module": "v1.7.*",
		"therealworld/module-1": "v1.1.*",
		"therealworld/module-2": "v1.1.*",
		"therealworld/module-3": "v1.1.*",
		"therealworld/module-4": "v1.1.*",
		"therealworld/theme-1": "v1.1.*",
		"phpmailer/phpmailer": "v5.2.27",
		"psr/container": "1.0.0",
		"psr/log": "1.1.0",
		"rmccue/requests": "v1.7.0",
		"seld/jsonlint": "1.7.1",
		"seld/phar-utils": "1.0.1",
		"smarty/smarty": "v2.6.31",
		"symfony/config": "v3.2.14",
		"symfony/console": "v2.8.49",
		"symfony/debug": "v3.0.9",
		"symfony/dependency-injection": "v3.1.10",
		"symfony/filesystem": "v3.4.25",
		"symfony/finder": "v2.8.49",
		"symfony/polyfill-ctype": "v1.11.0",
		"symfony/polyfill-mbstring": "v1.11.0",
		"symfony/process": "v2.8.49",
		"symfony/yaml": "v2.8.49",
		"webmozart/assert": "1.4.0",
		"webmozart/glob": "4.1.0",
		"webmozart/path-util": "2.3.0",
		"zendframework/zend-code": "3.1.0",
		"zendframework/zend-eventmanager": "3.2.1"
	},
	"config": {
		"preferred-install": {
			"*": "dist"
		}
	}
}

Für die privaten Repositories auf die nicht frei zugegriffen werden kann, gibt es folgende Lösung: Mit PuTTYgen habe ich ein ssh-RSA-Schlüsselpaar erstellt. Der öffentliche Schlüssel ist in allen Bitbucket-Repositories hinterlegt. Wenn ich nun lokal mit GitExtensions auf die Repositories zugreife, läuft parallel der pAgent von PuTTY und sorgt so für die Bereitstellung der Schlüssel und damit für den Zugriff auf die privaten Repositories.
Auch mein lokales composer, nutzt den im Hintergrund laufenden pAgent für den Zugriff auf die online-Repositories. Die lokalen composer updates mit meiner neuen composer.json liefen erfolgreich. Es wurde der source– und vendor-Ordner sowie die composer.lock parallel zur composer.json erstellt. In mein Projekt-Repository sind so am Ende nur vier Dateien. Der Rest wird per .gitignore ignoriert (ein großer Fortschritt gegenüber OXID4):

  • composer.json
  • composer.lock
  • readme.md
  • .gitignore

Online Installation

Online geht es ähnlich. Ich verbinde mich mit SSH (PuTTY) auf dem Server. Im PuTTY-Serverprofil ist das Agent-Forwarding aktiviert. Läuft also pAgent während meiner PuTTY-Sitzung, so gilt für meine Zugriffe vom Server auf die BitBucket-Repositories mein lokaler ssh-RSA-Schlüssel. Somit reicht es aus, in die composer.json im Bereich repositories alle Repositories zu ergänzen, die ich nicht per packagist.org zugänglich sind.

Die composer.json und composer.lock aus meinem Testprojekt habe ich der Einfachheit per FTP auf den Server geladen. Da ja alle Abhängigkeiten bereits lokal in die composer.lock geschrieben wurden, reichte es auf dem Server aus, mittels composer install den Shop online zu installieren.

Projekte, Module und Themes während der Arbeit

Für das Problem der lokalen Entwicklung der Module und Child-Themes habe ich folgende Lösung gefunden. Eine lokale Node.js-Installation ist für die Child-Theme-Arbeit unumgänglich. Dazu in einem späteren Artikel mehr. Mit Node.js erhält man den Paketmanager npm und damit Zugriff auf tausende JS-Script-Repositories für alle nur denkbaren Situationen. Dort gibt es CPX, ein CLI-Tool das nichts anderes macht, als Ordner zu überwachen und bei Änderungen diese an einer gewünschten anderen Stelle ebenfalls vorzunehmen. Mit dem weiteren Tool concurrently können die CPX-Prozesse auch parallel ausgeführt werden.
Mit einem kleinen command-Script lasse ich nun alle zum Projekt relevanten, lokalen Module- und Themes-Repositories beobachten und bei Veränderungen der Dateien direkt in den source-Ordner des lokalen Projektes kopieren.

concurrently ^
"cpx D:\path-to-module\module-1\**\* D:\path-to-shop\source\modules\trw\trwmodule1\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-module\module-2\**\* D:\path-to-shop\source\modules\trw\trwmodule2\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-module\module-3\**\* D:\path-to-shop\source\modules\trw\trwmodule3\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-module\module-4\**\* D:\path-to-shop\source\modules\trw\trwmodule4\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-theme\theme-1\de\**\* D:\path-to-shop\source\Application\views\theme1\de\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-theme\theme-1\en\**\* D:\path-to-shop\source\Application\views\theme1\en\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-theme\theme-1\tpl\**\* D:\path-to-shop\source\Application\views\theme1\tpl\ --no-initial --watch --verbose ^" ^
"cpx D:\path-to-theme\theme-1\out\**\* D:\path-to-shop\source\out\ --no-initial --watch --verbose ^"

Ich entwickle dadurch direkt in den lokalen Module- und Themes-Repositories und habe zum direkten Test meine lokale Shop-Installation.
Am Ende des Tages checke ich meine Veränderungen in den Module- und Themes-Repositories ein. Ein composer update im lokalen Projekt zieht dann die frischen Repositories und legt dann die Module und Themes „offiziell“ an.

Mit phpStorm, ist diese Art des lokalen Deployments auch mit einigem Konfigurationsaufwand möglich. Oben genannte Variante lässt sich aber für jede Individuelle Entwicklungsumgebung übernehmen.

Wechsele ich zwischen den Projekten, führe ich im neuen Projekt lokal zuerst ein composer update durch, damit ich die Anpassungen der übergreifenden Module, die ich in dem vorherigen Projekt vorgenommen habe, auch in diesem Projekt zur Verfügung stehen habe.