Passed
Push — sheepy/introspection ( 000d40...b6b869 )
by Marco
02:43
created

BaseIndex::doSearch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 7
c 4
b 1
f 0
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Indexes;
5
6
use Firesphere\SolrSearch\Factories\QueryComponentFactory;
7
use Firesphere\SolrSearch\Helpers\Synonyms;
8
use Firesphere\SolrSearch\Interfaces\ConfigStore;
9
use Firesphere\SolrSearch\Queries\BaseQuery;
10
use Firesphere\SolrSearch\Results\SearchResult;
11
use Firesphere\SolrSearch\Services\SchemaService;
12
use Firesphere\SolrSearch\Services\SolrCoreService;
13
use Firesphere\SolrSearch\Traits\BaseIndexTrait;
14
use Firesphere\SolrSearch\Traits\GetterSetterTrait;
15
use LogicException;
16
use SilverStripe\Control\Director;
17
use SilverStripe\Core\Config\Config;
18
use SilverStripe\Core\Config\Configurable;
19
use SilverStripe\Core\Extensible;
20
use SilverStripe\Core\Injector\Injector;
21
use SilverStripe\Dev\Deprecation;
22
use SilverStripe\SiteConfig\SiteConfig;
23
use SilverStripe\View\ArrayData;
24
use Solarium\Core\Client\Adapter\Guzzle;
25
use Solarium\Core\Client\Client;
26
use Solarium\QueryType\Select\Query\Query;
27
28
/**
29
 * Class BaseIndex
30
 * @package Firesphere\SolrSearch\Indexes
31
 */
32
abstract class BaseIndex
33
{
34
    use Extensible;
35
    use Configurable;
36
    use GetterSetterTrait;
37
    use BaseIndexTrait;
38
39
    private static $fieldTypes = [
40
        'FulltextFields',
41
        'SortFields',
42
        'FilterFields',
43
        'BoostedFields',
44
        'CopyFields',
45
        'DefaultField',
46
        'FacetFields',
47
    ];
48
    /**
49
     * @var SchemaService
50
     */
51
    protected $schemaService;
52
53
    /**
54
     * @var QueryComponentFactory
55
     */
56
    protected $queryFactory;
57
58
    /**
59
     * The query terms as an array
60
     * @var array
61
     */
62
    protected $queryTerms = [];
63
64
    /**
65
     * BaseIndex constructor.
66
     */
67 14
    public function __construct()
68
    {
69
        // Set up the client
70 14
        $config = Config::inst()->get(SolrCoreService::class, 'config');
71 14
        $config['endpoint'] = $this->getConfig($config['endpoint']);
72 14
        $this->client = new Client($config);
73 14
        $this->client->setAdapter(new Guzzle());
74
75
        // Set up the schema service, only used in the generation of the schema
76 14
        $schemaService = Injector::inst()->get(SchemaService::class, false);
77 14
        $schemaService->setIndex($this);
78 14
        $schemaService->setStore(Director::isDev());
79 14
        $this->schemaService = $schemaService;
80 14
        $this->queryFactory = Injector::inst()->get(QueryComponentFactory::class, false);
81
82 14
        $this->extend('onBeforeInit');
83 14
        $this->init();
84 14
        $this->extend('onAfterInit');
85 14
    }
86
87
    /**
88
     * Build a full config for all given endpoints
89
     * This is to add the current index to e.g. an index or select
90
     * @param array $endpoints
91
     * @return array
92
     */
93 14
    public function getConfig($endpoints): array
94
    {
95 14
        foreach ($endpoints as $host => $endpoint) {
96 14
            $endpoints[$host]['core'] = $this->getIndexName();
97
        }
98
99 14
        return $endpoints;
100
    }
101
102
    /**
103
     * @return string
104
     */
105
    abstract public function getIndexName();
106
107
    /**
108
     * Required to initialise the fields.
109
     * It's loaded in to the non-static properties for backward compatibility with FTS
110
     * Also, it's a tad easier to use this way, loading the other way around would be very
111
     * memory intensive, as updating the config for each item is not efficient
112
     */
113 7
    public function init()
114
    {
115 7
        if (!self::config()->get($this->getIndexName())) {
116 7
            Deprecation::notice('5', 'Please set an index name');
117
118
            // If the old init method is found, skip the config based init
119 7
            if (count($this->getClasses())) {
120 7
                Deprecation::notice(
121 7
                    '5',
122 7
                    'You are running init at the top of your method. The new API requires it to be at the bottom'
123
                );
124
            }
125
126 7
            return;
127
        }
128
129
130 4
        $config = self::config()->get($this->getIndexName());
131
132 4
        if (!array_key_exists('Classes', $config)) {
133
            throw new LogicException('No classes to index found!');
134
        }
135
136 4
        $this->setClasses($config['Classes']);
137
138
        // For backward compatibility, copy the config to the protected values
139
        // Saves doubling up further down the line
140 4
        foreach (self::$fieldTypes as $type) {
141 4
            if (array_key_exists($type, $config)) {
142 4
                $method = 'set' . $type;
143 4
                $this->$method($config[$type]);
144
            }
145
        }
146 4
    }
147
148
    /**
149
     * Default returns a SearchResult. It can return an ArrayData if FTS Compat is enabled
150
     *
151
     * @param BaseQuery $query
152
     * @return SearchResult|ArrayData|mixed
153
     */
154 3
    public function doSearch(BaseQuery $query)
155
    {
156 3
        $this->extend('onBeforeSearch', $query);
157
        // Build the actual query parameters
158 3
        $clientQuery = $this->buildSolrQuery($query);
159
160 3
        $result = $this->client->select($clientQuery);
161
162
        // Handle the after search first. This gets a raw search result
163 3
        $this->extend('onAfterSearch', $result);
164 3
        $searchResult = new SearchResult($result, $query, $this);
165
166
        // And then handle the search results, which is a useable object for SilverStripe
167 3
        $this->extend('updateSearchResults', $searchResult);
168
169 3
        return $searchResult;
170
    }
171
172
    /**
173
     * @param BaseQuery $query
174
     * @return Query
175
     */
176 3
    protected function buildSolrQuery(BaseQuery $query): Query
177
    {
178 3
        $clientQuery = $this->client->createSelect();
179
180 3
        $helper = $clientQuery->getHelper();
181
182 3
        $this->queryFactory->setQuery($query);
183 3
        $this->queryFactory->setClientQuery($clientQuery);
184 3
        $this->queryFactory->setHelper($helper);
185 3
        $this->queryFactory->setIndex($this);
186
187 3
        $clientQuery = $this->queryFactory->buildQuery();
188 3
        $this->queryTerms = $this->queryFactory->getQueryArray();
189
190 3
        $queryData = implode(' ', $this->queryTerms);
191 3
        $clientQuery->setQuery($queryData);
192
193 3
        return $clientQuery;
194
    }
195
196
    /**
197
     * @return array
198
     */
199 16
    public function getFieldsForIndexing(): array
200
    {
201
        // Return values to make the key reset
202
        // Only return unique values
203
        // And make it all a single array
204 16
        return array_values(
205 16
            array_unique(
206 16
                array_merge(
207 16
                    $this->getFulltextFields(),
208 16
                    $this->getSortFields(),
209 16
                    $this->getFilterFields()
210
                )
211
            )
212
        );
213
    }
214
215
    /**
216
     * Upload config for this index to the given store
217
     *
218
     * @param ConfigStore $store
219
     */
220 13
    public function uploadConfig(ConfigStore $store): void
221
    {
222
        // @todo use types/schema/elevate rendering
223
        // Upload the config files for this index
224
        // Create a default schema which we can manage later
225 13
        $schema = (string)$this->schemaService->generateSchema();
226 13
        $store->uploadString(
227 13
            $this->getIndexName(),
228 13
            'schema.xml',
229 13
            $schema
230
        );
231
232
233 13
        $synonyms = $this->getSynonyms();
234
235
        // Upload synonyms
236 13
        $store->uploadString(
237 13
            $this->getIndexName(),
238 13
            'synonyms.txt',
239 13
            $synonyms
240
        );
241
242
        // Upload additional files
243 13
        foreach (glob($this->schemaService->getExtrasPath() . '/*') as $file) {
244 13
            if (is_file($file)) {
245 13
                $store->uploadFile($this->getIndexName(), $file);
246
            }
247
        }
248 13
    }
249
250
    /**
251
     * Add synonyms. Public to be extendable
252
     * @param bool $eng Include UK to US synonyms
253
     * @return string
254
     */
255 14
    public function getSynonyms($eng = true): string
256
    {
257 14
        $engSynonyms = '';
258 14
        if ($eng) {
259 14
            $engSynonyms = Synonyms::getSynonymsAsString();
260
        }
261
262 14
        return $engSynonyms . SiteConfig::current_site_config()->getField('SearchSynonyms');
263
    }
264
265
    /**
266
     * @return array
267
     */
268 1
    public function getQueryTerms(): array
269
    {
270 1
        return $this->queryTerms;
271
    }
272
273
    /**
274
     * @return QueryComponentFactory
275
     */
276 1
    public function getQueryFactory(): QueryComponentFactory
277
    {
278 1
        return $this->queryFactory;
279
    }
280
}
281