nikic / PHP-Parser

A PHP parser written in PHP
BSD 3-Clause "New" or "Revised" License
17.04k stars 1.1k forks source link

Add Property::isVirtual() #1032

Open JanTvrdik opened 3 weeks ago

JanTvrdik commented 3 weeks ago

In order to determine whether property is writable, it is now required among other thing to determine if property is virtual, which requires iterating over body of property hooks.

It would be helpful if new isVirtual() method was added to Property class.

As far as I can tell, this is the C implementation that the PHP code should match.

nikic commented 3 weeks ago

This sounds like useful information to provide. As it requires a full recursive traversal of the body, this probably has to be via a visitor that adds an attribute?

JanTvrdik commented 3 weeks ago

Visitor adding attribute would be one option.

Another would to locally recusively iterate over hooks nodes.

/**
 * Whether the property is virtual.
 */
public function isVirtual(): bool {
    if (\count($this->hooks) === 0) {
        return false;
    }

    foreach ($this->hooks as $hook) {
        if ($hook->body instanceof Node\Expr) {
            if ($hook->name->name === 'set' || $this->isHookUsingProperty([$hook->body])) {
                return false;
            }
        } elseif (\is_array($hook->body)) {
            if ($this->isHookUsingProperty($hook->body)) {
                return false;
            }
        }
    }

    return true;
}

/**
 * @param array<mixed> $nodes
 */
private function isHookUsingProperty(array $nodes): bool {
    foreach ($nodes as $node) {
        if ($node instanceof Node\Expr\PropertyFetch || $node instanceof Node\Expr\NullsafePropertyFetch) {
            if ($node->name->name === $this->props[0]->name->name && $node->var instanceof Node\Expr\Variable && $node->var->name === 'this') {
                return true;
            }
        }

        if ($node instanceof Node) {
            foreach ($node->getSubNodeNames() as $name) {
                if ($this->isHookUsingProperty(\is_array($node->$name) ? $node->$name : [$node->$name])) {
                    return true;
                }
            }
        }
    }

    return false;
}