Completed
Branch feature/goaop-parser (08838c)
by Alexander
03:32
created

AbstractProxy   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 89.8%

Importance

Changes 6
Bugs 1 Features 1
Metric Value
wmc 22
c 6
b 1
f 1
lcom 2
cbo 0
dl 0
loc 163
ccs 44
cts 49
cp 0.898
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
__toString() 0 1 ?
A indent() 0 9 1
A getParameters() 0 13 3
A flattenAdvices() 0 13 4
A prepareArgsLine() 0 10 2
F getParameterCode() 0 28 11
1
<?php
2
/*
3
 * Go! AOP framework
4
 *
5
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\Proxy;
12
13
use ReflectionParameter;
14
use ReflectionMethod;
15
16
/**
17
 * Abstract class for building different proxies
18
 */
19
abstract class AbstractProxy
20
{
21
22
    /**
23
     * Indent for source code
24
     *
25
     * @var int
26
     */
27
    protected $indent = 4;
28
29
    /**
30
     * List of advices that are used for generation of child
31
     *
32
     * @var array
33
     */
34
    protected $advices = [];
35
36
    /**
37
     * PHP expression string for accessing LSB information
38
     *
39
     * @var string
40
     */
41
    protected static $staticLsbExpression = 'static::class';
42
43
    /**
44
     * Should proxy use variadics support or not
45
     *
46
     * @var bool
47
     */
48
    protected $useVariadics = false;
49
50
    /**
51
     * Constructs an abstract proxy class
52
     *
53
     * @param array $advices List of advices
54
     * @param bool $useVariadics Should proxy use variadics syntax or not
55
     */
56 5
    public function __construct(array $advices = [], $useVariadics = false)
57
    {
58 5
        $this->advices      = $this->flattenAdvices($advices);
59 5
        $this->useVariadics = $useVariadics;
60 5
    }
61
62
    /**
63
     * Returns text representation of class
64
     *
65
     * @return string
66
     */
67
    abstract public function __toString();
68
69
    /**
70
     * Indent block of code
71
     *
72
     * @param string $text Non-indented text
73
     *
74
     * @return string Indented text
75
     */
76 5
    protected function indent($text)
77
    {
78 5
        $pad   = str_pad('', $this->indent, ' ');
79
        $lines = array_map(function($line) use ($pad) {
80 5
            return $pad . $line;
81 5
        }, explode("\n", $text));
82
83 5
        return join("\n", $lines);
84
    }
85
86
    /**
87
     * Returns list of string representation of parameters
88
     *
89
     * @param array|ReflectionParameter[] $parameters List of parameters
90
     *
91
     * @return array
92
     */
93 5
    protected function getParameters(array $parameters)
94
    {
95 5
        $parameterDefinitions = [];
96 5
        foreach ($parameters as $parameter) {
97
            // Deprecated since PHP5.6 in the favor of variadics, needed for BC only
98 2
            if ($parameter->name == '...') {
99
                continue;
100
            }
101 2
            $parameterDefinitions[] = $this->getParameterCode($parameter);
102
        }
103
104 5
        return $parameterDefinitions;
105
    }
106
107
    /**
108
     * Return string representation of parameter
109
     *
110
     * @param ReflectionParameter $parameter Reflection parameter
111
     *
112
     * @return string
113
     */
114 2
    protected function getParameterCode(ReflectionParameter $parameter)
115
    {
116 2
        $type = '';
117 2
        if ($parameter->isArray()) {
118
            $type = 'array';
119 2
        } elseif ($parameter->isCallable()) {
120
            $type = 'callable';
121 2
        } elseif ($parameter->getClass()) {
122
            $type = '\\' . $parameter->getClass()->name;
123
        }
124 2
        $defaultValue = null;
125 2
        $isDefaultValueAvailable = $parameter->isDefaultValueAvailable();
126 2
        if ($isDefaultValueAvailable) {
127 2
             $defaultValue = var_export($parameter->getDefaultValue(), true);
128 2
        } elseif ($parameter->isOptional()) {
129
            $defaultValue = 'null';
130
        }
131
        $code = (
132 2
            ($type ? "$type " : '') . // Typehint
133 2
            ($parameter->isPassedByReference() ? '&' : '') . // By reference sign
134 2
            ($this->useVariadics && $parameter->isVariadic() ? '...' : '') . // Variadic symbol
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method isVariadic() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
135 2
            '$' . // Variable symbol
136 2
            ($parameter->name) . // Name of the argument
137 2
            ($defaultValue !== null ? (" = " . $defaultValue) : '') // Default value if present
138
        );
139
140 2
        return $code;
141
    }
142
143
    /**
144
     * Replace concrete advices with list of ids
145
     *
146
     * @param $advices
147
     *
148
     * @return array flatten list of advices
149
     */
150 5
    private function flattenAdvices($advices)
151
    {
152 5
        $flattenAdvices = [];
153 5
        foreach ($advices as $type => $typedAdvices) {
154 5
            foreach ($typedAdvices as $name => $concreteAdvices) {
155 5
                if (is_array($concreteAdvices)) {
156 5
                    $flattenAdvices[$type][$name] = array_keys($concreteAdvices);
157
                }
158
            }
159
        }
160
161 5
        return $flattenAdvices;
162
    }
163
164
    /**
165
     * Prepares a line with args from the method definition
166
     *
167
     * @param ReflectionMethod $method
168
     *
169
     * @return string
170
     */
171
    protected function prepareArgsLine(ReflectionMethod $method)
172
    {
173 5
        $args = join(', ', array_map(function(ReflectionParameter $param) {
174 2
            $byReference = $param->isPassedByReference() ? '&' : '';
175
176 2
            return $byReference . '$' . $param->name;
177 5
        }, $method->getParameters()));
178
179 5
        return $args;
180
    }
181
}
182