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