48

I have a config.php that is included to each page. In config I create an array that looks something like:

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

Then I have function.php, that is also included to almost each page, where I have to use global $config to get access to it - and this is what I would like to get rid of!

How do I access $config in the other parts of my code without using global?

Could anyone explain, WHY I shouldn't use global in my example? Some say it's a bad tone, others say it's not secure?

EDIT 1:

Example of where and how I use it:

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

EDIT 2:

Putting all of this in the class, as suggested by Vilx, would be cool but in this case, how would I tie it with the following loop that is extracting config key and value from database.
I oversimplified the idea of assigning $config array, here is an example:

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

EDIT 3:

Besides, I have to access other vars from functions that are set in config and it's few of them, e.g.:$db, $language and etc.

If I put them in the class will it really solve anything? If I use global what does it really change?

EDIT 4:

I read PHP global in functions where Gordon explains in the very nice way why you shouldn't use global. I agree on everything but I don't use global in my case to reassign the variables, which will result in, like he said, <-- WTF!!, ;)) yeah agree, it's crazy. But if I just need to access database from a function just by using global $db where is the problem in this case? How do you do this otherwise, without using global?

EDIT 5:

In the same PHP global in functions deceze says: "The one big reason against global is that it means the function is dependent on another scope. This will get messy very quickly."

But I'm talking here about basic 'INIT'. I basically set define but use vars - well that is wrong in technical way. But your function is not depended on anything - but the name of one var $db that you could keep in mind? It's really global need to use $db, where is the DEPENDENCY here and how to use it otherwise?

P.S. I just had a thought, that we're facing the conflict here of two different minds, e.g.: mine (yet NOT well understanding object-oriented programming) and those who could be called gurus (from my current point of view) in OOP - what looks obvious for them for me arises new questions. I think that's why this question is being asked over and over again. Personally for me it's gotten more clear after all but still there are things to clarify.

Martijn
  • 15,791
  • 4
  • 36
  • 68
Ilia Ross
  • 13,086
  • 11
  • 53
  • 88
  • Any example of where config is used currently where you need to use global? – Daedalus Sep 16 '12 at 09:57
  • Generally, you'd use a method that returns the configuration values from one source or another. However this looks to be functional though, so some variation there-of would work, otherwise, you can just use globals. – Korvin Szanto Sep 16 '12 at 09:59
  • 1
    `require_once` is not enough? – Marek Sebera Sep 16 '12 at 09:59
  • 4
    @MarekSebera: no, if `$config` is called from within a function you have to use `global`, otherwise a local variable called `$config` will be created. I don't think it is particularly unsecure, it's just that many people see "global" and they read "red alert", without actually reasoning about the specific problem at hand. – nico Sep 16 '12 at 10:07
  • I actually use `require` for those files not include! Why, how can it help me in accessing `$config`? – Ilia Ross Sep 16 '12 at 10:07
  • @Nico: Sounds same for me though! But don't want to write a code that will be ugly and I would have to rebuild it in near feature.. Better now.. – Ilia Ross Sep 16 '12 at 10:09
  • Well, the other option is to use a singleton. Of course this has the effect of silencing those who were complaining about `global` and fire up those who complain about singletons being evil incarnated... – nico Sep 16 '12 at 10:12
  • Thank you, Gordon I have updated my question. I really enjoyed reading your explanation but there is still, at least, one vague point for me - accessing database from a function? And why would that be wrong, if I used `global $db` not as variable but as static, as a predefined value? – Ilia Ross Sep 16 '12 at 11:40
  • PeeHaa, thanks! ;) I will take a good look at this!! – Ilia Ross Sep 16 '12 at 14:04
  • 1
    @IliaRostovtsev it's bad because the dependency is hidden. you expect some variable with a certain name to exist in the global scope instead of giving that function that variable when you call it, which is much more explicit and clear. – Gordon Sep 16 '12 at 17:48
  • Gordon, thank you for writing!;) I think, after reading about this matter the whole day today, I could say that I understood this quite well! – Ilia Ross Sep 16 '12 at 17:52
  • Possible duplicate of [Are global variables in PHP considered bad practice? If so, why?](http://stackoverflow.com/q/1557787/1255289) – miken32 Mar 11 '17 at 18:04
  • "But if I just need to access database from a function just by using global $db where is the problem in this case" There is absolutely no problem. Such absolutist rhetoric like 'dont use globals' is just fundamentalism and they are detached from reality. With that kind of logic, large applications like WordPress that allow extension would be impossible to maintain for you would have to pass every single plugin's entire class or variables around, leading to memory bloat. – unity100 Aug 25 '20 at 18:51

6 Answers6

57

The point against global variables is that they couple code very tightly. Your entire codebase is dependent on a) the variable name $config and b) the existence of that variable. If you want to rename the variable (for whatever reason), you have to do so everywhere throughout your codebase. You can also not use any piece of code that depends on the variable independently of it anymore.

Example with global variable:

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

Anywhere in the above lines you may get an error because the class or some code in SomeClass.php implicitly depends on a global variable $config. There's no indication of this whatsoever though just looking at the class. To solve this, you have to do this:

$config = array(...);

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

This code may still fail somewhere if you do not set the correct keys inside $config. Since it's not obvious what parts of the config array SomeClass needs or doesn't need and when it needs them, it's hard to recreate the correct environment for it to run correctly. It also creates conflicts if you happened to already have a variable $config used for something else wherever you want to use SomeClass.

So instead of creating implicit, invisible dependencies, inject all dependencies:

require 'SomeClass.php';

$arbitraryConfigVariableName = array(...);

$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();

By passing the config array explicitly as a parameter, all the above problems are solved. It's as simple as handing the required information around inside your app. It also makes the structure and flow of the application and what talks to what much clearer. To get to this state if your application is currently a big ball of mud may take some restructuring.


The bigger your codebase gets, the more you have to decouple the individual parts from each other. If every part is dependent on every other part in your codebase, you simply cannot test, use or reuse any part of it individually. That simply devolves into chaos. To separate parts from each other, code them as classes or functions which take all their required data as parameters. That creates clean seams (interfaces) between different parts of your code.


Trying to tie your question together into one example:

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', array('bar', 'baz'), $log);

I'll leave to implementation of the individual classes up as an exercise for the reader. When you try to implement them, you'll notice that they're very easy and clear to implement and do not require a single global. Every function and class gets all its necessary data passed in the form of function arguments. It should also be obvious that the above components can be plugged together in any other combination or that dependencies can easily be substituted for others. For example, the configuration does not need to come from the database at all, or the logger can log to a file instead of the database without Foo::conversion having to know about any of this.


Example implementation for ConfigManager:

class ConfigManager {

    public function loadConfigurationFromDatabase(Database $db) {
        $result = $db->query('SELECT ...');

        $config = array();
        while ($row = $result->fetchRow()) {
            $config[$row['name']] = $row['value'];
        }

        return $config;
    }

}

It's a very simple piece of code that doesn't even do much. You may ask why you'd want this as object oriented code. The point is that this makes using this code extremely flexible, since it isolates it perfectly from everything else. You give one database connection in, you get one array with a certain syntax back. Input → Output. Clear seams, clear interfaces, minimal, well defined responsibilities. You can do the same with a simple function.

The extra advantage an object has is that it even further decouples the code that calls loadConfigurationFromDatabase from any particular implementation of that function. If you'd just use a global function loadConfigurationFromDatabase(), you basically have the same problem again: that function needs to be defined when you try to call it and there are naming conflicts if you want to replace it with something else. By using an object, the critical part of the code moves here:

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

You can substitute $configManager here for any other object that also has a method loadConfigurationFromDatabase. That's "duck typing". You don't care what exactly $configManager is, as long as it has a method loadConfigurationFromDatabase. If it walks like a duck and quacks like a duck, it is a duck. Or rather, if it has a loadConfigurationFromDatabase method and gives back a valid config array, it's some sort of ConfigManager. You have decoupled your code from one particular variable $config, from one particular loadConfigurationFromDatabase function and even from one particular ConfigManager. All parts can be changed and swapped out and replaced and loaded dynamically from anywhere, because the code does not depend on any one particular other piece.

The loadConfigurationFromDatabase method itself also does not depend on any one particular database connection, as long as it can call query on it and fetch results. The $db object being passed into it could be entirely fake and read its data from an XML file or anywhere else instead, as long as its interface still behaves the same.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 2
    @Ilia If by *"just call `Config::$SiteName`* you mean you want to use static class properties: ***don't***. It's the same as a `global` variable. Every time you use code that depends on `Config::$SiteName`, you have to make sure you have that class loaded with the right config. It's no different than a `global $config`. You want to use *function parameters*. That's the only way to segregate variables into independent scopes. – deceze Sep 16 '12 at 11:51
  • @David - That is what I thought!! But how to answer to my `EDIT 5`? How do I use `$db` in the nice way in functions without using global? – Ilia Ross Sep 16 '12 at 11:55
  • @David you wrote: "*global is a bad pattern even for including global things such as $db resources. There will come the day when you want to rename $db but can't, because your whole application depends on the name.*" - so how do I connect to db than from a function? Wrap it in the class? What if I want to change the class neme in the future? – Ilia Ross Sep 16 '12 at 12:01
  • It looks neat! Thank you, David! There is one small problem for me is that I'm so new to classes and objects and looking at your code just doesn't shine as an open solution for me yet. To make this whole discussion productive for me, avoid any examples but one. Could you please take few lines from an array $config, like `$config['site_name']` and etc, then use `$db` and put it all in 1 small working example, so it could also be used from a function without globals - so far, it's too abstract for me. Sorry for not being able to grasp completely your beautiful example! – Ilia Ross Sep 16 '12 at 12:36
  • 3
    This is outstanding explanation and I bet this will be definitely useful for other programmers. If I could vote twice for your answer, I would!! Thank you for your time!:-) – Ilia Ross Sep 16 '12 at 15:37
  • 4
    Thanks! Finally I understand Dependency Injection! :) Now to figure out its Dark Side and when _not_ to use it. :) – Vilx- Sep 16 '12 at 23:23
  • 1
    @Vilx The dark side of dependency injection is the *object graph*. That means, deciding when and where to instantiate your actual objects and what injects what into what. Whenever you instantiate an object (`new MyClass`), you couple *that* line of code to that particular class. To avoid that, you typically use factories which you inject instead. But that factory needs to be instantiated somewhere too... if you take that to extremes, the complexity of object instantiation logic and dependency management will outweigh its benefits. – deceze Sep 17 '12 at 08:41
  • Thanks. :) Also, one other thing I've already noticed when trying to understand an opensource project which is DI-heavy is that you can never understand where the code you are looking for is. All you see is interfaces. You need to run it and check in debugger which class got instantiated. – Vilx- Sep 17 '12 at 20:48
  • 1
    Loose coupling and tight cohesion. Step-wise refinement. If nothing else, do these things. Learn as much object-oriented programming as possible, then learn as much as you can about design patterns. Pay attention to visibility, abstract classes, interfaces, and in PHP, traits. If nothing else, think factoring, encapsulation, and code-reuse. With work, everyone can write better code. I work to improve mine all of the time. There is always more to learn, and accepting that is the best way to make your code less dependent on things like the `global` keyword. – Anthony Rutledge Dec 20 '15 at 05:11
9

I've solved this with a class:

class Config
{
    public static $SiteName = 'My Cool Site';
}

function SomeFunction
{
    echo 'Welcome to ' , Config::$SiteName;
}

fcortes' suggestion to use constants is also a good one. I'd only like to suggest to give all constants a prefix, like CFG_SITE_NAME, so to avoid accidental name clashes with other constants.

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • 2
    @IliaRostovtsev - Eh, what? A class in PHP is pretty much the same as an array (which you have been using all the time), except the syntax is a bit different. – Vilx- Sep 16 '12 at 10:19
  • Vilx-, I think I'll manage to tie it to db, that's ok! But could you tell me please, why it would be BETTER to use object-oriented approach but not simple and old `global`? If I run away from something then I'd like to know why? That was part of my question! ;) Thanks! – Ilia Ross Sep 16 '12 at 10:35
  • Besides, I have to use `$db` and `$language` from functions; What do I do in all of these cases? Wrap it all in the class? – Ilia Ross Sep 16 '12 at 10:38
  • 9
    Class constants/static values are essentially the same as global variables. They solve none of the problems. – deceze Sep 16 '12 at 10:41
  • @deceze: - Yeah.. :) But what do I do? What is really wrong with `global`? How to use it correctly and nicely? – Ilia Ross Sep 16 '12 at 10:45
  • 3
    @IliaRostovtsev - I don't think there is any fundamental thing that is wrong with it. It's just a matter of preference. I like it this way because I don't have to write the `global` line all the time. Besides - you asked for an alternative, and that's what I gave. – Vilx- Sep 16 '12 at 16:49
  • @Vilx: I deeply appreciate your help!:) – Ilia Ross Sep 16 '12 at 20:15
  • 3
    Well this is not at all better than using a global: you are still tightly coupled with the Config class. – marco-fiset Sep 21 '12 at 13:21
  • @marco-fiset - Did I ever say it was bad to be tightly-coupled? :) – Vilx- Sep 21 '12 at 14:16
  • @Vilx- No, but it is. Here you are just using another form of global, but without the keyword. – marco-fiset Sep 21 '12 at 14:25
  • @marco-fiset Yes. It's easier to type, a bit. :) Anyways, I never said it was any better or worse, nor did the OP ask for anything "better" initially. He just wanted to get rid of the `global` keyword, presumably because of some internal regulation. – Vilx- Sep 21 '12 at 21:58
6

For your case I would create an only file constants.php with definitions (if your purpose is these "variables" never be changed in execution time):

define('SITE_NAME','site name');

define('BASE_PATH','/home/docs/public_html/');

...

Include this constants.php in all files where you will need it:

include_once('constants.php');
Nikola K.
  • 7,093
  • 13
  • 31
  • 39
fcortes
  • 1,338
  • 3
  • 11
  • 26
  • Please format your code properly. A code block is inserted by indenting 4 spaces before any line of code. I've formatted the code for you this time, but please format it properly next time. For further help, see the [Editing FAQ](http://stackoverflow.com/editing-help#code) – Madara's Ghost Sep 16 '12 at 10:05
  • I'd use this but call the data out of a database – Adsy2010 Sep 16 '12 at 10:19
  • How do I access other vars from a function that are set in config? Such as `$db` (connection to database) or `$language` (localization) – Ilia Ross Sep 16 '12 at 10:40
  • 3
    Constants are the same as `global` variables, they solve none of the problems. – deceze Sep 16 '12 at 10:53
  • 1
    @deceze They solve the addition to say that he wants to use `global $config` in the start of every function. – Krycke Sep 16 '12 at 12:29
3

There is a big discussing between object-oriented and procedural approaches (and more generally, between declarative and imperative ones) and each approach has its upsides and downsides.

I used 'Config' class that was a Singleton (an OOP version of global). It worked good for me until I had discovered a need to use several of previously-developed solutions together in one application - since all configs were global and referred by the same class (the same variable name, in your case) they conflicted and I had to switch to proper config every time I called code from other sub-application.

You have two ways:

a) either design your application in a way you got used to and you are familiar with (that will be better because you already have experience in it and you can predict how long the development will take and what problems may or may not arise); and after you will stuck into limitations of your current approach, refactor to avoid the globals;

b) look how its done in OOP frameworks (see at least three or four, i.e. Cake, CodeIgniter, Zend, Symfony, Flow3) and either borrow something, or switch to using a framework (or maybe you will be more sure that you do everything right).

irezvin
  • 101
  • 5
1

I created an easy small class:

class Config {

    private static $config = array();

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

this can easly be refactored to have a function isSet( $key ) or maybe a setAll( $array ).

EDIT: Now the syntax should be valid.

you can easily modify this class like follows:

class Config {

    private static $config = array();

    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( array $array ) {
        self::$config = $array;
    }

    public static function isKeySet( $key ) {
        return isset( self::$config[ $key ] );
    }
}

Config::setAll( array(
    'key' => 'value',
    'key2' => array( 'value',
                    'can be an',
                    'array' ) ) );
Config::set( 'my_config', 'the value' );

if( Config::isKeySet( 'my_config' ) ) {
    echo 'the config value is: ' . Config::get('my_config');
}

You still need to include the file in any another file that uses configs, or use an autoloader.

EDIT 2:

It's pretty much the same as using a global, with the difference you don't need to state that you want to use it in the beginning of every function. If you want to use Configs globally, then the Configs have to be, in some way global. When putting something in the global scope, you need to argue if this can be dangerous information to an other class not meant to see this information... default configurations? I think it's safe to have in the global scope, and then you just need something that is easy to modify and customize.

If you decide that it's dangerous information, that should not be reachable for a class other then the class it's meant for, then you might want to check in to Dependency injection. With dependency injections a class will take an object in it's constructor, placing it privately in a variable to use. This object can be an object from a configuration class, and then you need a wrapper class creating first the configuration object, and then the Template object injecting the configurations. This is a design often seen in more complex design patterns, like for instance Domain Driven Design.

Krycke
  • 3,106
  • 1
  • 17
  • 21
  • It contains invalid syntax. Could you please elaborate your answer with examples, on CodePad, maybe? – Ilia Ross Sep 16 '12 at 12:42
  • Thank you, Krycke! I will take a closer look a bit later and let you know!;) – Ilia Ross Sep 16 '12 at 13:33
  • 2
    It's nice as an object-oriented approach but why is it better than to use `global $config` and then use, e.g.: `$config['site_name']`? If I understood right, your solutions is just the same as Vilx-, correct? +1 for a nice working example in object oriented style. – Ilia Ross Sep 16 '12 at 14:17
0

config.php

<?php
class config {

    private static $config = array();

    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( array $array ) {
        self::$config = $array;
    }

    public static function isKeySet( $key ) {
        return isset( self::$config[ $key ] );
    }
}

// set valuable values

config::setAll( array(
    'key' => 'value',
    'key2' => array( 'value', 'can be an', 'array' ),
    'database' => array( "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
?>
canoodle
  • 506
  • 5
  • 10