Completed
Push — master ( 734eba...03e6ad )
by
unknown
02:56
created

SearchRunner::search()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 53
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 7.5251
c 0
b 0
f 0
cc 7
eloc 31
nc 6
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Charcoal\Search;
4
5
use \Exception;
6
use \InvalidArgumentException;
7
8
use \Psr\Log\LoggerAwareInterface;
9
use \Psr\Log\LoggerAwareTrait;
10
11
use \Charcoal\Factory\FactoryInterface;
12
13
use \Charcoal\Search\CustomSearch;
14
use \Charcoal\Search\SearchRunnerConfig;
15
use \Charcoal\Search\SearchInterface;
16
use \Charcoal\Search\SearchLog;
17
use \Charcoal\Search\SearchRunnerInterface;
18
19
/**
20
 *
21
 */
22
class SearchRunner implements SearchRunnerInterface, LoggerAwareInterface
23
{
24
    use LoggerAwareTrait;
25
26
    /**
27
     * @var FactoryInterface $modelFactory
28
     */
29
    private $modelFactory;
30
31
    /**
32
     * @var SearchRunnerConfig $searchConfig
33
     */
34
    private $searchConfig;
35
36
    /**
37
     * @var SearchLog $searchLog
38
     */
39
    private $searchLog;
40
41
    /**
42
     * @var array $results
43
     */
44
    private $results;
45
46
    /**
47
     * @var boolean $logDisabled
48
     */
49
    public $logDisabled = false;
50
51
    /**
52
     * @param array $data The constructor options.
53
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
54
     */
55
    public function __construct(array $data)
56
    {
57
        $this->setSearchConfig($data['search_config']);
58
        $this->setModelFactory($data['model_factory']);
59
        $this->setLogger($data['logger']);
60
    }
61
62
    /**
63
     * @param FactoryInterface $factory The factory used to create logs and models.
64
     * @return void
65
     */
66
    private function setModelFactory(FactoryInterface $factory)
67
    {
68
        $this->modelFactory = $factory;
69
    }
70
71
    /**
72
     * @throws Exception If the model factory was not properly set.
73
     * @return FactoryInterface
74
     */
75
    protected function modelFactory()
76
    {
77
        if ($this->modelFactory === null) {
78
            throw new Exception(
79
                'Can not access model factory, the dependency has not been set.'
80
            );
81
        }
82
        return $this->modelFactory;
83
    }
84
85
    /**
86
     * @param array|SearchRunnerConfig $searchConfig The search options / configuration.
87
     * @return  SearchRunner Chainable
88
     */
89
    protected function setSearchConfig($searchConfig)
90
    {
91
        if (!($searchConfig instanceof SearchRunnerConfig)) {
92
            $searchConfig = new SearchRunnerConfig($searchConfig);
93
        }
94
        $this->searchConfig = $searchConfig;
95
        return $this;
96
    }
97
98
    /**
99
     * Public access to the search config.
100
     * @return SearchRunnerConfig
101
     */
102
    public function searchConfig()
103
    {
104
        return $this->searchConfig;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->searchConfig; (Charcoal\Search\SearchRunnerConfig) is incompatible with the return type declared by the interface Charcoal\Search\SearchRu...Interface::searchConfig of type Charcoal\Search\SearchConfig.

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...
105
    }
106
107
    /**
108
     * Public access to the search log.
109
     * @return SearchLog
110
     */
111
    public function searchLog()
112
    {
113
        return $this->searchLog;
114
    }
115
116
    /**
117
     * @return array
118
     */
119
    public function results()
120
    {
121
        return $this->results;
122
    }
123
124
    /**
125
     * @param string $keyword The searched keyword.
126
     * @throws InvalidArgumentException If the keyword is not a string.
127
     * @return array The results.
128
     */
129
    final public function search($keyword)
130
    {
131
        if (!is_string($keyword)) {
132
            throw new InvalidArgumentException(
133
                'Search keyword must be a string.'
134
            );
135
        }
136
137
        if ($keyword == '') {
138
            throw new InvalidArgumentException(
139
                'Keyword can not be empty.'
140
            );
141
        }
142
143
        // Reset results
144
        $this->results = [];
145
146
        $searchConfig = $this->searchConfig();
147
148
        if (!isset($searchConfig['searches'])) {
149
            throw new InvalidArgumentException(
150
                'No searches defined in search config.'
151
            );
152
        }
153
154
        $numResults = 0;
155
        $searchObjects = $searchConfig['searches'];
156
157
        foreach ($searchObjects as $searchIdent => $searchObj) {
158
            if ($searchObj instanceof SearchInterface) {
159
                // Run search from Search object
160
                $results = $searchObj->search($keyword);
161
            } else {
162
                $searchOptions = array_merge($searchObj, [
163
                    'logger' => $this->logger,
164
                    'model_factory' => $this->modelFactory()
165
                ]);
166
                $search = new CustomSearch($searchOptions);
167
                $results =  $search->search($keyword);
168
            }
169
            $this->results[$searchIdent] = $results;
170
            $numResults += count($results);
171
        }
172
173
        $this->searchLog = $this->createLog([
174
            'search_ident'  => isset($searchConfig['ident']) ? $searchConfig['ident'] : '',
175
            'keyword'       => $keyword,
176
            'num_results'   => $numResults,
177
            'results'       => $this->results
178
        ]);
179
180
        return $this->results;
181
    }
182
183
    /**
184
     * @param array $logData Log data.
185
     * @return SearchLog
186
     */
187
    private function createLog(array $logData)
188
    {
189
        $log = $this->modelFactory()->create(SearchLog::class);
190
        $log->setData($logData);
191
192
        if ($this->logDisabled === false) {
193
            $log->save();
194
        }
195
196
        return $log;
197
    }
198
}
199