Smetti di usare `global` in PHP

Ho un config.php che è incluso in each pagina. In config creo un arrays che assomiglia a qualcosa:

 $config = arrays(); $config['site_name'] = 'Site Name'; $config['base_path'] = '/home/docs/public_html/'; $config['libraries_path'] = $config['base_path'] . '/libraries'; //etc... 

Poi ho function.php , che è anche incluso in quasi each pagina, where devo usare global $config per accedervi – ed è questo che mi piacerebbe sbarazzarmi!

Come posso accedere a $config nelle altre parti del mio codice senza usare global ?

Qualcuno potrebbe spiegare, perché non dovrei usare global nel mio esempio? Alcuni dicono che è un brutto tono, altri dicono che non è sicuro?

MODIFICA 1:

Esempio di where e come lo utilizzo:

 function conversion($Exec, $Param = arrays(), $Log = '') { global $config; $cmd = $config['phppath'] . ' ' . $config['base_path'] . '/' . $Exec; foreach ($Param as $s) { $cmd .= ' ' . $s; } } 

MODIFICA 2:

Mettere tutto questo nella class, come suggerito da Vilx , sarebbe bello, ma in questo caso, come lo legherei al seguente ciclo che estrae key e value configuration dal database.
Ho semplificato eccessivamente l'idea di assegnare $config arrays, ecco un esempio:

 $sql = "SELECT * from settings"; $rsc = $db->Execute($sql); if ( $rsc ) { while(!$rsc->EOF) { $field = $rsc->fields['setting_options']; $config[$field] = $rsc->fields['setting_values']; @$rsc->MoveNext(); } } 

MODIFICA 3:

Inoltre, devo accedere ad altre vars da funzioni che sono impostate in config e sono poche, ad esempio: $db , $language ed ecc.

Se li metto in class, risolverà davvero qualcosa? Se uso global cosa cambia davvero?

MODIFICA 4:

Leggo PHP globale in funzioni in cui Gordon spiega in modo molto bello il motivo per cui non dovresti usare global . Sono d'accordo su tutto ma non uso il global nel mio caso per riassegnare le variables, il che si tradurrà in, come ha detto, <-- WTF!! ,;)) Sì, sono d'accordo, è pazzesco. Ma se ho solo bisogno di accedere al database da una function solo usando global $db where è il problema in questo caso? Come si fa diversamente, senza usare il global ?

MODIFICA 5:

Nello stesso PHP globale in funzioni, l' inganno dice: "L'unica grande ragione contro il globale è che significa che la function dipende da un altro ambito, che diventerà molto caotico".

Ma sto parlando di "INIT" di base. Fondamentalmente io define ma uso vars – beh, questo è sbagliato in modo tecnico. Ma la tua function non dipende da nulla – ma il nome di una var $db che potresti tenere a mente? È davvero necessario utilizzare $db , dov'è la DIPENDENZA qui e come usarla altrimenti?

PS Ho solo pensato che siamo di fronte al conflitto qui di due menti diverse, ad esempio: la mia (ancora NON ben comprensibile la programmazione orientata agli oggetti) e coloro che potrebbero essere chiamati guru (dal mio punto di vista attuale) in OOP – Ciò che sembra ovvio per loro per me pone nuove domande. Penso che sia per questo che questa domanda viene ripetuta più e più volte. Personalmente per me è diventato più chiaro dopo tutto, ma ci sono ancora cose da chiarire.

Il punto contro le variables global è che accoppiano il codice molto strettamente. L' integer codice base dipende da a) il nome della variabile $config eb) l'esistenza di tale variabile. Se si desidera rinominare la variabile (per qualsiasi motivo), è necessario farlo in each parte della base di codici. Non è inoltre ansible utilizzare alcun pezzo di codice che dipende dalla variabile indipendentemente da esso.

Esempio con variabile global :

 require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

Ovunque nelle righe precedenti è ansible che si SomeClass.php un errore perché la class o qualche codice in SomeClass.php dipende implicitamente da una variabile globale $config . Non c'è alcuna indicazione su questo, anche se solo guardando la class. Per risolvere questo, devi fare questo:

 $config = arrays(...); require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

Questo codice potrebbe fallire da qualche parte se non si impostano le chiavi corrette all'interno di $config . Dato che non è ovvio quali parti della matrix di configuration di cui SomeClass necessita o non ha bisogno e quando ne ha bisogno, è difficile ricreare l'ambiente corretto perché funzioni correttamente. Crea anche conflitti se ti capita di avere già una variabile $config usata per qualcos'altro, ovunque tu voglia usare SomeClass .

Quindi, invece di creare dependencies implicite e invisibili, iniettare tutte le dependencies:

 require 'SomeClass.php'; $arbitraryConfigVariableName = arrays(...); $class = new SomeClass($arbitraryConfigVariableName); $class->doSomething(); 

Passando esplicitamente l'arrays config come parametro, tutti i problemi sopra indicati vengono risolti. È semplice come consegnare le informazioni necessarie all'interno della tua app. Rende anche la struttura e il stream dell'applicazione e ciò che parla di ciò che è molto più chiaro. Per arrivare a questo stato se la tua applicazione è attualmente una grossa palla di fango potrebbe richiedere qualche ristrutturazione.


Più grande è il codice, più devi separare le singole parti l'una dall'altra. Se each parte dipende da each altra parte della base di codice, non è ansible testare, utilizzare o riutilizzare singolarmente qualsiasi parte di essa. Questo si riduce semplicemente al caos. Per separare parti l'una dall'altra, codificale come classi o funzioni che prendono tutti i dati richiesti come parametri. Ciò crea cuciture pulite (interfacce) tra diverse parti del codice.


Cercando di bind la tua domanda insieme in un solo esempio:

 require_once 'Database.php'; require_once 'ConfigManager.php'; require_once 'Log.php'; require_once 'Foo.php'; // establishes a database connection $db = new Database('localhost', 'user', 'pass'); // loads the configuration from the database, // the dependency on the database is explicit without `global` $configManager = new ConfigManager; $config = $configManager->loadConfigurationFromDatabase($db); // creates a new logger which logs to the database, // note that it reuses the same $db as earlier $log = new Log($db); // creates a new Foo instance with explicit configuration passed, // which was loaded from the database (or anywhere else) earlier $foo = new Foo($config); // executes the conversion function, which has access to the configuration // passed at instantiation time, and also the logger which we created earlier $foo->conversion('foo', arrays('bar', 'baz'), $log); 

Partirò all'attuazione delle singole classi come esercizio per il lettore. Quando si tenta di implementarli, si noterà che sono molto facili e chiari da implementare e non richiedono un singolo global . Ogni function e class ottiene tutti i dati necessari passati sotto forma di argomenti di function. Dovrebbe anche essere ovvio che i suddetti componenti possono essere collegati insieme in qualsiasi altra combinazione o che le dependencies possono essere facilmente sostituite con altre. Ad esempio, non è necessario che la configuration provenga dal database o che il logger possa accedere a un file anziché al database senza che Foo::conversion debba sapere nulla di ciò.


Esempio di implementazione per ConfigManager :

 class ConfigManager { public function loadConfigurationFromDatabase(Database $db) { $result = $db->query('SELECT ...'); $config = arrays(); while ($row = $result->fetchRow()) { $config[$row['name']] = $row['value']; } return $config; } } 

È un pezzo di codice molto semplice che non fa nemless molto. Potresti chiedere perché vuoi questo come codice orientato agli oggetti. Il punto è che questo rende l'uso di questo codice estremamente flessibile, poiché lo isola perfettamente da qualsiasi altra cosa. Si fornisce una connessione al database, si ottiene un arrays con una certa syntax. Input → Output. Cuciture chiare, interfacce chiare, responsabilità minime e ben definite. Puoi fare lo stesso con una semplice function.

Il vantaggio in più di un object è che esso disaccoppia ulteriormente il codice che chiama loadConfigurationFromDatabase da qualsiasi implementazione particolare di quella function. Se si utilizza semplicemente una function loadConfigurationFromDatabase() globale function loadConfigurationFromDatabase() , si ha di nuovo lo stesso problema: quella function deve essere definita quando si tenta di chiamarla e ci sono conflitti di denominazione se si desidera sostituirla con qualcos'altro. Usando un object, la parte critica del codice si sposta qui:

 $config = $configManager->loadConfigurationFromDatabase($db); 

È ansible sostituire $configManager qui per qualsiasi altro object che abbia anche un metodo loadConfigurationFromDatabase . Questo è "duck typing". Non ti import cosa sia esattamente $configManager , purché abbia un metodo loadConfigurationFromDatabase . Se cammina come un'anatra e caga come un'anatra, è un'anatra. O meglio, se ha un metodo loadConfigurationFromDatabase e restituisce un arrays di configuration valido, è una sorta di ConfigManager. Hai disaccoppiato il tuo codice da una particolare variabile $config , da una particolare function loadConfigurationFromDatabase e persino da un particolare ConfigManager . Tutte le parti possono essere cambiate e sostituite e sostituite e caricate dynamicmente da qualsiasi luogo, poiché il codice non dipende da nessun altro particolare.

Anche il metodo loadConfigurationFromDatabase non dipende da una particolare connessione al database, purché possa ricall una query e recuperare i risultati. L'object $db che è stato passato in esso potrebbe essere completamente falso e leggere i suoi dati da un file XML o da qualsiasi altra parte, a condizione che la sua interface continui a comportrsi allo stesso modo.

Ho risolto questo con una class:

 class Config { public static $SiteName = 'My Cool Site'; } function SomeFunction { echo 'Welcome to ' , Config::$SiteName; } 

Anche il suggerimento di fcortes di usare le costanti è buono. Mi piacerebbe solo suggerire di dare a tutte le costanti un prefisso, come CFG_SITE_NAME , in modo da evitare scontri di nome accidentali con altre costanti.

Per il tuo caso creerei un solo file constants.php con definizioni (se il tuo scopo è che queste "variables" non vengano mai modificate nei tempi di esecuzione):

 define('SITE_NAME','site name'); define('BASE_PATH','/home/docs/public_html/'); ... 

Includi questo constants.php in tutti i file in cui ti servirà:

 include_once('constants.php'); 

C'è una grande discussione tra approcci orientati agli oggetti e procedurali (e più in generale, tra quelli dichiarativi e quelli imperativi) e each approccio ha i suoi lati positivi e negativi.

Ho usato la class 'Config' che era una Singleton (una versione OOP di globale). Ha funzionato bene fino a quando non ho scoperto la necessità di utilizzare diverse soluzioni precedentemente sviluppate in un'unica applicazione, poiché tutte le configurazioni erano globali e riferite dalla stessa class (lo stesso nome variabile, nel tuo caso) erano in conflitto e io avevo per passare alla configuration corretta each volta che ho chiamato il codice da un'altra sotto-applicazione.

Hai due modi:

a) progetta la tua applicazione come ti sei abituata e con cui hai familiarità (sarà meglio perché hai già esperienza in essa e puoi prevedere quanto tempo ci vorrà per lo sviluppo e quali problemi potrebbero o non potrebbero sorgere); e dopo aver bloccato i limiti del tuo attuale approccio, il refactoring eviterà i globals;

b) guarda come è fatto nei framework OOP (vedi alless tre o quattro, ovvero Cake, CodeIgniter, Zend, Symfony, Flow3) e prendi in prestito qualcosa, o passa a usare un framework (o forse sarai più sicuro di farlo tutto giusto).

Ho creato una class piccola e semplice:

 class Config { private static $config = arrays(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } Config::set( 'my_config', 'the value' ); echo 'the config value is: ' . Config::get('my_config'); 

questo può essere facilmente refactored per avere una function isSet( $key ) o forse un setAll( $arrays ) .

EDIT: ora la syntax dovrebbe essere valida.

puoi facilmente modificare questa class come segue:

 class Config { private static $config = arrays(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } public static function setAll( arrays $arrays ) { self::$config = $arrays; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } Config::setAll( arrays( 'key' => 'value', 'key2' => arrays( 'value', 'can be an', 'arrays' ) ) ); Config::set( 'my_config', 'the value' ); if( Config::isKeySet( 'my_config' ) ) { echo 'the config value is: ' . Config::get('my_config'); } 

È comunque necessario includere il file in qualsiasi altro file che utilizza le configurazioni o utilizzare un caricatore automatico .

MODIFICA 2:

È praticamente lo stesso di usare un globale, con la differenza che non è necessario dichiarare che si desidera utilizzarlo all'inizio di each function. Se vuoi usare Configs globalmente, allora i Configs devono essere, in qualche modo globali. Quando si inserisce qualcosa nell'ambito globale, è necessario discutere se questa può essere un'informazione pericolosa per un'altra class che non intende vedere questa informazione … configurazioni predefinite? Penso che sia sicuro avere uno scopo globale, e quindi hai solo bisogno di qualcosa che sia facile da modificare e personalizzare.

Se decidi che si tratta di informazioni pericolose, non dovrebbe essere raggiungibile per una class diversa dalla class per cui è stata concepita, quindi potresti voler effettuare il check in in Dependency injection . Con le iniezioni di dipendenza una class prenderà un object nel suo constructor, posizionandolo privatamente in una variabile da usare. Questo object può essere un object da una class di configuration e quindi è necessaria una class wrapper che crea prima l'object di configuration e quindi l'object Template che inserisce le configurazioni. Questo è un design spesso visto in schemi di progettazione più complessi, come ad esempio Domain Driven Design.

config.php

 <?php class config { private static $config = arrays(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { if( config::isKeySet( $key ) ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } public static function setAll( arrays $arrays ) { self::$config = $arrays; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } // set valuable values config::setAll( arrays( 'key' => 'value', 'key2' => arrays( 'value', 'can be an', 'arrays' ), 'database' => arrays( "username" => "root", "password" => "root") ) ); config::set( 'my_config', 'the value' ); ?> 

config.usage.php

 <?php require_once 'config.php'; $database_credentials = config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; echo '<br> the config value for password is ' . $database_credentials['password']; function additionalFunctionality($database_credentials) { echo '<br> the config value for password is ' . $database_credentials['password']; } ?> 

config.usage.too.php

 <?php require_once 'config.php'; // put this first require_once 'config.usage.php'; // include some functionality from another file $database_credentials = Config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; additionalFunctionality($database_credentials); // great ?>