These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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); |
||
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
|
|||
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, '_')) { |
||
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
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 | } |
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.