Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\HttpKernel\Profiler; Chris@0: Chris@17: use Psr\Log\LoggerInterface; Chris@0: use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; Chris@0: use Symfony\Component\HttpFoundation\Request; Chris@0: use Symfony\Component\HttpFoundation\Response; Chris@0: use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; Chris@0: use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; Chris@0: Chris@0: /** Chris@0: * Profiler. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class Profiler Chris@0: { Chris@0: private $storage; Chris@0: Chris@0: /** Chris@0: * @var DataCollectorInterface[] Chris@0: */ Chris@17: private $collectors = []; Chris@0: Chris@0: private $logger; Chris@14: private $initiallyEnabled = true; Chris@0: private $enabled = true; Chris@0: Chris@0: /** Chris@16: * @param bool $enable The initial enabled state Chris@0: */ Chris@14: public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, $enable = true) Chris@0: { Chris@0: $this->storage = $storage; Chris@0: $this->logger = $logger; Chris@14: $this->initiallyEnabled = $this->enabled = (bool) $enable; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Disables the profiler. Chris@0: */ Chris@0: public function disable() Chris@0: { Chris@0: $this->enabled = false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enables the profiler. Chris@0: */ Chris@0: public function enable() Chris@0: { Chris@0: $this->enabled = true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads the Profile for the given Response. Chris@0: * Chris@0: * @return Profile|false A Profile instance Chris@0: */ Chris@0: public function loadProfileFromResponse(Response $response) Chris@0: { Chris@0: if (!$token = $response->headers->get('X-Debug-Token')) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return $this->loadProfile($token); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads the Profile for the given token. Chris@0: * Chris@0: * @param string $token A token Chris@0: * Chris@0: * @return Profile A Profile instance Chris@0: */ Chris@0: public function loadProfile($token) Chris@0: { Chris@0: return $this->storage->read($token); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Saves a Profile. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function saveProfile(Profile $profile) Chris@0: { Chris@0: // late collect Chris@0: foreach ($profile->getCollectors() as $collector) { Chris@0: if ($collector instanceof LateDataCollectorInterface) { Chris@0: $collector->lateCollect(); Chris@0: } Chris@0: } Chris@0: Chris@0: if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { Chris@17: $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]); Chris@0: } Chris@0: Chris@0: return $ret; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Purges all data from the storage. Chris@0: */ Chris@0: public function purge() Chris@0: { Chris@0: $this->storage->purge(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finds profiler tokens for the given criteria. Chris@0: * Chris@0: * @param string $ip The IP Chris@0: * @param string $url The URL Chris@0: * @param string $limit The maximum number of tokens to return Chris@0: * @param string $method The request method Chris@0: * @param string $start The start date to search from Chris@0: * @param string $end The end date to search to Chris@0: * @param string $statusCode The request status code Chris@0: * Chris@0: * @return array An array of tokens Chris@0: * Chris@0: * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats Chris@0: */ Chris@0: public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null) Chris@0: { Chris@0: return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Collects data for the given Response. Chris@0: * Chris@0: * @return Profile|null A Profile instance or null if the profiler is disabled Chris@0: */ Chris@0: public function collect(Request $request, Response $response, \Exception $exception = null) Chris@0: { Chris@0: if (false === $this->enabled) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $profile = new Profile(substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); Chris@0: $profile->setTime(time()); Chris@0: $profile->setUrl($request->getUri()); Chris@0: $profile->setMethod($request->getMethod()); Chris@0: $profile->setStatusCode($response->getStatusCode()); Chris@0: try { Chris@0: $profile->setIp($request->getClientIp()); Chris@0: } catch (ConflictingHeadersException $e) { Chris@0: $profile->setIp('Unknown'); Chris@0: } Chris@0: Chris@0: $response->headers->set('X-Debug-Token', $profile->getToken()); Chris@0: Chris@0: foreach ($this->collectors as $collector) { Chris@0: $collector->collect($request, $response, $exception); Chris@0: Chris@0: // we need to clone for sub-requests Chris@0: $profile->addCollector(clone $collector); Chris@0: } Chris@0: Chris@0: return $profile; Chris@0: } Chris@0: Chris@14: public function reset() Chris@14: { Chris@14: foreach ($this->collectors as $collector) { Chris@14: if (!method_exists($collector, 'reset')) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: $collector->reset(); Chris@14: } Chris@14: $this->enabled = $this->initiallyEnabled; Chris@14: } Chris@14: Chris@0: /** Chris@0: * Gets the Collectors associated with this profiler. Chris@0: * Chris@0: * @return array An array of collectors Chris@0: */ Chris@0: public function all() Chris@0: { Chris@0: return $this->collectors; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the Collectors associated with this profiler. Chris@0: * Chris@0: * @param DataCollectorInterface[] $collectors An array of collectors Chris@0: */ Chris@17: public function set(array $collectors = []) Chris@0: { Chris@17: $this->collectors = []; Chris@0: foreach ($collectors as $collector) { Chris@0: $this->add($collector); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a Collector. Chris@0: */ Chris@0: public function add(DataCollectorInterface $collector) Chris@0: { Chris@14: if (!method_exists($collector, 'reset')) { Chris@14: @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: } Chris@14: Chris@0: $this->collectors[$collector->getName()] = $collector; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns true if a Collector for the given name exists. Chris@0: * Chris@0: * @param string $name A collector name Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function has($name) Chris@0: { Chris@0: return isset($this->collectors[$name]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a Collector by name. Chris@0: * Chris@0: * @param string $name A collector name Chris@0: * Chris@0: * @return DataCollectorInterface A DataCollectorInterface instance Chris@0: * Chris@0: * @throws \InvalidArgumentException if the collector does not exist Chris@0: */ Chris@0: public function get($name) Chris@0: { Chris@0: if (!isset($this->collectors[$name])) { Chris@0: throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); Chris@0: } Chris@0: Chris@0: return $this->collectors[$name]; Chris@0: } Chris@0: Chris@0: private function getTimestamp($value) Chris@0: { Chris@0: if (null === $value || '' == $value) { Chris@0: return; Chris@0: } Chris@0: Chris@0: try { Chris@0: $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); Chris@0: } catch (\Exception $e) { Chris@0: return; Chris@0: } Chris@0: Chris@0: return $value->getTimestamp(); Chris@0: } Chris@0: }