Completed
Push — master ( a31967...066d89 )
by De
02:25
created

Detector   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 260
Duplicated Lines 4.62 %

Coupling/Cohesion

Components 3
Dependencies 2

Importance

Changes 31
Bugs 1 Features 14
Metric Value
wmc 37
c 31
b 1
f 14
lcom 3
cbo 2
dl 12
loc 260
rs 8.6

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setKey() 0 5 1
A getKey() 0 8 3
A check() 0 21 3
A registerProcessorNamespace() 0 5 1
A declareProcessor() 0 5 1
A addProcssor() 0 7 1
A isProcessorDeclared() 0 4 1
A getProcessorClassName() 6 13 3
B getProcessor() 0 24 5
A registerCollectorNamespace() 0 5 1
A getCollectorClassName() 6 19 4
A on() 0 10 3
A onCheckPassed() 0 6 1
A onCheckFailed() 0 6 1
A isUnchecked() 0 4 1
A isPassed() 0 4 1
A isFailed() 0 4 1
A hasState() 0 4 1
A subscribe() 0 5 1
A trigger() 0 10 2

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace Sokil\FraudDetector;
4
5
use Sokil\DataType\PriorityList;
6
use Symfony\Component\EventDispatcher\EventDispatcher;
7
8
class Detector
9
{
10
    const STATE_UNCHECKED   = 'unckecked';
11
    const STATE_PASSED      = 'checkPassed';
12
    const STATE_FAILED      = 'checkFailed';
13
14
    private $state = self::STATE_UNCHECKED;
15
16
    /**
17
     *
18
     * @var mixed key to identify unique user
19
     */
20
    private $key;
21
22
    /**
23
     *
24
     * @var \Sokil\DataType\PriorityList
25
     */
26
    private $processorDeclarationList;
27
28
    private $processorList = array();
29
30
    private $processorNamespaces = array(
31
        '\Sokil\FraudDetector\Processor',
32
    );
33
34
    private $collectorNamespaces = array(
35
        '\Sokil\FraudDetector\Processor\RequestRate\Collector',
36
    );
37
38
    /**
39
     *
40
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
41
     */
42
    private $eventDispatcher;
43
44
    public function __construct()
45
    {
46
        $this->processorDeclarationList = new PriorityList();
47
        $this->eventDispatcher = new EventDispatcher();
48
    }
49
50
    /**
51
     * Key that uniquely identify user
52
     * @param type $key
53
     * @return \Sokil\FraudDetector\Detector
54
     */
55
    public function setKey($key)
56
    {
57
        $this->key = $key;
58
        return $this;
59
    }
60
61
    public function getKey()
62
    {
63
        if(!$this->key) {
64
            $this->key = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
65
        }
66
67
        return $this->key;
68
    }
69
70
    /**
71
     * Check if request is not fraud
72
     */
73
    public function check()
74
    {
75
        // check all conditions
76
        /* @var $processor \Sokil\FraudDetector\ProcessorInterface */
77
        foreach($this->processorDeclarationList->getKeys() as $processorName) {
78
            $processor = $this->getProcessor($processorName);
79
80
            if($processor->isPassed()) {
81
                $processor->afterCheckPassed();
82
                $this->trigger(self::STATE_PASSED . ':' . $processorName);
83
                $this->state = self::STATE_PASSED;
84
            } else {
85
                $processor->afterCheckFailed();
86
                $this->trigger(self::STATE_FAILED . ':' . $processorName);
87
                $this->state = self::STATE_FAILED;
88
                break;
89
            }
90
        }
91
92
        $this->trigger($this->state);
93
    }
94
95
    public function registerProcessorNamespace($namespace)
96
    {
97
        $this->processorNamespaces[] = rtrim($namespace, '\\');
98
        return $this;
99
    }
100
101
    /**
102
     * Add processor identified by its name.
103
     * If processor already added, it will be replaced by new instance.
104
     *
105
     * @param string $name name of processor
106
     * @param callable $callable configurator callable
107
     * @return \Sokil\FraudDetector\Detector
108
     */
109
    public function declareProcessor($name, $callable = null, $priority = 0)
110
    {
111
        $this->processorDeclarationList->set($name, $callable, $priority);
0 ignored issues
show
Documentation introduced by
$callable is of type callable|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
112
        return $this;
113
    }
114
115
    public function addProcssor($name, ProcessorInterface $processor, $priority = 0)
116
    {
117
        $this->declareProcessor($name, null, $priority);
118
        $this->processorList[$name] = $processor;
119
120
        return $this;
121
    }
122
123
    public function isProcessorDeclared($name)
124
    {
125
        return $this->processorDeclarationList->has($name);
126
    }
127
128
    /**
129
     * Factory method to create new check condition
130
     *
131
     * @param string $name name of check condition
132
     * @return \Sokil\FraudDetector\ProcessorInterface
133
     * @throws \Exception
134
     */
135
    private function getProcessorClassName($name)
136
    {
137
        $className = ucfirst($name) . 'Processor';
138
139 View Code Duplication
        foreach($this->processorNamespaces as $namespace) {
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...
140
            $fullyQualifiedClassName = $namespace . '\\' . $className;
141
            if(class_exists($fullyQualifiedClassName)) {
142
                return $fullyQualifiedClassName;
143
            }
144
        }
145
146
        throw new \Exception('Class ' . $fullyQualifiedClassName . ' not found');
147
    }
148
149
    public function getProcessor($processorName)
150
    {
151
        if(isset($this->processorList[$processorName])) {
152
            return $this->processorList[$processorName];
153
        }
154
155
        // create processor
156
        $processorClassName = $this->getProcessorClassName($processorName);
157
        $processor =  new $processorClassName($this);
158
159
        if (!($processor instanceof ProcessorInterface)) {
160
            throw new \Exception('Processor must inherit ProcessorInterface');
161
        }
162
163
        // configure processor
164
        $configuratorCallable = $this->processorDeclarationList->get($processorName);
165
        if($configuratorCallable && is_callable($configuratorCallable)) {
166
            call_user_func($configuratorCallable, $processor);
167
        }
168
169
        $this->processorList[$processorName] = $processor;
170
171
        return $processor;
172
    }
173
174
    public function registerCollectorNamespace($namespace)
175
    {
176
        $this->collectorNamespaces[] = rtrim($namespace, '\\');
177
        return $this;
178
    }
179
180
    public function getCollectorClassName($type)
181
    {
182
        if(false == strpos($type, '_')) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($type, '_') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
183
            $className = ucfirst($type);
184
        } else {
185
            $className = implode('', array_map('ucfirst', explode('_', $type)));
186
        }
187
188
        $className .= 'Collector';
189
190 View Code Duplication
        foreach($this->collectorNamespaces as $namespace) {
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...
191
            $fullyQualifiedClassName = $namespace . '\\' . $className;
192
            if(class_exists($fullyQualifiedClassName)) {
193
                return $fullyQualifiedClassName;
194
            }
195
        }
196
197
        throw new \Exception('Class ' . $fullyQualifiedClassName . ' not found');
198
    }
199
200
    private function on($stateName, $callable)
201
    {
202
        if($this->hasState(self::STATE_UNCHECKED)) {
203
            $this->subscribe($stateName, $callable);
204
        } elseif($this->hasState($stateName)) {
205
            call_user_func($callable);
206
        }
207
208
        return $this;
209
    }
210
211
    public function onCheckPassed($callable)
212
    {
213
        $this->on(self::STATE_PASSED, $callable);
214
215
        return $this;
216
    }
217
218
    public function onCheckFailed($callable)
219
    {
220
        $this->on(self::STATE_FAILED, $callable);
221
222
        return $this;
223
    }
224
225
    public function isUnchecked()
226
    {
227
        return $this->hasState(self::STATE_UNCHECKED);
228
    }
229
230
    public function isPassed()
231
    {
232
        return $this->hasState(self::STATE_PASSED);
233
    }
234
235
    public function isFailed()
236
    {
237
        return $this->hasState(self::STATE_FAILED);
238
    }
239
240
    private function hasState($state)
241
    {
242
        return $this->state === $state;
243
    }
244
245
    public function subscribe($eventName, $callable, $priority = 0)
246
    {
247
        $this->eventDispatcher->addListener($eventName, $callable, $priority);
248
        return $this;
249
    }
250
251
    /**
252
     *
253
     * @param string $eventName
254
     * @param mixed $target
255
     * @return \Sokil\FraudDetector\Event
256
     */
257
    public function trigger($eventName, $target = null)
258
    {
259
        $event = new Event();
260
261
        if($target) {
262
            $event->setTarget($target);
263
        }
264
265
        return $this->eventDispatcher->dispatch($eventName, $event);
266
    }
267
}