Type hinting for properties in PHP 7?

PhpAttributesPhp 7Type HintingVariable Types

Php Problem Overview


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 Solutions


Solution 1 - Php

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.

Solution 2 - Php

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

Solution 3 - Php

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

Solution 4 - Php

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 ...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCarlosCarucceView Question on Stackoverflow
Solution 1 - PhpAndreaView Answer on Stackoverflow
Solution 2 - PhpCarlosCarucceView Answer on Stackoverflow
Solution 3 - PhpBruno GuignardView Answer on Stackoverflow
Solution 4 - PhpRichardView Answer on Stackoverflow