framework icon indicating copy to clipboard operation
framework copied to clipboard

SelfValueVisitor throws exception on parsing class without namespace

Open changchang opened this issue 5 years ago • 3 comments

Error message:

Call to a member function toString() on null in vendor/goaop/framework/src/Instrument/Transformer/SelfValueVisitor.php:72

    public function enterNode(Node $node)
    {
        if ($node instanceof Stmt\Namespace_) {
            $this->namespace = $node->name->toString();

$node->name is null in a class without namespace.

changchang avatar May 31 '19 10:05 changchang

Could you please report your framework version, PHP version and steps to reproduce this isssue.

lisachenko avatar May 31 '19 10:05 lisachenko

Yeah, the goaop/framework version is 2.3.1 and the PHP version is 7.1.23.

More details about the exception stack trace:

Stack trace:
#0 vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(200): Go\Instrument\Transformer\SelfValueVisitor->enterNode(Object(PhpParser\Node\Stmt\Namespace_))
#1 vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(91): PhpParser\NodeTraverser->traverseArray(Array)
#2 vendor/goaop/framework/src/Instrument/Transformer/SelfValueTransformer.php(32): PhpParser\NodeTraverser->traverse(Array)
#3 vendor/goaop/framework/src/Instrument/Transformer/CachingTransformer.php(121): Go\Instrument\Transformer\SelfValueTransformer->transform(Object(Go\Instrument\Transformer\StreamMetaData))

I simplify my project as below:

annotation/test.php

<?php
namespace AopTest;
use Doctrine\Common\Annotations\Annotation;
/**
 * Pointcut annotation.
 * @Annotation
 * @Target("METHOD")
 */
class Test extends Annotation {
}

aspect/test.php

<?php
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;

/**
 * Test Aspect
 */
class AspectTest implements \Go\Aop\Aspect {
    /**
     * @Around("@execution(AopTest\Test)")
     */
    public function aroundTest(MethodInvocation $invocation) {
        echo "Before around<br/>";
        $res = $invocation->proceed();
        echo "After around<br/>";
        return $res;
    }
}

service/profit/test.php

<?php
use AopTest\Test;
/**
 * Example class to test aspects
 */
class ServiceProfitTest {
    /**
     * @Test
     */
    public function test()
    {
        echo "Test Result<br/>";
    }
}

aop_kernel.php

<?php
require_once (DIR_APPLICATION."aspect/test.php");
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;

/**
 * Application Aspect Kernel
 */
class AopKernel extends AspectKernel {
    protected function configureAop(AspectContainer $container) {
        $container->registerAspect(new AspectTest());
    }
}

index.php

// codes about initing the aspect kernel
$aop_kernel = AopKernel::getInstance();
$aop_kernel->init(array(
    'debug' => true,
    // Cache directory
    'cacheDir' => DIR_CACHE,
    'includePaths' => array(
        DIR_APPLICATION . 'service/'
    ),
    'excludePaths' => array(
        DIR_ROOT . 'vendor/',
        DIR_APPLICATION . 'aspect/',
    ),
));

And then I load and invoke the ServiceProfitTest::test method in a controller method.

    public function someControllerMethod() { 
        $this->load->service("profit/test");
        $this->service_profit_test->test();
    }

The $this->load->service above is the loader in my project which maintains the mapping of file name and class name. More detail is as below:

public function service($service, $data = array()) {
       $file = DIR_APPLICATION . 'service/' . $service . '.php';
       $class = 'Service' . preg_replace('/[^a-zA-Z0-9]/', '', $service);

       if (file_exists($file)) {
           // $this->classloader is the Composer\Autoload\ClassLoader instance
           if(empty($this->classloader)) {
               // regular route
               include_once($file);
           } else {
               // hack to make goaop work
               $this->classloader->addClassMap(array(
                   $class => $file,
               ));
           }
           $this->registry->set('service_' . str_replace('/', '_', $service), new $class($this->registry));
       } else {
           trigger_error('Error: Could not load service ' . $file . '!');
           exit();
       }
   }

It would throws an exception if the class ServiceProfitTest dose not has a namespace. And it would be OK if I add a namespace for the ServiceProfitTest.

It is OK if I do some check in the SelfValueVisitor::enterNode method as below:

public function enterNode(Node $node)
    {
        if ($node instanceof Stmt\Namespace_) {
            $this->namespace = $node->name->toString();
            if(!empty($node->name)) {
                $this->namespace = $node->name->toString();
            }

changchang avatar May 31 '19 11:05 changchang

Hi @lisachenko, is there any update here? I'd really appreciate the suggested fix for the v2.x - I'd be able to provide a PR if it helps?!

daniel-sc avatar Aug 21 '22 15:08 daniel-sc