How to convert my classes to Dependency injection?

Posted on

Problem

I am still learning to develop my skills in OOP.

It uses a combination of the factory and its real singletons?

As I did more research, I have realized this design pattern is bad because of global state and it is hard to test.

I can’t figure how how to convert this OOP to Dependency injection?

Section Class

class Section
{
    // Array contains of instantiations of Section
    protected static $instance;

    // Validation  instance of validation
    public $validation;

    public $something;

    // instance method returns a new section instance.
    public static function instance($name = "default")
    {
        if ($exist = static::getInstance($name)) {
            return $exist;
        }

        static::$instance[$name] = new static($name);
        return static::$instance[$name];
    }

    // Return a specific instance
    public static function getInstance($instanceKey = null)
    {
        if (!isset(static::$instance[$instanceKey])) {
            return false;
        }
        return static::$instance[$instanceKey];
    }

    // Get Validation instance or create it
    public function validation()
    {
        if ($this->validation instanceof Validation) {
            return $this->validation;
        }

        if (empty($this->validation)) {
            $this->validation = Validation::instance($this);
        }

        return $this->validation;
    }

    public function add($something)
    {
        $this->something = $something;
    }

}

Validation Class (Just a random name for now)

class Validation
{
    // Related section instance
    public $section;

    protected function __construct($section)
    {
        $this->section = $section;
    }

    public static function instance($section = "default")
    {

        if (is_string($section)) {
            $section = Section::instance($section);
        }

        return new static($section);
    }

    public static function getInstance($instanceKey = null)
    {
        $temp = Section::getInstance($instanceKey);
        if (!$temp) {
            return false;
        }

        return $temp->validation();
    }

    // Alias for $this->section->add()
    public function add($name) {
        $this->section->add($name);
        return $this->section->something;
    }
}

Testing:

$section = Section::instance("Monkey");
$validation = $section->validation();
echo $validation->add("This is just a test");

Solution

First, a few minor comments:

Class properties should almost always be protected or private. This forces you to use a known tested interface (the class methods) to interact with the state stored in the class. The properties section, something and validation should have all been protected or private.

Your method add does not add, it sets. add might be appropriate for concatenation (although I’d prefer append or prepend). It is best used for real addition. Your add implementation would have been better named set.

I have added to your example class to highlight the benefits of Dependency Injection. I have used text and title in place of your something.

Validation Interface

The validation interface defines the way to check for validity. If we need to check for validity and we are passed an object that implements this interface we can be sure that it will work. By relying on an interface rather than a specific object we reduce our coupling from a specific object to any object that implements the interface.

interface IValidation
{
    public function isValid($str);
}

Section Class

The constructor accepts all of the parameters that the class needs (the dependencies are injected). I have used an array in the constructor (as a personal preference to remove a required order of parameters and to use named parameters with the associative array). Standard SPL exceptions are thrown if the validation objects do not match the Validation interface.

class Section
{
    // Text for the section.
    protected $text;

    // Validation for the text.
    protected $textValidation;

    /// Title for the section.
    protected $title;

    // Validation for the title.
    protected $titleValidation;

    public function __construct(Array $setup)
    {
        $setup += array('Text'             => '',
                        'Text_Validation'  => NULL,
                        'Title'            => '',
                        'Title_Validation' => NULL);

        if (!$setup['Text_Validation'] instanceof IValidation)
        {
            throw new InvalidArgumentException(
                __METHOD__ . ' requires Text_Validator');
        }

        if (!$setup['Title_Validation'] instanceof IValidation)
        {
            throw new InvalidArgumentException(
                __METHOD__ . ' requires Title_Validation');
        }

        $this->text            = $setup['Text'];
        $this->textValidation  = $setup['Text_Validation'];
        $this->title           = $setup['Title'];
        $this->titleValidation = $setup['Title_Validation'];
    }

    public function setText($text)
    {
        $this->text = $text;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    // Return whether the section is valid.
    public function isValid()
    {
        return $this->textValidation->isValid($this->text) &&
            $this->titleValidation->isValid($this->title);
    }
}

Validation Class

Notice how this class implements the interface.

class Validation implements IValidation
{
    // The regex for a valid match.
    protected $validMatch;

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

    public function isValid($str)
    {
        return preg_match($this->validMatch, $str);
    }
}

Usage

We can see from the usage some of the benefits of dependency injection. There is no hardcoded dependencies in any of the classes and we can glue the components together when they are used. This allows us to define the exact type of validations that we require and pass them to the Section class with injection.

$alphanumUnderscores = new Validation('/^[[:alnum:]_]*$/');
$digits = new Validation('/^[[:digit:]]+$/');

$section = new Section(array('Text_Validation'  => $alphanumUnderscores,
                             'Title'            => 'InvalidTitle',
                             'Title_Validation' => $digits));

if (!$section->isValid())
{
    // The title should be digits.
    echo 'As expected the title is invalid.' . "n";
}

$section->setTitle(9876);

if ($section->isValid())
{
    echo 'As expected the title is valid with digits.' . "n";
}

Leave a Reply

Your email address will not be published. Required fields are marked *