Type hinting for properties in PHP 7?
Does php 7 support type hinting for class properties?
I mean, not just for setters/getters but for the property itself.
Something like:
class Foo {
/**
*
* @var Bar
*/
public $bar : Bar;
}
$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
PHP 7.4 will support typed properties like so:
class Person
{
public string $name;
public DateTimeImmutable $dateOfBirth;
}
PHP 7.3 and earlier do not support this, but there are some alternatives.
You can make a private property which is accessible only through getters and setters which have type declarations:
class Person
{
private $name;
public function getName(): string {
return $this->name;
}
public function setName(string $newName) {
$this->name = $newName;
}
}
You can also make a public property and use a docblock to provide type information to people reading the code and using an IDE, but this provides no runtime type-checking:
class Person
{
/**
* @var string
*/
public $name;
}
And indeed, you can combine getters and setters and a docblock.
If you're more adventurous, you could make a fake property with the __get
, __set
, __isset
and __unset
magic methods, and check the types yourself. I'm not sure if I'd recommend it, though.
7.4+:
Good news that it will be implemented in the new releases, as @Andrea pointed out. I will just leave this solution here in case someone wants to use it prior to 7.4
7.3 or less
Based on the notifications I still receive from this thread, I believe that many people out there had/is having the same issue that I had. My solution for this case was combining setters + __set
magic method inside a trait in order to simulate this behaviour.
Here it is:
trait SettersTrait
{
/**
* @param $name
* @param $value
*/
public function __set($name, $value)
{
$setter = 'set'.$name;
if (method_exists($this, $setter)) {
$this->$setter($value);
} else {
$this->$name = $value;
}
}
}
And here is the demonstration:
class Bar {}
class NotBar {}
class Foo
{
use SettersTrait; //It could be implemented within this class but I used it as a trait for more flexibility
/**
*
* @var Bar
*/
private $bar;
/**
* @param Bar $bar
*/
protected function setBar(Bar $bar)
{
//(optional) Protected so it wont be called directly by external 'entities'
$this->bar = $bar;
}
}
$foo = new Foo();
$foo->bar = new NotBar(); //Error
//$foo->bar = new Bar(); //Success
Explanation
First of all, define bar
as a private property so PHP will cast __set
automagically.
__set
will check if there is some setter declared in the current object (method_exists($this, $setter)
). Otherwise it will only set its value as it normally would.
Declare a setter method (setBar) that receives a type-hinted argument (setBar(Bar $bar)
).
As long as PHP detects that something that is not Bar
instance is being passed to the setter, it will automaticaly trigger a Fatal Error: Uncaught TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of NotBar given
Edit for PHP 7.4 :
Since PHP 7.4 you can type attributes (Documentation / Wiki) which means you can do :
class Foo
{
protected ?Bar $bar;
public int $id;
...
}
According to wiki, all acceptable values are :
- bool, int, float, string, array, object
- iterable
- self, parent
- any class or interface name
- ?type // where "type" may be any of the above
PHP < 7.4
It is actually not possible and you only have 4 ways to actually simulate it :
- Default values
- Decorators in comment blocks
- Default values in constructor
- Getters and setters
I combined all of them here
class Foo
{
/**
* @var Bar
*/
protected $bar = null;
/**
* Foo constructor
* @param Bar $bar
**/
public function __construct(Bar $bar = null){
$this->bar = $bar;
}
/**
* @return Bar
*/
public function getBar() : ?Bar{
return $this->bar;
}
/**
* @param Bar $bar
*/
public function setBar(Bar $bar) {
$this->bar = $bar;
}
}
Note that you actually can type the return as ?Bar since php 7.1 (nullable) because it could be null (not available in php7.0.)
You also can type the return as void since php7.1
You can use setter
class Bar {
public $val;
}
class Foo {
/**
*
* @var Bar
*/
private $bar;
/**
* @return Bar
*/
public function getBar()
{
return $this->bar;
}
/**
* @param Bar $bar
*/
public function setBar(Bar $bar)
{
$this->bar = $bar;
}
}
$fooInstance = new Foo();
// $fooInstance->bar = new NotBar(); //Error
$fooInstance->setBar($fooInstance);
Output:
TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...