Complex classes like DocumentsIndex often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DocumentsIndex, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | class DocumentsIndex |
||
14 | { |
||
15 | public $esIndex = 'documents'; |
||
16 | public $esType = 'document'; |
||
17 | |||
18 | /** |
||
19 | * @var Client |
||
20 | */ |
||
21 | public $client; |
||
22 | |||
23 | /** |
||
24 | * @var array |
||
25 | */ |
||
26 | public $usage = []; |
||
27 | |||
28 | /** |
||
29 | * @param Client $client |
||
30 | */ |
||
31 | public function __construct(Client $client) |
||
32 | { |
||
33 | $this->client = $client; |
||
34 | $this->esIndex = env('ES_INDEX', 'documents'); |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * Search for documents in ElasticSearch. |
||
39 | * |
||
40 | * @param SearchDocumentsRequest $request |
||
41 | * |
||
42 | * @return array |
||
43 | */ |
||
44 | public function search(SearchDocumentsRequest $request) |
||
45 | { |
||
46 | $payload = $this->basePayload(); |
||
47 | $payload['from'] = $request->offset ?: 0; |
||
48 | $payload['size'] = $request->limit ?: 25; |
||
49 | |||
50 | $query = $this->queryStringFromRequest($request); |
||
51 | if (!empty($query)) { |
||
52 | $payload['body']['query']['query_string']['query'] = $query; |
||
53 | } |
||
54 | |||
55 | try { |
||
56 | $response = $this->client->search($payload); |
||
57 | } catch (BadRequest400Exception $e) { |
||
58 | $msg = json_decode($e->getMessage(), true); |
||
59 | throw new InvalidQueryException($msg['error']); |
||
60 | } |
||
61 | $response['offset'] = $payload['from']; |
||
62 | |||
63 | return $response; |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * Return a single document identified by ID. |
||
68 | * |
||
69 | * @param int $id |
||
70 | * |
||
71 | * @return array |
||
72 | */ |
||
73 | public function get($id) |
||
74 | { |
||
75 | $payload = $this->basePayload(); |
||
76 | $payload['id'] = $id; |
||
77 | |||
78 | try { |
||
79 | $response = $this->client->get($payload); |
||
80 | } catch (Missing404Exception $e) { |
||
81 | return; |
||
82 | } |
||
83 | |||
84 | return $response['_source']; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Escape special characters |
||
89 | * http://lucene.apache.org/core/old_versioned_docs/versions/2_9_1/queryparsersyntax.html#Escaping Special Characters. |
||
90 | * |
||
91 | * @param string $value |
||
92 | * |
||
93 | * @return string |
||
94 | */ |
||
95 | public function sanitizeForQuery($value) |
||
113 | |||
114 | /** |
||
115 | * Builds a query string query from a SearchDocumentsRequest. |
||
116 | * |
||
117 | * @param SearchDocumentsRequest $request |
||
118 | * |
||
119 | * @return string |
||
120 | */ |
||
121 | public function queryStringFromRequest(SearchDocumentsRequest $request) |
||
122 | { |
||
123 | $query = []; |
||
124 | if ($request->has('q')) { |
||
125 | // Allow raw queries |
||
126 | $query[] = $request->q; |
||
127 | } |
||
128 | if ($request->has('collection')) { |
||
129 | $col = Collection::findOrFail($request->collection); |
||
130 | $query[] = 'collections:"' . $this->sanitizeForQuery($col->name) . '"'; |
||
131 | } |
||
132 | if ($request->has('subject')) { |
||
133 | $query[] = '(subjects.noubomn.prefLabel:"' . $this->sanitizeForQuery($request->subject) . '"' . |
||
134 | ' OR subjects.bare.prefLabel:"' . $this->sanitizeForQuery($request->subject) . '"' . |
||
135 | ' OR genres.noubomn.prefLabel:"' . $this->sanitizeForQuery($request->subject) . '")'; |
||
136 | // TODO: Vi bør vel antakelig skille mellom X som emne og X som form/sjanger ? |
||
137 | // Men da må frontend si fra hva den ønsker, noe den ikke gjør enda. |
||
138 | } |
||
139 | if ($request->has('language')) { |
||
140 | $query[] = 'language:"' . $this->sanitizeForQuery($request->language) . '"' ; |
||
|
|||
141 | } |
||
142 | if ($request->has('genre')) { |
||
143 | $query[] = 'genres.noubomn.prefLabel:"' . $this->sanitizeForQuery($request->genre) . '"'; |
||
144 | } |
||
145 | if ($request->has('real')) { |
||
146 | dd('`real` is (very) deprecated, please use `subject` instead.'); |
||
147 | } |
||
148 | $query = count($query) ? implode(' AND ', $query) : ''; |
||
149 | |||
150 | return $query; |
||
151 | } |
||
152 | |||
153 | public function basePayload() |
||
160 | |||
161 | public function getFullType($type) |
||
170 | |||
171 | /** |
||
172 | * Returns the number of documents the subject is used on. |
||
173 | * |
||
174 | * @param int $id |
||
175 | * |
||
176 | * @return int |
||
177 | */ |
||
178 | public function getUsageCount($id, $type) |
||
188 | |||
189 | /** |
||
190 | * Build an array of document usage count per subject. |
||
191 | * |
||
192 | * @param array|int $subject_ids |
||
193 | * |
||
194 | * @return array |
||
195 | */ |
||
196 | public function addToUsageCache($entity_ids, $type) |
||
217 | |||
218 | public function buildCompleteUsageCache() |
||
219 | { |
||
220 | $typemap = ['Colligator\\Subject' => 'subject', 'Colligator\\Genre' => 'genre']; |
||
221 | $query = \DB::table('entities') |
||
222 | ->select(['entity_id', 'entity_type', \DB::raw('count(document_id) as doc_count')]) |
||
223 | ->groupBy('entity_id', 'entity_type'); |
||
224 | $query->chunk(5000, function ($rows) use ($typemap) { |
||
225 | foreach ($rows as $row) { |
||
226 | $type = $typemap[$row->entity_type]; |
||
227 | array_set($this->usage, $type . '.' . $row->entity_id, intval($row->doc_count)); |
||
228 | } |
||
229 | }); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Add or update a document in the ElasticSearch index, making it searchable. |
||
234 | * |
||
235 | * @param Document $doc |
||
236 | * @param int $indexVersion |
||
237 | * |
||
238 | * @throws \ErrorException |
||
239 | */ |
||
240 | public function index(Document $doc, $indexVersion = null) |
||
258 | |||
259 | /** |
||
260 | * Add or update a document in the ElasticSearch index, making it searchable. |
||
261 | * |
||
262 | * @param int $docId |
||
263 | * |
||
264 | * @throws \ErrorException |
||
265 | */ |
||
266 | public function indexById($docId) |
||
270 | |||
271 | public function createVersion($version = null) |
||
272 | { |
||
273 | if (is_null($version)) { |
||
319 | |||
320 | public function dropVersion($version) |
||
330 | |||
331 | public function addAction(&$actions, $action, $version) |
||
337 | |||
338 | public function activateVersion($newVersion) |
||
348 | |||
349 | public function versionExists($version) |
||
353 | |||
354 | public function getCurrentVersion() |
||
365 | } |
||
366 |
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.