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:
global $settings
or its alternative $GLOBALS['settings']
. This relies on a global variable so not good.Settings
a singleton. Pretty much the same problem, it's hard to do unit testing with.Settings
to get the settings. This seems like a lot of overhead especially when the settings are already loaded.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?.
Very interesting, your approaches.
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
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
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.
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.