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 ...
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 ABC0, ABC1, ABC2 and __unset magic methods, and check the types yourself. I'm not sure if I'd recommend it, though.
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:
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 __setautomagically.
__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