What will 'P' resolve to in the context of class 'MyContext'
Given any PHP-class, can you resolve a type to its fully qualified class as if written in the context of a class as a hinted paramter-type?
Assume the following class
<?php
namespace X\Y\Z;
use A\B\C;
use D\E\F as G;
use V\W;
use J\K{L,M,N,O,P};
class MyContext {
/**
* @var P
*/
protected $p;
/**
* @var G
*/
protected $g;
public function some(W $w): C {
}
}
using reflection we can determine what W
and C
of method some
are:
$c = new \ReflectionClass('X\\Y\\Z\\MyContext');
$m = $c->getMethod('some');
$w = $m->getParameters()[0];
$fqClassNameW = $w->getClass()->getName();
// result: "V\W"
$fqClassNameC = $m->getReturnType()->getName();
// result: "A\B\C"
however how could one, using only the @var
of the properties $p
and $g
get to the same result?
How can I programmatically find out what 'P' would resolve to in the context of this class?
Use cases: inspection of annotated properties to determine types (ORM, Code-Generation, etc.).
Solution using eval()
So far I can not find any way of resolving these without using eval()
:
function resolveNamespaceInClassContext(string $localClassName, string $hostClassName, string $classFileContents): string {
$hostClassNameParts = explode('\\', $hostClassName);
$hostClassName = array_pop($hostClassNameParts);
$fqClassName = null;
if(!preg_match('/^\<\?php(.*)[\s]*class[\s]*'.preg_quote($hostClassName).'[\s]*[^\{]*\{/s', $classFileContents, $matches)) {
throw new \Exception('could not match class header, please check syntax');
}
$evalSrc = $matches[1]; // contains the namespace part and use-statements
$evalSrc .= '$f = function('.$localClassName.' $x) {};'."\n";
$evalSrc .= '$r = new \\ReflectionFunction($f);'."\n";
$evalSrc .= '$p = $r->getParameters()[0];'."\n";
$evalSrc .= '$fqClassName = $p->getClass()->getName();'."\n";
try {
eval($evalSrc);
} catch(\ReflectionException $e) {
throw new \Exception('could not resolve local class type `'.$localClassName.'` in class `'.$hostClassName.'`');
}
return $fqClassName;
}
So this is extracting the upper part of the class-file which contains the namespace
-declaration and use
-statements and then just puts together a simple function by which we can resolve the type using standard reflection.
Parsing the namespace / imports
This crossed my mind but I would like to avoid for these reasons:
- Reflection will always work even if features around namespaces change between versions
- Parsing a single class does not tell me the entire story (is there actually a class in the current namespace which therefore does not have a use-statement?)
- generally want to avoid implementing a parser which must perfectly mimic PHPs behaviour
Is there a better more "pure" way?
Is there a better way to do this without using eval
?
I could not find any Reflection class or method that would allow to do this at runtime. I think the problem simply is that before executing the code php resolves all types into fully qualified namespaces and then throws away the information of what was imported etc. So at runtime it has no way of resolving types in relation to the defined namespace
- and use
-statements.
I am aware that as of PHP 7.4 properties can be typed directly thus allowing for standard reflection. In lower versions however that is not the case.
Aucun commentaire:
Enregistrer un commentaire