16
class Expense {

    /**
     * @var int
     */
    private $id;
}

I would like to obtain the type hint of a variable in my class, using reflection, because the default value is null.

7 Answers 7

21

Try:

<?php
class Expense {

    /**
     * @var int
     */
    private $id;
}

$refClass = new ReflectionClass('Expense');
foreach ($refClass->getProperties() as $refProperty) {
    if (preg_match('/@var\s+([^\s]+)/', $refProperty->getDocComment(), $matches)) {
        list(, $type) = $matches;
        var_dump($type);
    }
}

Output:

string(3) "int"
0
5

For PHP 7.4

$reflection = new \ReflectionProperty('className', 'propertyName');
echo $reflection->getType()->getName();
3

Obtain the complete Docblock:

$reflection = new ReflectionProperty('Expense', 'id');

$doc = $reflection->getDocComment();
3

If PHPDoc comments prove to be missing or unreliable, you can type hint all the properties of a class provided they have a matching getter.

public function getClassPropertiesType(string $className): array {
    $reflectionClass = new \ReflectionClass($className);

    $reflectionProperties = $reflectionClass->getProperties();
    $properties = [];
    foreach ($reflectionProperties as $reflectionProperty) {
        $properties[] = $reflectionProperty->getName();
    }

    $methods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
    $results = [];

    foreach ($properties as $property) {
        foreach ($methods as $method) {
            // get only methods that have 0 parameter and start with 'get'
            if ($method->getNumberOfParameters() === 0 && 
                  strpos($method->getName(),'get') !== false && 
                  stripos($method->getName(), $property) !== false) {

                $results[$property] = (string)$method->getReturnType();
            }
        }
    }

    return $results;
}

Logically, there should be only one getter for each property of the class.

If I dump the properties of some class:

  0 => "id"
  1 => "email"
  2 => "password"
  3 => "firstName"
  4 => "lastName"
  5 => "gender"
  6 => "position"
  7 => "isActive"
  9 => "dateEmployedFrom"
  10 => "dateEmployedTo"
  11 => "dateOfBirth"
  12 => "ssn"
  13 => "mobilePhone"
  14 => "homePhone"
  15 => "address"
  16 => "zipCode"
  17 => "city"
  18 => "country"

This is what you get:

  "id" => "int"
  "email" => "string"
  "password" => "string"
  "firstName" => "string"
  "lastName" => "string"
  "gender" => "bool"
  "position" => "string"
  "isActive" => "bool"
  "dateEmployedFrom" => "DateTimeInterface"
  "dateEmployedTo" => "DateTimeInterface"
  "dateOfBirth" => "DateTimeInterface"
  "ssn" => "string"
  "mobilePhone" => "string"
  "homePhone" => "string"
  "address" => "string"
  "zipCode" => "string"
  "city" => "string"
  "country" => "string"

Limitations + workaround

If a property doesn't have any getter, you can start looking for setters (or methods that start with 'add', 'is', 'remove'), provided the method's arguments are type hinted. You could also include private properties in your search $methods = $reflectionClass->getMethods(); // no filter

I suggest expanding like this, before returning:

        $missingProperties = array_diff_key(array_flip($properties), $results);

        if (!empty($missingProperties)) { // some properties are missing

            foreach ($missingProperties as $missingProperty => $val) {
                // get only methods that have 1 parameter and start with 'set'
                if ($method->getNumberOfParameters() === 1 && strpos($method->getName(), 'set') !== false) { 
                    $parameters = $method->getParameters();

                    // if not already in results, and parameter is required 
                    // and is a class property
                    if(!array_key_exists($parameters[0]->getName(), $results) &&
                                !$parameters[0]->isOptional() && 
                                in_array($parameters[0]->getName(), $properties, true)) {

                        $string = $parameters[0]->__toString();

                        $string = substr($string, strlen('Parameter #0 [ <required> '));
                        $pos = strpos($string, ' '); // get first space after type
                        $string = substr($string, 0, $pos); // get type

                        $results[$parameters[0]->getName()] = $string;
                    }
                }
            }
        }

Of course, this is not 100% bulletproof, but hopefully it will help. :-)

Last: PHP 7.4 introduces ReflectionParameter::getType So, you could drop the above string manipulation and just write:

                $type = $parameters[0]->getType();
                $results[$parameters[0]->getName()] = $type->__toString();
0
1

A bit of a warning - PHP accelerators and some libraries themselves (i.e. symfony core) strip comments, quite often on the second run.

3
  • Indeed, that's why I proposed a solution using reflection
    – Hugues D
    Commented Oct 19, 2019 at 23:52
  • Well, it might have changed in the last 8 years, didn't keep up to date with PHP. Commented Oct 20, 2019 at 0:23
  • The joys of Unity3D? - Sure seems great :-)
    – Hugues D
    Commented Oct 20, 2019 at 0:36
0

You can use the ReflectionDocBlock.

Installation

composer require phpdocumentor/reflection-docblock

Usage:

$factory  = \phpDocumentor\Reflection\DocBlockFactory::createInstance();

$reflectionClass = new ReflectionClass(MyClass::class);
$property = $reflectionClass->getProperty('foo');

$docBlock = $factory->create($property->getDocComment());

//it returns an array, as a property might declare many types
//for example @var int|string|null
$types = $docBlock->getTagsByName('var')[0]->getType()

See the reference of ReflectionClass and ReflectionProperty

-1

In PHP7.4

    public function getKeysAndTypes(): array
    {
        $return = [];
        $reflectionClass = new \ReflectionClass(static::class);
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
            if ($reflectionProperty->isPublic()) {
                $return[$reflectionProperty->getName()] = $reflectionProperty->getType()->getName();
            }
        }
        return $return;
    }

Not the answer you're looking for? Browse other questions tagged or ask your own question.