WordPress & Git

Viele unserer Web-Projekte werden auf Basis von WordPress umgesetzt. Daher ist ein effizienter Entwicklungsprozess dafür sehr wichtig. In diesem Blogeintrag schauen wir uns an, wie man WordPress am besten in Kombination mit Git verwendet.

WordPress ist eines der beliebtesten Open Source CMS Systeme, die es momentan gibt. Ein Großteil aller Webseiten läuft auf dessen Basis. Grund dafür ist seine hervorragende Benutzerfreundlichkeit. Es lässt sich recht einfach installieren und betreiben. Will man jedoch größere Projekte damit umsetzen, an denen womöglich noch mehrere Entwickler mitarbeiten sollen, so kommt man um das Thema Versionskontrolle nicht herum. Da aber der Fokus von WordPress nicht auf einem optimalen Entwicklungsprozess liegt, sondern eben auf der einfachen Benutzung, ist es nicht ganz leicht, auf WordPress basierende Projekte unter Versionskontrolle zu stellen. Es gibt dafür einige Ansätze, aber keine perfekte Lösung. Ich möchte nun in diesem Blog kurz meine Idee dazu vorstellen.

Die große Frage ist, welche Dateien sollen versioniert werden und welche kann man ignorieren. Als Faustregel gilt hier: Nur alle applikationsspezifischen Quellcode-Dateien! Auf keinen Fall sollten Konfigurationsdateien, Bibliotheken auf denen das Projekt basiert oder inhaltsspezifische Dateien in die Versionskontrolle eingepflegt werden. Wie kann man diese Regel im Falle eines WordPress Projekts anwenden? Sehen wir uns einmal alle Dateien an, die wir nach dem Entpacken von WordPress erhalten:

wp-admin/
wp-content/
wp-includes/
index.php
license.txt
readme.html
wp-activate.php
wp-blog-header.php
wp-comments-post.php
wp-config-sample.php
wp-cron.php
wp-links-opml.php
wp-load.php
wp-login.php
wp-mail.php
wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php

Die projektspezifischen Dateien, welche während der Entwicklung angepasst bzw. neu erstellt werden, sind meist Teil eines Themes und/oder Plugins und befinden sich in dem Ordner wp-content. Alles andere gehört zum WordPress-Core und sollte nicht modifiziert werden. Daher würde ich den WordPress-Core als Dependency betrachten und nicht mit in die Versionskontrolle aufnehmen. Dafür wäre es gut, Abhängigkeiten und applikationsspezifischen Code voneinander zu trennen. Das heißt, das wp-content Verzeichniss soll sich nicht mehr innerhalb des Worpress-Verzeichnisses befinden, sondern außerhalb. Wir bekommen dadurch nun folgende Dateistruktur:

wp/
wp-content/
    - languages/
    - plugins/
    - themes/
index.php
wp-config.php

Der wp Ordner soll den WordPress-Core enthalten und der wp-content Order nun jenen Code, den wir versionieren wollen. Wir müssen nun WordPress mitteilen, dass sich die Verzeichnisstruktur geändert hat. Das können wir mittels der wp-config.php tun, welche ebenfalls aus dem WordPress-Core eine Verzeichnisebene höher kopiert wurde. Folgendes müssen wir dazu eintragen:

/** Set Custom Content Directory. */
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content' );


/** Set Home and Siteurl. */
define('WP_HOME','http://' . $_SERVER['HTTP_HOST']);
define('WP_SITEURL','http://' . $_SERVER['HTTP_HOST'] . '/wp');

Gleiches gilt auch für die index.php Datei. Da sich das Root-Verzeichnis des Webservers nun ebenfalls eine Verzeichnisebene höher als der wp Ordner befindet, muss diese auch angepasst werden. Hier einfach den Pfad zur wp-blog-header.php Datei berichtigen:

<?php
define('WP_USE_THEMES', true);

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

Eine etwas genauere Anleitung dazu findet man sonst auch im WordPress Codex hier.

Nun haben wir die gewünschte Trennung erfolgreich zu Wege gebracht. Jetzt müssen wir noch dafür sorgen, dass sich das Projekt trotz der Dateien, die wir nicht versionieren, problemlos von jedem Entwickler aufsetzen lässt. Abhilfe schafft hier ein Package-Manager, welcher die zusätzlich benötigten Dateien und Bibliotheken verwaltet und beim Setup automatisch installiert. Ein solcher Package Manager, welcher sich für WordPress bzw. PHP Projekte eignet, ist Composer. Mittels der Datei composer.json, kann man alle Bibliotheken, die benötigt werden, spezifizieren. Als Packet-Quelle muss hier WP Packagist angegeben werden. Diese Quelle beinhaltet alle Themes und Plugins aus dem WordPress Plugin– bzw. Theme-Directory. Leider gibt es für den WordPress-Core keine offizielle Packet-Quelle. Hier hat jedoch ein Entwickler ein Repository angelegt, welches sich alle 15 min das offizielle WordPress SVN Repository synchronisiert. Die composer.json Datei ist in unserem Fall wie folgt aufgebaut:

{
    "name": "mblum/test_wp",
    "authors": [
        {
            "name": "Mathias Blum",
            "email": "email"
        }
    ],
    "repositories": [
        {
            "type": "composer",
            "url": "https://wpackagist.org"
        }
    ],
    "require": {
        "johnpbloch/wordpress": "^4.9",
        "wpackagist-theme/twentyseventeen": "^1.6"
    },
    "extra": {
        "wordpress-install-dir": "public/wp",
        "installer-paths": {
            "public/wp-content/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
            "public/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "public/wp-content/themes/{$name}/": ["type:wordpress-theme"]
        }
    }
}

Unterhalb von „require“ können nun die Dependencies inklusive Versionsnummer angegeben werden, konkret habe ich hier WordPress und ein zugehöriges Theme angegeben. Mittels des Feldes „extra“ müssen wir noch den Pfad des Installationsverzeichnisses, sowie der Themes und Plugins angeben, damit Composer die Dateien in die richtigen Ordner installiert. Nun kann man mit dem Befehl composer install das Projekt installieren. Nun erhalten wir folgende Dateistruktur in unserem Git Repository:


.git/
public/
vendor/
.gitignore
composer.json
composer.lock
wp-config-development.php
wp-config-sample.php

Als Web-Root sollte der public Ordner gesetzt sein. In diesem befinden sich die zuvor besprochenen Dateien. Das vendor Verzeichnis wird von Composer benötigt um die Dependencies zu installieren. Folgende Dateien werden nun in der .gitignore spezifiziert, um nicht versioniert zu werden:

/public/wp

/vendor

wp-config-*.php
!wp-config-sample.php

!public/wp-content/themes
public/wp-content/themes/*
!public/wp-content/themes/custom-theme

!public/wp-content/plugins
public/wp-content/plugins/*
!public/wp-content/plugins/custom-plugin

public/wp-content/uploads/*
!public/wp-content/uploads/.htaccess

!public/wp-content/languages
public/wp-content/languages/*

Will man nun noch unterschiedliche Umgebungen und Konfiguration unterstützen, so kann man in der wp-config.php noch folgenden Eintrag hinzufügen:

/** Load environment specific config file. */
if ( file_exists( dirname( __FILE__ ) . '/../wp-config-production.php' ) ) {
    define( 'WP_LOCAL_DEV', false );
    include( dirname( __FILE__ ) . '/../wp-config-production.php' );
}
else if ( file_exists( dirname( __FILE__ ) . '/../wp-config-staging.php' ) ) {
    define( 'WP_LOCAL_DEV', false );
    include( dirname( __FILE__ ) . '/../wp-config-staging.php' );
}
else {
    define( 'WP_LOCAL_DEV', true );
    include( dirname( __FILE__ ) . '/../wp-config-development.php' );
}

Zum Schluss noch einen Tipp zum Umgang mit den Uploads. Mittels .htaccess Datei kann man diese direkt vom Live-System beziehen, indem man einen URL-Redirect festlegt, welcher prüft ob die angeforderte Datei lokal vorhanden ist oder nicht.


<IfModule mod_rewrite.c>
  RewriteEngine on

  # Attempt to load files from production if
  # they're not in our local version
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule wp-content/uploads/(.*) \
    http://{PROD}/wp-content/uploads/$1 [NC,L]
</IfModule>

Den gesamte Source Code zu dem Blogartikel findest du auch auf Github (https://github.com/blumma/wp-template). Ich habe mir hier ein WordPress Projekt Template angelegt.