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 a User class of mine:
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 with the above function:
"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 a getter, you can start looking for setters - or methods that start with 'add', 'is', 'remove', provided the method's arguments are type hinted.
I suggest expanding like this, right before the return:
$missingProperties = array_diff_key(array_flip($properties), $results);
if (!empty($missingProperties)) { // some properties are missing
foreach ($missingProperties as $missingProperty => $val) {
if ($publicMethod->getNumberOfParameters() === 1 && strpos($publicMethod->getName(), 'set') !== false) { // get only methods that have 1 parameter and start with 'set'
$parameters = $publicMethod->getParameters();
// if not already in results, andparameter 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;