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