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";
}