0

How can I access application settings in my objects while remaining DRY?

I'm not referring to things like database credentials or API keys, those are kept in environment variables. The settings I'm referring to here are things like:

WORK_DAY_START_TIME     04:00:00
MAX_HOURS_PER_DAY       13
ALLOW_DAY_CROSSOVER     false

The settings affect business logic, are global to the application, and varies across different organizations. The intended users are managers who may not be technical so therefore there's a user interface that allows them to change settings.

The values are persisted into a database (though the persistence medium is irrelevant here as I can easily read from a XML/INI/JSON/YAML file).

Here's a simple class for accessing the settings (it's a bit more than that but for the purpose of this question it's sufficient):

<?php

class Settings implements \ArrayAccess
{
    private $settings = array();

    public function __construct() {
        // get settings from DB and put them in $this->settings
    }

    public function offsetGet($key) {
        return $this->settings[$key];
    }
}

// In the entry point file:
$settings = new Settings();

I need the value when checking things:

class Shift extends EntityObject
{
    private function isValid() {
        // For illustrative purposes only
        return !$settings['ALLOW_DAY_CROSSOVER'] && $this->start < $settings['WORK_DAY_START_TIME'] && $this->end < $settings['WORK_DAY_START_TIME'];
    }
}

Here are some options I came up with:

  1. global $settings or its alternative $GLOBALS['settings']. This relies on a global variable so not good.
  2. Make Settings a singleton. Pretty much the same problem, it's hard to do unit testing with.
  3. In the constructor of each class, instantiate a Settings to get the settings. This seems like a lot of overhead especially when the settings are already loaded.
  4. Pass the instance of Settings into the constructor of each object that requires settings (so basically all the objects). This sounds like dependency injection if I understand it correctly but it seems repetitive?

How should I approach this problem?

I think I'd run into the same problem if I used Zend_Config.

A related question is $settings array or Config Class to store project settings?.

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
  • Did you try $_SESSION? If you DON'T want to use $_SESSION you can always use php file that returns an array. Another option would be to use a cache server like http://memcached.org/. There isn't any other way – Stanimir Dimitrov Aug 22 '15 at 04:39
  • I would go with option 4. Settings is by definition a dependency of your classes (or particular values within the settings). Doing anything but injecting the dependency hides that fact. – Orangepill Aug 22 '15 at 05:01

2 Answers2

2
  1. Use a Global. This is a bad idea for many reasons. First it breaks encapsulation which is the whole point of object oriented programming. It also hides a dependency.. the fact is your class needs the settings to perform it's work but that fact is not apparent without reading all of your code. Unit testing also becomes more difficult because you have to consider global state prior to running each of your tests. See Also spooky action at a distance

  2. Make Settings a singleton. This basically suffers all of the same problems that a global variable has... it's just wrapped in object notation. A good watch on why you shouldn't use singleton is expressed in this Clean Code Talk

  3. Instantiating a new settings object for each method call might be a good solution given that you have many different variations of these settings file.

  4. Injecting the dependency into the constructor is good practice. It makes the fact that your class needs a settings object to perform it's work. This option comes with some consideration though, often supplying your classes with collaborators during construction can be tricky. Using a Dependency Injection Container can make this much simpler.

Another thing to note is that your name Setting is not really very descriptive and doesn't really convey what this object's responsibility is. You might want to use more descriptive class names to convey exactly what the object is.

<?php

class EmployeeShiftPolicy {
    private $max_hours_per_shift;
    private $allow_day_crossover;
    private $workday_start_time;
    private $workday_end_time;

    public function __construct(
                                $max_hours,
                                $allow_crossover,
                                \DateTimeInterface $workday_start,
                                \DateTimeInterface $workday_end){
        $this->max_hours_per_shift = $max_hours;
        $this->allow_day_crossover = $allow_crossover;
        $this->workday_start_time  = $workday_start;
        $this->workday_end_time  = $workday_end;

    }

    public function getMaxHoursPerShift(){
        return $this->max_hours_per_shift;
    }

    // other accessors

    public function validateShiftProposal(\DateTimeInterface $startTime, \DateTimeInterface $endTime){
        ...
    }
}

Then when you construct a shift you could then supply it with an appropriate policy. i.e. one for weekend, one for school age kids, one for persons with work limitations... etc.

Hopefully this will offer some guidance on how you can model your domain.

Orangepill
  • 24,500
  • 3
  • 42
  • 63
0

Very interesting, your approaches.

  1. Agree, don't use the $GLOBALS
  2. Agree, singelton is not OOP
  3. Agree, difficult to maintain
  4. Nice approach, DI is handy for different implementations. You have one implementation now, but DI makes your application more extendable/scalable. This option is very OOP!
  5. (Sessions are an alternative. Easy to unit test and stored in memory (if you have enough) with e.g. memcached or redis, it's very fast.)
schellingerht
  • 5,726
  • 2
  • 28
  • 56
  • Sessions are just another type of global state and they suffer the same drawbacks as options 1 and 2 – Orangepill Aug 22 '15 at 05:18
  • Orangepill, I understand. But with e.g. phpunit, you can unit test sessions easily. Combined with the speed, it would be nice. But, option 4 for the best design. – schellingerht Aug 22 '15 at 05:29
  • Global state is global state and speed should be the least of your concerns when talking about testing. And usually if speed is that much of an issue it's because you aren't unit testing... your building up and tearing down huge sections of your application for the sake of the test as opposed to small isolated tests. – Orangepill Aug 22 '15 at 05:43