Every last Friday of the month we have Office Hours, which is an open video conference where we invite you to casually talk to us about professional software development with PHP and related technologies. I'm happy to observe our Office Hours gradually turning into a meeting point for the German PHP community.
Last week during Office Hours, a discussion arose about naming Named Constructors. I gave some examples of how I name Named Constructors in my projects. Since I received questions about this via email over the weekend, I've written down my thoughts here.
from() Methods
A widespread way of naming is from()
, or variations derived from it. variations. Since named constructors are traditionally mainly for value objects, this approach seems like a good idea, because the method name makes clear, that an object is created from other objects or from scalar data.
Up to version 7, PHP was a less type-safe language, but now you can also write type-safe code these days. So where you used to need method names like fromInt(...)
or fromString(...)
, you can now achieve more type safety by using a scalar type declaration, keeping the same level of readability: fromString($value)
then becomes from(string $value)
.
But this again raises the problem that we cannot overload the from()
method. So what do we do if we want to be able to create an object from different types?
If we're honest, in a type-safe world, there aren't that many good reasons to do it anymore.
Let's take the following example:
class SomeValueObject |
{ |
public static function fromInt ( int $value ) : self |
{ |
... |
} |
public static function fromString ( string $value ) : self |
{ |
... |
} |
} |
If we alternatively offer only one factory method
class SomeValueObject |
{ |
public static function from ( string $value ) : self |
{ |
... |
} |
} |
then, of course, one can argue that the caller is now responsible for the type cast and that the error handling that may be required for this will result in duplicated code. After all, we write
$object = SomeValueObject :: from ( (string) $integer ) ; |
wherever the value object must be created. However: in an increasingly type-safe world, an int
is already an int
,
because we can give our parameter object (which is often also the request object) methods that return certain types:
final class ProcessFormRequest |
{ |
... |
public function parameterAsString ( string $name ) : string |
{ |
return (string) $this -> parameter ( $name ) ; |
} |
public function parameterAsInt ( string $name ) : int |
{ |
return (int) $this -> parameter ( $name ) ; |
} |
... |
} |
If we view a variable as a dynamic type, as was the case in classic PHP development, then the problem of type conversion occurs relatively late, as in this example, when the value object is created. This is because we have passed a value with an unspecified type pretty far into the application.
More Type Safety
If we think instead rather of typed variables, then we should specify the type of a parameter as early as possible. This is where the ProcessFormRequest
shown above helps us, which we can, for example, design as a wrapper around (or adapter for) the Request object of the framework we are using:
final class ProcessFormRequest |
{ |
private HttpRequestOfYourFramework $httpRequest ; |
... |
public function parameterAsString ( string $name ) : string |
{ |
return (string) $this -> httpRequest -> get ( $name ) ; |
} |
... |
} |
The problem of having to create a value object from different scalar types deep within the application then no longer arises, because type conversion has moved much closer to the "front line" of the program.
In this sense, method names like create()
or make()
, which are commonly used in common frameworks and projects, are probably just as good as from()
. However, this mainly applies to classic value objects. We discuss services and infrastructure objects below.
Even Smaller Objects
Another frequently cited use case for named constructors is to generate an HTTP request in two ways, once from parameters and once from the superglobal variables. More or less every framework does this to decouple the application from the global system state in the form of the superglobal variables:
class HttpRequest |
{ |
public static function fromSuperglobals ( ) : self |
{ |
... |
} |
public static function from ( ... ) : self |
{ |
... |
} |
} |
I was happy and satisfied with such a solution for a long time, until I realized that my request object is still too big, because it is responsible for two different things, testing and production.
One can argue whether writing code specifically for testing is a good idea. However, I would argue that it is an essential task of the framework to promote and facilitate testing of the application. Whether this should lead to such a close integration of test code and production code as is the case in Laravel, for example, is another question that I won't go into here today.
If - back in our example - we split the real and the "fake" HTTP request into two objects, then we have two smaller and more specific objects, each serving only one purpose. This is a better design.
For example, in my HTTP framework for content delivery, there are two separate classes for this purpose that implement a common interface, namely
final class RealHttpRequest implements HttpRequest |
{ |
public static function fromSuperglobals ( ) : self |
{ |
... |
} |
... |
} |
and
final class FakeHttpRequest implements HttpRequest |
{ |
private string $method ; |
private string $path ; |
private array $formData ; |
public static function get ( string $path ) : self |
{ |
return new self ( 'GET' , $path ) ; |
} |
public static function head ( string $path ) : self |
{ |
return new self ( 'HEAD' , $path ) ; |
} |
public static function post ( |
string $path , |
array $formData |
) : self |
{ |
return new self ( 'POST' , $path , $formData ) ; |
} |
... |
} |
The "fake" HTTP request provides several explicit factory methods to generate the appropriate request as needed, for example:
$request = FakeHttpRequest :: get ( '/the-url-path' ) ; |
The bootstrap file of the application, on the other hand, says:
$request = RealHttpRequest :: fromSuperglobals ( ) ; |
We definitely benefit from better readability when we offer multiple factory methods. Here, however, it is no longer a question of what data we want to generate the request from, but what kind of request we want to generate.
Different Types of Creation
Even though we have already accepted that the creation of a value object from different scalar data is no longer a common use case per se, there is still often the requirement to create an object in different ways. Whenever serialization or persistence is in play, we need to be able to create an object repeatedly in its domain-oriented lifecycle after the initial creation for technical reasons, namely when it is loaded from persistence.
Let's take a UUID as an example. The domain-oriented generation of a UUID roughly corresponds to rolling a random number. We name the constructor accordingly:
final class UUID |
{ |
... |
public static function generate ( ) : self |
{ |
... |
} |
... |
} |
If we want to recreate an object representing this UUID later, we use:
final class UUID |
{ |
... |
public static function from ( string $uuid ) : self |
{ |
... |
} |
... |
} |
Another example would be an object that represents an event. Initially, the event is created from the existing data:
class EmailVerificationRequestedEvent implements Event |
{ |
... |
public static function from ( |
AccountId $accountId , |
EmailAddress $emailAddress |
) : self |
{ |
... |
} |
... |
} |
If the event is later loaded from an event store where it was stored serialized as JSON, we use another constructor:
class EmailVerificationRequestedEvent implements Event |
{ |
... |
public static function fromJson ( Json $json ) : self |
{ |
... |
} |
... |
} |
The Json
class generically encapsulates a Json document and is responsible for both json_decode()
and clean error handling when accessing data from the document.
Of course, in this example you could also implement a separate data mapper that creates the event object from the JSON data. But I think it's better not to have to edit the mapper and the event object itself in parallel, but to have the mapping code directly in the fromJson()
method and thus in the object itself. (For entities or even aggregates my opinion would be completely different!).
Infrastructure
In some of my projects I have meanwhile switched to writing a named constructor for all objects, even though this may be technically superfluous. To create an object that is supplied with collaborators via the constructor using dependency injection, I use the method name collaboratingWith()
:
$dispatcher = SynchronousEventDispatcher :: collaboratingWith ( |
$eventHandlerLocator |
) ; |
An event dispatcher is not created from a locator, but works together with it. The from()
would definitely no longer be a suitable name here, also make()
or create()
would not be very appropriate in my opinion.
As another example we have here the factory of a framework, which has to cooperate with the application factory, because only the latter can can create the objects written by the application developer:
final class PeregrineFactory implements Factory |
{ |
... |
public static function collaboratingWith ( |
ApplicationFactory $applicationFactory |
) : self |
{ |
return new self ( $applicationFactory ) ; |
} |
... |
} |
I like that the naming suggests that the objects are collaborating at eye level
, so to speak.
I also feel that the naming distinction between from()
and collaboratingWith()
helps me to better distribute the responsibilities between the individual objects, respectively to make the objects more concise.
Exceptions
I also use named constructors or factory methods consistently for exceptions. One must always remember that PHP does not write the line in the stacktrace that contains the throw
, but the number of the line that contains the new
. Of course, you will only notice that there is a difference if you use named constructors, because otherwise the new
and the throw
are usually on the same line. But if you know this and then look into the next frame of the stacktrace to find out where an exception was thrown, then this is no problem.
So, to create an exception, we can also use a static factory method or a named constructor:
throw LongbowException :: noCommandSpecifiedFor ( $commandHandler ) ; |
I find this makes the code compact and very readable, plus it's even a little easier to find specific call locations and. Most importantly, I have all exception messages concentrated in one place, namely in the exception class itself, and can much more easily ensure consistency and uniformity there than if the texts are scattered across the codebase.
By the way, exceptions are again a very good example of objects that are created from different data:
class LongbowException extends RuntimeException |
{ |
public static function noCommandSpecifiedFor ( |
string $commandHandler |
) : self |
{ |
return new self ( sprintf ( '...' , $commandHandler ) ) ; |
} |
public static function factoryHasNoMethod ( |
object $factory , |
string $shortName |
) : self |
{ |
... |
} |
public static function noEventSpecifiedFor ( |
string $eventHandler |
) : self |
{ |
... |
} |
} |
However, nobody would think of calling the methods from()
here, would they?
Parameterless Constructors
There remain a few edge cases where finding "good" names is a problem, for example constructors which have no parameters:
$factory = Factory :: from ( ) ; |
That does not look nice, does it? I have solved that problem like this:
class Factory implements ApplicationFactory |
{ |
... |
public static function withDefaults ( ) : self |
{ |
return new self ( |
Configuration :: from ( |
Environment :: fromSuperglobals ( ) |
) |
) ; |
} |
... |
} |
Here the name emphasizes that the factory is initialized with default values. Of course, I can do this differently if the use case requires it:
class Factory implements ApplicationFactory |
{ |
... |
public static function with ( |
Configuration $configuration |
) : self |
{ |
... |
} |
... |
} |
Especially for top-level calls, for example where you initialize the application in the bootstrap file, I find parameterless constructors great. For objects in the middle of the application I usually see it differently.
Constructors and Factory Methods
To answer the question "What is your constructor called?", you have to ask what purpose the constructor serves.
Especially in PHP it is not the case that the constructor creates the object, it is just an interceptor method that is called automatically after the object has already been created (otherwise $this
in the constructor would not work). So a constructor initializes, configures or parameterizes an object, depending on what you want to call it.
A static factory method looks the same on the outside as a named constructor, but serves the purpose of selecting an implementation without the client needing to know its concrete class name. The transition between the two concepts is smooth and there is some overlap. When I call Language::en()
, it matters less whether I get back a Language
instance or an instance of English
, which is a subclass of Language
.
Is Language::en()
now a named constructor or a factory method?
In fact, it doesn't really matter, we in particular don't care if we evolve from one to the other in the future. This is exactly one big advantage of the whole thing.
If we "lock away" the new
statements by relegating them to static methods, then we've stripped our code a bit further from implementation details
(here: which classes exist or what are their names).
This is forward-compatible because it allows us to change the structure of our code in the future by introducing or removing subclasses, and without having to modify the calling code.
All in all, it may not be such a good idea to enforce a universal "one size fits all" rule for naming named constructors, especially since we don't really separate them from factory methods in everyday consideration. It seems much more sensible to choose method names that are more differentiated and technically meaningful. In this context, we should always remember that even the designation "constructor" is misleading, because nothing is created here, but only an object is initialized.
Addendum
It was just brought to my attention that Andreas Möller , one of the visitors to our Office Hours, had already taken the discussion on Friday as an opportunity to write this blogpost . Thanks for your thoughts on the topic, Andreas.