FieldProvider   F
last analyzed

Complexity

Total Complexity 89

Size/Duplication

Total Lines 677
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 9

Importance

Changes 0
Metric Value
wmc 89
lcom 5
cbo 9
dl 0
loc 677
rs 1.923
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A setApp() 0 4 1
A setSessionStore() 0 4 1
A setRouter() 0 4 1
A setRequest() 0 4 1
A setEnvironment() 0 4 1
A setDataCollector() 0 4 1
A extend() 0 4 1
A setTokenProvider() 0 4 1
A resolveValues() 0 21 3
A __call() 0 9 2
A getSessionId() 0 14 2
A getLaravelVersion() 0 4 1
A getSqlQueries() 0 28 5
B mergeBindings() 0 37 6
A getRouteName() 0 9 2
A getUrl() 0 16 3
A getQueryStringArray() 0 30 5
B getPostDataArray() 0 52 10
A parseRequestFieldValue() 0 26 5
A getRequestMethod() 0 9 2
A getServerIp() 0 9 2
A getClientIp() 0 9 2
A getClientUserAgent() 0 9 2
A getEnvironment() 0 4 1
A getFromSession() 0 9 2
A getGroupId() 0 22 5
C getUserId() 0 38 13
A getArtisanCommandName() 0 7 3
A getRunningInConsole() 0 4 1
A getLoggerVersion() 0 4 1
A getProcessIdentifier() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php namespace Understand\UnderstandLaravel5;
2
3
use Illuminate\Support\Arr;
4
use Illuminate\Support\Str;
5
use Understand\UnderstandLaravel5\UniqueProcessIdentifier;
6
use \Illuminate\Session\Store AS SessionStore;
7
use \Illuminate\Routing\Router;
8
use Illuminate\Http\Request;
9
use Illuminate\Foundation\Application;
10
11
class FieldProvider
12
{
13
14
    /**
15
     * The registered field providers.
16
     *
17
     * @var array
18
     */
19
    protected $providers = [];
20
21
    /**
22
     * Default field
23
     *
24
     * @var array
25
     */
26
    protected $defaultProviders = [
27
        'getSessionId',
28
        'getRouteName',
29
        'getUrl',
30
        'getRequestMethod',
31
        'getServerIp',
32
        'getClientIp',
33
        'getClientUserAgent',
34
        'getEnvironment',
35
        'getFromSession',
36
        'getProcessIdentifier',
37
        'getUserId',
38
        'getGroupId',
39
        'getLaravelVersion',
40
        'getSqlQueries',
41
        'getArtisanCommandName',
42
        'getRunningInConsole',
43
        'getLoggerVersion',
44
        'getPostDataArray',
45
        'getQueryStringArray',
46
    ];
47
48
    /**
49
     * Session store
50
     *
51
     * @var \Illuminate\Session\Store
52
     */
53
    protected $session;
54
55
    /**
56
     * Router
57
     *
58
     * @var Router
59
     */
60
    protected $router;
61
62
    /**
63
     * Server variable
64
     *
65
     * @var Request
66
     */
67
    protected $request;
68
69
    /**
70
     * Token provider
71
     *
72
     * @var UniqueProcessIdentifier
73
     */
74
    protected $tokenProvider;
75
76
    /**
77
     * Current environment
78
     *
79
     * @var string
80
     */
81
    protected $environment;
82
83
    /**
84
     * @var DataCollector
85
     */
86
    protected $dataCollector;
87
88
    /**
89
     * @var Application
90
     */
91
    protected $app;
92
93
    /**
94
     * @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...
95
     */
96
    public function __construct()
97
    {
98
        foreach ($this->defaultProviders as $defaultProviderName)
99
        {
100
            $this->extend($defaultProviderName, [$this, $defaultProviderName]);
101
        }
102
    }
103
104
    /**
105
     * @param Application $app
106
     */
107
    public function setApp(Application $app)
108
    {
109
        $this->app = $app;
110
    }
111
112
    /**
113
     * Set session store
114
     *
115
     * @param type $service
116
     */
117
    public function setSessionStore(SessionStore $service)
118
    {
119
        $this->session = $service;
120
    }
121
122
    /**
123
     * Set router
124
     *
125
     * @param Router $router
126
     */
127
    public function setRouter(Router $router)
128
    {
129
        $this->router = $router;
130
    }
131
132
    /**
133
     * Set request
134
     *
135
     * @param Request $request
136
     */
137
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
138
    {
139
        $this->request = $request;
140
    }
141
142
    /**
143
     * Set current environment
144
     *
145
     * @param string $environment
146
     */
147
    public function setEnvironment($environment)
148
    {
149
        $this->environment = $environment;
150
    }
151
152
    /**
153
     * @param DataCollector $dataCollector
154
     */
155
    public function setDataCollector(DataCollector $dataCollector)
156
    {
157
        $this->dataCollector = $dataCollector;
158
    }
159
160
    /**
161
     * Register a custom HTML macro.
162
     *
163
     * @param string $name
164
     * @param  mixed  $macro
0 ignored issues
show
Bug introduced by
There is no parameter named $macro. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
165
     * @return void
166
     */
167
    public function extend($name, $provider)
168
    {
169
        $this->providers[$name] = $provider;
170
    }
171
172
    /**
173
     * Set token provider
174
     *
175
     * @param UniqueProcessIdentifier $tokenProvider
176
     */
177
    public function setTokenProvider(TokenProvider $tokenProvider)
178
    {
179
        $this->tokenProvider = $tokenProvider;
0 ignored issues
show
Documentation Bug introduced by
It seems like $tokenProvider of type object<Understand\Unders...Laravel5\TokenProvider> is incompatible with the declared type object<Understand\Unders...niqueProcessIdentifier> of property $tokenProvider.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
180
    }
181
182
    /**
183
     * Return resolved field-value array
184
     *
185
     * @param array $callbacks
186
     * @param array $log
187
     * @return array
188
     */
189
    public function resolveValues(array $callbacks, array $log)
190
    {
191
        $data = [];
192
193
        foreach ($callbacks as $fieldName => $caller)
194
        {
195
            if (!is_array($caller))
196
            {
197
                $caller = [$caller];
198
            }
199
200
            $callback = Arr::get($caller, 0);
201
            $args = [$log];
202
203
            $value = call_user_func_array($callback, $args);
204
205
            $data[$fieldName] = $value;
206
        }
207
208
        return $data;
209
    }
210
211
    /**
212
     * Handle class calls
213
     *
214
     * @param string $name
215
     * @param  mixed $params
216
     * @return mixed
217
     *
218
     * @throws \BadMethodCallException
219
     */
220
    public function __call($name, $params)
221
    {
222
        if (isset($this->providers[$name]))
223
        {
224
            return call_user_func_array($this->providers[$name], $params);
225
        }
226
227
        throw new \BadMethodCallException("Method {$name} does not exist.");
228
    }
229
230
    /**
231
     * Return hashed version of session id
232
     *
233
     * @return string
234
     */
235
    protected function getSessionId()
236
    {
237
        if ( ! $this->session)
238
        {
239
            return null;
240
        }
241
242
        $sessionId = $this->session->getId();
243
244
        // by default we provide only hashed version of session id
245
        $hashed = sha1($sessionId);
246
247
        return $hashed;
248
    }
249
250
    /**
251
     * @return string
252
     */
253
    protected function getLaravelVersion()
254
    {
255
        return Application::VERSION;
256
    }
257
258
    /**
259
     * @return array
260
     */
261
    protected function getSqlQueries()
262
    {
263
        if ( ! $this->dataCollector)
264
        {
265
            return [];
266
        }
267
268
        $queries = $this->dataCollector->getByKey('sql_queries');
269
270
        if ( ! $queries)
271
        {
272
            return null;
273
        }
274
275
        $bindingsEnabled = $this->app['config']->get('understand-laravel.sql_bindings');
276
277
        foreach($queries as $key => $queryArray)
278
        {
279
            if ($bindingsEnabled)
280
            {
281
                $queries[$key]['query'] = $this->mergeBindings($queryArray);
282
            }
283
284
            unset($queries[$key]['bindings']);
285
        }
286
287
        return $queries;
288
    }
289
290
    /**
291
     * @param $queryArray
292
     * @return mixed
293
     */
294
    protected function mergeBindings($queryArray)
295
    {
296
        $sqlQuery = $queryArray['query'];
297
        $placeholder = '?';
298
299
        foreach($queryArray['bindings'] as $key => $value)
300
        {
301
            try
302
            {
303
                if ($value instanceof \DateTimeInterface)
304
                {
305
                    $binding = $value->format('Y-m-d H:i:s');
306
                }
307
                elseif (is_bool($value))
308
                {
309
                    $binding = (int) $value;
310
                }
311
                else
312
                {
313
                    $binding = (string)$value;
314
                }
315
            }
316
            catch (\Exception $e)
317
            {
318
                $binding = '[handler error]';
319
            }
320
321
            $position = strpos($sqlQuery, $placeholder);
322
323
            if ($position !== false)
324
            {
325
                $sqlQuery = substr_replace($sqlQuery, $binding, $position, strlen($placeholder));
326
            }
327
        }
328
329
        return $sqlQuery;
330
}
331
332
    /**
333
     * Return current route name
334
     *
335
     * @return string
336
     */
337
    protected function getRouteName()
338
    {
339
        if ( ! $this->router)
340
        {
341
            return null;
342
        }
343
344
        return $this->router->getCurrentRoute()->getName();
345
    }
346
347
    /**
348
     * Return current url
349
     *
350
     * @return string
351
     */
352
    protected function getUrl()
353
    {
354
        if ( ! $this->request)
355
        {
356
            return null;
357
        }
358
359
        $url = $this->request->path();
360
361
        if ( ! Str::startsWith($url, '/'))
362
        {
363
            $url = '/' . $url;
364
        }
365
366
        return $url;
367
    }
368
369
    /**
370
     * @return array|null
371
     */
372
    protected function getQueryStringArray()
373
    {
374
        $enabled = $this->app['config']->get('understand-laravel.query_string_enabled');
375
376
        if ( ! $enabled)
377
        {
378
            return null;
379
        }
380
381
        if ( ! $this->request->query instanceof \IteratorAggregate)
382
        {
383
            return null;
384
        }
385
386
        $queryString = [];
387
388
        foreach($this->request->query as $key => $value)
389
        {
390
            try
391
            {
392
                $queryString[$key] = $this->parseRequestFieldValue($key, $value);
393
            }
394
            catch (\Exception $e)
395
            {
396
                $queryString[$key] = '[handler error]';
397
            }
398
        }
399
400
        return $queryString;
401
    }
402
403
    /**
404
     * @return array|null
405
     */
406
    protected function getPostDataArray()
407
    {
408
        $enabled = $this->app['config']->get('understand-laravel.post_data_enabled');
409
410
        if ( ! $enabled)
411
        {
412
            return null;
413
        }
414
415
        // Laravel merge and update the symphony `request` property that should hold only POST data
416
        // by checking if the request method is not GET or HEAD we can decide whether to use it or not
417
        if (in_array($this->request->getRealMethod(), ['GET', 'HEAD']))
418
        {
419
            return null;
420
        }
421
422
        $source = null;
0 ignored issues
show
Unused Code introduced by
$source is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
423
424
        if (method_exists($this->request, 'json') && method_exists($this->request, 'isJson') && $this->request->isJson())
425
        {
426
            $source = $this->request->json();
427
        }
428
        else if ($this->request->request instanceof \IteratorAggregate)
429
        {
430
            $source = $this->request->request;
431
        }
432
        else
433
        {
434
            return;
435
        }
436
437
        if ( ! $source)
438
        {
439
            return;
440
        }
441
442
        $postData = [];
443
444
        foreach($source as $key => $value)
445
        {
446
            try
447
            {
448
                $postData[$key] = $this->parseRequestFieldValue($key, $value);
449
            }
450
            catch (\Exception $e)
451
            {
452
                $postData[$key] = '[handler error]';
453
            }
454
        }
455
456
        return $postData;
457
    }
458
459
    /**
460
     * @param $key
461
     * @param $value
462
     * @return mixed|string
463
     */
464
    protected function parseRequestFieldValue($key, $value)
465
    {
466
        $hiddenFields = $this->app['config']->get('understand-laravel.hidden_fields', []);
467
468
        if (in_array($key, $hiddenFields))
469
        {
470
            return '[value hidden]';
471
        }
472
473
        if (is_scalar($value))
474
        {
475
            return $value;
476
        }
477
478
        if (is_array($value))
479
        {
480
            return print_r($value, true);
481
        }
482
483
        if (is_object($value))
484
        {
485
            return get_class($value);
486
        }
487
488
        return (string)$value;
489
    }
490
491
    /**
492
     * Return request method
493
     *
494
     * @return string
495
     */
496
    protected function getRequestMethod()
497
    {
498
        if ( ! $this->request)
499
        {
500
            return null;
501
        }
502
503
        return $this->request->method();
504
    }
505
506
    /**
507
     * Return server ip address
508
     *
509
     * @return string
510
     */
511
    protected function getServerIp()
512
    {
513
        if ( ! $this->request)
514
        {
515
            return null;
516
        }
517
518
        return $this->request->server->get('SERVER_ADDR');
519
    }
520
521
    /**
522
     * Return client ip
523
     *
524
     * @return string
525
     */
526
    protected function getClientIp()
527
    {
528
        if ( ! $this->request)
529
        {
530
            return null;
531
        }
532
533
        return $this->request->getClientIp();
534
    }
535
536
    /**
537
     * Return client user agent string
538
     *
539
     * @return string
540
     */
541
    protected function getClientUserAgent()
542
    {
543
        if ( ! $this->request)
544
        {
545
            return null;
546
        }
547
548
        return $this->request->server->get('HTTP_USER_AGENT');
549
    }
550
551
    /**
552
     * Return current enviroment
553
     *
554
     * @return string
555
     */
556
    protected function getEnvironment()
557
    {
558
        return $this->environment;
559
    }
560
561
    /**
562
     * Retrive parameter from current session
563
     *
564
     * @param string $key
565
     * @return string
566
     */
567
    protected function getFromSession($key)
568
    {
569
        if ( ! $this->session)
570
        {
571
            return null;
572
        }
573
574
        return $this->session->get($key);
575
    }
576
577
    /**
578
     * Return group id
579
     *
580
     * @param array $log
581
     * @return string
582
     */
583
    protected function getGroupId(array $log)
584
    {
585
        $parts = [];
586
587
        foreach(['class', 'file', 'line', 'code'] as $field)
588
        {
589
            // only include `code` if it's not null value
590
            // the `code` attribute of the exception object is useful to differentiate SQL and other exceptions
591
            //
592
            // https://www.php.net/manual/en/exception.getcode.php
593
            // https://www.php.net/manual/en/pdo.errorinfo.php
594
            // https://docs.oracle.com/cd/E15817_01/appdev.111/b31228/appd.htm
595
            if ($field == 'code' && empty($log[$field]))
596
            {
597
                continue;
598
            }
599
600
            $parts[] = isset($log[$field]) ? (string)$log[$field] : null;
601
        }
602
603
        return sha1(implode('#', $parts));
604
    }
605
606
    /**
607
     * Return current active user id
608
     *
609
     * @return int
610
     */
611
    protected function getUserId()
612
    {
613
        try
614
        {
615
            if (class_exists('\Auth') && ($userId = \Auth::id()))
616
            {
617
                return $userId;
618
            }
619
        }
620
        catch (\Throwable $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class Throwable 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...
621
        {}
622
        catch (\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
623
        {}
624
625
        try
626
        {
627
            if (class_exists('\Sentinel') && ($user = \Sentinel::getUser()))
628
            {
629
                return $user->id;
630
            }
631
        }
632
        catch (\Throwable $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class Throwable 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...
633
        {}
634
        catch (\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
635
        {}
636
637
        try
638
        {
639
            if (class_exists('\Sentry') && ($user = \Sentry::getUser()))
640
            {
641
                return $user->id;
642
            }
643
        }
644
        catch (\Throwable $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class Throwable 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...
645
        {}
646
        catch (\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
647
        {}
648
    }
649
650
    /**
651
     * @return string
652
     */
653
    protected function getArtisanCommandName()
654
    {
655
        if ($this->app->runningInConsole() && isset($_SERVER['argv']))
656
        {
657
            return implode(' ', $_SERVER['argv']);
658
        }
659
    }
660
661
    /**
662
     * @return bool
663
     */
664
    protected function getRunningInConsole()
665
    {
666
        return $this->app->runningInConsole();
667
    }
668
669
    /**
670
     * @return float
671
     */
672
    protected function getLoggerVersion()
673
    {
674
        return Logger::VERSION;
675
    }
676
677
    /**
678
     * Return process identifier token
679
     *
680
     * @return string
681
     */
682
    protected function getProcessIdentifier()
683
    {
684
        return $this->tokenProvider->getToken();
685
    }
686
687
}
688