views:

264

answers:

11

I often switch between .NET and PHP development. With ASP.NET sites I save configuration information (e.g. connection strings, directories, application setting) in the web.config file which is appropriately protected and easy to access the values, etc.

In PHP, I solve this with a class that has static methods for each variable:

class webconfig {
    public static function defaultPageIdCode() {
        return 'welcome';
    }
}

The file is included by the app variables are accessed with a one-line:

$dp = webconfig::defaultPageIdCode();

And since PHP isn't compiled, it is easy to telnet in and change a value for a website anyway, so this solution works fairly well and gives me these two advantages:

  • I can add logic to a config variable without breaking its interface with the application
  • these config variables appear as intellisense in my e.g. Eclipse, NetBeans, etc.

But I can imagine there are other ways people solve saving web config settings in PHP which may have other advantages.

Especially those who have experience with a number of PHP frameworks, what are other ways of saving config variables and their advantages and disadvantages?

+3  A: 

The way I do it is directly store them in an array and save the file as config.php

<?php

$config['dbname'] = "mydatabase";
$config['WebsiteName'] = "Fundoo Site";
$config['credits'] = true;
$config['version'] = "4.0.4";

?>

Thi is the way most PHP frameworks like Wordpress, etc do it.

shamittomar
I started doing this as well, but one advantage of the static methods is that I can easily had logic to my variables without breaking the interface with the application, e.g. could put an if statement in the defaultPageIdCode() which returned a different page based on e.g. user location, cookie information, etc.
Edward Tanguay
@Edward: If you're doing that, it probably shouldn't be in a config file anymore.
Amber
@Edward, That is a whole different thing. I was giving you an alternate of `web.config`
shamittomar
yes, that is pretty 1-to-1 with an XML web.config file, I often want to put logic in variables in the web.config as well and am forced to put them elsewhere, I suppose you could also say `$config['version'] = getDynamicVersion();`. But another advantage of the static methods are that they pop up in my Eclipse intellisense.
Edward Tanguay
there are downsides to this approach: the $config is a global variable with a generic name, so you have to a) write `global $config;` wherever you use it and b) due to it having a generic name, it can be overwritten by mistake.
Raveren
@Reveren, you can change your variable `$config` name to any name you want like `$rev_config` to avoid conflicts.
shamittomar
Well that's more typing/less self-explanatory and you still have to do a). Alternatively, you can access it via `$GLOBALS['config']`, but that's even more so. Another disadvantage not mentioned before is that no IDE currently supports autocompleting arrays (while all support autocompleting defines, if you know how it begins).
Raveren
@Raveren I have no idea why you would use `global $config;` or `$GLOBALS[]`. It makes a lot more sense to pass the needed config directives directly or pass them to a container object to be passed out internally.
anomareh
@Edward Config values should not be dynamic. When running, from start to finish, there should be no need for a config value to change. For example, database credentials, app version, etc. — For what you are describing you should be defining a constant to be used at the start of the script to be used throughout, or you should be storing them internally to be used. They do not belong in a config file.
anomareh
@anomareh, so you have to pass every configuration value to every method that needs it? For simply sending mail I need: sender name, sender address, bcc email, current environment, is the site localized and base url of the site. Also, who are you to decide whether someones logic uses dynamic or static config values.
Raveren
@Raveren this answer referred to this method as the most common way in __frameworks__. This is a convenient method of configuration when you are using a framework made up of __objects__ which is what I said you should pass them to. Perhaps you are thinking of functions? In which case you will find another method of configuration to be of more use.
anomareh
@Raveren As far as whether or not config values should be dynamic or not, you can see I'm not the only one that believes they should be static. Not to mention, constants, which are commonly used for config values, are immutable. If the value is __dynamic__ chances are it's changing on a page by page basis and isn't really an application configuration setting and as such shouldn't be treated like one.
anomareh
+1  A: 

In PHP I always use a ".htaccess" to protect my config file (Double protection)

Oyeme
+1  A: 

Most applications just make config settings some variant of variables in a PHP file:

config.php:

<?php
$foo = "bar";
$baz = "qux";
?>

app.php:

<?php

    include("config.php");
    do_something_with_config($foo, $baz);

?>

Using an array to namespace the config variables is an common sight as well, it prevents collision when include()ing the file.

Overall, the "plain PHP file" approach has the advantages of simplicity, conciseness, and flexibility. You incur a minimum of overhead when looking up config values (since it's just a variable lookup), but you still have the power of the rest of the language at your disposal to generate configuration options.

Amber
This is a terrible practice, having differently named globals all over the application.
Raveren
As I mentioned, there is always the option of namespacing via an array (or an object, if so desired). The core concept was simply that at the core, there's no need to do anything special beyond just `include`-ing a file to have "configuration settings". (Your suggestion of `define` isn't really much better in this regard...)
Amber
That's absolutely irrelevant, including a config file is a necessary and obvious step, I didn't even mention it! `Define`s main purpose is to store configuration values - and it's **much** better than using global variables. I'm absolutely sure, the upvotes you got is due to sheer high reputation of yours.
Raveren
+2  A: 

Note: "Best way" never exists. Every application and framework doing it their own style. While your example is doing the trick i think it's a little bit resource-heavy for a simple config file.

  • You can do it with single variables like Amber pointed out
  • You can do it with arrays this is the most common approach and you can always easily edit your config file.
  • You can do it with .ini files that PHP easily parsing

Edit:

Edward please take a look at parse_ini_file's examples. You can load the .ini file with a simple command then you can use the variables in a class like in your example.

fabrik
and don't forget yaml files, xml files ... and so on
maggie
@maggie +1, almost zillions of solutions out there.
fabrik
but with all of these options, you don't get intellisense, right? and with e.g. ini/xml/etc., you can't add any logic to your config variables, right? these are two disadvantages which steer me away from these approaches
Edward Tanguay
@maggie right but "best way" always exists in context, I just want to hear "what is your best way", "what is your best way" and I'll consider all those and figure out my best way in each context :-)
Edward Tanguay
@Edward i love yaml files, they are readable and easy to edit/understand. and still if i need specific configuration for lets say development, well then just load the dev-config yaml (see symfony(2) yml config for an example)
maggie
@Edward: with .ini files you can do a lot of logic to your variables. e.g. you can even create multidimensional arrays inside of them that can be parsed with a Config class (like in your example) and that's it! PHP must do the hard thing not us! :)
fabrik
A: 

The usual route is to use define:

define('MYSQL_USER', 'ROOT');

and access it all over the application via MYSQL_USER :

$user = MYSQL_USER;

However arrays are not supported in this way.

Raveren
that's littering the global namespace with constants
Gordon
But it's still a fine method and constants are there to be defined.
Martin Bean
@Gordan, I agree, Constants aren't the only entity you can use within the global space.
RobertPitt
+2  A: 

There are pretty much possibilities I think, but the most common methods are storing as plain text in files like .csv, .ini, .xml. With little tricks you can protect these files, so that no one can load the files directly.

an INI-File example:

;<?php die(); ?>
[config1]
var1 = 'value1';
var2 = 'value2';
...
[config2]
...

The ; is considered a comment in ini files. So when you read in the file with an ini-parser, this line will be ignored. If someone accesses the file directly via url the die()-function will be executed. This works only, if the INI-file wears a file-extension like .php or something similar like .phtml so that the server knows, that this should be executed and not diplayed as plain text.

Possible disadvantage of most file-base-config-storages are problems with some utf8-characters.

Zend_Config is a component of the Zend-Framework, which provides possibilities for several storage-adapters with an easy to use api.

faileN
+1 nice trick, thx
Edward Tanguay
As a bonus, the ini file format is also readable by Phing
Nev Stokes
For the trick to work, the ini file has to have a .php extension (or any other that apache executes not displays - like phtml)
Raveren
Sorry - you are right! I forgot to mention that. Thanks :)
faileN
A: 

There are few possibilities:

  1. you can use config file (ini, json, xml or yaml). For ini you have parse_ini_file, for json thee is json_decode (+file_get_contents), for yaml you have to use external library (search for sfYaml)

  2. you can have a config file with variables or constants (better for immutable config and accessible in all scopes), which you includes in your script:

    define('ROOT_DIR', '\home\www');

    $sRootDir = '\home\www';

If you are OO-oriented, you can wrap it in class, as properties - you do not have getter method for every property, you can just have:

 class config{
   public $var1 = 'xxx';
   public $var2 = 'yyy';

 }

($c = new config(); print $c->var1)

or

class static_config{
   public static $var1 = 'xxx';
   public static  $var2 = 'yyy';

 }

(print c::$var1)

The best is to have registry-type class, implementing singleton patern and capable to read config from given file

ts
+6  A: 

I tend to use a Settings static class in PHP, this is because

  • It has a global scope
  • You can enable / disable changes to protected configs
  • You can add any settings during anywhere within runtime.
  • You can make the class automated to fetch public configs from a file / DB.

Example:

abstract class Settings
{
    static private $protected = array(); //For DB / Passwords etc
    static private $public = array(); //For all public strings such as meta stuff for site

    public static function getProtected($key)
    {
        return isset(self::$protected[$key]) ? self::$protected[$key] : false;
    }

    public static function getPublic($key)
    {
        return isset(self::$public[$key]) ? self::$public[$key] : false;
    }

    public static function setProtected($key,$value)
    {
        self::$protected[$key] = $value;
    }

    public static function setPublic($key,$value)
    {
        self::$public[$key] = $value;
    }

    public function __get($key)
    {//$this->key // returns public->key
        return isset(self::$public[$key]) ? self::$public[$key] : false;
    }

    public function __isset($key)
    {
        return isset(self::$public[$key]);
    }
}

Then within your runtime, if you loaded this file first, followed by your database config file, your database config file would look like so:

<?php
Settings::setProtected('db_hostname','localhost');
Settings::setProtected('db_username','root');
Settings::setProtected('db_password','');
Settings::setProtected('db_database','root');
Settings::setProtected('db_charset','UTF-8');
//...
echo Settings::getProtected('db_hostname'); //localhost
//...
Settings::setPublic('config_site_title','MySiteTitle');
Settings::setPublic('config_site_charset','UTF-8');
Settings::setPublic('config_site_root','http://localhost/dev/');
?>

As you can see the we have a method __get that should only be allowed to grab public variables, An example of why we have this is as follows:

$Template = new Template();
$Template->Assign('settings',new Settings());

Regardless the fact that we have used this object as a static object, the values should still stand so within the template you can now do, lets say.

<html>
    <head>
        <?php echo isset($settings->config_site_title) ? $settings->config_site_title : 'Fallback Title'?>
    </head>
</html>

And this will only allow you to have access to the public data during the initialized period.

This can get a lot more complex but more system friendly, some examples:

  • A LoadConfig method to automatically parse a config file, xml,php,yaml
  • if you register an shutdown_function you can auto update the DB with new settings.
  • you can auto populated the class with config from that database
  • you can implement iterator's to make it compatible with looping
  • Lots mroe.

This too me is by far the best methods to complete this job.

RobertPitt
The advantages are indeed many and I see no disadvantage but having a (teeny-tiny) overhead of calling a method. I'd rather have a unified `get($parameterName)` method though.
Raveren
Keeping key/value pairs within an array allows you to make a small class work for itself, the main principle of OOP, `"Write less, Do more"`, The overhead is very very minor though :)
RobertPitt
Yeah, I agree on both pointers, but I think you misunderstood me on the `get` method, I'd prefer using it instead of the two `getProtected` and `getPublic` ones. Or do they have a purpose that I am missing?
Raveren
Usually when I build an application I have a public variable and a protected variable to separate, the GUI configuration with the Server configuration, so that a developer knows that he should only use getPublic when he is fetching data that will be used by the GUI.
RobertPitt
Note that the `__get` method will most probably never be called as it's an abstract *and* static class (unless you instantiate a child of the `Settings` class). Also, I'd return a `NULL` or raise an exception if code tries to access an unset value, as `false` is a valid value that can be set to a parameter, whereas NULL usually means `undefined`.
Raveren
Yes, the reason for the `__get` is so if you assign an instanced object to a View then the view can only see public variables.
RobertPitt
A: 

Since PHP is able to use OO, I like to go with a "Config class":

class Config
{
    /**
     * ---------------------------------
     * Database - Access
     * --------------------------------- 
     */

    /**
     * @var String
     */
    const DB_DRIVER = 'pgsql';

    const DB_USER = 'postgres';

    const DB_PASSWORD = 'postgres';

    const DB_NAME = 'postgres';
}

It is easy accessable with Config::DB_DRIVER. No need to include the file since the application autoloader will do that for you. Of course, protecting the file still needs to be done.

DrColossos
This method has no advantages over `define`s, has the latters disadvantage of not being able to use arrays **and** has it's unique disadvantage in that it can't use expressions in assignments, like `'http://'.$_SERVER['HTTP_HOST'] . '/'` or `dirname()` which are all needed most of the cases.
Raveren
oh, also this is not OOP. It's just a meaningless wrapper for variables.
Raveren
+1  A: 

Telnet? OMG I've fallen into a timewarp and arrived in 1992!

But seriously, IIRC there are tools which allow asp.net (and other languages) to parse session data - which is just a serialized php array. I'd have a go at implementing the global settings as a sort of shadow session within PHP. Even if you don't store your config settings as a serialized PHP array, you could map them into the session at runtime using your own session handler.

In terms of where you store the data - that's a more tricky one when you're presumably running on a Microsoft platform. Obviously you don't want the expense of disk access for every request. Although NT does some disk caching it's not (IME) quite as effective as other OS. Memcached appears to be one solution to this. It does appear to be usable from asp.net.

HTH

symcbean
omg did I say telnet? I meant "ssh in" or "putty in" :-)
Edward Tanguay
+4  A: 

I've decided to list all known methods along with their advantages and disadvantages.

I've marked this answer as a community wiki so collaboration is easier.


Global Constants

Assigning:

  • define('CONFIG_DIRECTIVE', 'VALUE');

Accessing:

  • $obj = new myObj(CONFIG_DIRECTIVE);

Advantages:

  • Has global scope.
  • Autocompleted by most IDEs.
  • Has an agreed upon naming convention (UPPERCASE_UNDERSCORE_SEPARATED).

Disadvantages:

  • Directives cannot contain arrays.

Special Notes:

  • Cannot be reassigned.

Alternate Syntax Files

For example: XML, INI, YAML, etc.

Assigning:

  • Simply edit the file in it's specific language. (For example, for INI files: config_directive = value.)

Accessing:

  • The config file needs to be parsed. (For example, for INI's: parse_ini_file().)

Advantages:

  • Most likely has a syntax more suited for a config file.

Disadvantages:

  • Possible overhead of accessing and parsing the file.

Array

Assigning:

  • $config['directive'] = 'VALUE';

Accessing:

  • The cleanest method of accessing configuration values using this method is to pass the required values to the object that needs them on creation, or pass them to your container object and let it handle passing them out internally.
    • $obj = new myObj($config['directive']);
    • $container = new myContainer($config);

Advantages:

  • Directives can be arrays.

Disadvantages:

  • No autocompletion.

Special Notes:

  • Variable collisions can occur. If this is a concern, name your array appropriately to avoid them.

Class

Assigning:

  • There are many different class based implementations.
    • Static class.
      • myCfgObj::setDirective('DIRECTIVE', 'VALUE');
    • Instanced class.
      • myCfgObj->setDirective('DIRECTIVE', 'VALUE');

Accessing:

  • Again there are various class based implementations.
    • Static class.
      • $obj = new myObj(myCfgObj::getDirective('DIRECTIVE'));
    • Instanced class.
      • $obj = new myObj(myCfgObj->getDirective('DIRECTIVE'));

Advantages:

  • Can be autoloaded.

Disadvantages:

  • Tends to be a bit verbose.
  • Can be hard to maintain if a container class is not being used.
Raveren
@last editor: What the hell, if you wanted a whole another answer, you could have posted it! You changed it *completely* and while I agree, some of the changes are welcome, the accessing logic is completely backwards! You mean to tell me, that **global** configuration variables need to be passed wherever they are used?? Directory constants, environment values, localization settings are just some of the constants that need to be used all over the application and need to be accessible as easily as possible, not sent one by one as parameters or fields!
Raveren
Also, how do you suppose an autoloader should work if no system directory paths are set? Summa summarum, I'm not going to rollback, but I will no longer participate in this answer.
Raveren
@Raveren This answer was a complete mess and was __EXTREMELY__ flawed. For example a common listed _disadvantage_ was whether the config value was _easily_ embedded in a string. __1)__ a config value should rarely have to be embedded in a string and __2)__ when you do, it should not be inline in double quotes. That is __TERRIBLE__ practice and awful for visibility.
anomareh
@Raveren As far as passing config values, that was only in reference to using an array. — Objects are made up of methods. Perhaps you are thinking of functions? I said they should be passed to the objects that require them. — If you need config values __EVERYWHERE__ in your app, and not just in the back-end, you should choose a different method of making your config values available to your app. — As far as your autoloader concern, perhaps you should look into `__DIR__`, `__FILE__`, and `dirname()`.
anomareh