Search code examples
phpoopdesign-patternsmodel-view-controllersolid-principles

PHP MVC: Default values for the constructor parameters


Before I begin:

My question was put on hold as primarily opinion-based. But I've done my best to reedit it in a more precise way again. In the hope, that its content will support its reopening.

So here is my question again:

It's about my HMVC project, a PHP framework, in which the M, V & C components are encapsulated in "independent" blocks (module directories). The project should not contain any static class members, static methods, singletons or service locators. I am using a dependency injection container, therefore beeing able to provide inversion of control (IoC).

In a class AbstractTemplate I assign the needed root paths of the template files as default values to the parameters of the class constructor:

abstract class AbstractTemplate {

    public function __construct(
        , $appLayoutsPath = '[app-root-path]/Layouts/Default'
        , $moduleLayoutsPath = '[module-root-path]/Templates/Layouts'
        , $moduleTemplatesPath = '[module-root-path]/Templates/Templates'
    ) {
        //...
    }

}

But in this way I couple the class to a hard-coded representation of the file system.

Therefore I considered passing the default values by using a separate class, which in turn holds the required values as class constants:

class Constants {

    const APP_LAYOUTS_PATH = '[app-root-path]/Layouts/Default';
    const MODULE_LAYOUTS_PATH = '[module-root-path]/Templates/Layouts';
    const MODULE_TEMPLATES_PATH = '[module-root-path]/Templates/Templates';

}

abstract class AbstractTemplate {

    public function __construct(
        , $appLayoutsPath = Constants::APP_LAYOUTS_PATH
        , $moduleLayoutsPath = Constants::MODULE_LAYOUTS_PATH
        , $moduleTemplatesPath = Constants::MODULE_TEMPLATES_PATH
    ) {
        //...
    }

}

This way I couple the abstract class to the concrete implementation Constants.

I'd like to ask you:

  1. Can the second option be tested without problems?

  2. Is there another concrete possibility to provide default values and in the mean time to preserve a good testability?

I appreciate your answers and thank you for your time.


Solution

  • You can argue for both and you will not be worng since it is just an example detatched from any "real" code. But in my opinion this should be your starting code:

    abstract class AbstractTemplate {
        const DEFAULT_APP_LAYOUTS_PATH = '[app-root-path]/Layouts/Default';
        const DEFAULT_MODULE_LAYOUTS_PATH = '[module-root-path]/Templates/Layouts';
        const DEFAULT_MODULE_TEMPLATES_PATH = '[module-root-path]/Templates/Templates';
    
        public function __construct(
            $appLayoutsPath = AbstractTemplate::DEFAULT_APP_LAYOUTS_PATH, 
            $moduleLayoutsPath = AbstractTemplate::DEFAULT_MODULE_LAYOUTS_PATH, 
            $moduleTemplatesPath = AbstractTemplate::DEFAULT_MODULE_TEMPLATES_PATH
        ) {
            //...
        }
    
    }
    

    So you have your constants and you may reuse it (hopefully in the same class or in whatever class makes it "real", and not outside of it!)

    You could argue that "class Constants" would be better if you are going to reuse the constants from it in many different classes. The problem with that approach is that it goes against very basic OOP principles. That is: You should have just one object that does sth for you with theese pathes. And if you need different things to be done you just reuse this one object in many different objects in many different ways...

    Also Unit Testing or Dependency Injection and Inversion of Controll does not change anything here. But if you are providing code that you "know" that will only be used with IoC, you could argue if having any defaults is good idea at all and if it would not be better to inject everything from container...