In un progetto PHP, quali modelli esistono per archiviare, accedere e organizzare gli oggetti di supporto? [chiuso]


114

Come organizzi e gestisci i tuoi oggetti di supporto come il motore di database, le notifiche agli utenti, la gestione degli errori e così via in un progetto orientato agli oggetti basato su PHP?

Supponiamo che io abbia un CMS PHP di grandi dimensioni. Il CMS è organizzato in varie classi. Alcuni esempi:

  • l'oggetto database
  • Gestione utenti
  • un'API per creare / modificare / eliminare elementi
  • un oggetto di messaggistica per visualizzare i messaggi all'utente finale
  • un gestore di contesto che ti porta alla pagina giusta
  • una classe della barra di navigazione che mostra i pulsanti
  • un oggetto di registrazione
  • possibilmente, gestione degli errori personalizzata

eccetera.

Ho a che fare con l'eterna domanda, come rendere al meglio questi oggetti accessibili a ogni parte del sistema che ne ha bisogno.

il mio primo apporach, molti anni fa, fu di avere un'applicazione globale $ che conteneva istanze inizializzate di queste classi.

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

Quindi sono passato al pattern Singleton e a una funzione di fabbrica:

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

ma non sono contento neanche di questo. I test unitari e l'incapsulamento diventano sempre più importanti per me e, nella mia comprensione, la logica dietro le globali / singleton distrugge l'idea di base di OOP.

Poi c'è ovviamente la possibilità di dare a ogni oggetto un numero di puntatori agli oggetti helper di cui ha bisogno, probabilmente il modo più pulito, di risparmio di risorse e di facile verifica, ma ho dubbi sulla manutenibilità di questo a lungo termine.

La maggior parte dei framework PHP che ho esaminato utilizza il pattern singleton o le funzioni che accedono agli oggetti inizializzati. Entrambi gli approcci sono buoni, ma come ho detto non sono soddisfatto di nessuno dei due.

Vorrei ampliare il mio orizzonte su quali modelli comuni esistono qui. Sto cercando esempi, idee aggiuntive e suggerimenti verso risorse che discutano di questo da una prospettiva a lungo termine , nel mondo reale .

Inoltre, mi interessa conoscere approcci specializzati, di nicchia o semplicemente strani al problema.


1
Ho appena fatto una domanda estremamente simile che aveva anche una taglia. Si può apprezzare alcune risposte ci: stackoverflow.com/questions/1967548/...
philfreo

3
Solo un avvertimento, restituire un nuovo oggetto per riferimento $mh=&factory("messageHandler");è inutile e non produce alcun vantaggio in termini di prestazioni. Inoltre, questo è deprecato in 5.3.
Ryeguy

Risposte:


68

Eviterei l'approccio Singleton suggerito da Flavius. Ci sono numerosi motivi per evitare questo approccio. Viola i buoni principi OOP. Il blog di test di Google ha alcuni buoni articoli sul Singleton e su come evitarlo:

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

alternative

  1. un fornitore di servizi

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

  2. iniezione di dipendenza

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

    e una spiegazione php:

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

Questo è un buon articolo su queste alternative:

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

Implementazione dell'inserimento delle dipendenze (DI):

Qualche pensiero in più sulla soluzione di Flavio. Non voglio che questo post sia un anti-post, ma penso che sia importante vedere perché l'iniezione di dipendenza è, almeno per me, migliore dei globali.

Anche se non è un'implementazione "vera" di Singleton , penso ancora che Flavius ​​abbia sbagliato. Lo stato globale è cattivo . Si noti che tali soluzioni utilizzano anche metodi statici difficili da testare .

So che molte persone lo fanno, lo approvano e lo usano. Ma leggere gli articoli del blog di Misko Heverys ( un esperto di testabilità di Google ), rileggerlo e digerire lentamente ciò che dice ha cambiato molto il mio modo di vedere il design.

Se vuoi essere in grado di testare la tua applicazione, dovrai adottare un approccio diverso alla progettazione della tua applicazione. Quando esegui una programmazione test-first, avrai difficoltà con cose come questa: 'ora voglio implementare la registrazione in questa parte di codice; Scriviamo prima un test che registri un messaggio di base 'e poi inventiamo un test che ti costringe a scrivere e utilizzare un logger globale che non può essere sostituito.

Sto ancora lottando con tutte le informazioni che ho ricevuto da quel blog e non è sempre facile da implementare, e ho molte domande. Ma non c'è modo che io possa tornare a quello che ho fatto prima (sì, stato globale e Singletons (grande S)) dopo aver capito cosa stava dicendo Misko Hevery :-)


+1 per DI. Anche se non lo uso quanto vorrei, è stato molto utile in qualunque piccola quantità l'ho usato.
Anurag

1
@koen: vuoi fornire un esempio PHP di un'implementazione DI / SP in PHP? Forse il codice @Flavius ​​implementato utilizzando i modelli alternativi che hai suggerito?
Alix Axel

Aggiunto un collegamento all'implementazione e al contenitore DI nella mia risposta.
Thomas

Sto leggendo tutto questo ora ma non l'ho ancora letto tutto, vorrei chiedere, un framework di iniezione di dipendenza sarebbe fondamentalmente un registro?
JasonDavis

No, non proprio. Ma un contenitore di inserimento delle dipendenze può anche servire come registro. Basta leggere i link che ho pubblicato nella mia risposta. Il conpect di DI è spiegato davvero praticamente.
Thomas,

16
class Application {
    protected static $_singletonFoo=NULL;

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

}

Questo è il modo in cui lo farei. Crea l'oggetto su richiesta:

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

È il modo in cui lo sto facendo, rispetta i principi OOP, è meno codice di come lo stai facendo adesso e l'oggetto viene creato solo quando il codice ne ha bisogno per la prima volta.

Nota : quello che ho presentato non è nemmeno un vero e proprio pattern singleton. Un singleton consentirebbe solo un'istanza di se stesso definendo il costruttore (Foo :: __ constructor ()) come privato. È solo una variabile "globale" disponibile per tutte le istanze "Applicazione". Ecco perché penso che il suo utilizzo sia valido in quanto NON ignora i buoni principi OOP. Naturalmente, come qualsiasi cosa al mondo, anche questo "modello" non dovrebbe essere abusato!

Ho visto questo essere utilizzato in molti framework PHP, Zend Framework e Yii tra di loro. E dovresti usare un framework. Non ti dirò quale.

Addendum Per quelli tra voi che si preoccupano del TDD , potete ancora inventare qualche cablaggio per iniettarlo in dipendenza. Potrebbe assomigliare a questo:

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;
        }
}

C'è abbastanza spazio per miglioramenti. È solo un PoC, usa la tua immaginazione.

Perché è così? Bene, la maggior parte delle volte l'applicazione non verrà testata in unità, verrà effettivamente eseguita, si spera in un ambiente di produzione . La forza di PHP è la sua velocità. PHP NON è e non sarà mai un "linguaggio OOP pulito", come Java.

All'interno di un'applicazione, c'è solo una classe Application e solo un'istanza di ciascuno dei suoi helper, al massimo (come per il caricamento lento come sopra). Certo, i singleton sono cattivi, ma poi di nuovo, solo se non aderiscono al mondo reale. Nel mio esempio, lo fanno.

Le "regole" stereotipate come "i singleton sono cattivi" sono la fonte del male, sono per i pigri che non vogliono pensare da soli.

Sì, lo so, il manifesto PHP è MALE, tecnicamente parlando. Eppure è un linguaggio di successo, nel suo modo hacker.

appendice

Uno stile di funzione:

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();

2
Ho svalutato la risposta perché credo che suggerire il modello singleton per gestire il problema vada contro i solidi principi OOP.
koen

1
@koen: quello che stai dicendo è vero, in generale, MA per quanto ho capito il suo problema, sta parlando di aiutanti per l'applicazione, e all'interno di un'applicazione ce n'è solo uno ... uhm, applicazione.
Flavius

Nota: quello che ho presentato non è nemmeno un vero e proprio pattern singleton. Un singleton consentirebbe solo un'istanza di una classe definendo il costruttore come privato. È solo una variabile "globale" disponibile per tutte le istanze "Applicazione". Ecco perché penso che sia valido NON trascura i buoni principi OOP. Naturalmente, come qualsiasi cosa al mondo, anche questo "modello" non dovrebbe essere abusato.
Flavius

-1 anche da me. Potrebbe essere solo la metà del Singleton DP, ma è il brutto: "fornisci un accesso globale ad esso".
solo qualcuno il

2
Questo rende effettivamente il suo approccio attuale molto più pulito.
Daniel Von Fange

15

Mi piace il concetto di Dependency Injection:

"Dependency Injection è dove ai componenti vengono assegnate le loro dipendenze tramite i loro costruttori, metodi o direttamente nei campi. (Dal sito web di Pico Container )"

Fabien Potencier ha scritto una serie di articoli davvero carini sull'iniezione di dipendenze e sulla necessità di usarli. Offre anche un grazioso e piccolo contenitore di iniezione delle dipendenze chiamato Pimple che mi piace molto usare (maggiori informazioni su GitHub ).

Come detto sopra, non mi piace l'uso di Singletons. Un buon riassunto sul motivo per cui i Singletons non sono un buon design può essere trovato qui nel blog di Steve Yegge .


Mi piace l'implementazione tramite Closures in PHP, lettura molto interessante
Juraj Blahunka

Anch'io e lui ha altre cose necessarie per quanto riguarda le chiusure sul suo sito: fabien.potencier.org/article/17/…
Thomas

2
speriamo che le webhouse tradizionali migrino presto a PHP 5.3, poiché non è ancora comune vedere un server php 5.3 completo
Juraj Blahunka

Dovranno, quando sempre più progetti richiedono PHP 5.3 come Zend Framework 2.0, framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
L'iniezione di dipendenza è stata accettata anche come risposta alla domanda su decupling from GOD object: stackoverflow.com/questions/1580210/… con un esempio molto carino
Juraj Blahunka

9

L'approccio migliore è avere una sorta di contenitore per quelle risorse. Alcuni dei modi più comuni per implementare questo contenitore :

Singleton

Non consigliato perché è difficile da testare e implica uno stato globale. (Singletonitis)

Registro

Elimina la singletonite, bug che non consiglierei anche al registro, perché è anche una specie di singleton. (Difficile da testare)

Eredità

Peccato, non esiste un'ereditarietà multipla in PHP, quindi questo limita tutto alla catena.

Iniezione di dipendenza

Questo è un approccio migliore, ma un argomento più ampio.

Tradizionale

Il modo più semplice per farlo è usare il costruttore o l'iniezione setter (passare l'oggetto dipendenza usando setter o nel costruttore della classe).

Frameworks

È possibile eseguire il rollio del proprio iniettore di dipendenza o utilizzare alcuni dei framework di inserimento delle dipendenze, ad es. yadif

Risorsa dell'applicazione

Puoi inizializzare ciascuna delle tue risorse nel bootstrap dell'applicazione (che funge da contenitore) e accedervi ovunque nell'app accedendo all'oggetto bootstrap.

Questo è l'approccio implementato in Zend Framework 1.x

Caricatore di risorse

Una sorta di oggetto statico che carica (crea) la risorsa necessaria solo quando necessaria. Questo è un approccio molto intelligente. Lo si può vedere in azione, ad esempio implementando il componente Dependency Injection di Symfony

Iniezione a strato specifico

Le risorse non sono sempre necessarie ovunque nell'applicazione. A volte ne hai solo bisogno, ad esempio nei controller (MV C ). Quindi puoi iniettare le risorse solo lì.

L'approccio comune a questo è l'utilizzo di commenti docblock per aggiungere metadati di injection.

Guarda il mio approccio a questo qui:

Come utilizzare l'inserimento delle dipendenze in Zend Framework? - Stack Overflow

Alla fine, vorrei aggiungere una nota su una cosa molto importante qui: la cache.
In generale, nonostante la tecnica che scegli, dovresti pensare a come le risorse verranno memorizzate nella cache. La cache sarà la risorsa stessa.

Le applicazioni possono essere molto grandi e caricare tutte le risorse su ogni richiesta è molto costoso. Ci sono molti approcci, incluso questo appserver-in-php - Project Hosting su Google Code .


6

Se vuoi rendere gli oggetti disponibili a livello globale, il pattern del registro potrebbe essere interessante per te. Per ispirazione, dai un'occhiata a Zend Registry .

Quindi anche la domanda Registry vs. Singleton .


Se non vuoi usare Zend Framework, ecco una bella implementazione del pattern di registro per PHP5: phpbar.de/w/Registry
Thomas

Preferisco un pattern di registro digitato, come Registry :: GetDatabase ("master"); Registry :: getSession ($ user-> SessionKey ()); Registry :: GetConfig ( "locale"); [...] e definendo un'interfaccia per ogni tipo. In questo modo ti assicuri di non sovrascrivere accidentalmente una chiave usata per qualcosa di diverso (es. Potresti avere un "database principale" e una "configurazione principale". Utilizzando le interfacce ti assicuri che vengano utilizzati solo oggetti validi. Ofc questo potrebbe essere realizzata anche con l'utilizzo di più classi di registro, ma imho uno solo è più semplice e più facile da usare, ma ha ancora il vantaggio.
Morfildur

O ovviamente quello integrato in PHP - $ _GLOBALS
Gnuffo1

4

Gli oggetti in PHP occupano una buona quantità di memoria, come probabilmente avrai visto dai tuoi unit test. Pertanto è ideale distruggere gli oggetti non necessari il prima possibile per risparmiare memoria per altri processi. Con questo in mente trovo che ogni oggetto si adatti a uno dei due stampi.

1) L'oggetto potrebbe avere molti metodi utili o deve essere chiamato più di una volta, nel qual caso implemento un singleton / registro:

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

2) L'oggetto esiste solo per la vita del metodo / funzione che lo chiama, nel qual caso una semplice creazione è utile per evitare che i riferimenti a oggetti persistenti mantengano gli oggetti in vita troppo a lungo.

$object = new Class();

La memorizzazione di oggetti temporanei OVUNQUE potrebbe portare a perdite di memoria perché i riferimenti ad essi potrebbero essere dimenticati per mantenere l'oggetto in memoria per il resto dello script.


3

Andrei per la funzione che restituisce oggetti inizializzati:

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

Nell'ambiente di test è possibile definirlo per restituire mock-up. Puoi persino rilevare all'interno chi chiama la funzione usando debug_backtrace () e restituire oggetti diversi. Puoi registrare al suo interno chi vuole ottenere quali oggetti per avere un'idea di cosa sta effettivamente succedendo all'interno del tuo programma.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.