RemoteTemplateFinder   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 339
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 10
dl 0
loc 339
rs 9.0399
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A hasRemoteInformation() 0 4 1
B findRemotePathView() 0 44 8
A isForbiddenUrl() 0 12 4
A hasNamespace() 0 10 2
A parseRemoteNamespaceSegments() 0 9 2
A getRemoteHost() 0 10 2
A urlHasIgnoredSuffix() 0 12 3
A getTemplateUrlForIdentifier() 0 12 3
A callModifyTemplateUrlCallback() 0 10 3
A getViewFolder() 0 12 4
A fetchContentFromRemoteHost() 0 14 3
A callResponseHandler() 0 10 3
A pushResponseHandler() 0 6 2
A setModifyTemplateUrlCallback() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like RemoteTemplateFinder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RemoteTemplateFinder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Schnoop\RemoteTemplate\View;
4
5
use Closure;
6
use Exception;
7
use GuzzleHttp\Client;
8
use GuzzleHttp\Psr7\Response;
9
use Illuminate\Contracts\Config\Repository;
10
use Illuminate\Filesystem\Filesystem;
11
use Illuminate\Support\Str;
12
use InvalidArgumentException;
13
use Psr\Http\Message\ResponseInterface;
14
use RuntimeException;
15
use Schnoop\RemoteTemplate\Exceptions\IgnoredUrlSuffixException;
16
use Schnoop\RemoteTemplate\Exceptions\RemoteHostNotConfiguredException;
17
use Schnoop\RemoteTemplate\Exceptions\RemoteTemplateNotFoundException;
18
use Schnoop\RemoteTemplate\Exceptions\UrlIsForbiddenException;
19
20
/**
21
 * Class RemoteTemplateFinder.
22
 */
23
class RemoteTemplateFinder
24
{
25
    /**
26
     * @var string
27
     */
28
    protected $remotePathDelimiter;
29
30
    /**
31
     * @var Client
32
     */
33
    protected $client;
34
35
    /**
36
     * @var Repository
37
     */
38
    protected $config;
39
40
    /**
41
     * @var Closure[]
42
     */
43
    protected $handler;
44
45
    /**
46
     * @var Filesystem
47
     */
48
    protected $files;
49
50
    /**
51
     * @var Closure
52
     */
53
    protected $templateUrlCallback;
54
55
    /**
56
     * Create a new file view loader instance.
57
     *
58
     * @param Filesystem $files
59
     * @param Repository $config
60
     * @param Client $client
61
     */
62
    public function __construct(Filesystem $files, Repository $config, Client $client)
63
    {
64
        $this->client = $client;
65
        $this->files = $files;
66
        $this->config = $config;
67
        $this->remotePathDelimiter = $this->config->get('remote-view.remote-delimiter');
68
    }
69
70
    /**
71
     * Returns true if template is a remote resource.
72
     *
73
     * @param string $name Name of template
74
     *
75
     * @return bool
76
     */
77
    public function hasRemoteInformation($name): bool
78
    {
79
        return Str::startsWith($name, $this->remotePathDelimiter);
80
    }
81
82
    /**
83
     * Fetch template from remote resource, store locally and return path to local file.
84
     *
85
     * @param string $name Remote URL to fetch template from
86
     *
87
     * @return string
88
     * @throws IgnoredUrlSuffixException
89
     * @throws RemoteTemplateNotFoundException
90
     * @throws RemoteHostNotConfiguredException
91
     * @throws UrlIsForbiddenException
92
     */
93
    public function findRemotePathView($name): string
94
    {
95
        $name = trim(Str::replaceFirst($this->remotePathDelimiter, '', $name));
96
97
        $namespace = 'default';
98
        if ($this->hasNamespace($name) === true) {
99
            $elements = $this->parseRemoteNamespaceSegments($name);
100
            $namespace = $elements[0];
101
            $name = $elements[1];
102
        }
103
104
        $remoteHost = $this->getRemoteHost($namespace);
105
106
        // Check if URL suffix is ignored
107
        if ($this->urlHasIgnoredSuffix($name, $remoteHost) === true) {
108
            throw new IgnoredUrlSuffixException('URL # '.$name.' has an ignored suffix.');
109
        }
110
111
        // Check if URL is forbidden.
112
        if ($this->isForbiddenUrl($name, $remoteHost) === true) {
113
            throw new UrlIsForbiddenException('URL # '.$name.' is forbidden.', 404);
114
        }
115
116
        $url = $this->getTemplateUrlForIdentifier($name, $remoteHost);
117
        $url = $this->callModifyTemplateUrlCallback($url);
118
119
        $path = $this->getViewFolder($namespace);
120
        $path .= Str::slug($url).'.blade.php';
121
        if ($remoteHost['cache'] === true && $this->files->exists($path) === true) {
122
            return $path;
123
        }
124
125
        $url = rtrim($remoteHost['host'], '/').'/'.ltrim($url, '/');
126
127
        $content = $this->fetchContentFromRemoteHost($url, $remoteHost);
128
        if ($content instanceof Response === true) {
129
            $content = $content->getBody()->getContents();
130
        } elseif ($content instanceof \Illuminate\Http\Response) {
131
            $content = (string) $content->getContent();
132
        }
133
        $this->files->put($path, $content);
134
135
        return $path;
136
    }
137
138
    /**
139
     * Returns true if given url is forbidden.
140
     *
141
     * @param string $url
142
     * @param array $remoteHost
143
     *
144
     * @return bool
145
     */
146
    private function isForbiddenUrl($url, $remoteHost): bool
147
    {
148
        $ignoreUrlSuffix = $this->config->get('remote-view.ignore-urls');
149
        if (isset($remoteHost['ignore-urls']) === true && is_array($remoteHost['ignore-urls']) === true) {
150
            $ignoreUrlSuffix = array_merge($ignoreUrlSuffix, $remoteHost['ignore-urls']);
151
        }
152
153
        $parsedUrl = parse_url($url, PHP_URL_PATH);
154
155
        return in_array(pathinfo($parsedUrl, PATHINFO_DIRNAME), $ignoreUrlSuffix, true)
156
            || in_array(pathinfo($parsedUrl, PATHINFO_BASENAME), $ignoreUrlSuffix, true);
157
    }
158
159
    /**
160
     * Check for valid namespace.
161
     *
162
     * @param string $name
163
     *
164
     * @return bool
165
     */
166
    protected function hasNamespace($name): bool
167
    {
168
        try {
169
            $this->parseRemoteNamespaceSegments($name);
170
        } catch (Exception $e) {
171
            return false;
172
        }
173
174
        return true;
175
    }
176
177
    /**
178
     * Get the segments of a template with a named path.
179
     *
180
     * @param string $name
181
     *
182
     * @return array
183
     *
184
     * @throws InvalidArgumentException
185
     */
186
    protected function parseRemoteNamespaceSegments($name): array
187
    {
188
        $segments = explode(FileViewFinder::HINT_PATH_DELIMITER, $name);
189
        if (count($segments) !== 2) {
190
            throw new InvalidArgumentException("View [{$name}] has an invalid name.");
191
        }
192
193
        return $segments;
194
    }
195
196
    /**
197
     * Return array with remote host config.
198
     *
199
     * @param string $namespace
200
     *
201
     * @return array
202
     * @throws RemoteHostNotConfiguredException
203
     */
204
    protected function getRemoteHost($namespace): array
205
    {
206
        $config = $this->config->get('remote-view.hosts');
207
        if (isset($config[$namespace]) === false) {
208
            throw new RemoteHostNotConfiguredException('No remote host configured for namespace # '
209
                .$namespace.'. Please check your remote-view.php config file.');
210
        }
211
212
        return $config[$namespace];
213
    }
214
215
    /**
216
     * Returns true if given url is static.
217
     *
218
     * @param string $url
219
     * @param array $remoteHost
220
     *
221
     * @return bool
222
     */
223
    protected function urlHasIgnoredSuffix($url, $remoteHost): bool
224
    {
225
        $parsedUrl = parse_url($url, PHP_URL_PATH);
226
        $pathInfo = pathinfo($parsedUrl, PATHINFO_EXTENSION);
227
228
        $ignoreUrlSuffix = $this->config->get('remote-view.ignore-url-suffix');
229
        if (isset($remoteHost['ignore-url-suffix']) === true && is_array($remoteHost['ignore-url-suffix']) === true) {
230
            $ignoreUrlSuffix = array_merge($ignoreUrlSuffix, $remoteHost['ignore-url-suffix']);
231
        }
232
233
        return in_array($pathInfo, $ignoreUrlSuffix, true);
234
    }
235
236
    /**
237
     * Returns remote url for given $identifier.
238
     *
239
     * @param string $identifier
240
     * @param array $remoteHost
241
     *
242
     * @return string
243
     */
244
    protected function getTemplateUrlForIdentifier($identifier, $remoteHost): string
245
    {
246
        $route = $identifier;
247
        if (isset($remoteHost['mapping'][$identifier]) === true) {
248
            $route = $remoteHost['mapping'][$identifier];
249
        }
250
        if (strpos($route, '/') > 0) {
251
            return '/'.$route;
252
        }
253
254
        return $route;
255
    }
256
257
    /**
258
     * Call callback that will be called after template url has been set.
259
     *
260
     * @param string $url
261
     *
262
     * @return string
263
     */
264
    protected function callModifyTemplateUrlCallback(string $url): string
265
    {
266
        if ($this->templateUrlCallback !== null
267
            && is_callable($this->templateUrlCallback) === true
268
        ) {
269
            return call_user_func($this->templateUrlCallback, $url);
270
        }
271
272
        return $url;
273
    }
274
275
    /**
276
     * Get folder where fetched views will be stored.
277
     *
278
     * @param string $namespace
279
     *
280
     * @return string
281
     * @throws RuntimeException
282
     */
283
    protected function getViewFolder($namespace): string
284
    {
285
        $path = $this->config->get('remote-view.view-folder');
286
        $path = rtrim($path, '/').'/'.$namespace.'/';
287
        if (is_dir($path) === false) {
288
            if (! mkdir($path, 0777, true) && ! is_dir($path)) {
289
                throw new RuntimeException(sprintf('Directory "%s" was not created', $path));
290
            }
291
        }
292
293
        return $path;
294
    }
295
296
    /**
297
     * Fetch content from $url.
298
     *
299
     * @param string $url
300
     * @param array $remoteHost
301
     *
302
     * @return ResponseInterface|Response
303
     * @throws RemoteTemplateNotFoundException
304
     */
305
    public function fetchContentFromRemoteHost($url, $remoteHost)
306
    {
307
        $options = ['http_errors' => false];
308
        if (isset($remoteHost['request_options']) === true) {
309
            $options = array_merge($options, $remoteHost['request_options']);
310
        }
311
        try {
312
            $result = $this->client->get($url, $options);
313
314
            return $this->callResponseHandler($result, $remoteHost);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->callResponseHandler($result, $remoteHost); of type Psr\Http\Message\Respons...lluminate\Http\Response adds the type Illuminate\Http\Response to the return on line 314 which is incompatible with the return type documented by Schnoop\RemoteTemplate\V...chContentFromRemoteHost of type Psr\Http\Message\ResponseInterface.
Loading history...
315
        } catch (Exception $e) {
316
            throw new RemoteTemplateNotFoundException($url, 404);
317
        }
318
    }
319
320
    /**
321
     * Call handler if any defined.
322
     *
323
     * @param ResponseInterface $result
324
     * @param array $remoteHost
325
     *
326
     * @return ResponseInterface|\Illuminate\Http\Response|Response
327
     */
328
    protected function callResponseHandler($result, array $remoteHost)
329
    {
330
        if (isset($this->handler[$result->getStatusCode()]) === true
331
            && is_callable($this->handler[$result->getStatusCode()]) === true
332
        ) {
333
            return call_user_func($this->handler[$result->getStatusCode()], $result, $remoteHost, $this);
334
        }
335
336
        return $result;
337
    }
338
339
    /**
340
     * Push a handler to the stack.
341
     *
342
     * @param int|array $statusCodes
343
     * @param callable $callback
344
     */
345
    public function pushResponseHandler($statusCodes, $callback)
346
    {
347
        foreach ((array) $statusCodes as $statusCode) {
348
            $this->handler[$statusCode] = $callback;
349
        }
350
    }
351
352
    /**
353
     * Set a callback that will be called after template url has been set.
354
     *
355
     * @param Closure $callback
356
     */
357
    public function setModifyTemplateUrlCallback($callback)
358
    {
359
        $this->templateUrlCallback = $callback;
360
    }
361
}
362