Clean Code
I fully agree with George Dryser in that option 2 is cleaner. I don't quite agree with him on his second point though:
I'd strongly recommend not to avoid entire functionality such as static class members purely for design or stylistic considerations, it's there for a reason.
The language features exist, but that doesn't necessarily mean that they should be used for this particular purpose. Also, the question isn't as much about style as much as it is about (de-)coupling.
Interdependence
To address your 3rd point:
... class Constants is introducing this interdependence/coupling ...
There's no interdependence here, since AbstractTemplate
depends on Constants
, but Constants
has no dependencies. Your 2nd option can be tested, but it's not very flexible.
Coupling
In your 2nd point you say:
Is there a real issue, that each class using option 2 will depend on a Constants class?
The issue isn't that there's a dependency being introduced, but what kind of dependency. Reading values from a specific, named member of the application is tight coupling, which you should try to avoid. Instead, make the default values constant only to those classes that read the values:

How objects that implement IDefaultsProvider
get their values does not concern the AbstractTemplate
class at all.
Possible Solution
For the sake of being thorough, I'm going to reinvent the wheel here.
In PHP, the interface for IDefaultsProvider
can be written like this:
interface IDefaultsProvider {
/** Returns the value of a variable identified by `$name`. */
public function read($name);
}
The interface is a contract that says: "When you have an object that implements IDefaultsProvider
, you can read default values using its read()
method and it will return the default value you requested".
I'll get to specific implementations of the interface further below. First, let's see how the code for AbstractTemplate
might look like:
abstract class AbstractTemplate {
private static function getDefaultsProvider() {
// Let "someone else" decide what object to use as the provider.
// Not `AbstractTemplate`'s job.
return Defaults::getProvider();
}
private static function readDefaultValue($name) {
return static::getDefaultsProvider()->read($name);
}
public function __construct(
, $appLayoutsPath = static::readDefaultValue('app_layouts_path')
, $moduleLayoutsPath = static::readDefaultValue('module_layouts_path')
, $moduleTemplatesPath = static::readDefaultValue('module_templates_path')
) {
//...
}
}
We've gotten rid of Constants
and its members (const APP_LAYOUTS_PATH
etc). AbstractTemplate
is now blissfully ignorant of where the default values come from. Now, AbstractTemplate
and the default values are loosely coupled.
The implementation of AbstractTemplate
only knows is how to get a IDefaultsProvider
object (see method getDefaultsProvider()
). In my example, I'm using the following class for that:
class Defaults {
/** @var IDefaultsProvider $provider */
private $provider;
/** @returns IDefaultsProvider */
public static function getProvider() {
return static::$provider;
}
/**
* Changes the defaults provider instance that is returned by `getProvider()`.
*/
public static function useInstance(IDefaultsProvider $instance) {
static::$instance = $instance;
}
}
At this point, the reading part is complete, since AbstractTemplate
can get a defaults provider using Defaults::getProvider()
. Let's look at bootstrapping next. This is where we can start to address different scenarios like testing, development and production.
For testing, we might have a file called bootstrap.test.php
that's included only when tests are being run. It needs to be included before AbstractTemplate
, of course:
<?php
// bootsrap.test.php
include_once('Defaults.php');
include_once('TestingDefaultsProvider.php');
Defaults::useInstance(new TestingDefaultsProvider());
The other scenarios require their own bootstrapping as well.
<?php
// bootsrap.production.php
include_once('Defaults.php');
include_once('ProductionDefaultsProvider.php');
Defaults::useInstance(new ProductionDefaultsProvider());
... and so on.
What remains to be done are the implementations of IDefaultProvider
. Let's start with TestingDefaultsProvider
:
class TestingDefaultsProvider implements IDefaultsProvider {
public function read($name) {
return $this->values[$name];
}
private $values = [
'app_layouts_path' => '[app-root-path]/Layouts/Default',
'module_layouts_path' => '[module-root-path]/Templates/Layouts',
'module_templates_path' => '[module-root-path]/Templates/Templates',
// ... more defaults ...
];
}
It might actually be as simple as that.
Let's assume that, in production, we want the configuration data to reside in a configuration file:
// defaults.json
{
"app_layouts_path": "[app-root-path]/Layouts/Default",
"module_layouts_path": "[module-root-path]/Templates/Layouts",
"module_templates_path": "[module-root-path]/Templates/Templates",
// ... more defaults ...
}
In order to get to the defaults in the file, all we need to do is read it once, parse the JSON data and return the default values when requested. For the sake of this example, I'm gonna go with lazy reading & parsing.
class ProductionDefaultsProvider implements IDefaultsProvider {
public function read($name) {
$parsedContent = $this->getAllDefaults();
return $parsedContent[$name];
}
private static $parsedContent = NULL;
private static function getAllDefaults() {
// only read & parse file content once:
if (static::$parsedContent == NULL) {
static::$parsedContent = static::readAndParseDefaults();
}
return static::$parsedContent;
}
private static readAndParseDefaults() {
// just an example path:
$content = file_get_contents('./config/defaults.json');
return json_decode($content, true);
}
}
Here's the whole shebang:

Conclusion
Is there a better alternative to provide default values?
Yes, provided that it's worth the effort. The key principle is inversion of control (also IoC). The purpose of my example was to show how IoC can be implemented. You can apply IoC to configuration data, complex object dependencies or, in your case, defaults.
If you only have a few default values in your application, it might be overkill to invert control. If there's plenty of default values in your application or if you cannot expect the number of defaults, configuration variables, etc to stay very low in the future though, you might want to look into dependency injection.
Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.
— Martin Fowler
Also, this:
Basically, instead of having your objects creating a dependency or asking a factory object to make one for them, you pass the needed dependencies in to the object externally, and you make it somebody else's problem.
— SO Answer to "What is Dependency Injection?" by wds
The good news is that there are plenty of DI frameworks around: