Welche Muster gibt es in einem PHP-Projekt zum Speichern, Zugreifen auf und Organisieren von Hilfsobjekten? [geschlossen]

114

Wie organisieren und verwalten Sie Ihre Hilfsobjekte wie das Datenbankmodul, Benutzerbenachrichtigungen, Fehlerbehandlung usw. in einem PHP-basierten, objektorientierten Projekt?

Angenommen, ich habe ein großes PHP-CMS. Das CMS ist in verschiedene Klassen unterteilt. Einige Beispiele:

  • das Datenbankobjekt
  • Benutzerverwaltung
  • eine API zum Erstellen / Ändern / Löschen von Elementen
  • Ein Nachrichtenobjekt zum Anzeigen von Nachrichten für den Endbenutzer
  • Ein Kontext-Handler, der Sie zur richtigen Seite führt
  • Eine Navigationsleistenklasse, die Schaltflächen anzeigt
  • ein Protokollierungsobjekt
  • möglicherweise benutzerdefinierte Fehlerbehandlung

etc.

Ich beschäftige mich mit der ewigen Frage, wie diese Objekte am besten für jeden Teil des Systems zugänglich gemacht werden können, der sie benötigt.

Mein erster Ansatz vor vielen Jahren war es, eine globale $ -Anwendung zu haben, die initialisierte Instanzen dieser Klassen enthielt.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

Ich habe dann auf das Singleton-Muster und eine Factory-Funktion umgestellt:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

aber damit bin ich auch nicht zufrieden. Unit-Tests und Kapselung werden für mich immer wichtiger, und nach meinem Verständnis zerstört die Logik hinter Globalen / Singletons die Grundidee von OOP.

Dann gibt es natürlich die Möglichkeit, jedem Objekt eine Reihe von Zeigern auf die benötigten Hilfsobjekte zu geben, wahrscheinlich die sauberste, ressourcenschonendste und testfreundlichste Methode, aber ich habe Zweifel an der Wartbarkeit auf lange Sicht.

Die meisten PHP-Frameworks, die ich untersucht habe, verwenden entweder das Singleton-Muster oder Funktionen, die auf die initialisierten Objekte zugreifen. Beide guten Ansätze, aber wie gesagt, ich bin mit keinem zufrieden.

Ich möchte meinen Horizont dahingehend erweitern, welche gemeinsamen Muster hier existieren. Ich suche nach Beispielen, zusätzlichen Ideen und Hinweisen auf Ressourcen, die dies aus einer langfristigen , realen Perspektive diskutieren .

Ich bin auch daran interessiert, etwas über spezialisierte, Nischen- oder einfach seltsame Ansätze für das Problem zu erfahren.

Pekka 웃
quelle
1
Ich habe gerade eine sehr ähnliche Frage gestellt, die auch ein Kopfgeld hatte. Sie können einige Antworten dort schätzen: stackoverflow.com/questions/1967548/…
philfreo
3
Nur ein Kopf hoch, ein neues Objekt als Referenz zurückzugeben - wie $mh=&factory("messageHandler");es sinnlos ist und keinen Leistungsvorteil bringt. Darüber hinaus ist dies in 5.3 veraltet.
Ryeguy

Antworten:

68

Ich würde den von Flavius ​​vorgeschlagenen Singleton-Ansatz vermeiden. Es gibt zahlreiche Gründe, diesen Ansatz zu vermeiden. Es verstößt gegen gute OOP-Prinzipien. Der Google-Testblog enthält einige gute Artikel über Singleton und wie man dies vermeidet:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Alternativen

  1. ein Dienstleister

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. Abhängigkeitsspritze

    http://en.wikipedia.org/wiki/Dependency_injection

    und eine PHP-Erklärung:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

Dies ist ein guter Artikel über diese Alternativen:

http://martinfowler.com/articles/injection.html

Implementieren der Abhängigkeitsinjektion (DI):

Noch ein paar Gedanken zu Flavius ​​'Lösung. Ich möchte nicht, dass dieser Beitrag ein Anti-Beitrag ist, aber ich denke, es ist wichtig zu sehen, warum die Abhängigkeitsinjektion zumindest für mich besser ist als globale.

Auch wenn es keine "echte" Singleton- Implementierung ist, denke ich, dass Flavius ​​es falsch verstanden hat. Der globale Zustand ist schlecht . Beachten Sie, dass solche Lösungen auch schwer zu testende statische Methoden verwenden .

Ich weiß, dass viele Leute es tun, genehmigen und verwenden. Aber das Lesen von Misko Heverys Blog-Artikeln ( ein Experte für Google-Testbarkeit ), das erneute Lesen und das langsame Verdauen dessen, was er sagt, haben die Art und Weise, wie ich Design sehe, stark verändert.

Wenn Sie Ihre Anwendung testen möchten, müssen Sie beim Entwerfen Ihrer Anwendung einen anderen Ansatz wählen. Wenn Sie Test-First-Programmierung durchführen, werden Sie Schwierigkeiten mit folgenden Dingen haben: 'Als nächstes möchte ich die Protokollierung in diesem Code implementieren. Lassen Sie uns zuerst einen Test schreiben, der eine grundlegende Nachricht protokolliert, und dann einen Test erstellen, der Sie zwingt, einen globalen Logger zu schreiben und zu verwenden, der nicht ersetzt werden kann.

Ich habe immer noch Probleme mit all den Informationen, die ich von diesem Blog erhalten habe, und es ist nicht immer einfach zu implementieren, und ich habe viele Fragen. Aber ich kann auf keinen Fall zu dem zurückkehren, was ich zuvor getan habe (ja, Global State und Singletons (großes S)), nachdem ich verstanden habe, was Misko Hevery gesagt hat :-)

koen
quelle
+1 für DI. Obwohl ich es nicht so oft benutze, wie ich möchte, war es in allen kleinen Mengen, in denen ich es verwendet habe, sehr hilfreich.
Anurag
1
@koen: Möchtest du ein PHP-Beispiel für eine DI / SP-Implementierung in PHP geben? Vielleicht wurde @ Flavius-Code mit den von Ihnen vorgeschlagenen alternativen Mustern implementiert?
Alix Axel
In meiner Antwort wurde ein Link zur DI-Implementierung und zum Container hinzugefügt.
Thomas
Ich lese jetzt alles durch, aber ich habe noch nicht alles gelesen, möchte ich fragen, ob ein Abhängigkeitsinjektions-Framework im Grunde genommen eine Registrierung ist.
JasonDavis
Nein nicht wirklich. Ein Dependency Injection Container kann jedoch auch als Registrierung dienen. Lesen Sie einfach die Links, die ich in meiner Antwort gepostet habe. Der Aspekt von DI wird wirklich praktisch erklärt.
Thomas
16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

So würde ich es machen. Es erstellt das Objekt auf Anfrage:

Application::foo()->bar();

Es ist die Art und Weise, wie ich es mache, es respektiert die OOP-Prinzipien, es ist weniger Code als das, was Sie gerade machen, und das Objekt wird nur erstellt, wenn der Code es zum ersten Mal benötigt.

Hinweis : Was ich vorgestellt habe, ist nicht einmal ein echtes Singleton-Muster. Ein Singleton würde nur eine Instanz von sich selbst zulassen, indem er den Konstruktor (Foo :: __ constructor ()) als privat definiert. Es ist nur eine "globale" Variable, die allen "Anwendungs" -Instanzen zur Verfügung steht. Deshalb denke ich, dass seine Verwendung gültig ist, da es gute OOP-Prinzipien NICHT missachtet. Natürlich sollte dieses "Muster" wie alles auf der Welt auch nicht überstrapaziert werden!

Ich habe gesehen, dass dies in vielen PHP-Frameworks verwendet wird, darunter auch in Zend Framework und Yii. Und Sie sollten ein Framework verwenden. Ich werde dir nicht sagen, welche.

Nachtrag Für diejenigen unter Ihnen, die sich Sorgen um TDD machen , können Sie immer noch eine Verkabelung für die Abhängigkeitsinjektion erstellen. Es könnte so aussehen:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

Es gibt genug Raum für Verbesserungen. Es ist nur ein PoC, verwenden Sie Ihre Fantasie.

Warum so? Nun, die meiste Zeit wird die Anwendung nicht Unit-getestet, sondern tatsächlich ausgeführt, hoffentlich in einer Produktionsumgebung . Die Stärke von PHP ist seine Geschwindigkeit. PHP ist NICHT und wird niemals eine "saubere OOP-Sprache" wie Java sein.

Innerhalb einer Anwendung gibt es höchstens eine Anwendungsklasse und höchstens eine Instanz jedes ihrer Helfer (gemäß verzögertem Laden wie oben). Sicher, Singletons sind schlecht, aber nur dann, wenn sie nicht der realen Welt entsprechen. In meinem Beispiel tun sie es.

Stereotype "Regeln" wie "Singletons sind schlecht" sind die Quelle des Bösen, sie sind für faule Leute, die nicht bereit sind, für sich selbst zu denken.

Ja, ich weiß, das PHP-Manifest ist technisch gesehen SCHLECHT. Dennoch ist es eine erfolgreiche Sprache in ihrer hackigen Art.

Nachtrag

Ein Funktionsstil:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();
Flavius
quelle
2
Ich habe die Antwort abgelehnt, weil ich glaube, dass das Vorschlagen des Singleton-Musters zur Lösung des Problems gegen solide OOP-Prinzipien verstößt.
Koen
1
@koen: Was Sie sagen, ist im Allgemeinen wahr, ABER soweit ich sein Problem verstanden habe, spricht er über Helfer für die Anwendung, und innerhalb einer Anwendung gibt es nur eine ... ähm, Anwendung.
Flavius
Hinweis: Was ich vorgestellt habe, ist nicht einmal ein echtes Singleton-Muster. Ein Singleton würde nur eine Instanz einer Klasse zulassen, indem der Konstruktor als privat definiert wird. Es ist nur eine "globale" Variable, die allen "Anwendungs" -Instanzen zur Verfügung steht. Deshalb denke ich, dass seine Gültigkeit gute OOP-Prinzipien NICHT außer Acht lässt. Natürlich sollte dieses "Muster" wie alles auf der Welt auch nicht überstrapaziert werden.
Flavius
-1 auch von mir. Es ist vielleicht nur die eine Hälfte des Singleton DP, aber es ist die hässliche: "globalen Zugriff darauf gewähren".
Nur jemand
2
Dies macht seinen bestehenden Ansatz in der Tat viel sauberer.
Daniel Von Fange
15

Ich mag das Konzept der Abhängigkeitsinjektion:

"Bei der Abhängigkeitsinjektion erhalten Komponenten ihre Abhängigkeiten über ihre Konstruktoren, Methoden oder direkt in Felder. (Von der Pico Container-Website )"

Fabien Potencier hat eine wirklich schöne Artikelserie über Dependency Injection und die Notwendigkeit, sie zu verwenden, geschrieben. Er bietet auch einen schönen und kleinen Dependency Injection Container namens Pimple an, den ich sehr gerne benutze (mehr Infos zu Github ).

Wie oben erwähnt, mag ich die Verwendung von Singletons nicht. Eine gute Zusammenfassung darüber, warum Singletons kein gutes Design sind, finden Sie hier in Steve Yegges Blog .

Thomas
quelle
Ich mag die Implementierung durch Closures in PHP, sehr interessante Lektüre
Juraj Blahunka
Ich auch und er hat einige andere Bedürfnisse in Bezug auf Schließungen auf seiner Website: fabien.potencier.org/article/17/…
Thomas
2
Hoffen wir, dass die Mainstream-Webhouses bald auf PHP 5.3 migrieren, da es immer noch nicht üblich ist, einen voll funktionsfähigen PHP 5.3-Server zu sehen
Juraj Blahunka,
Sie müssen, wenn immer mehr Projekte PHP 5.3 wie Zend Framework 2.0 erfordern. Framework.zend.com/wiki/display/ZFDEV2/…
Thomas
1
Abhängigkeitsinjektion wurde auch akzeptiert Antwort auf Frage über decupling from GOD object: stackoverflow.com/questions/1580210/… mit einem sehr schönen Beispiel
Juraj Blahunka
9

Der beste Ansatz besteht darin, eine Art Container für diese Ressourcen zu haben. Einige der gebräuchlichsten Methoden zum Implementieren dieses Containers :

Singleton

Nicht empfohlen, da es schwer zu testen ist und einen globalen Zustand impliziert. (Singletonitis)

Registrierung

Beseitigt Singletonitis, Fehler Ich würde die Registrierung auch nicht empfehlen, da es sich auch um eine Art Singleton handelt. (Schwer zu Unit Test)

Erbe

Schade, dass es in PHP keine Mehrfachvererbung gibt, daher beschränkt sich dies auf die gesamte Kette.

Abhängigkeitsspritze

Dies ist ein besserer Ansatz, aber ein größeres Thema.

Traditionell

Der einfachste Weg, dies zu tun, ist die Verwendung der Konstruktor- oder Setter-Injektion (Übergeben des Abhängigkeitsobjekts mit dem Setter oder im Klassenkonstruktor).

Frameworks

Sie können Ihren eigenen Abhängigkeitsinjektor rollen oder einige der Abhängigkeitsinjektions-Frameworks verwenden, z. Yadif

Anwendungsressource

Sie können jede Ihrer Ressourcen im Anwendungs-Bootstrap (der als Container fungiert) initialisieren und an einer beliebigen Stelle in der App auf das Bootstrap-Objekt zugreifen.

Dies ist der in Zend Framework 1.x implementierte Ansatz

Ressourcenlader

Eine Art statisches Objekt, das die benötigte Ressource nur bei Bedarf lädt (erstellt). Dies ist ein sehr kluger Ansatz. Sie können es in Aktion sehen, z. B. die Implementierung der Dependency Injection-Komponente von Symfony

Injektion in eine bestimmte Schicht

Die Ressourcen werden nicht immer irgendwo in der Anwendung benötigt. Manchmal braucht man sie nur, zB in den Controllern (MV C ). Dann können Sie die Ressourcen nur dort injizieren.

Der übliche Ansatz hierfür ist die Verwendung von Docblock-Kommentaren zum Hinzufügen von Injektionsmetadaten.

Sehen Sie hier meinen Ansatz dazu:

Wie verwende ich die Abhängigkeitsinjektion in Zend Framework? - Paketüberfluss

Am Ende möchte ich hier einen Hinweis zu einer sehr wichtigen Sache hinzufügen - dem Caching.
Im Allgemeinen sollten Sie trotz der von Ihnen gewählten Technik überlegen, wie die Ressourcen zwischengespeichert werden. Der Cache ist die Ressource selbst.

Die Anwendungen können sehr groß sein, und das Laden aller Ressourcen bei jeder Anforderung ist sehr teuer. Es gibt viele Ansätze, einschließlich dieses Appserver-in-PHP - Project Hosting auf Google Code .

Takeshin
quelle
6

Wenn Sie Objekte global verfügbar machen möchten, könnte das Registrierungsmuster für Sie interessant sein. Inspiration finden Sie in der Zend-Registrierung .

So auch die Frage Registry vs. Singleton .

Felix Kling
quelle
Wenn Sie Zend Framework nicht verwenden möchten, finden Sie hier eine schöne Implementierung des Registrierungsmusters für PHP5: phpbar.de/w/Registry
Thomas
Ich bevorzuge ein typisiertes Registrierungsmuster wie Registry :: GetDatabase ("master"); Registry :: GetSession ($ user-> SessionKey ()); Registry :: GetConfig ("local"); [...] und Definieren einer Schnittstelle für jeden Typ. Auf diese Weise stellen Sie sicher, dass Sie nicht versehentlich einen Schlüssel überschreiben, der für etwas anderes verwendet wird (dh Sie haben möglicherweise eine "Master-Datenbank" und eine "Master-Konfiguration". Durch die Verwendung von Schnittstellen stellen Sie sicher, dass nur gültige Objekte verwendet werden kann auch durch die Verwendung mehrerer Registrierungsklassen implementiert werden, aber imho eine einzige ist einfacher und einfacher zu verwenden, hat aber immer noch die Vorteile.
Morfildur
Oder natürlich die in PHP eingebaute - $ _GLOBALS
Gnuffo1
4

Objekte in PHP beanspruchen viel Speicher, wie Sie wahrscheinlich aus Ihren Unit-Tests gesehen haben. Daher ist es ideal, nicht benötigte Objekte so schnell wie möglich zu zerstören , um Speicher für andere Prozesse zu sparen. In diesem Sinne finde ich, dass jedes Objekt in eine von zwei Formen passt.

1) Das Objekt verfügt möglicherweise über viele nützliche Methoden oder muss mehrmals aufgerufen werden. In diesem Fall implementiere ich ein Singleton / eine Registrierung:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) Das Objekt existiert nur für die Lebensdauer der Methode / Funktion, die es aufruft. In diesem Fall ist eine einfache Erstellung hilfreich, um zu verhindern, dass verweilende Objektreferenzen Objekte zu lange am Leben erhalten.

$object = new Class();

Das Speichern temporärer Objekte ÜBERALL kann zu Speicherverlusten führen, da Verweise darauf möglicherweise vergessen werden, das Objekt für den Rest des Skripts im Speicher zu belassen .

Xeoncross
quelle
3

Ich würde mich für eine Funktion entscheiden, die initialisierte Objekte zurückgibt:

A('Users')->getCurrentUser();

In der Testumgebung können Sie definieren, dass Modelle zurückgegeben werden. Mit debug_backtrace () können Sie sogar erkennen, wer die Funktion aufruft, und verschiedene Objekte zurückgeben. Sie können sich darin registrieren, wer welche Objekte erhalten möchte, um einige Einblicke zu erhalten, was tatsächlich in Ihrem Programm vor sich geht.

Kamil Szot
quelle
-1

Warum nicht das schöne Handbuch lesen?

http://php.net/manual/en/language.oop5.autoload.php

gcb
quelle
Danke gcb, aber das Laden von Klassen ist nicht mein Anliegen, meine Frage ist eher architektonischer Natur.
Pekka
Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
jjnguy