📜 ⬆️ ⬇️

How to use named constructors in PHP

tl; dr - Do not limit yourself to one constructor in the classroom. Use static factory methods.

PHP allows you to use only one constructor in a class, which is rather annoying. Probably, we will never get a normal opportunity to overload constructors in PHP, but something can still be done. For example, take a simple class that stores the value of time. What is the best way to create a new object:

<?php $time = new Time("11:45"); $time = new Time(11, 45); 

The correct answer is "depending on the situation." Both methods may be correct in terms of the result obtained. We implement support for both methods:

 <?php final class Time { private $hours, $minutes; public function __construct($timeOrHours, $minutes = null) { if(is_string($timeOrHours) && is_null($minutes)) { list($this->hours, $this->minutes) = explode($timeOrHours, ':', 2); } else { $this->hours = $timeOrHours; $this->minutes = $minutes; } } } 

Looks disgusting. In addition, class support will be difficult. What happens if we need to add a few more ways to create instances of the Time class?
')
 <?php $minutesSinceMidnight = 705; $time = new Time($minutesSinceMidnight); 

It is also probably worth adding support for numeric lines (fool protection does not hurt):

 <?php $time = new Time("11", "45"); 

Reorganize code using named constructors


Add a few static methods to initialize Time. This will allow us to get rid of the conditions in the code (which is often a good idea).

 <?php final class Time { private $hours, $minutes; public function __construct($hours, $minutes) { $this->hours = (int) $hours; $this->minutes = (int) $minutes; } public static function fromString($time) { list($hours, $minutes) = explode($time, ':', 2); return new Time($hours, $minutes); } public static function fromMinutesSinceMidnight($minutesSinceMidnight) { $hours = floor($minutesSinceMidnight / 60); $minutes = $minutesSinceMidnight % 60; return new Time($hours, $minutes); } } 

Now each method satisfies the principle of Unified Responsibility. The public interface is simple and straightforward. It seems to be finished? I'm still worried about the constructor, it uses the internal representation of the object, which makes it difficult to change the interface. Let us suppose that for some reason we need to store the combined time value in string format, and not individually, as before:

 <?php final class Time { private $time; public function __construct($hours, $minutes) { $this->time = "$hours:$minutes"; } public static function fromString($time) { list($hours, $minutes) = explode($time, ':', 2); return new Time($hours, $minutes); } // ... } 

This is ugly: we have to break the string in order to reconnect it in the constructor. Do we need a constructor for the constructor?

We built in you the designer in the designer ....

No, do without it. We reorganize the work of the methods, to work with the internal representation directly, and the designer will be made private:

 <?php final class Time { private $hours, $minutes; //    , ..         private function __construct(){} public static function fromValues($hours, $minutes) { $time = new Time; $time->hours = $hours; $time->minutes = $minutes; return $time; } // ... } 

Uniform language constructs


Our code has become cleaner, we have acquired several useful methods for initializing a new object. But as often happens with good design decisions - previously hidden flaws are selected to the surface. Take a look at an example of using our methods:

 <?php $time1 = Time::fromValues($hours, $minutes); $time2 = Time::fromString($time); $time3 = Time::fromMinutesSinceMidnight($minutesSinceMidnight); 

Did you notice anything? Method naming is not uniform:

As language zadrot a geek, as well as an adherent of the Domain-Driven Design approach, I could not ignore this. Since The Time class is part of our subject area, I suggest using the terms of this same subject area to name methods:

This focus on the subject area gives us a wide scope for action:

 <?php $customer = new Customer($name); //         //  ,    : $customer = Customer::fromRegistration($name); $customer = Customer::fromImport($name); 

Perhaps this approach will not always be justified, and this level of detail is superfluous. We can use any of the options, but the most important thing is that named constructors give us the opportunity to choose.


Part 2: When to use static methods

Source: https://habr.com/ru/post/279919/


All Articles