Completed
Push — master ( b6d688...cba8ad )
by Alexander
11s
created

GeneralAspectLoaderExtension::getTargets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Core;
13
14
use Closure;
15
use Go\Aop\Advisor;
16
use Go\Aop\Aspect;
17
use Go\Aop\Framework;
18
use Go\Aop\Intercept\Interceptor;
19
use Go\Aop\Pointcut;
20
use Go\Aop\PointFilter;
21
use Go\Aop\Support\DefaultPointcutAdvisor;
22
use Go\Lang\Annotation;
23
use Go\Lang\Annotation\BaseInterceptor;
24
use ReflectionMethod;
25
use Reflector;
26
27
/**
28
 * General aspect loader add common support for general advices, declared as annotations
29
 */
30
class GeneralAspectLoaderExtension extends AbstractAspectLoaderExtension
31
{
32
33
    /**
34
     * General aspect loader works with annotations from aspect
35
     */
36
    public function getKind() : string
37
    {
38
        return self::KIND_ANNOTATION;
39
    }
40
41
    /**
42
     * General aspect loader works only with methods of aspect
43
     */
44 2
    public function getTargets() : array
45
    {
46 2
        return [self::TARGET_METHOD];
47
    }
48
49
    /**
50
     * Checks if loader is able to handle specific point of aspect
51
     *
52
     * @param Aspect $aspect Instance of aspect
53
     * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point
54
     * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method
55
     *
56
     * @return boolean true if extension is able to create an advisor from reflection and metaInformation
57
     */
58
    public function supports(Aspect $aspect, $reflection, $metaInformation = null) : bool
59
    {
60
        return $metaInformation instanceof Annotation\Interceptor
61
                || $metaInformation instanceof Annotation\Pointcut;
62
    }
63
64
    /**
65
     * Loads definition from specific point of aspect into the container
66
     *
67
     * @param Aspect $aspect Instance of aspect
68
     * @param Reflector|ReflectionMethod $reflection Reflection of point
69
     * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method
70
     *
71
     * @return array|Pointcut[]|Advisor[]
72
     *
73
     * @throws \UnexpectedValueException
74
     */
75
    public function load(Aspect $aspect, Reflector $reflection, $metaInformation = null) : array
76
    {
77
        $loadedItems    = [];
78
        $pointcut       = $this->parsePointcut($aspect, $reflection, $metaInformation);
79
        $methodId       = get_class($aspect) . '->' . $reflection->name;
0 ignored issues
show
Bug introduced by
Accessing name on the interface Reflector suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
80
        $adviceCallback = $reflection->getClosure($aspect);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getClosure() does only exist in the following implementations of said interface: Doctrine\Common\Reflection\StaticReflectionMethod, Go\Aop\Support\AnnotatedReflectionMethod, Go\Aop\Support\NamespacedReflectionFunction, Go\ParserReflection\ReflectionFunction, Go\ParserReflection\ReflectionMethod, ReflectionFunction, ReflectionMethod.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
81
82
        switch (true) {
83
            // Register a pointcut by its name
84
            case ($metaInformation instanceof Annotation\Pointcut):
85
                $loadedItems[$methodId] = $pointcut;
86
                break;
87
88
            case ($pointcut instanceof PointFilter):
89
                $advice = $this->getInterceptor($metaInformation, $adviceCallback);
90
91
                $loadedItems[$methodId] = new DefaultPointcutAdvisor($pointcut, $advice);
0 ignored issues
show
Compatibility introduced by
$pointcut of type object<Go\Aop\PointFilter> is not a sub-type of object<Go\Aop\Pointcut>. It seems like you assume a child interface of the interface Go\Aop\PointFilter to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
92
                break;
93
94
            default:
95
                throw new \UnexpectedValueException("Unsupported pointcut class: " . get_class($pointcut));
96
        }
97
98
        return $loadedItems;
99
    }
100
101
    /**
102
     * Returns an interceptor instance by meta-type annotation
103
     *
104
     * @param BaseInterceptor $metaInformation Meta-information from the annotation
105
     * @param Closure $adviceCallback Advice closure
106
     * @return Interceptor Instance of interceptor by meta-information
107
     *
108
     * @throws \UnexpectedValueException For unsupported annotations
109
     */
110
    protected function getInterceptor(BaseInterceptor $metaInformation, Closure $adviceCallback) : Interceptor
111
    {
112
        $adviceOrder        = $metaInformation->order;
113
        $pointcutExpression = $metaInformation->value;
114
        switch (true) {
115
            case ($metaInformation instanceof Annotation\Before):
116
                return new Framework\BeforeInterceptor($adviceCallback, $adviceOrder, $pointcutExpression);
117
118
            case ($metaInformation instanceof Annotation\After):
119
                return new Framework\AfterInterceptor($adviceCallback, $adviceOrder, $pointcutExpression);
120
121
            case ($metaInformation instanceof Annotation\Around):
122
                return new Framework\AroundInterceptor($adviceCallback, $adviceOrder, $pointcutExpression);
123
124
            case ($metaInformation instanceof Annotation\AfterThrowing):
125
                return new Framework\AfterThrowingInterceptor($adviceCallback, $adviceOrder, $pointcutExpression);
126
127
            default:
128
                throw new \UnexpectedValueException("Unsupported method meta class: " . get_class($metaInformation));
129
        }
130
    }
131
}
132