Home > Archiv > Zend MVC und Skripts für die Kommandozeile

Zend MVC und Skripts für die Kommandozeile

Geschrieben von   admin on    Juni 17, 2011

Wenn man Skripte für die Kommandozeile erstellen will, die die Klassen eines Zend MVC-Projektes benötigen, wird man feststellen, dass die Projektstruktur eine solche Aufgabe nicht gut unterstützt. So könnte man z.B. auf Model-Klassen zugreifen wollen. Oder aber auch ganz andere Teile des Projektes. Ideal wäre doch eigentlich, wenn man nicht allzu große Unterschiede hätte, egal, ob es sich um die Webanwendung oder ein Konsolenprogramm handelt.

Das unten stehende ist sicherlich nichts für Zend Framework-Einsteiger. Machen Sie sich als Einsteiger bitte erst mit dem Zend Framework vertraut, bevor Sie hier weiterlesen.

Ich habe nachträglich ein paar Recherchen angestellt, um herauszufinden, wie andere Leute mit dieser Problemstellung umgegangen sind. Unter anderem gab es Vorschläge, man solle per require_once alles nötige inkludieren. Das erscheint mir zu aufwendig zu sein. Warum soll man nicht von eingebauten Mechanismen von Zend MVC-Anwendungen profitieren. Zudem benutze ich das Zend Framework nur in der Autoload-Variante, bei der alle "require once"-Aufrufe entfernt sind - bis auf zwei Dateien: Zend/Loader/Autoloader.php und Zend/Application.php. Allerdings muss in der public/index.php dann folgendes stehen (direkt oberhalb der Stelle, bei der das $application-Objekt instantiert wird):

    require_once 'Zend/Loader/Autoloader.php';
    Zend_Loader_Autoloader::getInstance();

Sämtliche Klassen können nun per Autoloading eingebunden werden, was performanter ist. Dazu muss der include_path nur passend gesetzt sein.

Im Performance Guide der Dokumentation zum Zend Framework, Abschnitt "Class Loading", Unterabschnitt "How can I eliminate unnecessary require_once statements?" ist das genauer beschrieben. Tipp: Unter Windows steht einem sed nicht zur Verfügung. Ein nützliches Tool mit grafischer Oberfläche, was auch Ersetzungen mittels regulären Ausdrücken in Dateien eines ganzen Verzeichnisbaums durchführen kann ist PowerGREP. Zwar kostenpflichtig, aber genial! Ich setze es fast täglich ein.

Zurück zum eigentlichen Thema. Die Technik, die ich hier vorstelle, setze ich übrigens in ähnlicher Form auch gerne für Unit-Tests ein. Die Ausführungen gelten für die Autoload-Variante des Zend Framework.

Die übliche Projektstruktur, die man erhält, wenn man das Kommandozeilen-Tool zf einsetzt, erzeugt eine Datei public/index.php, die die Konstante APPLICATION_PATH erzeugt, den include-Path festlegt, das Objekt $application erzeugt, darüber das Bootstrapping ausführt, und mittes $application->run(); die Webanwendung "startet".
Für Konsolen-Skripts wäre es praktisch, wenn das Bootstrapping stattfindet, aber $application->run() nicht aufgerufen wird.

Statt nun die public/index.php zu kopieren und etwas abzuwandeln, habe ich die Sache anders gelöst. Schließlich gilt dass Prinzip DRY (Don't repeat yourself). Meine public/index.php besteht nur aus diesen beiden Zeilen (MyProject ist der Stellvertretername für das konkrete Projekt):

require_once(realpath(__DIR__ . '/../library/MyProject/Application/Init.php'));
MyProject_Application_Init::init(true)->run();

Es wird also eine Datei Init.php aus dem Verzeichnis library/MyProject/Application eingebunden. Ich lege zum Projekt gehörende indivduelle Klassen, die in vielen Anwendungsteilen benötigt werden, gerne in das library-Verzeichnis. Da dieses ohnehin im include-Path liegt, ergeben sich keine weiteren Komplikationen. Die Datei enthält folgende Klasse:

class MyProject_Application_Init
{
    /**
     * Initialisiert die Umgebung und stößt das Bootstrappen an.
     *
     * Der Code ist der, den man normalerweise in public/index.php findet
     * - bis auf $application->run(), was hier fehlt, wird aber eben auch für 
     * Kommandozeilenprogramme benötigt.
     *
     * @return Zend_Application
     */
    public static function init($isWebApp = false)
    {
        defined('APPLICATION_PATH') || define(
            'APPLICATION_PATH',
            realpath(__DIR__ . '/../../../application')
        );
        // Der Bequemlichkeit halber: Pfad zum Wurzelverzeichnis im
        // Dateisystem definieren
        defined('APPLICATION_ROOT') || define(
            'APPLICATION_ROOT',
            realpath(__DIR__ . '/../../../')
        );
        // Ablaufumgebung: Wenn nicht anders definiert,
        // wird von production ausgegangen
        defined('APPLICATION_ENV') || define(
            'APPLICATION_ENV',
            (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') 
                                       : 'production')
        );
        // Sicherstellen, dass library im include_path ist, sowie aktuelles
        // Verzeichnis, sonst nichts
        set_include_path(implode(PATH_SEPARATOR, array(
            realpath(APPLICATION_ROOT . '/library'),
            '.'
        )));
        // Es wird die autoload-Version des Zend Frameworks verwendet, da
        // erheblich performanter. Dafür ist es aber zwingend erforderlich,
        // den Autoloader zu starten (an sich wird er für ZF-interne
        // Sonderfälle eingesetzt, beherrscht aber auch das konventionelle
        // Autoloading nach PEAR-Konventionen)
        require_once 'Zend/Loader/Autoloader.php';
        Zend_Loader_Autoloader::getInstance();
        $application = new Zend_Application(
            APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'
        );
        $application->bootstrap();
        // Hier gibt's bewusst kein $application->run(), denn die Einrichtung
        // der Umgebung wird eben auch für Kommandozeilenprogramme benötigt.
        if (!$isWebApp) {
            // Der gleiche "Trick", der auch bei Tests zum Einsatz kommt,
            // um den Front-Controller zu instanzieren, und das Bootstrap-
            // Objekt an den Front-Controller zu übergeben
            $bootstrap = $application->getBootstrap();
            $front = $bootstrap->getResource('FrontController');
            $front->setParam('bootstrap', $bootstrap);
        }
        return $application;
    }
}

Wie die public/index.php aussieht, steht weiter oben. Wie sieht nun aber ein typisches PHP-Skript für die Kommandozeile aus?

Ein Beispiel:

require_once(realpath(__DIR__ .'/../library/MyProject/Application/Init.php'));
// Im Gegensatz zur Webanwendung gibt's an dieser stelle kein ...->run()
MyProject_Application_Init::init();
// Einfachen Zugriff auf die Konfiguration in application.ini ermöglichen
$config = new Zend_Config_Ini(
    APPLICATION_PATH . '/configs/application.ini',
    APPLICATION_ENV
);
// Ab hier können z.B. mit den Model-Klassen Importe durchgeführt werden,
// oder was einem sonst so einfällt.

Ideal ist so etwas für Cron Jobs / Tasks, wie z.B. automatische wiederkehrende Importe. Achten Sie aber unbedingt darauf, das Skript außerhalb des Web Roots zu platzieren, damit es nicht böswillig per Browser aufgerufen werden kann.

 Tags:

Zend Framework

Kommentare

Kommentar schreiben



(Ihre E-Mail-Adresse wird nicht angezeigt.)


Ungültiger Sicherheitscode

Bitte klicken Sie das Bild an, um einen neuen Sicherheitscode zu laden.