Search code examples
phpphp-7.4

Get type of typed property in PHP 7.4


I have a DTO with typed PHP variables:

class CreateMembershipInputDto extends BaseDto
{
    public bool $is_gift;
    public int $year;
    public string $name;
    public \DateTime $shipping_date;
    public ContactInputDto $buyer;
    public ?ContactInputDto $receiver;
}

I am trying to make some kind of automapper, which fills the properties, but I need to check the type of the variable but that seems to be impossible.

class BaseDto
{
    public function __construct($json)
    {
        $jsonArray = json_decode($json, true);
        foreach($jsonArray as $key=>$value){
            $type = gettype($this->$key);
            if($type instanceof BaseDto)
                $this->$key = new $type($value);
            else
                $this->$key = $value;
        }
    }
}

ContactInputDto:

class ContactInputDto extends BaseDto
{
    public string $firstname;
    public string $lastname;
    public string $street_housenumber;
    public string $postal_code;
    public string $place;
    public string $country;
    public string $email;
    public string $phone;
}

Is it somehow possible to make that line "gettype($this->$key)" work, without php throwing the following error:

Typed property App\Dto\CreateMembershipInputDto::$is_gift must not be accessed before initialization


Solution

  • While the manual does not seem to currently document it, there is a method added to ReflectionProperty to allow you to get the type. This is actually specified in the RFC for typed properties

    Here's how you would use it:

    class CreateMembershipInputDto extends BaseDto {
        public bool $is_gift;
        public int $year;
        public string $name;
        public \DateTime $shipping_date;
        public ContactInputDto $buyer;
        public ?ContactInputDto $receiver;
    }
    
    class BaseDto
    {
        public function __construct($json)
        {   
            $r = new \ReflectionClass(static::class); //Static should resolve the the actual class being constructed
            $jsonArray = json_decode($json, true);
            foreach($jsonArray as $key=>$value){
                $prop = $r->getProperty($key);
                if (!$prop || !$prop->getType()) { continue; } // Not a valid property or property has no type   
                $type = $prop->getType();
                if($type->getName() === BaseDto::class) //types names are strings
                    $this->$key = new $type($value);
                else
                    $this->$key = $value;
            }
        }
    }
    
    

    If you want to check if the type extends BaseDto you will need (new \ReflectionClass($type->getName()))->isSubclassOf(BaseDto::class)

    Note that getName refers to to ReflectionNamedType::getName. Prior to PHP 8 this was the only possible instance you could get $prop->getType() however starting PHP 8 you may also get a ReflectionUnionType which contains multiple types