Completed
Pull Request — master (#72)
by Nate
03:35
created

DefaultProxyFactory::create()   F

Complexity

Conditions 19
Paths 341

Size

Total Lines 127
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 127
rs 3.6524
c 0
b 0
f 0
cc 19
eloc 73
nc 341
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Retrofit\Internal;
10
11
use InvalidArgumentException;
12
use LogicException;
13
use PhpParser\BuilderFactory;
14
use PhpParser\Node\Expr\ConstFetch;
15
use PhpParser\Node\Expr\FuncCall;
16
use PhpParser\Node\Expr\MethodCall;
17
use PhpParser\Node\Expr\Variable;
18
use PhpParser\Node\Name;
19
use PhpParser\Node\NullableType;
20
use PhpParser\Node\Scalar\String_;
21
use PhpParser\Node\Stmt\Return_;
22
use PhpParser\PrettyPrinterAbstract;
23
use ReflectionClass;
24
use RuntimeException;
25
use Tebru\PhpType\TypeToken;
26
use Tebru\Retrofit\HttpClient;
27
use Tebru\Retrofit\Internal\ServiceMethod\ServiceMethodFactory;
28
use Tebru\Retrofit\Proxy;
29
use Tebru\Retrofit\Proxy\AbstractProxy;
30
use Tebru\Retrofit\ProxyFactory;
31
32
/**
33
 * Class DefaultProxyFactory
34
 *
35
 * @author Nate Brunette <[email protected]>
36
 */
37
final class DefaultProxyFactory implements ProxyFactory
38
{
39
    public const PROXY_PREFIX = 'Tebru\Retrofit\Proxy\\';
40
41
    /**
42
     * @var BuilderFactory
43
     */
44
    private $builderFactory;
45
46
    /**
47
     * @var PrettyPrinterAbstract
48
     */
49
    private $printer;
50
51
    /**
52
     * @var ServiceMethodFactory
53
     */
54
    private $serviceMethodFactory;
55
56
    /**
57
     * @var HttpClient
58
     */
59
    private $httpClient;
60
61
    /**
62
     * @var Filesystem
63
     */
64
    private $filesystem;
65
66
    /**
67
     * @var bool
68
     */
69
    private $enableCache;
70
71
    /**
72
     * @var string
73
     */
74
    private $cacheDir;
75
76
    /**
77
     * Constructor
78
     *
79
     * @param BuilderFactory $builderFactory
80
     * @param PrettyPrinterAbstract $printer
81
     * @param ServiceMethodFactory $serviceMethodFactory
82
     * @param HttpClient $httpClient
83
     * @param Filesystem $filesystem
84
     * @param bool $enableCache
85
     * @param string $cacheDir
86
     */
87
    public function __construct(
88
        BuilderFactory $builderFactory,
89
        PrettyPrinterAbstract $printer,
90
        ServiceMethodFactory $serviceMethodFactory,
91
        HttpClient $httpClient,
92
        Filesystem $filesystem,
93
        bool $enableCache,
94
        string $cacheDir
95
    ) {
96
        $this->builderFactory = $builderFactory;
97
        $this->printer = $printer;
98
        $this->serviceMethodFactory = $serviceMethodFactory;
99
        $this->httpClient = $httpClient;
100
        $this->filesystem = $filesystem;
101
        $this->enableCache = $enableCache;
102
        $this->cacheDir = $cacheDir;
103
    }
104
105
    /**
106
     * Create a new proxy class given an interface name. This returns a class
107
     * in a string to be cached.
108
     *
109
     * @param string $service
110
     * @return Proxy
111
     * @throws \RuntimeException
112
     * @throws \LogicException
113
     * @throws \Tebru\PhpType\Exception\MalformedTypeException
114
     * @throws \InvalidArgumentException
115
     */
116
    public function create(string $service): ?Proxy
117
    {
118
        $className = self::PROXY_PREFIX.$service;
119
        if ($this->enableCache && class_exists($className)) {
120
            return new $className($this->serviceMethodFactory, $this->httpClient);
121
        }
122
123
        if (!$this->enableCache && class_exists($className, false)) {
124
            return new $className($this->serviceMethodFactory, $this->httpClient);
125
        }
126
127
        if (!interface_exists($service)) {
128
            throw new InvalidArgumentException(sprintf('Retrofit: %s is expected to be an interface', $service));
129
        }
130
131
        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
132
        $reflectionClass = new ReflectionClass($service);
133
        $builder = $this->builderFactory
134
            ->class($reflectionClass->getShortName())
135
            ->extend('\\'.AbstractProxy::class)
136
            ->implement('\\'.$reflectionClass->getName());
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
137
138
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
139
            $methodBuilder = $this->builderFactory
140
                ->method($reflectionMethod->getName())
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
141
                ->makePublic();
142
143
            if ($reflectionMethod->isStatic()) {
144
                $methodBuilder->makeStatic();
145
            }
146
147
            foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
148
                $paramBuilder = $this->builderFactory->param($reflectionParameter->getName());
0 ignored issues
show
Bug introduced by
Consider using $reflectionParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
149
150
                if ($reflectionParameter->isDefaultValueAvailable()) {
151
                    $paramBuilder->setDefault($reflectionParameter->getDefaultValue());
152
                }
153
154
                if ($reflectionParameter->getType() === null) {
155
                    throw new LogicException(sprintf(
156
                        'Retrofit: Parameter types are required. None found for parameter %s in %s::%s()',
157
                        $reflectionParameter->getName(),
0 ignored issues
show
Bug introduced by
Consider using $reflectionParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
158
                        $reflectionClass->getName(),
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
159
                        $reflectionMethod->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
160
                    ));
161
                }
162
163
                $reflectionTypeName = $reflectionParameter->getType()->getName();
164
                if ((new TypeToken($reflectionTypeName))->isObject()) {
165
                    $reflectionTypeName = '\\'.$reflectionTypeName;
166
                }
167
168
                $type = $reflectionParameter->getType()->allowsNull() ? new NullableType($reflectionTypeName): $reflectionTypeName;
169
                $paramBuilder->setTypeHint($type);
170
171
                if ($reflectionParameter->isPassedByReference()) {
172
                    $paramBuilder->makeByRef();
173
                }
174
175
                if ($reflectionParameter->isVariadic()) {
176
                    $paramBuilder->makeVariadic();
177
                }
178
179
                $methodBuilder->addParam($paramBuilder->getNode());
180
            }
181
182
            if (!$reflectionMethod->hasReturnType()) {
183
                throw new LogicException(sprintf(
184
                    'Retrofit: Method return types are required. None found for %s::%s()',
185
                    $reflectionClass->getName(),
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
186
                    $reflectionMethod->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
187
                ));
188
            }
189
190
            /** @noinspection NullPointerExceptionInspection */
191
            $methodBuilder->setReturnType('\\'.$reflectionMethod->getReturnType()->getName());
192
193
            $methodBuilder->addStmt(
194
                new Return_(
195
                    new MethodCall(
196
                        new Variable('this'),
197
                        '__handleRetrofitRequest',
198
                        [
199
                            new String_($reflectionClass->getName()),
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
200
                            new ConstFetch(new Name('__FUNCTION__')),
201
                            new FuncCall(new Name('func_get_args'))
202
                        ]
203
                    )
204
                )
205
            );
206
207
            $builder->addStmt($methodBuilder->getNode());
208
        }
209
210
211
        $namespaceBuilder = $this->builderFactory
212
            ->namespace(self::PROXY_PREFIX.$reflectionClass->getNamespaceName())
213
            ->addStmt($builder);
214
215
        $source = $this->printer->prettyPrint([$namespaceBuilder->getNode()]);
216
217
        eval($source);
218
219
        if (!$this->enableCache) {
220
            return new $className($this->serviceMethodFactory, $this->httpClient);
221
        }
222
223
        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
224
        $reflectionClass = new ReflectionClass($className);
225
        $directory = $this->cacheDir.DIRECTORY_SEPARATOR.$reflectionClass->getNamespaceName();
226
        $directory = str_replace('\\', DIRECTORY_SEPARATOR, $directory);
227
        $filename = $directory.DIRECTORY_SEPARATOR.$reflectionClass->getShortName().'.php';
228
229
        $class = '<?php'.PHP_EOL.PHP_EOL.$source;
230
        if (!$this->filesystem->makeDirectory($directory)) {
231
            throw new RuntimeException(sprintf(
232
                'Retrofit: There was an issue creating the cache directory: %s',
233
                $directory)
234
            );
235
        }
236
237
        if (!$this->filesystem->put($filename, $class)) {
238
            throw new RuntimeException(sprintf('Retrofit: There was an issue writing proxy class to: %s', $filename));
239
        }
240
241
        return new $className($this->serviceMethodFactory, $this->httpClient);
242
    }
243
}
244