Type hinting for properties in PHP 7?
Asked Answered
C

4

99

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
Coarse answered 16/5, 2016 at 13:7 Comment(2)
Not that I'm aware of. However, generally speaking any constraints on a property's value should be done through a setter anyway. Since the setter can easily have a typehint for the "value" argument, you're good to go.Levenson
Many frameworks out there makes use of protected attributes (mostly for controllers). For those cases in particular it would be very useful.Coarse
L
160

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.

Legislation answered 16/5, 2016 at 21:28 Comment(4)
Sounds really good. Can't wait to see whats coming on the next releases!Coarse
Another significant issue is the handling of references, which don't really interact well with type declarations and might have to be disabled for such properties. Even without the performance issues, not being able to do, say, array_push($this->foo, $bar) or sort($this->foobar) would be a big deal.Legislation
How will coercion of types work? For example: (new Person())->dateOfBirth = '2001-01-01';... Provided declare(strict_types=0); that is. Will it throw or use the DateTimeImmutable constructor? And if that's the case, which kind of error will be thrown if the string is an invalid date? TypeError?Upwind
@Imerino There is no implicit conversion to DateTime(Immutable) and never has beenLegislation
C
13

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

Coarse answered 19/6, 2018 at 18:3 Comment(0)
X
10

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

Xanthein answered 4/9, 2018 at 8:32 Comment(0)
L
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 ...
Lour answered 16/5, 2016 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.