Completed
Push — master ( fb073d...0ff0ab )
by Piotr
03:24
created

EsiTentacles::defaultOptions()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 69
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 69
rs 6.9081
c 0
b 0
f 0
cc 7
eloc 45
nc 1
nop 0

How to fix   Long Method   

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
namespace CrazyGoat\Octophpus;
4
5
use CrazyGoat\Octophpus\Validator\OptionsValidator;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\Exception\ConnectException;
8
use GuzzleHttp\Promise\EachPromise;
9
use GuzzleHttp\Psr7\Response;
10
use Psr\Cache\CacheItemPoolInterface;
11
use Psr\Log\LoggerInterface;
12
13
class EsiTentacles
14
{
15
    const ON_REJECT_EMPTY = 'empty';
16
    const ON_REJECT_EXCEPTION = 'exception';
17
    const ON_TIMEOUT_H_INCLUDE = 'hinclude';
18
    const ON_TIMEOUT_EXCEPTION = 'exception';
19
20
    /**
21
     * @var array options
22
     */
23
    private $options;
24
25
    /**
26
     * @var CacheItemPoolInterface
27
     */
28
    private $cachePool = null;
29
30
    /**
31
     * @var LoggerInterface
32
     */
33
    private $logger;
34
35
    /**
36
     * Mantle constructor.
37
     * @param array $options
38
     */
39
    public function __construct(array $options = [])
40
    {
41
        $validator = new OptionsValidator($options);
42
        $validator->validate();
43
44
        $this->options = array_merge($this->defaultOptions(), $options);
45
        $this->logger = $this->options ['logger'];
46
        $this->cachePool = $this->options ['cache_pool'];
47
    }
48
49
    public function decorate(string $data): string
50
    {
51
        $parser = new EsiParser();
52
53
        $recurrency = $this->options['recurecny_lever'];
54
55
        while ($parser->parse($data) && $recurrency > 0) {
56
57
            /** @var EsiRequest[] $esiRequests */
58
            $esiRequests = $parser->esiRequests();
59
60
            $work = (new EachPromise(
61
                $this->createRequestPromises()($esiRequests),
62
                [
63
                    'concurrency' => $this->options['concurrency'],
64
                    'fulfilled' => $this->options['fulfilled']($data, $esiRequests),
65
                    'rejected' =>  $this->options['rejected']($data, $esiRequests)
66
                ]
67
            ));
68
            $work->promise()->wait();
69
            $recurrency--;
70
        }
71
72
        return $data;
73
    }
74
75
    private function createRequestPromises()
76
    {
77
        return (function (array $esiRequests) {
78
            $client = new Client($this->clientOptions());
79
            /** @var EsiRequest $esiRequest */
80
            foreach ($esiRequests as $esiRequest) {
81
82
                $cacheKey = $this->options['cache_prefix'].':'.base64_encode($esiRequest->getSrc());
83
84
                if ($this->cachePool instanceof CacheItemPoolInterface &&
85
                    $this->cachePool->hasItem($cacheKey)
86
                ) {
87
                    $value = $this->cachePool->getItem($cacheKey)->get();
88
                    yield new Response(200, [], $value);
89
                    continue;
90
                }
91
92
                yield $client->requestAsync(
93
                    'GET',
94
                    $esiRequest->getSrc(),
95
                    array_merge($this->options['request_options'], $esiRequest->requestOptions())
96
                );
97
            }
98
        });
99
    }
100
101
    private function defaultOptions(): array
102
    {
103
        return [
104
            'concurrency' => 5,
105
            'timeout' => 2.0,
106
            'on_reject' => static::ON_REJECT_EXCEPTION,
107
            'on_timeout' => static::ON_TIMEOUT_EXCEPTION,
108
            'base_uri' => '',
109
            'cache_prefix' => 'esi:include',
110
            'cache_ttl' => 3600,
111
            'request_options' => [],
112
            'recurecny_lever' => 1,
113
            'cache_poo;' => null,
114
            'logger' => new VoidLogger(),
115
            'fulfilled' => function (string &$data, array $esiRequests)
116
            {
117
                return (function (Response $response, int $index) use (&$data, $esiRequests)
118
                {
119
                    /** @var EsiRequest $esiRequest */
120
                    $esiRequest = $esiRequests[$index];
121
                    $needle = $esiRequest->getEsiTag();
122
                    $pos = strpos($data, $needle);
123
                    if ($pos !== false) {
124
125
                        if ($this->cachePool instanceof CacheItemPoolInterface && !$esiRequest->isNoCache()) {
126
                            $cacheKey = $this->options['cache_prefix'].':'.base64_encode($esiRequest->getSrc());
127
128
                            $this->cachePool->save(
129
                                $this->cachePool
130
                                    ->getItem($cacheKey)
131
                                    ->set($response->getBody()->getContents())
132
                                    ->expiresAfter($this->options['cache_ttl'])
133
                            );
134
                        }
135
136
                        $data = substr_replace($data, $response->getBody()->getContents(), $pos, strlen($needle));
137
                    } else {
138
                        $this->logger->error('This should not happen. Could not replace previously found esi tag.');
139
                    }
140
                });
141
            },
142
            'rejected' => function (string &$data, array $esiRequests) {
143
                return (function (\Exception $reason, int $index) use (&$data, $esiRequests) {
144
                    /** @var EsiRequest $esiRequest */
145
                    $esiRequest = $esiRequests[$index];
146
147
                    $this->logger->error(
148
                        'Could not fetch ['.$esiRequest->getSrc().']. Reason: '.$reason->getMessage()
149
                    );
150
                    if ($reason instanceof ConnectException && (
151
                        $this->options['on_timeout'] == static::ON_TIMEOUT_H_INCLUDE
152
                        )) {
153
                        $data = str_replace(
154
                            $esiRequest->getEsiTag(),
155
                            '<hx:include src="'.$this->options['base_uri'].$esiRequest->getSrc().'"></hx:include>',
156
                            $data
157
                        );
158
                        return;
159
                    }
160
161
                    if ($this->options['on_reject'] == static::ON_REJECT_EMPTY) {
162
                        $data = str_replace($esiRequest->getEsiTag(), '', $data);
163
                    } else {
164
                        throw $reason;
165
                    }
166
                });
167
            }
168
        ];
169
    }
170
171
    /**
172
     * @param array $options
173
     * @return EsiTentacles
174
     */
175
    public function setOptions(array $options): EsiTentacles
176
    {
177
        $validator = new OptionsValidator($options);
178
        $validator->validate();
179
180
        $this->options = $options;
181
        return $this;
182
    }
183
184
    /**
185
     * @return array
186
     */
187
    public function getOptions(): array
188
    {
189
        return $this->options;
190
    }
191
192
    private function clientOptions() : array
193
    {
194
        return [
195
            'concurrency' => $this->options['concurrency'],
196
            'timeout' => $this->options['timeout'],
197
            'base_uri' => $this->options['base_uri'],
198
        ];
199
    }
200
}