The return type declarations require functions to return certain data type at call time. It clarifies expected usage of code and helps IDEs with autocomplete functionality without need of PhpDoc comments.
As of version 7, PHP added support for return type declaration and as of version 7.1 void return type and nullable return values.
Let’s create an imaginary car factory class CarFactory
to play with the
return type hinting.
When using fluent interface for a method chaining, you might be using class name in a return type hint similar to this:
1<?php
2
3public function addEngine(EngineInterface $engine): CarFactory
4{
5 $this->engine = $engine;
6
7 return $this;
8}
You might save some typing using self
keyword, which refers to the class
in which it is called, instead of a class name. The method declaration might
look similar to this:
1<?php
2
3public function addWheel(WheelInterface $wheel): self
4{
5 $this->wheels[] = $wheel;
6
7 return $this;
8}
The question is. Can we save some typing at interface and trait declarations?
Let’s create the CarFactoryInterface
and let’s declare a method to add a car body:
1public function addBody(BodyInterface $body): CarFactory;
The declaration defines a return type CarFactory
for the method.
Now we might add an engine to the body and use the self
keyword instead:
1public function addEngine(EngineInterface $engine): self;
If we try to make a car now:
1<?php
2
3$carFactory = new \Lh\CarFactory();
4$carFactory
5 ->addBody(new \Lh\SportBody())
6 ->addEngine(new \Lh\V8Engine())
7;
We will get a PHP Fatal error:
1 PHP Fatal error: Declaration of Lh\CarFactory::addEngine(Lh\EngineInterface $engine): Lh\CarFactory must be compatible with Lh\CarFactoryInterface::addEngine(Lh\EngineInterface $engine)
This is because the self
keyword refers to the CarFactoryInterface
instead
of the CarFactory
.
Are we going to get the same result if we use self
in a trait?
Let’s create a door adding functionality to our CarFactory
class and create
a trait for this:
1<?php
2
3trait DoorTrait
4{
5 /** @var DoorInterface[] */
6 private $doors;
7
8 public function addDoor(DoorInterface $door): self
9 {
10 $this->doors[] = $door;
11
12 return $this;
13 }
14}
15
16// ---
17
18class CarFactory implements CarFactoryInterface
19{
20 use DoorTrait;
21
22 ...
23}
We can update the car manufacturing process and run our script:
1<?php
2
3$carFactory = new \Lh\CarFactory();
4$carFactory
5 ->addBody(new \Lh\SportBody())
6 ->addEngine(new \Lh\V8Engine())
7 ->addDoor(new \Lh\SportDoor())
8;
We will find that no errors occur this time. This is because the traits are
essentially language assisted copy and paste, which happens at the code execution.
The self
keyword thus refers to the CarFactory
where it is use
d instead of
the DoorTrait
itself.
This can be useful if we use traits to reuse some functionality with fluent interface and various return types.
How to use declare methods in an interface if we want to use the self
as our return type hint then?
We might declare methods with a concrete return type hint or don’t declare a type hint and override the declaration in a class which implements the interface:
1<?php
2
3interface CarFactoryInterface
4{
5 public function addEngine(EngineInterface $engine): CarFactory;
6
7 public function addDoor(DoorInterface $door);
8
9 ...
10}
The whole code example can by found here
comments powered by Disqus