Skip to content

Instantly share code, notes, and snippets.

@WinterSilence
Last active November 30, 2021 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WinterSilence/ce586345d1ed4b147cc9385aa923ae83 to your computer and use it in GitHub Desktop.
Save WinterSilence/ce586345d1ed4b147cc9385aa923ae83 to your computer and use it in GitHub Desktop.
The reflection class reports information about a PHP7 file. Collects functions, interfaces, classes, traits with his constants, properties, methods.
<?php
namespace EnsoStudio;
use Reflector;
use Reflection;
use ReflectionClass;
use ReflectionType;
use ReflectionNamedType;
use ReflectionFunctionAbstract;
/**
* The reflection class reports information about a PHP >= 7 file. Collects functions/interfaces/classes/traits with
* his constants/properties/methods.
*
* @author info@ensostudio.ru
* @license MIT
*/
class ReflectionFile
{
/**
* Types to fix
*/
const FIX_TYPES = ['boolean' => 'bool', 'integer' => 'int'];
/**
* @var array
*/
protected $declared = [
'constants' => [],
'functions' => [],
'interfaces' => [],
'traits' => [],
'classes' => [],
];
/**
* @var array
*/
protected $comments = [];
/**
* @var array
*/
protected $constants = [];
/**
* @var array
*/
protected $functions = [];
/**
* @var array
*/
protected $interfaces = [];
/**
* @var array
*/
protected $traits = [];
/**
* @var array
*/
protected $classes = [];
/**
* @param string $file the path to PHP file
*/
public function __construct($file)
{
foreach (array_keys($this->declared) as $type) {
$this->declared[$type] = $this->getDeclared($type);
}
require_once realpath($file);
}
/**
* @param string $type
* @return array
*/
protected function getDeclared($type)
{
if (in_array($type, ['constants', 'functions'])) {
$result = "get_defined_{$type}"(true);
return $result['user'] ?? [];
}
return "get_declared_{$type}"();
}
/**
* @param string $comment DocComment
* @return array [description: string, tags: array]
*/
public function parseComment($comment)
{
$description = '';
$tags = [];
$matches = [];
$comment = trim($comment);
if ($comment) {
// $comment = strtr(substr($comment, 2, -2), ["\r" => '']);
// Remove '/*', '*/'
$comment = substr($comment, 2, -2);
if (preg_match_all('/^\s*\*\s+?(.*)$/m', $comment, $matches)) {
$comment = [];
foreach ($matches[1] as $line) {
if (substr($line, 0, 1) == '@') {
$comment[] = trim($line);
} else {
$comment[count($comment) - 1] .= ' ' . trim($line);
}
}
foreach ($comment as $line) {
if (preg_match('/^@([-a-z]+)\s*(.*)$/mi', $line, $matches)) {
list(, $tag, $line) = $matches;
if (!isset($tags[$tag])) {
$tags[$tag] = [];
}
if ($tag == 'param') {
$line = preg_split('/\s+/', $line, 3);
if (count($line) > 1) {
$tags[$tag][substr($line[1], 1)] = [
'type' => strtr($line[0], self::FIX_TYPES),
'description' => $line[2] ?? '',
];
}
} elseif (in_array($tag, ['var', 'return', 'throws'])) {
$line = preg_split('/\s+/', $line, 2);
if (count($line) > 1) {
$tags[$tag][] = [
'type' => strtr($line[1], self::FIX_TYPES),
'description' => $line[1] ?? '',
];
}
} else {
$tags[$tag][] = $line;
}
} else {
$description = $line;
}
}
}
}
return compact('description', 'tags');
}
/**
* Parse comment.
*
* @param Reflector $r
* @param string|null $returnTag
* @return mixed
*/
protected function getComment(Reflector $r, $returnTag = null)
{
if (property_exists($r, 'class')) {
$key = $r->class . '::' . $r->name;
} else {
$key = $r->name;
}
if (!isset($this->comments[$key])) {
$comment = $r->getDocComment();
$this->comments[$key] = $this->parseComment($comment);
}
if ($returnTag) {
return $this->comments[$key]['tags'][$returnTag] ?? null;
}
return $this->comments[$key];
}
/**
* Returns modifier names.
*
* @param Reflector $r
* @return array
*/
public function getModifierNames(Reflector $r)
{
return Reflection::getModifierNames($r->getModifiers());
}
/**
* Returns type name.
*
* @param ReflectionType $type
* @return string
*/
public function getTypeName(ReflectionType $type)
{
if ($type instanceof ReflectionNamedType) {
return $type->getName();
}
return (string) $type;
}
/**
* Returns declaring class.
*
* @param Reflector $reflection the class to reflect
* @return string
*/
public function getDeclaringClass(Reflector $reflection)
{
$declaringClass = $reflection->getDeclaringClass();
return $declaringClass === $reflection->class ? 'self' : $declaringClass;
}
/**
* Returns class constants.
*
* @param Reflector $class the class to reflect
* @return array
*/
public function getClassConstants(Reflector $class)
{
$constants = [];
foreach ($class->getConstants() as $name => $value) {
$constant = $class->getReflectionConstant($name);
$constants[$name] = [
'value' => $value,
'modifiers' => $this->getModifierNames($constant),
'description' => $this->getComment($constant),
'inherited' => $this->getDeclaringClass($constant),
];
}
return $constants;
}
/**
* Returns class properties.
*
* @param Reflector $class the class to reflect
* @return array
*/
public function getProperties(Reflector $class)
{
$properties = [];
foreach ($class->getDefaultProperties() as $name => $value) {
$property = $class->getProperty($name);
$comment = $this->getComment($property);
if (preg_match('/@var\s+([^\s]+)(.*)/i', $comment, $matches)) {
$type = $matches[1];
$comment = trim($matches[2]);
} else {
$type = 'null';
}
$properties[$name] = [
'type' => $type,
'value' => $value,
'modifiers' => $this->getModifierNames($property),
'description' => $comment,
'inherited' => $this->getDeclaringClass($property),
];
}
return $properties;
}
/**
* Returns result of function/method.
*
* @param ReflectionFunction|ReflectionMethod $function function/method
* @return string
*/
public function getFunctionReturn(ReflectionFunctionAbstract $function)
{
if ($function->hasReturnType()) {
return $this->getTypeName($function->getReturnType());
}
$comment = $this->getComment($function);
if (!empty($comment) && preg_match('/@return\s+([^\s\[]+)/ui', $comment, $matches) === 1) {
return $matches[1];
}
return 'void';
}
/**
* Returns parameters of function/method.
*
* @param ReflectionFunction|ReflectionMethod $function function/method
* @return array
*/
public function getFunctionParameters(ReflectionFunctionAbstract $function)
{
$params = [];
$tags = $this->getComment($function, 'param');
foreach ($function->getParameters() as $param) {
if ($param->isDefaultValueAvailable()) {
if ($param->isDefaultValueConstant()) {
$value = 'const ' . $param->getDefaultValueConstantName();
} else {
$value = $param->getDefaultValue();
}
} else {
$value = null;
}
$name = $param->getName();
$type = $comment = '';
if ($param->hasType()) {
$type = $this->getTypeName($param->getType());
} elseif (isset($tags[$name])) {
$type = $tags[$name][0];
if (!empty($tags[$name][1])) {
$comment = $tags[$name][1];
}
}
$params[$name] = [
'type' => $type,
'value' => $value,
'description' => $comment,
'optional' => $param->isOptional(),
];
}
return $params;
}
/**
* @param string $type
* @return array
*/
public function get($type)
{
if (!is_array($this->{$type})) {
$this->{$type} = array_diff($this->getDeclared($type), $this->declared[$type]);
unset($this->declared[$type]);
foreach ($this->{$type} as $key => $name) {
$class = new ReflectionClass($name);
$parentClass = $class->getParentClass();
$this->{$type}[$key] = [
'parent' => $parentClass ? $parentClass->getName() : '',
'interfaces' => $class->getInterfaceNames(),
'traits' => $class->isInterface() ? [] : $class->getTraitNames(),
'modifiers' => $this->getModifierNames($class),
'description' => $this->getComment($class),
'constants' => $this->getClassConstants($class),
'properties' => $class->isInterface() ? [] : $this->getProperties($class),
'methods' => $this->getMethods($class),
];
}
}
return $this->{$type};
}
/**
* Returns class methods.
*
* @param Reflector $class the class to reflect
* @return array
*/
public function getMethods(Reflector $class)
{
$methods = [];
foreach ($class->getMethods() as $name) {
$method = $class->getMethod($name);
$methods[$name] = [
'parameters' => $this->getFunctionParameters(),
'return' => $this->getFunctionReturn($method),
'modifiers' => $this->getModifierNames($method),
'description' => $this->getComment($method),
'inherited' => $this->getDeclaringClass($method),
];
}
return $methods;
}
/**
* @return array
*/
public function getConstants()
{
return $this->get('constants');
}
/**
* @return array
*/
public function getFunctions()
{
return $this->get('functions');
}
/**
* @return array
*/
public function getInterfaces()
{
return $this->get('interfaces');
}
/**
* @return array
*/
public function getTraits()
{
return $this->get('traits');
}
/**
* @return array
*/
public function getClasses()
{
return $this->get('classes');
}
}
<?php
// Explore php file
$reflection = new Enso\ReflectionFile('path/to/class.php');
var_dump(
$reflection->getConstants(),
$reflection->getFunctions(),
$reflection->getInterfaces(),
$reflection->getTraits(),
$reflection->getClasses()
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment