Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\HttpKernel\Profiler;
|
Chris@0
|
13
|
Chris@17
|
14 use Psr\Log\LoggerInterface;
|
Chris@0
|
15 use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
|
Chris@0
|
16 use Symfony\Component\HttpFoundation\Request;
|
Chris@0
|
17 use Symfony\Component\HttpFoundation\Response;
|
Chris@0
|
18 use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
|
Chris@0
|
19 use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
Chris@0
|
20
|
Chris@0
|
21 /**
|
Chris@0
|
22 * Profiler.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
25 */
|
Chris@0
|
26 class Profiler
|
Chris@0
|
27 {
|
Chris@0
|
28 private $storage;
|
Chris@0
|
29
|
Chris@0
|
30 /**
|
Chris@0
|
31 * @var DataCollectorInterface[]
|
Chris@0
|
32 */
|
Chris@17
|
33 private $collectors = [];
|
Chris@0
|
34
|
Chris@0
|
35 private $logger;
|
Chris@14
|
36 private $initiallyEnabled = true;
|
Chris@0
|
37 private $enabled = true;
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@16
|
40 * @param bool $enable The initial enabled state
|
Chris@0
|
41 */
|
Chris@14
|
42 public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, $enable = true)
|
Chris@0
|
43 {
|
Chris@0
|
44 $this->storage = $storage;
|
Chris@0
|
45 $this->logger = $logger;
|
Chris@14
|
46 $this->initiallyEnabled = $this->enabled = (bool) $enable;
|
Chris@0
|
47 }
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * Disables the profiler.
|
Chris@0
|
51 */
|
Chris@0
|
52 public function disable()
|
Chris@0
|
53 {
|
Chris@0
|
54 $this->enabled = false;
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 /**
|
Chris@0
|
58 * Enables the profiler.
|
Chris@0
|
59 */
|
Chris@0
|
60 public function enable()
|
Chris@0
|
61 {
|
Chris@0
|
62 $this->enabled = true;
|
Chris@0
|
63 }
|
Chris@0
|
64
|
Chris@0
|
65 /**
|
Chris@0
|
66 * Loads the Profile for the given Response.
|
Chris@0
|
67 *
|
Chris@0
|
68 * @return Profile|false A Profile instance
|
Chris@0
|
69 */
|
Chris@0
|
70 public function loadProfileFromResponse(Response $response)
|
Chris@0
|
71 {
|
Chris@0
|
72 if (!$token = $response->headers->get('X-Debug-Token')) {
|
Chris@0
|
73 return false;
|
Chris@0
|
74 }
|
Chris@0
|
75
|
Chris@0
|
76 return $this->loadProfile($token);
|
Chris@0
|
77 }
|
Chris@0
|
78
|
Chris@0
|
79 /**
|
Chris@0
|
80 * Loads the Profile for the given token.
|
Chris@0
|
81 *
|
Chris@0
|
82 * @param string $token A token
|
Chris@0
|
83 *
|
Chris@0
|
84 * @return Profile A Profile instance
|
Chris@0
|
85 */
|
Chris@0
|
86 public function loadProfile($token)
|
Chris@0
|
87 {
|
Chris@0
|
88 return $this->storage->read($token);
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 /**
|
Chris@0
|
92 * Saves a Profile.
|
Chris@0
|
93 *
|
Chris@0
|
94 * @return bool
|
Chris@0
|
95 */
|
Chris@0
|
96 public function saveProfile(Profile $profile)
|
Chris@0
|
97 {
|
Chris@0
|
98 // late collect
|
Chris@0
|
99 foreach ($profile->getCollectors() as $collector) {
|
Chris@0
|
100 if ($collector instanceof LateDataCollectorInterface) {
|
Chris@0
|
101 $collector->lateCollect();
|
Chris@0
|
102 }
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 if (!($ret = $this->storage->write($profile)) && null !== $this->logger) {
|
Chris@17
|
106 $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]);
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 return $ret;
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 /**
|
Chris@0
|
113 * Purges all data from the storage.
|
Chris@0
|
114 */
|
Chris@0
|
115 public function purge()
|
Chris@0
|
116 {
|
Chris@0
|
117 $this->storage->purge();
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 /**
|
Chris@0
|
121 * Finds profiler tokens for the given criteria.
|
Chris@0
|
122 *
|
Chris@0
|
123 * @param string $ip The IP
|
Chris@0
|
124 * @param string $url The URL
|
Chris@0
|
125 * @param string $limit The maximum number of tokens to return
|
Chris@0
|
126 * @param string $method The request method
|
Chris@0
|
127 * @param string $start The start date to search from
|
Chris@0
|
128 * @param string $end The end date to search to
|
Chris@0
|
129 * @param string $statusCode The request status code
|
Chris@0
|
130 *
|
Chris@0
|
131 * @return array An array of tokens
|
Chris@0
|
132 *
|
Chris@0
|
133 * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats
|
Chris@0
|
134 */
|
Chris@0
|
135 public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null)
|
Chris@0
|
136 {
|
Chris@0
|
137 return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode);
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 /**
|
Chris@0
|
141 * Collects data for the given Response.
|
Chris@0
|
142 *
|
Chris@0
|
143 * @return Profile|null A Profile instance or null if the profiler is disabled
|
Chris@0
|
144 */
|
Chris@0
|
145 public function collect(Request $request, Response $response, \Exception $exception = null)
|
Chris@0
|
146 {
|
Chris@0
|
147 if (false === $this->enabled) {
|
Chris@0
|
148 return;
|
Chris@0
|
149 }
|
Chris@0
|
150
|
Chris@0
|
151 $profile = new Profile(substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6));
|
Chris@0
|
152 $profile->setTime(time());
|
Chris@0
|
153 $profile->setUrl($request->getUri());
|
Chris@0
|
154 $profile->setMethod($request->getMethod());
|
Chris@0
|
155 $profile->setStatusCode($response->getStatusCode());
|
Chris@0
|
156 try {
|
Chris@0
|
157 $profile->setIp($request->getClientIp());
|
Chris@0
|
158 } catch (ConflictingHeadersException $e) {
|
Chris@0
|
159 $profile->setIp('Unknown');
|
Chris@0
|
160 }
|
Chris@0
|
161
|
Chris@0
|
162 $response->headers->set('X-Debug-Token', $profile->getToken());
|
Chris@0
|
163
|
Chris@0
|
164 foreach ($this->collectors as $collector) {
|
Chris@0
|
165 $collector->collect($request, $response, $exception);
|
Chris@0
|
166
|
Chris@0
|
167 // we need to clone for sub-requests
|
Chris@0
|
168 $profile->addCollector(clone $collector);
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 return $profile;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@14
|
174 public function reset()
|
Chris@14
|
175 {
|
Chris@14
|
176 foreach ($this->collectors as $collector) {
|
Chris@14
|
177 if (!method_exists($collector, 'reset')) {
|
Chris@14
|
178 continue;
|
Chris@14
|
179 }
|
Chris@14
|
180
|
Chris@14
|
181 $collector->reset();
|
Chris@14
|
182 }
|
Chris@14
|
183 $this->enabled = $this->initiallyEnabled;
|
Chris@14
|
184 }
|
Chris@14
|
185
|
Chris@0
|
186 /**
|
Chris@0
|
187 * Gets the Collectors associated with this profiler.
|
Chris@0
|
188 *
|
Chris@0
|
189 * @return array An array of collectors
|
Chris@0
|
190 */
|
Chris@0
|
191 public function all()
|
Chris@0
|
192 {
|
Chris@0
|
193 return $this->collectors;
|
Chris@0
|
194 }
|
Chris@0
|
195
|
Chris@0
|
196 /**
|
Chris@0
|
197 * Sets the Collectors associated with this profiler.
|
Chris@0
|
198 *
|
Chris@0
|
199 * @param DataCollectorInterface[] $collectors An array of collectors
|
Chris@0
|
200 */
|
Chris@17
|
201 public function set(array $collectors = [])
|
Chris@0
|
202 {
|
Chris@17
|
203 $this->collectors = [];
|
Chris@0
|
204 foreach ($collectors as $collector) {
|
Chris@0
|
205 $this->add($collector);
|
Chris@0
|
206 }
|
Chris@0
|
207 }
|
Chris@0
|
208
|
Chris@0
|
209 /**
|
Chris@0
|
210 * Adds a Collector.
|
Chris@0
|
211 */
|
Chris@0
|
212 public function add(DataCollectorInterface $collector)
|
Chris@0
|
213 {
|
Chris@14
|
214 if (!method_exists($collector, 'reset')) {
|
Chris@14
|
215 @trigger_error(sprintf('Implementing "%s" without the "reset()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', DataCollectorInterface::class, \get_class($collector)), E_USER_DEPRECATED);
|
Chris@14
|
216 }
|
Chris@14
|
217
|
Chris@0
|
218 $this->collectors[$collector->getName()] = $collector;
|
Chris@0
|
219 }
|
Chris@0
|
220
|
Chris@0
|
221 /**
|
Chris@0
|
222 * Returns true if a Collector for the given name exists.
|
Chris@0
|
223 *
|
Chris@0
|
224 * @param string $name A collector name
|
Chris@0
|
225 *
|
Chris@0
|
226 * @return bool
|
Chris@0
|
227 */
|
Chris@0
|
228 public function has($name)
|
Chris@0
|
229 {
|
Chris@0
|
230 return isset($this->collectors[$name]);
|
Chris@0
|
231 }
|
Chris@0
|
232
|
Chris@0
|
233 /**
|
Chris@0
|
234 * Gets a Collector by name.
|
Chris@0
|
235 *
|
Chris@0
|
236 * @param string $name A collector name
|
Chris@0
|
237 *
|
Chris@0
|
238 * @return DataCollectorInterface A DataCollectorInterface instance
|
Chris@0
|
239 *
|
Chris@0
|
240 * @throws \InvalidArgumentException if the collector does not exist
|
Chris@0
|
241 */
|
Chris@0
|
242 public function get($name)
|
Chris@0
|
243 {
|
Chris@0
|
244 if (!isset($this->collectors[$name])) {
|
Chris@0
|
245 throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name));
|
Chris@0
|
246 }
|
Chris@0
|
247
|
Chris@0
|
248 return $this->collectors[$name];
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 private function getTimestamp($value)
|
Chris@0
|
252 {
|
Chris@0
|
253 if (null === $value || '' == $value) {
|
Chris@0
|
254 return;
|
Chris@0
|
255 }
|
Chris@0
|
256
|
Chris@0
|
257 try {
|
Chris@0
|
258 $value = new \DateTime(is_numeric($value) ? '@'.$value : $value);
|
Chris@0
|
259 } catch (\Exception $e) {
|
Chris@0
|
260 return;
|
Chris@0
|
261 }
|
Chris@0
|
262
|
Chris@0
|
263 return $value->getTimestamp();
|
Chris@0
|
264 }
|
Chris@0
|
265 }
|