Issues (202)

System/Solr/Service/AbstractSolrService.php (2 issues)

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 3 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\System\Solr\ResponseAdapter;
31
use ApacheSolrForTypo3\Solr\Util;
32
33
use Solarium\Client;
34
use Solarium\Core\Client\Endpoint;
35
use Solarium\Core\Client\Request;
36
use Solarium\Core\Query\QueryInterface;
37
use Solarium\Exception\HttpException;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
40
abstract class AbstractSolrService {
41
42
    /**
43
     * @var array
44
     */
45
    protected static $pingCache = [];
46
47
    /**
48
     * @var TypoScriptConfiguration
49
     */
50
    protected $configuration;
51
52
    /**
53
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
54
     */
55
    protected $logger = null;
56
57
    /**
58
     * @var Client
59
     */
60
    protected $client = null;
61
62
    /**
63
     * SolrReadService constructor.
64
     */
65
    public function __construct(Client $client, $typoScriptConfiguration = null, $logManager = null)
66
    {
67
        $this->client = $client;
68
        $this->configuration = $typoScriptConfiguration ?? Util::getSolrConfiguration();
69 131
        $this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
70
    }
71 131
72 130
    /**
73
     * Returns the path to the core solr path + core path.
74 130
     *
75 130
     * @return string
76 130
     */
77 130
    public function getCorePath()
78
    {
79
        $endpoint = $this->getPrimaryEndpoint();
80
        return is_null($endpoint) ? '' : $endpoint->getPath() .'/'. $endpoint->getCore();
81
    }
82
83
    /**
84 130
     * Return a valid http URL given this server's host, port and path and a provided servlet name
85
     *
86 130
     * @param string $servlet
87 130
     * @param array $params
88 1
     * @return string
89
     */
90 130
    protected function _constructUrl($servlet, $params = [])
91
    {
92
        $queryString = count($params) ? '?' . http_build_query($params, null, '&') : '';
93
        return $this->__toString() . $servlet . $queryString;
94
    }
95
96
    /**
97
     * Creates a string representation of the Solr connection. Specifically
98 77
     * will return the Solr URL.
99
     *
100 77
     * @return string The Solr URL.
101
     */
102
    public function __toString()
103
    {
104
        $endpoint = $this->getPrimaryEndpoint();
105
        if (!$endpoint instanceof Endpoint) {
106
            return '';
107
        }
108 3
109
        return  $endpoint->getScheme(). '://' . $endpoint->getHost() . ':' . $endpoint->getPort() . $endpoint->getPath() . '/' . $endpoint->getCore() . '/';
110 3
    }
111
112
    /**
113
     * @return Endpoint|null
114
     */
115
    public function getPrimaryEndpoint()
116
    {
117
        return is_array($this->client->getEndpoints()) ? reset($this->client->getEndpoints()) : null;
0 ignored issues
show
The condition is_array($this->client->getEndpoints()) is always true.
Loading history...
118
    }
119 131
120
    /**
121
     * Central method for making a get operation against this Solr Server
122 131
     *
123 2
     * @param string $url
124
     * @return ResponseAdapter
125
     */
126 130
    protected function _sendRawGet($url)
127 130
    {
128 1
        return $this->_sendRawRequest($url, Request::METHOD_GET);
129
    }
130
131
    /**
132 130
     * Central method for making a HTTP DELETE operation against the Solr server
133
     *
134 130
     * @param string $url
135 1
     * @return ResponseAdapter
136
     */
137 130
    protected function _sendRawDelete($url)
138
    {
139
        return $this->_sendRawRequest($url, Request::METHOD_DELETE);
140
    }
141
142
    /**
143
     * Central method for making a post operation against this Solr Server
144
     *
145
     * @param string $url
146
     * @param string $rawPost
147 130
     * @param string $contentType
148
     * @return ResponseAdapter
149 130
     */
150
    protected function _sendRawPost($url, $rawPost, $contentType = 'text/xml; charset=UTF-8')
151 130
    {
152 2
        $initializeRequest = function(Request $request) use ($rawPost, $contentType) {
153
            $request->setRawData($rawPost);
154
            $request->addHeader('Content-Type: ' . $contentType);
155
            return $request;
156 2
        };
157
158
        return $this->_sendRawRequest($url, Request::METHOD_POST, $rawPost, $initializeRequest);
159 130
    }
160
161
    /**
162
     * Method that performs an http request with the solarium client.
163
     *
164
     * @param string $url
165
     * @param string $method
166
     * @param string $body
167
     * @param \Closure $initializeRequest
168
     * @return ResponseAdapter
169 66
     */
170
    protected function _sendRawRequest($url, $method = Request::METHOD_GET, $body = '', \Closure $initializeRequest = null)
171 66
    {
172 66
        $logSeverity = SolrLogManager::INFO;
173
        $exception = null;
174
175 66
        try {
176 8
            $request = $this->buildSolariumRequestFromUrl($url, $method);
177 8
178 8
            if($initializeRequest !== null) {
179
                $request = $initializeRequest($request);
180
            }
181 66
182 8
            $response = $this->executeRequest($request);
183
        } catch (HttpException $exception) {
184
            $logSeverity = SolrLogManager::ERROR;
185 66
            $response = new ResponseAdapter($exception->getBody(), $exception->getCode(), $exception->getMessage());
186
        }
187
188
        if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) {
189
            $message = 'Querying Solr using '.$method;
190
            $this->writeLog($logSeverity, $message, $url, $response, $exception, $body);
191
        }
192
193
        return $response;
194
    }
195 4
196
    /**
197 4
     * Build the log data and writes the message to the log
198 4
     *
199
     * @param integer $logSeverity
200
     * @param string $message
201 4
     * @param string $url
202 4
     * @param ResponseAdapter $solrResponse
203 4
     * @param \Exception $exception
204
     * @param string $contentSend
205 4
     */
206 4
    protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '')
207
    {
208
        $logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend);
209
        $this->logger->log($logSeverity, $message, $logData);
210
    }
211
212
    /**
213 4
     * Parses the solr information to build data for the logger.
214
     *
215
     * @param ResponseAdapter $solrResponse
216
     * @param \Exception $e
217 4
     * @param string $url
218
     * @param string $contentSend
219
     * @return array
220
     */
221
    protected function buildLogDataFromResponse(ResponseAdapter $solrResponse, \Exception $e = null, $url = '', $contentSend = '')
222
    {
223
        $logData = ['query url' => $url, 'response' => (array)$solrResponse];
224
225
        if ($contentSend !== '') {
226
            $logData['content'] = $contentSend;
227
        }
228
229 94
        if (!empty($e)) {
230
            $logData['exception'] = $e->__toString();
231 94
            return $logData;
232 94
        } else {
233
            // trigger data parsing
234
            // @extensionScannerIgnoreLine
235 94
            $solrResponse->response;
236
            $logData['response data'] = print_r($solrResponse, true);
237
            return $logData;
238
        }
239
    }
240
241 94
    /**
242
     * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
243
     * server is available.
244
     *
245 94
     * Simply overrides the SolrPhpClient implementation, changing ping from a
246
     * HEAD to a GET request, see http://forge.typo3.org/issues/44167
247
     *
248
     * Also does not report the time, see https://forge.typo3.org/issues/64551
249
     *
250
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
251
     * @return bool TRUE if Solr can be reached, FALSE if not
252
     */
253
    public function ping($useCache = true)
254
    {
255
        try {
256
            $httpResponse = $this->performPingRequest($useCache);
257
        } catch (HttpException $exception) {
258
            return false;
259 8
        }
260
261 8
        return ($httpResponse->getHttpStatus() === 200);
262 8
    }
263 8
264
    /**
265
     * Call the /admin/ping servlet, can be used to get the runtime of a ping request.
266
     *
267
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
268
     * @return double runtime in milliseconds
269
     * @throws \ApacheSolrForTypo3\Solr\PingFailedException
270
     */
271
    public function getPingRoundTripRuntime($useCache = true)
272
    {
273
        try {
274 8
            $start = $this->getMilliseconds();
275
            $httpResponse = $this->performPingRequest($useCache);
276 8
            $end = $this->getMilliseconds();
277
        } catch (HttpException $e) {
278 8
            $message = 'Solr ping failed with unexpected response code: ' . $e->getCode();
279
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
280
            $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message);
281
            throw $exception;
282 8
        }
283 8
284 8
        if ($httpResponse->getHttpStatus() !== 200) {
285
            $message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getHttpStatus();
286
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
287
            $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message);
288
            throw $exception;
289
        }
290
291
        return $end - $start;
292
    }
293
294
    /**
295
     * Performs a ping request and returns the result.
296
     *
297
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
298
     * @return ResponseAdapter
299 22
     */
300
    protected function performPingRequest($useCache = true)
301 22
    {
302 22
        $cacheKey = (string)($this);
303 22
        if ($useCache && isset(static::$pingCache[$cacheKey])) {
304
            return static::$pingCache[$cacheKey];
305
        }
306
307
        $pingQuery = $this->client->createPing();
308
        $pingResult = $this->createAndExecuteRequest($pingQuery);
309
310
        if ($useCache) {
311 7
            static::$pingCache[$cacheKey] = $pingResult;
312
        }
313 7
314 7
        return $pingResult;
315
    }
316
317
    /**
318
     * Returns the current time in milliseconds.
319
     *
320
     * @return double
321
     */
322
    protected function getMilliseconds()
323
    {
324
        return GeneralUtility::milliseconds();
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\G...Utility::milliseconds() has been deprecated: will be removed in TYPO3 v11.0. Use the native PHP functions round(microtime(true) * 1000) instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

324
        return /** @scrutinizer ignore-deprecated */ GeneralUtility::milliseconds();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
325
    }
326
327
    /**
328
     * @param QueryInterface $query
329
     * @return ResponseAdapter
330 72
     */
331
    protected function createAndExecuteRequest(QueryInterface $query): ResponseAdapter
332 72
    {
333 72
        $request = $this->client->createRequest($query);
334
        return $this->executeRequest($request);
335
    }
336
337
    /**
338
     * @param $request
339
     * @return ResponseAdapter
340
     */
341
    protected function executeRequest($request): ResponseAdapter
342
    {
343
        $result = $this->client->executeRequest($request);
344 3
        return new ResponseAdapter($result->getBody(), $result->getStatusCode(), $result->getStatusMessage());
345
    }
346 3
347 3
    /**
348 3
     * @param string $url
349
     * @param string $httpMethod
350 3
     * @return Request
351 1
     */
352
    protected function buildSolariumRequestFromUrl($url, $httpMethod = Request::METHOD_GET): Request
353 1
    {
354 1
        $params = [];
355 1
        parse_str(parse_url($url, PHP_URL_QUERY), $params);
356
357
        $path = parse_url($url, PHP_URL_PATH);
358 2
        $endpoint = $this->getPrimaryEndpoint();
359
        $coreBasePath = $endpoint->getPath() . '/' . $endpoint->getCore() . '/';
360
        $handler = str_replace($coreBasePath, '', $path);
361
362
        $request = new Request();
363
        $request->setMethod($httpMethod);
364
        $request->setParams($params);
365
        $request->setHandler($handler);
366
        return $request;
367
    }
368
}
369