Profiler::runGroup()   B
last analyzed

Complexity

Conditions 6
Paths 18

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6.288

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 31
ccs 20
cts 25
cp 0.8
rs 8.439
cc 6
eloc 19
nc 18
nop 1
crap 6.288
1
<?php
2
3
namespace Ndrx\Profiler;
4
5
use Monolog\Handler\AbstractProcessingHandler;
6
use Ndrx\Profiler\Components\Timeline;
7
use Ndrx\Profiler\Collectors\Data\Request;
8
use Ndrx\Profiler\Context\Cli;
9
use Ndrx\Profiler\Context\Contracts\ContextInterface;
10
use Ndrx\Profiler\Context\Http;
11
use Ndrx\Profiler\Collectors\Contracts\CollectorInterface;
12
use Ndrx\Profiler\Collectors\Contracts\FinalCollectorInterface;
13
use Ndrx\Profiler\Collectors\Contracts\StartCollectorInterface;
14
use Ndrx\Profiler\Collectors\Contracts\StreamCollectorInterface;
15
use Ndrx\Profiler\DataSources\Contracts\DataSourceInterface;
16
use Ndrx\Profiler\Events\DispatcherAwareInterface;
17
use Ndrx\Profiler\Events\HttpFoundationResponse;
18
use Psr\Log\LoggerAwareTrait;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\HttpFoundation\Response;
21
use \Ndrx\Profiler\Collectors\Data\Response as ResponseCollector;
22
23
/**
24
 * Class Profiler
25
 *
26
 *
27
 * @method void start($key, $label, $data = null, $timetamp = null) Start a timeline event
28
 * @method void stop($key, $timestamp = null) Stop a timeline event
29
 * @method mixed monitor($label, \Closure $closure) Monitor a function
30
 *
31
 * @method null emergency($message, array $context = array())
32
 * @method null alert($message, array $context = array())
33
 * @method null critical($message, array $context = array())
34
 * @method null error($message, array $context = array())
35
 * @method null warning($message, array $context = array())
36
 * @method null notice($message, array $context = array())
37
 * @method null info($message, array $context = array())
38
 * @method null debug($message, array $context = array())
39
 * @method null log($level, $message, array $context = array())
40
 *
41
 * @package Ndrx\Profiler
42
 */
43
class Profiler implements ProfilerInterface
44
{
45
    use LoggerAwareTrait;
46
47
    /**
48
     * @var ContextInterface
49
     */
50
    protected $context;
51
52
    /**
53
     * @var DataSourceInterface
54
     */
55
    protected $datasource;
56
57
    /**
58
     * @var Timeline
59
     */
60
    protected $timeline;
61
62
    /**
63
     * @var array
64
     */
65
    protected $collectors;
66
67
    /**
68
     * @var string
69
     */
70
    public static $environment;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $terminated = false;
76
77
    /**
78
     *
79
     */
80 126
    public function __construct()
81
    {
82 126
        $this->collectors = [
83 126
            'initial' => [],
84 126
            'final' => [],
85 126
            'stream' => []
86 126
        ];
87
88
        // set the good context
89 126
        if (self::detectEnv() === 'cli') {
90 122
            $this->context = new Cli();
91 122
        } else {
92
            // cli-server is considered as a http server
93 4
            $this->context = new Http();
94
        }
95
96 126
        $this->context->initiate();
97 126
    }
98
99
    /**
100
     * Try to terminate the profiler at the destruction if it was not done manually
101
     */
102 98
    public function __destruct()
103
    {
104 98
        $this->terminate();
105 98
    }
106
107
    /**
108
     * @return string
109
     */
110 126
    public static function detectEnv()
111
    {
112 126
        if (self::$environment !== null) {
113 100
            return self::$environment;
114
        }
115
116 26
        return php_sapi_name();
117
    }
118
119
    /**
120
     * Add a data collector to the profiler
121
     * @param CollectorInterface $collector
122
     * @throws \RuntimeException
123
     */
124 124
    public function registerCollector(CollectorInterface $collector)
125
    {
126 124
        if ($collector instanceof StartCollectorInterface) {
127 116
            $this->collectors['initial'][$collector->getPath()] = $collector;
128 124
        } elseif ($collector instanceof FinalCollectorInterface) {
129 106
            $this->collectors['final'][$collector->getPath()] = $collector;
130 114
        } elseif ($collector instanceof StreamCollectorInterface) {
131 108
            $this->collectors['stream'][$collector->getPath()] = $collector;
132 108
        } else {
133 2
            throw new \RuntimeException('Collector must be implementation of StartCollectorInterface, '
134 2
                . 'FinalCollectorInterface or StreamCollectorInterface Path=' . $collector->getPath());
135
        }
136 122
    }
137
138
    /**
139
     * Register multiple collector to the profiler
140
     * @param array $collectors
141
     * @throws \RuntimeException
142
     */
143 2
    public function registerCollectors(array $collectors)
144
    {
145 2
        foreach ($collectors as $collector) {
146 2
            $this->registerCollector($collector);
147 2
        }
148 2
    }
149
150
    /**
151
     * Build and register collector class
152
     * @param $className
153
     * @throws \RuntimeException
154
     */
155 110
    public function registerCollectorClass($className)
156
    {
157
        /** @var CollectorInterface $collector */
158 110
        $collector = new $className($this->context->getProcess(), $this->datasource);
159 110
        $this->registerCollector($collector);
160 108
    }
161
162
    /**
163
     * Build and register collector classes
164
     * @param array $collectors
165
     * @throws \RuntimeException
166
     */
167 106
    public function registerCollectorClasses(array $collectors)
168
    {
169 106
        foreach ($collectors as $collector) {
170 106
            $this->registerCollectorClass($collector);
171 106
        }
172 106
    }
173
174
    /**
175
     * Run start collector at the profiler creation
176
     */
177 12
    public function initiate()
178
    {
179 12
        $this->runGroup('initial');
180 12
    }
181
182
    /**
183
     * Run final collectors, just before the profiler was destroy
184
     */
185 102
    public function terminate()
186
    {
187 102
        if ($this->terminated) {
188
            return;
189
        }
190 102
        $this->runGroup('final');
191 102
    }
192
193 104
    public function runGroup($name)
194
    {
195
        /** @var CollectorInterface $collector */
196 104
        foreach ($this->collectors[$name] as $collector) {
197
            try {
198 96
                $collector->resolve();
199 96
                if ($collector instanceof Request) {
200 6
                    $data = $collector->getData();
201
202 6
                    $this->datasource->saveSummary($this->context->getProcess(), [
203 6
                        'id' => $this->context->getProcess()->getId(),
204 6
                        'method' => $data['method'],
205 6
                        'uri' => $data['uri'],
206 6
                        'time' => time()
207 6
                    ]);
208 96
                } elseif ($collector instanceof ResponseCollector) {
209 4
                    $this->datasource->saveSummary($this->context->getProcess(), [
210 4
                        'status' => $collector->getStatusCode()
211 4
                    ]);
212 4
                }
213
214 96
                $collector->persist();
215 96
            } catch (\Exception $e) {
216
                if (!$this->logger === null) {
217
                    $this->emergency($e->getMessage(), [
218
                        'collector', get_class($collector)
219
                    ]);
220
                }
221
            }
222 104
        }
223 104
    }
224
225
    /**
226
     * @param DataSourceInterface $datasource
227
     */
228 126
    public function setDataSource($datasource)
229
    {
230 126
        $this->datasource = $datasource;
231 126
    }
232
233
    /**
234
     * @return DataSourceInterface
235
     */
236 28
    public function getDatasource()
237
    {
238 28
        return $this->datasource;
239
    }
240
241
    /**
242
     * @param $id
243
     * @return mixed
244
     */
245 18
    public function getProfile($id)
246
    {
247 18
        return (new JsonPatch())->compile($this->datasource->getProcess($id));
248
    }
249
250
    /**
251
     * @return ContextInterface
252
     */
253 120
    public function getContext()
254
    {
255 120
        return $this->context;
256
    }
257
258
    /**
259
     * @return array
260
     */
261 24
    public function getCollectors()
262
    {
263 24
        return $this->collectors;
264
    }
265
266
    /**
267
     * @param LoggerInterface $logger
268
     * @return $this
269
     */
270 102
    public function setLogger(LoggerInterface $logger)
271
    {
272 102
        if ($logger instanceof DispatcherAwareInterface) {
273 102
            $logger->setDispatcher($this->context->getProcess()->getDispatcher());
274 102
        }
275
276 102
        $this->logger = $logger;
277
278 102
        return $this;
279
    }
280
281
    /**
282
     * @return LoggerInterface|AbstractProcessingHandler
283
     */
284 4
    public function getLogger()
285
    {
286 4
        return $this->logger;
287
    }
288
289
    /**
290
     * @param $name
291
     * @param $arguments
292
     * @return mixed
293
     * @throws \BadMethodCallException
294
     */
295 6
    public function __call($name, $arguments)
296
    {
297 6
        if ($this->isMethodAvailable($this->logger, $name)) {
298 4
            return call_user_func_array(array($this->logger, $name), $arguments);
299
        }
300
301 2
        if ($this->isMethodAvailable($this->timeline, $name)) {
302 2
            return call_user_func_array(array($this->timeline, $name), $arguments);
303
        }
304
305
        throw new \BadMethodCallException('Method ' . $name . ' does not exist or is not callable');
306
    }
307
308
    /**
309
     * @param $object
310
     * @param $methode
311
     * @return bool
312
     */
313 6
    protected function isMethodAvailable($object, $methode)
314
    {
315 6
        return $object !== null && method_exists($object, $methode);
316
    }
317
318
    /**
319
     * @param Timeline $timeline
320
     */
321 100
    public function setTimeline($timeline)
322
    {
323 100
        $this->timeline = $timeline;
324 100
    }
325
326
    /**
327
     * @param Response $response
328
     */
329
    public function setResponse(Response $response)
330
    {
331
        $this->getContext()->getProcess()->getDispatcher()->dispatch(HttpFoundationResponse::EVENT_NAME, new HttpFoundationResponse($response));
332
    }
333
}
334