Skip to content

Instantly share code, notes, and snippets.

@milesj
Last active April 20, 2020 03:27
Show Gist options
  • Save milesj/1097f6f8a4f7801710e1 to your computer and use it in GitHub Desktop.
Save milesj/1097f6f8a4f7801710e1 to your computer and use it in GitHub Desktop.
HHI Definition Generator
<?php
/**
* This file can be used to generate HHVM/Hack HHI definitions.
* It requires a specific extension to be passed as an argument on the command line.
* It will then loop through all constants, functions, and classes and print accordingly.
*
* For example, to generate HHI definitions for the SPL extension.
*
* php generate.php spl
*
* This will output directly to the console. To save to a file, simply pipe it.
*
* php generate.php spl > spl.hhi
*
* This MUST BE RAN with PHP 5.6+ and not with HHVM. HHVM does not have full support
* for the `ReflectionExtension` class. Issue here: https://github.com/facebook/hhvm/issues/4958
*
* KNOWN ISSUES:
* - Function/method arguments cannot access default values for built-ins, only user-land.
* All default values are set to `null` or an empty array `[]`.
*/
if (empty($argv[1])) {
echo "Please enter an extension name as the 1st argument.\n";
exit(1);
} else if ($argv[1] === '--list') {
$list = get_loaded_extensions(true) + get_loaded_extensions();
sort($list);
print_r($list);
exit(0);
}
// Detect the extension
try {
$ext = new ReflectionExtension($argv[1]);
} catch (Exception $e) {
echo $e->getMessage() . ".\n";
exit(1);
}
// Helper functions
function get_type_hint($value) {
if (is_float($value)) {
return 'float';
} else if (is_bool($value)) {
return 'bool';
} else if (is_int($value)) {
return 'int';
} else if (is_array($value)) {
return 'array';
} else if (is_object($value)) {
return get_class($value);
} else if (!is_null($value) && !is_string($value)) {
var_dump('UNKNOWN TYPE', $value);
}
return 'string';
}
function get_type_value($value) {
if (is_int($value) || is_float($value)) {
return $value;
} else if (is_bool($value)) {
return $value ? 'true' : 'false';
} else if (is_null($value)) {
return 'null';
}
return sprintf("'%s'", $value);
}
function render_arguments(array $params) {
$args = [];
foreach ($params as $param) {
$args[] = render_argument($param);
}
return implode(', ', $args);
}
function render_argument(ReflectionParameter $arg) {
$name = '';
$type = '';
$default = '';
if ($arg->isArray()) {
$type = 'array';
} else if ($arg->isCallable()) {
$type = 'callable';
} else if ($class = $arg->getClass()) {
$type = $class->getName();
}
if ($type && $arg->allowsNull()) {
$type = '?' . $type;
}
if ($arg->isPassedByReference()) {
$name .= '&';
}
if ($arg->isVariadic()) {
$name .= '...';
}
$name .= '$' . $arg->getName();
if ($arg->isOptional()) {
$default = '= ';
try {
if ($value = $arg->getDefaultValueConstantName()) {
$default .= $value;
} else if ($value = $arg->getDefaultValue()) {
$default .= get_type_value($value);
}
} catch (Exception $e) {
if ($arg->isArray()) {
$default .= '[]';
} else {
$default .= 'null';
if ($type && $type[0] !== '?') {
$type = '?' . $type;
}
}
}
}
return trim(sprintf('%s %s %s', $type, $name, $default));
}
function render_method(ReflectionMethod $method, $interface = false) {
$modifiers = '';
if (!$interface) {
if ($method->isFinal()) {
$modifiers .= 'final ';
} else if ($method->isAbstract()) {
$modifiers .= 'abstract ';
}
}
if ($method->isPrivate()) {
$modifiers .= 'private ';
} else if ($method->isProtected()) {
$modifiers .= 'protected ';
} else {
$modifiers .= 'public ';
}
if ($method->isStatic()) {
$modifiers .= 'static ';
}
return trim(sprintf('%s function %s(%s);', trim($modifiers), $method->getName(), render_arguments($method->getParameters())));
}
function render_property(ReflectionProperty $prop) {
$modifiers = '';
if ($prop->isPrivate()) {
$modifiers .= 'private ';
} else if ($prop->isProtected()) {
$modifiers .= 'protected ';
} else {
$modifiers .= 'public ';
}
if ($prop->isStatic()) {
$modifiers .= 'static ';
}
return trim(sprintf('%s $%s;', trim($modifiers), $prop->getName()));
}
// Loop constants first
foreach ($ext->getConstants() as $constant => $value) {
printf("const %s %s = %s;\n", get_type_hint($value), $constant, get_type_value($value));
}
echo "\n";
// Loop global functions second
$functions = $ext->getFunctions();
ksort($functions);
foreach ($functions as $name => $function) {
printf("function %s(%s);\n", $name, render_arguments($function->getParameters()));
}
echo "\n";
// Loop classes last
foreach ($ext->getClasses() as $class) {
$modifiers = '';
$methods = [];
if (!$class->isTrait() && !$class->isInterface()) {
if ($class->isAbstract()) {
$modifiers .= 'abstract ';
} else if ($class->isFinal()) {
$modifiers .= 'final ';
}
}
if ($class->isTrait()) {
$modifiers .= 'trait';
} else if ($class->isInterface()) {
$modifiers .= 'interface';
} else {
$modifiers .= 'class';
}
printf('%s %s', $modifiers, $class->getName());
if ($parent = $class->getParentClass()) {
printf(' extends %s', $parent->getName());
}
if ($interfaces = $class->getInterfaceNames()) {
$interfaces = array_diff($interfaces, $parent ? $parent->getInterfaceNames() : []);
if ($interfaces) {
printf(' implements %s', implode(', ', $interfaces));
}
}
echo " {\n";
if ($traits = $class->getTraitNames()) {
printf(" use %s;\n", implode(', ', $traits));
}
foreach ($class->getConstants() as $constant => $value) {
if (!$parent || !$parent->hasConstant($constant)) {
printf(" const %s %s = %s;\n", get_type_hint($value), $constant, ($value === null) ? "''" : get_type_value($value));
}
}
foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
printf(" %s\n", render_property($prop));
}
foreach ($class->getMethods() as $method) {
$methods[$method->getName()] = $method;
}
ksort($methods);
foreach ($methods as $name => $method) {
if (!$parent || !$parent->hasMethod($name)) {
printf(" %s\n", render_method($method, $class->isInterface()));
}
}
echo "}\n\n";
}
exit(0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment