Completed
Push — feature/moar-test-optimizing ( 28ebf8...091eca )
by Lucas
08:26
created

HttpLoader::fetchFile()   B

Complexity

Conditions 3
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.6018

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
ccs 7
cts 16
cp 0.4375
rs 8.9713
cc 3
eloc 14
nc 5
nop 1
crap 4.6018
1
<?php
2
/**
3
 * HttpLoader
4
 */
5
6
namespace Graviton\ProxyBundle\Definition\Loader;
7
8
use Graviton\ProxyBundle\Definition\ApiDefinition;
9
use Graviton\ProxyBundle\Definition\Loader\DispersalStrategy\DispersalStrategyInterface;
10
use Doctrine\Common\Cache\CacheProvider;
11
use Guzzle\Http\Client;
12
use Guzzle\Http\Message\RequestInterface;
13
use Psr\Log\LoggerInterface;
14
use Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\Validator\Constraints\Url;
16
use Symfony\Component\Validator\Validator\ValidatorInterface;
17
18
/**
19
 * load a file over http and process the data
20
 *
21
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
22
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
23
 * @link     http://swisscom.ch
24
 */
25
class HttpLoader implements LoaderInterface
26
{
27
    /**
28
     * @var ValidatorInterface
29
     */
30
    private $validator;
31
32
    /**
33
     * @var Client
34
     */
35
    private $client;
36
37
    /**
38
     * @var LoggerInterface
39
     */
40
    private $logger;
41
42
    /**
43
     * @var DispersalStrategyInterface
44
     */
45
    private $strategy;
46
47
    /**
48
     * doctrine cache
49
     *
50
     * @var CacheProvider
51
     */
52
    private $cache;
53
54
    /**
55
     * doctrine cache lifetime
56
     *
57
     * @var int
58
     */
59
    private $cacheLifetime;
60
61
    /**
62
     * @var array curl options to apply on each request
63
     */
64
    private $curlOptions = [];
65
66
    /**
67
     * @var array
68
     */
69
    private $options = [
70
        'storeKey' => 'httpLoader',
71
    ];
72
73
    /**
74
     * constructor
75
     *
76
     * @param ValidatorInterface $validator validator
77
     * @param Client             $client    http client
78
     * @param LoggerInterface    $logger    Logger
79
     */
80 4
    public function __construct(ValidatorInterface $validator, Client $client, LoggerInterface $logger)
81
    {
82 4
        $this->validator = $validator;
83 4
        $this->client = $client;
84 4
        $this->logger = $logger;
85 4
    }
86
87
    /**
88
     * @inheritDoc
89
     *
90
     * @param DispersalStrategyInterface $strategy dispersal strategy
91
     *
92
     * @return void
93
     */
94 3
    public function setDispersalStrategy($strategy)
95
    {
96 3
        $this->strategy = $strategy;
97 3
    }
98
99
    /**
100
     * @inheritDoc
101
     *
102
     * @param CacheProvider $cache          doctrine cache provider
103
     * @param string        $cacheNamespace cache namespace
104
     * @param int           $cacheLifetime  cache lifetime
105
     *
106
     * @return void
107
     */
108 1
    public function setCache(CacheProvider $cache, $cacheNamespace, $cacheLifetime)
109
    {
110 1
        $this->cache = $cache;
111 1
        $this->cache->setNamespace($cacheNamespace);
112 1
        $this->cacheLifetime = $cacheLifetime;
113 1
    }
114
115
    /**
116
     * set curl options
117
     *
118
     * @param array $curlOptions the curl options
119
     *
120
     * @return void
121
     */
122
    public function setCurlOptions(array $curlOptions)
123
    {
124
        $this->curlOptions = $curlOptions;
125
    }
126
127
    /**
128
     * @inheritDoc
129
     *
130
     * @param array $options cache strategy
131
     *
132
     * @return void
133
     */
134 1
    public function setOptions($options)
135
    {
136 1
        if (!empty($options['prefix'])) {
137 1
            $options['storeKey'] = $options['prefix'];
138 1
            unset($options['prefix']);
139 1
        }
140
141 1
        $this->options = array_merge($this->options, $options);
142 1
    }
143
144
    /**
145
     * check if the url is valid
146
     *
147
     * @param string $url url
148
     *
149
     * @return boolean
150
     */
151 1
    public function supports($url)
152
    {
153 1
        $error = $this->validator->validate($url, [new Url()]);
154
155 1
        return 0 === count($error);
156
    }
157
158
    /**
159
     * Applies the specified curl option on a request
160
     *
161
     * @param RequestInterface $request request
162
     *
163
     * @return void
164
     */
165 3
    protected function applyCurlOptions($request)
166
    {
167 3
        $curl = $request->getCurlOptions();
168 3
        foreach ($this->curlOptions as $option => $value) {
169
            $option = 'CURLOPT_'.strtoupper($option);
170
            $curl->set(constant($option), $value);
171 3
        }
172 3
        $curl->set(constant('CURLOPT_CAINFO'), __DIR__.'/../../Resources/cert/cacert.pem');
173 3
    }
174
175
    /**
176
     * @inheritDoc
177
     *
178
     * @param string $input url
179
     *
180
     * @return ApiDefinition
181
     */
182 3
    public function load($input)
183
    {
184 3
        $retVal = null;
185 3
        if (isset($this->strategy)) {
186 3
            $request = $this->client->get($input);
187 3
            $this->applyCurlOptions($request);
188 3
            if (isset($this->cache) && $this->cache->contains($this->options['storeKey'])) {
189 1
                $content = $this->cache->fetch($this->options['storeKey']);
190
191 1
                if (empty($content)) {
192
                    $content = $this->fetchFile($request);
193
                }
194 1
            } else {
195 2
                $content = $this->fetchFile($request);
196
            }
197
198
            // store current host (name or ip) serving the API. This MUST be the host only and does not include the
199
            // scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the
200
            // documentation is to be used (including the port)
201 3
            $fallbackHost = array();
202 3
            $fallbackHost['host'] = sprintf(
203 3
                '%s://%s:%d',
204 3
                $request->getScheme(),
205 3
                $request->getHost(),
206 3
                $request->getPort()
207 3
            );
208 3
            if ($this->strategy->supports($content)) {
209 2
                $retVal = $this->strategy->process($content, $fallbackHost);
210 2
            }
211 3
        }
212
213 3
        return $retVal;
214
    }
215
216
    /**
217
     * fetch file from remote destination
218
     *
219
     * @param RequestInterface $request request
220
     *
221
     * @return string
222
     */
223 2
    private function fetchFile($request)
224
    {
225 2
        $content = "{}";
226
        try {
227 2
            $response = $request->send();
228 2
            $content = $response->getBody(true);
229 2
            if (isset($this->cache)) {
230
                $this->cache->save($this->options['storeKey'], $content, $this->cacheLifetime);
231
            }
232 2
        } catch (\Guzzle\Http\Exception\RequestException $e) {
233
234
            $this->logger->info(
235
                "Unable to fetch File!",
236
                [
237
                    "message" => $e->getMessage(),
238
                    "url" => $request->getUrl(),
239
                    "code" => $e->getResponse()->getStatusCode()
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Guzzle\Http\Exception\RequestException as the method getResponse() does only exist in the following sub-classes of Guzzle\Http\Exception\RequestException: Guzzle\Http\Exception\BadResponseException, Guzzle\Http\Exception\ClientErrorResponseException, Guzzle\Http\Exception\ServerErrorResponseException, Guzzle\Http\Exception\TooManyRedirectsException. 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...
240
                ]
241
            );
242
243
        }
244
245 2
        return $content;
246
    }
247
}
248