Completed
Push — master ( 697bdb...7833c1 )
by Lucas
13:27 queued 03:29
created

HttpLoader::fetchFile()   B

Complexity

Conditions 3
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 6.087

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
ccs 3
cts 10
cp 0.3
rs 8.9713
cc 3
eloc 14
nc 5
nop 1
crap 6.087
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 4
     * constructor
75
     *
76 4
     * @param ValidatorInterface $validator validator
77 4
     * @param Client             $client    http client
78 4
     * @param LoggerInterface    $logger    Logger
79
     */
80
    public function __construct(ValidatorInterface $validator, Client $client, LoggerInterface $logger)
81
    {
82
        $this->validator = $validator;
83
        $this->client = $client;
84
        $this->logger = $logger;
85
    }
86
87 3
    /**
88
     * @inheritDoc
89 3
     *
90 3
     * @param DispersalStrategyInterface $strategy dispersal strategy
91
     *
92
     * @return void
93
     */
94
    public function setDispersalStrategy($strategy)
95
    {
96
        $this->strategy = $strategy;
97
    }
98
99
    /**
100
     * @inheritDoc
101 1
     *
102
     * @param CacheProvider $cache          doctrine cache provider
103 1
     * @param string        $cacheNamespace cache namespace
104 1
     * @param int           $cacheLifetime  cache lifetime
105 1
     *
106 1
     * @return void
107
     */
108
    public function setCache(CacheProvider $cache, $cacheNamespace, $cacheLifetime)
109
    {
110
        $this->cache = $cache;
111
        $this->cache->setNamespace($cacheNamespace);
112
        $this->cacheLifetime = $cacheLifetime;
113
    }
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 1
    /**
128
     * @inheritDoc
129 1
     *
130 1
     * @param array $options cache strategy
131 1
     *
132 1
     * @return void
133
     */
134 1
    public function setOptions($options)
135 1
    {
136
        if (!empty($options['prefix'])) {
137
            $options['storeKey'] = $options['prefix'];
138
            unset($options['prefix']);
139
        }
140
141
        $this->options = array_merge($this->options, $options);
142
    }
143
144 1
    /**
145
     * check if the url is valid
146 1
     *
147
     * @param string $url url
148 1
     *
149
     * @return boolean
150
     */
151
    public function supports($url)
152
    {
153
        $error = $this->validator->validate($url, [new Url()]);
154
155
        return 0 === count($error);
156
    }
157
158 3
    /**
159
     * Applies the specified curl option on a request
160 3
     *
161 3
     * @param RequestInterface $request request
162
     *
163
     * @return void
164 3
     */
165 3
    protected function applyCurlOptions($request)
166 3
    {
167
        $curl = $request->getCurlOptions();
168
        foreach ($this->curlOptions as $option => $value) {
169
            $option = 'CURLOPT_'.strtoupper($option);
170
            $curl->set(constant($option), $value);
171
        }
172
        $curl->set(constant('CURLOPT_CAINFO'), __DIR__.'/../../Resources/cert/cacert.pem');
173
    }
174
175 3
    /**
176
     * @inheritDoc
177 3
     *
178 3
     * @param string $input url
179 3
     *
180 3
     * @return ApiDefinition
181 3
     */
182 1
    public function load($input)
183
    {
184 1
        $retVal = null;
185
        if (isset($this->strategy)) {
186
            $request = $this->client->get($input);
187 1
            $this->applyCurlOptions($request);
188 2
            if (isset($this->cache) && $this->cache->contains($this->options['storeKey'])) {
189
                $content = $this->cache->fetch($this->options['storeKey']);
190
191
                if (empty($content)) {
192
                    $content = $this->fetchFile($request);
193
                }
194 3
            } else {
195 3
                $content = $this->fetchFile($request);
196 3
            }
197 3
198 3
            // store current host (name or ip) serving the API. This MUST be the host only and does not include the
199 3
            // scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the
200 3
            // documentation is to be used (including the port)
201 3
            $fallbackHost = array();
202 2
            $fallbackHost['host'] = sprintf(
203 2
                '%s://%s:%d',
204 3
                $request->getScheme(),
205
                $request->getHost(),
206 3
                $request->getPort()
207
            );
208
            if ($this->strategy->supports($content)) {
209
                $retVal = $this->strategy->process($content, $fallbackHost);
210
            }
211
        }
212
213
        return $retVal;
214
    }
215
216 2
    /**
217
     * fetch file from remote destination
218
     *
219 2
     * @param RequestInterface $request request
220 2
     *
221
     * @return string
222
     */
223
    private function fetchFile($request)
224
    {
225
        $content = "{}";
226
        try {
227
            $response = $request->send();
228
            $content = $response->getBody(true);
229 2
            if (isset($this->cache)) {
230 2
                $this->cache->save($this->options['storeKey'], $content, $this->cacheLifetime);
231
            }
232
        } catch (\Guzzle\Http\Exception\RequestException $e) {
233
234 2
            $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
        return $content;
246
    }
247
}
248