Skip to content

Instantly share code, notes, and snippets.

@niisan-tokyo
Last active November 4, 2020 06:57
Show Gist options
  • Save niisan-tokyo/7ca552da7ea3d73bd553513b048807e1 to your computer and use it in GitHub Desktop.
Save niisan-tokyo/7ca552da7ea3d73bd553513b048807e1 to your computer and use it in GitHub Desktop.
interfaceから実装を作るジェネレータ
<?php
namespace Niisan\App\Generator;
use Reflection;
use ReflectionClass;
use ReflectionMethod;
use RuntimeException;
class ImplementGenerator
{
const INDENT = 4;
private $defaultNameSpace = 'Niisan\\App';
private ReflectionClass $interface;
private $uses;
public function from(string $class_name): self
{
$reflection = new ReflectionClass($class_name);
if ($reflection->isInterface() === false) {
throw new RuntimeException('interfaceではない');
}
$this->interface = $reflection;
return $this;
}
public function output(string $filename)
{
$interface_name = $this->interface->getName();
$this->uses[] = $interface_name;
$methods = $this->analyzeMethods();
$text = $this->buildText($filename, $methods);
file_put_contents('src/' . $filename . '.php', $text);
}
private function analyzeMethods(): array
{
$methods = [];
foreach ($this->interface->getMethods() as $method) {
$methods[] = $this->analyzeMethod($method);
}
return $methods;
}
private function analyzeMethod(ReflectionMethod $method)
{
$method_name = $method->getName();
$modifier = Reflection::getModifierNames($method->getModifiers());
$modifier = array_filter($modifier, fn($name) => $name !== 'abstract');
$return_type = ($method->hasReturnType()) ? $this->removeNamespace($method->getReturnType()->__toString()) : null;
$args = [];
foreach ($method->getParameters() as $param) {
$arg = [
'type' => null,
'optional' => $param->isOptional(),
'name' => $param->getName(),
];
if ($param->hasType()) {
$type = $param->getType();
$optional = (strpos($type->__toString(), '?') === 0 and $type->isBuiltin() === false) ? '?': '';
$arg['type'] = $optional . $this->removeNamespace($type->__toString());
if ($type->isBuiltin() === false) {
$this->uses[] = ltrim($type->__toString(), '?');
}
}
if ($arg['optional']) {
$default = $param->getDefaultValue();
$arg['default'] = is_string($default)? "'$default'": $default;
}
$args[] = $arg;
}
return [
'name' => $method_name,
'modifier' => implode(' ', $modifier),
'return_type' => $return_type,
'args' => $args
];
}
private function buildText(string $filename, array $methods)
{
$text = "<?php\n";
$text .= 'namespace ' . $this->getNameSpace($filename) . ";\n\n";
$this->uses = array_unique($this->uses);
foreach ($this->uses as $use) {
$text .= 'use ' . $use . ";\n";
}
$text .= "\n" . 'class ' . $this->getClassName($filename) . ' implements ' . $this->removeNamespace($this->interface->getName());
$text .= "\n{";
$indent = str_repeat(' ', self::INDENT);
foreach ($methods as $method) {
$text .= "\n" . $indent . $method['modifier'] . ' function ' . $method['name'] . '(';
$args = [];
foreach ($method['args'] as $arg) {
$args[] .= (($arg['type']) ? $arg['type'] . ' ' : '')
. ('$' . $arg['name'])
. (($arg['optional']) ? ' = ' . $arg['default'] : '');
}
$text .= implode(', ', $args) . ')';
$text .= ($method['return_type']) ? ': ' . $method['return_type'] : '';
$text .= "\n" . $indent . "{\n\n" . $indent . "}\n";
}
$text .= '}';
return $text;
}
private function getNameSpace(string $filename)
{
$segment = explode('/', $filename);
if (count($segment) === 1) {
return $this->defaultNameSpace;
}
$name = $this->defaultNameSpace;
foreach ($segment as $key => $val) {
if ($key === count($segment) - 1) {
break;
}
$name .= '\\' . $val;
}
return $name;
}
private function getClassName(string $filename)
{
$segment = explode('/', $filename);
return $segment[count($segment) - 1];
}
private function removeNamespace(string $class): string
{
$segment = explode('\\', $class);
return $segment[count($segment) - 1];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment