Completed
Push — master ( 5919d7...94bd1b )
by Rafael
05:28
created

initializeTimeoutFromConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
namespace ApacheSolrForTypo3\Solr\System\Solr\Service;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2017 Timo Hund <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\PingFailedException;
28
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use ApacheSolrForTypo3\Solr\Util;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
abstract class AbstractSolrService extends \Apache_Solr_Service {
34
35
    const SCHEME_HTTP = 'http';
36
    const SCHEME_HTTPS = 'https';
37
38
    /**
39
     * @var array
40
     */
41
    protected static $pingCache = [];
42
43
    /**
44
     * Server connection scheme. http or https.
45
     *
46
     * @var string
47
     */
48
    protected $_scheme = self::SCHEME_HTTP;
49
50
    /**
51
     * @var TypoScriptConfiguration
52
     */
53
    protected $configuration;
54
55
    /**
56
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
57
     */
58
    protected $logger = null;
59
60
    /**
61
     * SolrAdminService constructor.
62
     * @param string $host
63
     * @param string $port
64
     * @param string $path
65
     * @param string $scheme
66
     * @param TypoScriptConfiguration $typoScriptConfiguration
67
     * @param SolrLogManager $logManager
68
     */
69
    public function __construct($host = '', $port = '8983', $path = '/solr/', $scheme = 'http', $typoScriptConfiguration = null, $logManager = null)
70
    {
71
        $this->setScheme($scheme);
72
        parent::__construct($host, $port, $path);
73
74
        $this->configuration = is_null($typoScriptConfiguration) ? Util::getSolrConfiguration() : $typoScriptConfiguration;
75
        $this->logger = is_null($logManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $logManager;
76
        $this->initializeTimeoutFromConfiguration();
77
    }
78
79
    /**
80
     * Initializes the timeout from TypoScript when configuration is present.
81
     *
82
     * @return void
83
     */
84
    protected function initializeTimeoutFromConfiguration()
85
    {
86
        $timeout = $this->configuration->getSolrTimeout();
87
        if ($timeout > 0) {
88
            $this->getHttpTransport()->setDefaultTimeout($timeout);
89
        }
90
    }
91
92
    /**
93
     * Creates a string representation of the Solr connection. Specifically
94
     * will return the Solr URL.
95
     *
96
     * @return string The Solr URL.
97
     */
98
    public function __toString()
99
    {
100
        return $this->_scheme . '://' . $this->_host . ':' . $this->_port . $this->_path;
101
    }
102
103
    /**
104
     * Returns the set scheme
105
     *
106
     * @return string
107
     */
108
    public function getScheme()
109
    {
110
        return $this->_scheme;
111
    }
112
113
    /**
114
     * Set the scheme used. If empty will fallback to constants
115
     *
116
     * @param string $scheme Either http or https
117
     * @throws \UnexpectedValueException
118
     */
119
    public function setScheme($scheme)
120
    {
121
        // Use the provided scheme or use the default
122
        if (empty($scheme)) {
123
            throw new \UnexpectedValueException('Scheme parameter is empty', 1380756390);
124
        }
125
126
        $isHttpOrHttps = in_array($scheme, [self::SCHEME_HTTP, self::SCHEME_HTTPS]);
127
        if (!$isHttpOrHttps) {
128
            throw new \UnexpectedValueException('Unsupported scheme parameter, scheme must be http or https', 1380756442);
129
        }
130
131
        // we have a valid scheme
132
        $this->_scheme = $scheme;
133
134
        if ($this->_urlsInited) {
135
            $this->_initUrls();
136
        }
137
    }
138
139
    /**
140
     * Return a valid http URL given this server's scheme, host, port, and path
141
     * and a provided servlet name.
142
     *
143
     * @param string $servlet Servlet name
144
     * @param array $params Additional URL parameters to attach to the end of the URL
145
     * @return string Servlet URL
146
     */
147
    protected function _constructUrl($servlet, $params = [])
148
    {
149
        $url = parent::_constructUrl($servlet, $params);
150
151
        if (!GeneralUtility::isFirstPartOfStr($url, $this->_scheme)) {
152
            $parsedUrl = parse_url($url);
153
154
            // unfortunately can't use str_replace as it replace all
155
            // occurrences of $needle and can't be limited to replace only once
156
            $url = $this->_scheme . substr($url, strlen($parsedUrl['scheme']));
157
        }
158
159
        return $url;
160
    }
161
162
    /**
163
     * Central method for making a get operation against this Solr Server
164
     *
165
     * @param string $url
166
     * @param float|bool $timeout Read timeout in seconds
167
     * @return \Apache_Solr_Response
168
     */
169
    protected function _sendRawGet($url, $timeout = false)
170
    {
171
        $logSeverity = SolrLogManager::INFO;
172
        $exception = null;
173
174
        try {
175
            $response = parent::_sendRawGet($url, $timeout);
0 ignored issues
show
Bug introduced by
It seems like $timeout defined by parameter $timeout on line 169 can also be of type boolean; however, Apache_Solr_Service::_sendRawGet() does only seem to accept false|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
176
        } catch (\Apache_Solr_HttpTransportException $exception) {
177
            $logSeverity = SolrLogManager::ERROR;
178
            $response = $exception->getResponse();
179
        }
180
181
        if ($this->configuration->getLoggingQueryRawGet() || $response->getHttpStatus() != 200) {
182
            $this->writeLog($logSeverity, 'Querying Solr using GET', $url, $response, $exception);
183
        }
184
185
        return $response;
186
    }
187
188
    /**
189
     * Central method for making a HTTP DELETE operation against the Solr server
190
     *
191
     * @param string $url
192
     * @param bool|float $timeout Read timeout in seconds
193
     * @return \Apache_Solr_Response
194
     */
195
    protected function _sendRawDelete($url, $timeout = false)
196
    {
197
        $logSeverity = SolrLogManager::INFO;
198
        $exception = null;
199
200
        try {
201
            $httpTransport = $this->getHttpTransport();
202
            $httpResponse = $httpTransport->performDeleteRequest($url, $timeout);
203
            $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
204
205
            if ($solrResponse->getHttpStatus() != 200) {
206
                throw new \Apache_Solr_HttpTransportException($solrResponse);
207
            }
208
        } catch (\Apache_Solr_HttpTransportException $exception) {
209
            $logSeverity = SolrLogManager::ERROR;
210
            $solrResponse = $exception->getResponse();
211
        }
212
213
        if ($this->configuration->getLoggingQueryRawDelete() || $solrResponse->getHttpStatus() != 200) {
214
            $this->writeLog($logSeverity, 'Querying Solr using DELETE', $url, $solrResponse, $exception);
215
        }
216
217
        return $solrResponse;
218
    }
219
220
    /**
221
     * Central method for making a post operation against this Solr Server
222
     *
223
     * @param string $url
224
     * @param string $rawPost
225
     * @param float|bool $timeout Read timeout in seconds
226
     * @param string $contentType
227
     * @return \Apache_Solr_Response
228
     */
229
    protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
230
    {
231
        $logSeverity = SolrLogManager::INFO;
232
        $exception = null;
233
234
        try {
235
            $response = parent::_sendRawPost($url, $rawPost, $timeout, $contentType);
0 ignored issues
show
Bug introduced by
It seems like $timeout defined by parameter $timeout on line 229 can also be of type boolean; however, Apache_Solr_Service::_sendRawPost() does only seem to accept false|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
236
        } catch (\Apache_Solr_HttpTransportException $exception) {
237
            $logSeverity = SolrLogManager::ERROR;
238
            $response = $exception->getResponse();
239
        }
240
241
        if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) {
242
            $this->writeLog($logSeverity, 'Querying Solr using POST', $url, $response, $exception, $rawPost);
243
        }
244
245
        return $response;
246
    }
247
248
249
    /**
250
     * Build the log data and writes the message to the log
251
     *
252
     * @param integer $logSeverity
253
     * @param string $message
254
     * @param string $url
255
     * @param \Apache_Solr_Response $solrResponse
256
     * @param \Exception $exception
257
     * @param string $contentSend
258
     */
259
    protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '')
260
    {
261
        $logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend);
262
        $this->logger->log($logSeverity, $message, $logData);
263
    }
264
265
    /**
266
     * Parses the solr information to build data for the logger.
267
     *
268
     * @param \Apache_Solr_Response $solrResponse
269
     * @param \Exception $e
270
     * @param string $url
271
     * @param string $contentSend
272
     * @return array
273
     */
274
    protected function buildLogDataFromResponse(\Apache_Solr_Response $solrResponse, \Exception $e = null, $url = '', $contentSend = '')
275
    {
276
        $logData = ['query url' => $url, 'response' => (array)$solrResponse];
277
278
        if ($contentSend !== '') {
279
            $logData['content'] = $contentSend;
280
        }
281
282
        if (!empty($e)) {
283
            $logData['exception'] = $e->__toString();
284
            return $logData;
285
        } else {
286
            // trigger data parsing
287
            $solrResponse->response;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
288
            $logData['response data'] = print_r($solrResponse, true);
289
            return $logData;
290
        }
291
    }
292
293
294
    /**
295
     * Returns the core name from the configured path without the core name.
296
     *
297
     * @return string
298
     */
299
    public function getCoreBasePath()
300
    {
301
        $pathWithoutLeadingAndTrailingSlashes = trim(trim($this->_path), "/");
302
        $pathWithoutLastSegment = substr($pathWithoutLeadingAndTrailingSlashes, 0, strrpos($pathWithoutLeadingAndTrailingSlashes, "/"));
303
        return '/' . $pathWithoutLastSegment . '/';
304
    }
305
306
    /**
307
     * Returns the core name from the configured path.
308
     *
309
     * @return string
310
     */
311
    public function getCoreName()
312
    {
313
        $paths = explode('/', trim($this->_path, '/'));
314
        return (string)array_pop($paths);
315
    }
316
317
    /**
318
     * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
319
     * server is available.
320
     *
321
     * Simply overrides the SolrPhpClient implementation, changing ping from a
322
     * HEAD to a GET request, see http://forge.typo3.org/issues/44167
323
     *
324
     * Also does not report the time, see https://forge.typo3.org/issues/64551
325
     *
326
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
327
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
328
     * @return bool TRUE if Solr can be reached, FALSE if not
329
     */
330
    public function ping($timeout = 2, $useCache = true)
331
    {
332
        $httpResponse = $this->performPingRequest($timeout, $useCache);
333
        return ($httpResponse->getStatusCode() === 200);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $httpResponse->getStatusCode() === 200; (boolean) is incompatible with the return type of the parent method Apache_Solr_Service::ping of type double|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
334
    }
335
336
    /**
337
     * Call the /admin/ping servlet, can be used to get the runtime of a ping request.
338
     *
339
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
340
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
341
     * @return double runtime in milliseconds
342
     * @throws \ApacheSolrForTypo3\Solr\PingFailedException
343
     */
344
    public function getPingRoundTripRuntime($timeout = 2, $useCache = true)
345
    {
346
        $start = $this->getMilliseconds();
347
        $httpResponse = $this->performPingRequest($timeout, $useCache);
348
        $end = $this->getMilliseconds();
349
350
        if ($httpResponse->getStatusCode() !== 200) {
351
            $message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getStatusCode();
352
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
353
            $exception = GeneralUtility::makeInstance(PingFailedException::class, $message);
354
            $exception->setHttpResponse($httpResponse);
355
            throw $exception;
356
        }
357
358
        return $end - $start;
359
    }
360
361
    /**
362
     * Make a request to a servlet (a path) that's not a standard path.
363
     *
364
     * @param string $servlet Path to be added to the base Solr path.
365
     * @param array $parameters Optional, additional request parameters when constructing the URL.
366
     * @param string $method HTTP method to use, defaults to GET.
367
     * @param array $requestHeaders Key value pairs of header names and values. Should include 'Content-Type' for POST and PUT.
368
     * @param string $rawPost Must be an empty string unless method is POST or PUT.
369
     * @param float|bool $timeout Read timeout in seconds, defaults to FALSE.
370
     * @return \Apache_Solr_Response Response object
371
     * @throws \Apache_Solr_HttpTransportException if returned HTTP status is other than 200
372
     */
373
    public function requestServlet($servlet, $parameters = [], $method = 'GET', $requestHeaders = [], $rawPost = '', $timeout = false)
374
    {
375
        // Add default parameters
376
        $parameters['wt'] = self::SOLR_WRITER;
377
        $parameters['json.nl'] = $this->_namedListTreatment;
378
        $url = $this->_constructUrl($servlet, $parameters);
379
380
        $httpResponse = $this->getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout);
381
        $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
382
        if ($solrResponse->getHttpStatus() != 200) {
383
            throw new \Apache_Solr_HttpTransportException($solrResponse);
384
        }
385
386
        return $solrResponse;
387
    }
388
389
    /**
390
     * Decides which transport method to used, depending on the request method and retrieves the response.
391
     *
392
     * @param string $url
393
     * @param string $method
394
     * @param array $requestHeaders
395
     * @param string $rawPost
396
     * @param float|bool $timeout
397
     * @return \Apache_Solr_HttpTransport_Response
398
     */
399
    protected function getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout)
400
    {
401
        $httpTransport = $this->getHttpTransport();
402
403
        if ($method == self::METHOD_GET) {
404
            return $httpTransport->performGetRequest($url, $timeout);
405
        }
406
        if ($method == self::METHOD_POST) {
407
            // FIXME should respect all headers, not only Content-Type
408
            return $httpTransport->performPostRequest($url, $rawPost, $requestHeaders['Content-Type'], $timeout);
409
        }
410
411
        throw new \InvalidArgumentException('$method should be GET or POST');
412
    }
413
414
    /**
415
     * Performs a ping request and returns the result.
416
     *
417
     * @param int $timeout
418
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
419
     * @return \Apache_Solr_HttpTransport_Response
420
     */
421
    protected function performPingRequest($timeout = 2, $useCache = true)
422
    {
423
        $cacheKey = (string)($this);
424
        if ($useCache && isset(static::$pingCache[$cacheKey])) {
425
            return static::$pingCache[$cacheKey];
426
        }
427
428
        $pingResult = $this->getHttpTransport()->performGetRequest($this->_pingUrl, $timeout);
429
430
        if ($useCache) {
431
            static::$pingCache[$cacheKey] = $pingResult;
432
        }
433
434
        return $pingResult;
435
    }
436
437
    /**
438
     * Returns the current time in milliseconds.
439
     *
440
     * @return double
441
     */
442
    protected function getMilliseconds()
443
    {
444
        return GeneralUtility::milliseconds();
445
    }
446
}