Completed
Pull Request — master (#253)
by Alexander
02:59
created

AbstractProxy   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 91.53%

Importance

Changes 7
Bugs 2 Features 1
Metric Value
wmc 26
c 7
b 2
f 1
lcom 1
cbo 0
dl 0
loc 168
ccs 54
cts 59
cp 0.9153
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
__toString() 0 1 ?
A indent() 0 9 1
A getParameters() 0 9 2
D getParameterCode() 0 28 10
A flattenAdvices() 0 13 4
C prepareArgsLine() 0 28 8
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 ReflectionFunctionAbstract;
14
use ReflectionParameter;
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
     * Constructs an abstract proxy class
45
     *
46
     * @param array $advices List of advices
47
     */
48 5
    public function __construct(array $advices = [])
49
    {
50 5
        $this->advices = $this->flattenAdvices($advices);
51 5
    }
52
53
    /**
54
     * Returns text representation of class
55
     *
56
     * @return string
57
     */
58
    abstract public function __toString();
59
60
    /**
61
     * Indent block of code
62
     *
63
     * @param string $text Non-indented text
64
     *
65
     * @return string Indented text
66
     */
67 5
    protected function indent($text)
68
    {
69 5
        $pad   = str_pad('', $this->indent, ' ');
70 5
        $lines = array_map(function($line) use ($pad) {
71 5
            return $pad . $line;
72 5
        }, explode("\n", $text));
73
74 5
        return join("\n", $lines);
75
    }
76
77
    /**
78
     * Returns list of string representation of parameters
79
     *
80
     * @param array|ReflectionParameter[] $parameters List of parameters
81
     *
82
     * @return array
83
     */
84 5
    protected function getParameters(array $parameters)
85
    {
86 5
        $parameterDefinitions = [];
87 5
        foreach ($parameters as $parameter) {
88 2
            $parameterDefinitions[] = $this->getParameterCode($parameter);
89
        }
90
91 5
        return $parameterDefinitions;
92
    }
93
94
    /**
95
     * Return string representation of parameter
96
     *
97
     * @param ReflectionParameter $parameter Reflection parameter
98
     *
99
     * @return string
100
     */
101 2
    protected function getParameterCode(ReflectionParameter $parameter)
102
    {
103 2
        $type = '';
104 2
        if ($parameter->isArray()) {
105
            $type = 'array';
106 2
        } elseif ($parameter->isCallable()) {
107
            $type = 'callable';
108 2
        } elseif ($parameter->getClass()) {
109
            $type = '\\' . $parameter->getClass()->name;
110
        }
111 2
        $defaultValue = null;
112 2
        $isDefaultValueAvailable = $parameter->isDefaultValueAvailable();
113 2
        if ($isDefaultValueAvailable) {
114 2
            $defaultValue = var_export($parameter->getDefaultValue(), true);
115 2
        } elseif ($parameter->isOptional()) {
116
            $defaultValue = 'null';
117
        }
118
        $code = (
119 2
            ($type ? "$type " : '') . // Typehint
120 2
            ($parameter->isPassedByReference() ? '&' : '') . // By reference sign
121 2
            ($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...
122 2
            '$' . // Variable symbol
123 2
            ($parameter->name) . // Name of the argument
124 2
            ($defaultValue !== null ? (" = " . $defaultValue) : '') // Default value if present
125
        );
126
127 2
        return $code;
128
    }
129
130
    /**
131
     * Replace concrete advices with list of ids
132
     *
133
     * @param $advices
134
     *
135
     * @return array flatten list of advices
136
     */
137 5
    private function flattenAdvices($advices)
138
    {
139 5
        $flattenAdvices = [];
140 5
        foreach ($advices as $type => $typedAdvices) {
141 5
            foreach ($typedAdvices as $name => $concreteAdvices) {
142 5
                if (is_array($concreteAdvices)) {
143 5
                    $flattenAdvices[$type][$name] = array_keys($concreteAdvices);
144
                }
145
            }
146
        }
147
148 5
        return $flattenAdvices;
149
    }
150
151
    /**
152
     * Prepares a line with args from the method definition
153
     *
154
     * @param ReflectionFunctionAbstract $functionLike
155
     *
156
     * @return string
157
     */
158 5
    protected function prepareArgsLine(ReflectionFunctionAbstract $functionLike)
159
    {
160 5
        $argumentsPart = [];
161 5
        $arguments     = [];
162 5
        $hasOptionals  = false;
163
164 5
        foreach ($functionLike->getParameters() as $parameter) {
165 2
            $byReference  = ($parameter->isPassedByReference() && !$parameter->isVariadic()) ? '&' : '';
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...
166 2
            $hasOptionals = $hasOptionals || $parameter->isOptional();
167
168 2
            $arguments[] = $byReference . '$' . $parameter->name;
169
        }
170
171 5
        $isVariadic = $functionLike->isVariadic();
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionFunctionAbstract as the method isVariadic() does only exist in the following sub-classes of ReflectionFunctionAbstract: Go\ParserReflection\ReflectionFunction, Go\ParserReflection\ReflectionMethod. 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...
172 5
        if ($isVariadic) {
173
            $argumentsPart[] = array_pop($arguments);
174
        }
175 5
        if (!empty($arguments)) {
176
            // Unshifting to keep correct order
177 2
            $argumentLine = '[' . join(', ', $arguments) . ']';
178 2
            if ($hasOptionals) {
179 2
                $argumentLine = "\\array_slice($argumentLine, 0, \\func_num_args())";
180
            }
181 2
            array_unshift($argumentsPart, $argumentLine);
182
        }
183
184 5
        return join(', ', $argumentsPart);
185
    }
186
}
187