configuration variables passed to objects

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

configuration variables passed to objects

Post by s.dot »

When relying on configuration variables to do execution in your scripts (such as a mathematical value that gets operated on), what's a good way of implementing the configuration values so they can be accessed in objects?

Right now I've got config.php which is a basic php file like this:

Code: Select all

$config = array();
$config['key'] = 'value';
$config['key2'] = 'value2';
I've got this file included in every page (via my roll of site-wide includes). And accessing them within an object requires globalization:

Code: Select all

<?php

class test
{
    public function add()
    {
        global $config;
        return $config['key'] + $config['key2'];
    }
}
Seems sort of hackish.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

Post by arjan.top »

I used this a while ago, may not be the best option:

Code: Select all

	private static function load_vars($file){
		
	    if (file_exists($file) == false) {
	    	return false;
	    }

        include ($file);
        unset($file);
        
        $defined = get_defined_vars();

        foreach($defined as $key => $val){
        	self::$vars[$key] = $val;
        }
        
        unset($defined);

	}
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

You never want to bring a global into class scope - infact you should avoid using them period.

What I would do is create an config object, initialize it...and inject it into the objects scope.

Code: Select all

class MyConfig{
  private $_var = '';

  function getVar(){ return $this->_var; }
  function setVar($var){ $this->_var = $var; }
}

class MyObject{
  function __construct($config){ $this->_config = $config; }

  function soSomething(){ return true; }
}

$cfg = new MyConfig();
$cfg->setVar(100);

$obj = new MyObject($cfg); // Inject the depdency into the class (now it's abstract not concrete)
p.s-What I've demonstarted is often called constructor injection. You may also use a setter or mutator method. So instead of injecting the dependency at construction you set it after construction using the setter. This is handy in instances where the dependency can change after the point of primary object construction, so you may want to consider that approach as well - depending on your situation.

Personally I always start with construction injection to prevent anything but the line of code that creates the object from changing the dependency. If the need arises I refactor and add a setter method. KISS. :)

HTH :)
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

So I should load my config file into an object and pass that around rather than globalize the array.

Something like..

Code: Select all

class config
{
    private $_config = array();

    public function __construct($configArray)
    {
        $this->_config = $configArray;
    }

    public function __get($key)
    {
        if (isset($this->_config[$key]))
        {
            return $this->_config[$key];
        }
    }
}
So then my object instantiation would look like

Code: Select all

//load the config array
require 'config.php';

$cfg = new config($config);
Since the configuration object will be a one-time needed object, I should probably make it a singleton?

I don't really want the configuration values to be modifiable at run time. But rather they have to be changed in the config.php file.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

Post by arjan.top »

wouldn't be it better to use for example singleton here, or at least static class?

EDIT:
pass array as parameter or use function I gave you in my first post (so you don't have $config defined)
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

I'm unclear of how to pass the configuration object to other objects.

Let's say somewhere on one of my pages I instantiate the config object.. is it available inside of other classes? Or do I have to pass the config object to all of the other classes I will need? (seems messy) Another alternative would be to have all classes extend config.. but that seems even more messy.

Sorry, I'm just getting into learning the OO ways.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

arjan.top wrote:(so you don't have $config defined)
That's a good idea. I can just require in the __construct().
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Edit: Found this article may be of interest (http://martinfowler.com/articles/injection.html). Explains the problem your facing and it's possible solutions.

Yes, you would pass the array of configuration details to the config object during construction. Unless you hardcoded the config values into the class like so:

Code: Select all

class MyConfig{

  private $_datafile = 'test.xml';
  private $_name = 'My Application';

}
It's usually best to keep configuraiton data outside of classes, as it's easier to write a script to parse a INI file than it is a class. So something like this is best:

Code: Select all

config.ini

datafile = "test.xml"
name = "My Application"

Code: Select all

//
// Load config data from external file and pass to config object
$array = parse_ini_file('config.ini');
$config = new MyConfig($array);

//
// Inject config object into primary object
$obj = new MyObject($config);
If you do not wish to have your config object changed, just make sure your member variables are private and if you have setters make them protected, so only derived classes can change the data on you.
User avatar
arjan.top
Forum Contributor
Posts: 305
Joined: Sun Oct 14, 2007 4:36 am
Location: Hoče, Slovenia

Post by arjan.top »

I still think singleton is the way to go, if you want to have add for example set() method, you will be always accessing the same object, if you create config file with new you can have multiple instances of config objects, so that can lead to bugs etc.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

arjan.top wrote:I still think singleton is the way to go, if you want to have add for example set() method, you will be always accessing the same object, if you create config file with new you can have multiple instances of config objects, so that can lead to bugs etc.
You could pass the config variable by reference in this case. But I think it's almost the same as using global variables...
There are 10 types of people in this world, those who understand binary and those who don't
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

arjan.top wrote:I still think singleton is the way to go, if you want to have add for example set() method, you will be always accessing the same object, if you create config file with new you can have multiple instances of config objects, so that can lead to bugs etc.
Singletons usually just introduce more complexity - it's yet another object to introduce.

Although two or more config objects don't make sense...I hardly think it would throw an exception if one were created. Using the single would also introduce a concrete dependency on the singleton.

Code: Select all

MyConfig::getInstance()->getData()
There is no benefit to using a singlton here I don't think. Granted it's easy enough to search & replace with a more dynamic implementaiton later, introducing the singleton object just adss clutter to the code. Start simple first and add these things as the need arises, don't just start hammering out design patterns because it's a documented best practice for a situation like this.

I say wait until the system requires the object remain at a single instance.
Let's say somewhere on one of my pages I instantiate the config object.. is it available inside of other classes? Or do I have to pass the config object to all of the other classes I will need? (seems messy) Another alternative would be to have all classes extend config.. but that seems even more messy.

Sorry, I'm just getting into learning the OO ways.
Here is where programmer discipline comes in...if your application is model 1 (multiple independent scripts) you need to take great care and make sure the config object is only created once at the start of every script. If you have a model 2 paradigm, you would likely create the object *once* inside the application entry point shortly before you instantiated your front controller.

This is why people tend to move towards model 2 - to avoid the nessecary disicpline in making sure the config object is created at the start of every script (and other reasons of centralized logic).

Here is a example:

Code: Select all

<?php // aboutus.php

  include 'includes/config.php';

  $obj = new MyObject($cfg); // Inject configuration object into MyObject() instance

  $two = new MyTwoObject($cfg); // Inject configuration object again into another object

?>

Code: Select all

<?php // includes/config.php

  $array = parse_ini_file('config.ini');
  $config = new MyConfig($array);

?>
The latter example is an include which contains all your scripts common initialization code - config object, timezone, locale, etc

The first example is the actual script file which you invoke via the web browser. It creates 2 objects and injects the config object. I will show you how each of those objects would use the injected config object now:

Code: Select all

<?php

  class MyObject{

    $_config = null;    

    function __construct($config)
    {
      // Objects in PHP are passed by reference so this member is basically pointing back to the original config object
      // but the dependency is no longer concrete, such as if we did something like this:
      // $this->_config = new MyConfig('config.ini');

      $this->_config = $config; 
    }

    function executeQuery($keyword)
    {
        //
        // Assume we store the user ID of a single blocked user inside the config object. We want the query to show all records
        // except those of the blocked user (blog comments or similar). We use the injected config object and retreive the user ID
        // store in a local (easier reading) and pass that along to the SQL query along with the single $keyword argument.
        $userid = $this->_config->getUserId();
        $res = mysql_query("SELECT * FROM table WHERE keyword LIKE $keyword AND userid = $userid");
    }
  }
Using the injection technique, you have removed the conrete dependency MyObject had on MyConfig and made it abstract. If you ever changed the name of the MyConfig class - say to some super config class called MySuperConfig, you need to only replace the single config object instantiation, not all the MyObject like classes that use the config class. Not only that but the configuration is left to the caller not the callee - this is typically much better design and makes for looser coupled classes - making them more reusable in the long run.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

VladSun wrote:You could pass the config variable by reference in this case. But I think it's almost the same as using global variables...
Not really. For starters all objects are passed as reference in PHP 5. A global is just that GLOBAL. It can be accessed anywhere, especially if it's a super global. A reference is...well it's context is limited to whatever you set it to...

A function parameter is accessible only inside that function call...not in the caller namespace, etc...
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Post by VladSun »

Hockey wrote:Not really.
Too short ...
Hockey wrote:For starters all objects are passed as reference in PHP 5.
OK ...
Hockey wrote:A global is just that GLOBAL. It can be accessed anywhere, especially if it's a super global.
OK ...
Hockey wrote:A reference is...well it's context is limited to whatever you set it to...
OK ...
Hockey wrote:A function parameter is accessible only inside that function call...not in the caller namespace, etc...
OK ...

Yes all of your statements are true in general (except the first one - I couldn't' interpret it). But you didn't say anything closely related to the issue above.

I meant that either having a global variable (declared as by scope modifier "global") inside a function or either passing this variable by reference to this function is almost the same because:
* You can change its value in global scope;
* If its value gets changed while the function is executing, then it will continue to operate on its new value.
There are 10 types of people in this world, those who understand binary and those who don't
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

I can see where your heading with GLOBALS and references being alike, however there is the distinction that a reference is not typically accessible *everywhere* like a global. IMHO thats a key difference and important to understand.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

So not to stray off into something I don't understand (it's heading that way :P), all of my base classes will have to be injected with the config data (in which I also think a singleton is a good idea). Once the base class is injected, all classes that extend that class will already have the data injected by means of extension.

I just thought it was a bit ugly to pass the object as a parameter to the constructer method, but I guess this is accepted practice.

So, I pass the config object to a different class like so..

Code: Select all

class test
{
    private $_cfg;
    public function __construct($cfgObject)
    {
        $this->_cfg = $cfgObject;
    }
}
How do I access the config object properties within the class?

Code: Select all

return $this->_cfg->value + $this->_cfg->value2;
Like that?
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
Post Reply