Completed
Push — master ( 904c19...2c4bed )
by
unknown
10:30
created

SearchLog   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 463
Duplicated Lines 9.5 %

Coupling/Cohesion

Components 4
Dependencies 1

Importance

Changes 0
Metric Value
wmc 50
lcom 4
cbo 1
dl 44
loc 463
rs 8.6206
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A setSearchIdent() 0 12 2
A searchIdent() 0 4 1
A setKeyword() 0 12 2
A keyword() 0 4 1
B setOptions() 8 21 5
A options() 0 4 1
A setNumResults() 0 6 1
A numResults() 0 4 1
B setResults() 8 23 6
A results() 0 4 1
A setSessionId() 0 17 3
A sessionId() 0 4 1
A setIp() 0 19 4
A ip() 0 4 1
A setLang() 14 14 3
A lang() 0 4 1
A setOrigin() 14 14 3
A resolveOrigin() 0 13 2
A origin() 0 4 1
B setTs() 0 27 5
A ts() 0 4 1
A preSave() 0 20 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SearchLog 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 SearchLog, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Charcoal\Search;
4
5
use \DateTime;
6
use \DateTimeInterface;
7
use \Traversable;
8
use \InvalidArgumentException;
9
10
// From 'charcoal-core'
11
use \Charcoal\Model\AbstractModel;
12
13
/**
14
 * Search logs should be saved every time a client initiates a search request.
15
 */
16
class SearchLog extends AbstractModel implements SearchLogInterface
17
{
18
    /**
19
     * The search identifier this specific search log belongs to.
20
     *
21
     * @var string
22
     */
23
    private $searchIdent;
24
25
    /**
26
     * The searched keyword.
27
     *
28
     * @var string
29
     */
30
    private $keyword;
31
32
    /**
33
     * The search options, if defined.
34
     *
35
     * @var array|null
36
     */
37
    private $options;
38
39
    /**
40
     * Number of search results.
41
     *
42
     * @var integer|null
43
     */
44
    private $numResults;
45
46
    /**
47
     * Detailed results, if available.
48
     *
49
     * @var array|null
50
     */
51
    private $results;
52
53
    /**
54
     * Client session ID
55
     *
56
     * @var string|null
57
     */
58
    private $sessionId;
59
60
    /**
61
     * Client IP address of the end-user.
62
     *
63
     * @var integer|null
64
     */
65
    private $ip;
66
67
    /**
68
     * Language of the end-user or source URI.
69
     *
70
     * @var string|null
71
     */
72
    private $lang;
73
74
    /**
75
     * The search origin; an identifier representing where the search was executed from.
76
     *
77
     * @var string|null
78
     */
79
    private $origin;
80
81
    /**
82
     * Timestamp of the search request.
83
     *
84
     * @var DateTimeInterface|null
85
     */
86
    private $ts;
87
88
    /**
89
     * Set the log's associated search identifier.
90
     *
91
     * @param  string $ident The search identifier.
92
     * @throws InvalidArgumentException If the identifier is not a string.
93
     * @return SearchLog Chainable
94
     */
95
    public function setSearchIdent($ident)
96
    {
97
        if (!is_string($ident)) {
98
            throw new InvalidArgumentException(
99
                'Search ident must be a string.'
100
            );
101
        }
102
103
        $this->searchIdent = $ident;
104
105
        return $this;
106
    }
107
108
    /**
109
     * Retrieve the log's associated search identifier.
110
     *
111
     * @return string
112
     */
113
    public function searchIdent()
114
    {
115
        return $this->searchIdent;
116
    }
117
118
    /**
119
     * Set the searched term.
120
     *
121
     * @param  string $kw The searched term / keyword.
122
     * @throws InvalidArgumentException If the keyword is not a string.
123
     * @return SearchLog Chainable
124
     */
125
    public function setKeyword($kw)
126
    {
127
        if (!is_string($kw)) {
128
            throw new InvalidArgumentException(
129
                'Keyword must be a string'
130
            );
131
        }
132
133
        $this->keyword = $kw;
134
135
        return $this;
136
    }
137
138
    /**
139
     * Retrieve the searched term.
140
     *
141
     * @return string
142
     */
143
    public function keyword()
144
    {
145
        return $this->keyword;
146
    }
147
148
    /**
149
     * Set the options applied to the search.
150
     *
151
     * @param  mixed $options The search options, if defined.
152
     * @throws InvalidArgumentException If the options is not an array or invalid JSON.
153
     * @return SearchLog Chainable
154
     */
155
    public function setOptions($options)
156
    {
157
        if ($options === null) {
158
            $this->options = null;
159 View Code Duplication
        } elseif (is_string($options)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
            $this->options = json_decode($options, true);
161
            if (json_last_error() !== JSON_ERROR_NONE) {
162
                throw new InvalidArgumentException(
163
                    sprintf('Invalid JSON for search options: "%s"', $options)
164
                );
165
            }
166
        } elseif (is_array($options)) {
167
            $this->options = $options;
168
        } else {
169
            throw new InvalidArgumentException(
170
                'Invalid search options. Must be a JSON string, an array, or NULL.'
171
            );
172
        }
173
174
        return $this;
175
    }
176
177
    /**
178
     * Retrieve the options applied to the search.
179
     *
180
     * @return array
181
     */
182
    public function options()
183
    {
184
        return $this->options;
185
    }
186
187
    /**
188
     * Set the result count.
189
     *
190
     * @param  integer $count The number of results from the search.
191
     * @return SearchLog Chainable
192
     */
193
    public function setNumResults($count)
194
    {
195
        $this->numResults = (int)$count;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Retrieve the result count.
202
     *
203
     * @return integer
204
     */
205
    public function numResults()
206
    {
207
        return $this->numResults;
208
    }
209
210
    /**
211
     * Set the collection of results.
212
     *
213
     * @param  mixed $results The search results data, if available.
214
     * @throws InvalidArgumentException If the results is not an array or invalid JSON.
215
     * @return SearchLog Chainable
216
     */
217
    public function setResults($results)
218
    {
219
        if ($results === null) {
220
            $this->results = null;
221 View Code Duplication
        } elseif (is_string($results)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
            $this->results = json_decode($results, true);
223
            if (json_last_error() !== JSON_ERROR_NONE) {
224
                throw new InvalidArgumentException(
225
                    sprintf('Invalid JSON for search results: "%s"', $results)
226
                );
227
            }
228
        } elseif (is_array($results)) {
229
            $this->results = $results;
230
        } elseif ($results instanceof Traversable) {
231
            $this->results = iterator_to_array($results, false);
232
        } else {
233
            throw new InvalidArgumentException(
234
                'Invalid search results type. Must be a JSON string, an array, an iterator, or NULL.'
235
            );
236
        }
237
238
        return $this;
239
    }
240
241
    /**
242
     * Retrieve the collection of results.
243
     *
244
     * @return array
245
     */
246
    public function results()
247
    {
248
        return $this->results;
249
    }
250
251
    /**
252
     * Set the client session ID.
253
     *
254
     * @param  string $id The session identifier. Typically, {@see session_id()}.
255
     * @throws InvalidArgumentException If the session id is not a string.
256
     * @return SearchLog Chainable
257
     */
258
    public function setSessionId($id)
259
    {
260
        if ($id === null) {
261
            $this->sessionId = null;
262
            return $this;
263
        }
264
265
        if (!is_string($id)) {
266
            throw new InvalidArgumentException(
267
                'The session ID must be a string.'
268
            );
269
        }
270
271
        $this->sessionId = $id;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Retrieve the client session ID.
278
     *
279
     * @return string
280
     */
281
    public function sessionId()
282
    {
283
        return $this->sessionId;
284
    }
285
286
    /**
287
     * Set the client IP address.
288
     *
289
     * @param  integer|null $ip The remote IP at object creation.
290
     * @return SearchLog Chainable
291
     */
292
    public function setIp($ip)
293
    {
294
        if ($ip === null) {
295
            $this->ip = null;
296
            return $this;
297
        }
298
299
        if (is_string($ip)) {
300
            $ip = ip2long($ip);
301
        } elseif (is_numeric($ip)) {
302
            $ip = (int)$ip;
303
        } else {
304
            $ip = 0;
305
        }
306
307
        $this->ip = $ip;
308
309
        return $this;
310
    }
311
312
    /**
313
     * Retrieve the client IP address.
314
     *
315
     * @return integer|null
316
     */
317
    public function ip()
318
    {
319
        return $this->ip;
320
    }
321
322
    /**
323
     * Set the origin language.
324
     *
325
     * @param  string $lang The language code.
326
     * @throws InvalidArgumentException If the argument is not a string.
327
     * @return SearchLog Chainable
328
     */
329 View Code Duplication
    public function setLang($lang)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
    {
331
        if ($lang !== null) {
332
            if (!is_string($lang)) {
333
                throw new InvalidArgumentException(
334
                    'Language must be a string'
335
                );
336
            }
337
        }
338
339
        $this->lang = $lang;
340
341
        return $this;
342
    }
343
344
    /**
345
     * Retrieve the language.
346
     *
347
     * @return string
348
     */
349
    public function lang()
350
    {
351
        return $this->lang;
352
    }
353
354
    /**
355
     * Set the origin of the search request.
356
     *
357
     * @param  string $origin The source URL or identifier of the submission.
358
     * @throws InvalidArgumentException If the argument is not a string.
359
     * @return SearchLog Chainable
360
     */
361 View Code Duplication
    public function setOrigin($origin)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
    {
363
        if ($origin !== null) {
364
            if (!is_string($origin)) {
365
                throw new InvalidArgumentException(
366
                    'Origin must be a string.'
367
                );
368
            }
369
        }
370
371
        $this->origin = $origin;
372
373
        return $this;
374
    }
375
376
    /**
377
     * Resolve the origin of the search.
378
     *
379
     * @return string
380
     */
381
    public function resolveOrigin()
382
    {
383
        $uri = 'http';
384
385
        if (getenv('HTTPS') === 'on') {
386
            $uri .= 's';
387
        }
388
389
        $uri .= '://';
390
        $uri .= getenv('HTTP_HOST').getenv('REQUEST_URI');
391
392
        return $uri;
393
    }
394
395
    /**
396
     * Retrieve the origin of the search request.
397
     *
398
     * @return string
399
     */
400
    public function origin()
401
    {
402
        return $this->origin;
403
    }
404
405
    /**
406
     * Set when the search was initiated.
407
     *
408
     * @param  DateTime|string|null $timestamp The timestamp of search request.
409
     *     NULL is accepted and instances of DateTimeInterface are recommended;
410
     *     any other value will be converted (if possible) into one.
411
     * @throws InvalidArgumentException If the timestamp is invalid.
412
     * @return SearchLog Chainable
413
     */
414
    public function setTs($timestamp)
415
    {
416
        if ($timestamp === null) {
417
            $this->ts = null;
418
            return $this;
419
        }
420
421
        if (is_string($timestamp)) {
422
            try {
423
                $timestamp = new DateTime($timestamp);
424
            } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class Charcoal\Search\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
425
                throw new InvalidArgumentException(
426
                    sprintf('Invalid timestamp: %s', $e->getMessage())
427
                );
428
            }
429
        }
430
431
        if (!$timestamp instanceof DateTimeInterface) {
432
            throw new InvalidArgumentException(
433
                'Invalid timestamp value. Must be a date/time string or a DateTime object.'
434
            );
435
        }
436
437
        $this->ts = $timestamp;
438
439
        return $this;
440
    }
441
442
    /**
443
     * Retrieve the creation timestamp.
444
     *
445
     * @return DateTime|null
446
     */
447
    public function ts()
448
    {
449
        return $this->ts;
450
    }
451
452
    /**
453
     * Event called before _creating_ the object.
454
     *
455
     * @see    Charcoal\Source\StorableTrait::preSave() For the "create" Event.
456
     * @return boolean
457
     */
458
    public function preSave()
459
    {
460
        $result = parent::preSave();
461
462
        $this->setTs('now');
463
464
        if (session_id()) {
465
            $this->setSessionId(session_id());
466
        }
467
468
        if (getenv('REMOTE_ADDR')) {
469
            $this->setIp(getenv('REMOTE_ADDR'));
470
        }
471
472
        if (!isset($this->origin)) {
473
            $this->setOrigin($this->resolveOrigin());
474
        }
475
476
        return $result;
477
    }
478
}
479