nette / php-generator

🐘 Generates neat PHP code for you. Supports new PHP 8.3 features.
https://doc.nette.org/php-generator
Other
2.11k stars 138 forks source link

Loading a class and continue editing that class. #40

Closed doitonlinemedia closed 5 years ago

doitonlinemedia commented 5 years ago

First of all, great package, amazing really! I'm trying to use it to build my models, and that works great already.

Creating things like:

public function properties()
    {
        return [
            (new Text)->name('Naam')
                ->alias('name')
                ->required()
                ->placement('left'),
        ];
    }

This "new Text" is a type that I choose using a form. A second time I want to edit this model with that form, I could want to remove a type for instance. That's fine and possible if I generate the whole class again.

The problem is that I want it to be open for modification. So if I add a method to it. Let's say:

public function properties()
    {
        return [
            (new Text)->name('Naam')
                ->alias('name')
                ->required()
                ->placement('left'),
        ];
    }

public function test()
{
    return 'test';
}

That method is not in the form where I edit the model. So if I generate it again I can't know if there are methods that need to be preserved.

My feature request therefor is that there is an option to load a full class with everything. This way I can just edit a method instead of regenerating everything.

dg commented 5 years ago

I do not understand what you need. Can you show code example using PhpGenerator?

doitonlinemedia commented 5 years ago

Actually it's just loading a class, with everything. The "from" method only loads "the skeleton" no method bodies and so on.

But regeneration the file can in some cases remove added methods. So I created a method that does just that.

Could probably be refactored a bit (some duplicated code).

  public function loadFromReflection(\ReflectionClass $from): PhpNamespace
    {
        $fullBody = file_get_contents($from->getFileName());

        $namespace = new PhpNamespace($from->getNamespaceName());
        $class = $namespace->addClass($from->getShortName());

        $fromClass = $from->isAnonymous()
            ? new ClassType
            : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));

        $class->setType($from->isInterface() ? $fromClass::TYPE_INTERFACE : ($from->isTrait() ? $fromClass::TYPE_TRAIT : $fromClass::TYPE_CLASS));
        $class->setFinal($from->isFinal() && $fromClass->getType() === $fromClass::TYPE_CLASS);
        $class->setAbstract($from->isAbstract() && $fromClass->getType() === $fromClass::TYPE_CLASS);

        $ifaces = $from->getInterfaceNames();
        foreach ($ifaces as $iface) {
            $ifaces = array_filter($ifaces, function ($item) use ($iface) {
                return !is_subclass_of($iface, $item);
            });
        }
        $class->setImplements($ifaces);

        $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
        if ($from->getParentClass()) {
            $class->setExtends($from->getParentClass()->getName());
            $class->setImplements(array_diff($fromClass->getImplements(), $from->getParentClass()->getInterfaceNames()));
        }
        $props = $methods = [];
        foreach ($from->getProperties() as $prop) {
            if ($prop->isDefault() && $prop->getDeclaringClass()->getName() === $from->getName()) {
                $props[] = $this->fromPropertyReflection($prop);
            }
        }
        $class->setProperties($props);

        foreach ($from->getMethods() as $method) {
            $start = $method->getStartLine();
            $end   = $method->getEndLine();

            $originalInArray = explode(PHP_EOL,  $fullBody);
            $body = implode(
                PHP_EOL,
                array_slice($originalInArray, $start + 1, ($end - $start) - 2)
            );

            if ($method->getDeclaringClass()->getName() === $from->getName()) {
                $methods[] = $this->fromMethodReflection($method, $body);
            }
        }

        $class->setMethods($methods);
        $class->setConstants($from->getConstants());

        return $namespace;
    }
dg commented 5 years ago

Simply you would like that Factory::fromClassReflection will read body of methods?

Because it's very hard to do it right (minified code, zipped phar, etc…), I do not want to implement it at all. I'll leave it on your own extension.