8889841cPK­¡[†+   Profile.phpnu„[µü¤ * * 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 Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; /** * Profile. * * @author Fabien Potencier */ class Profile { private $token; /** * @var DataCollectorInterface[] */ private $collectors = []; private $ip; private $method; private $url; private $time; private $statusCode; /** * @var Profile */ private $parent; /** * @var Profile[] */ private $children = []; public function __construct(string $token) { $this->token = $token; } public function setToken(string $token) { $this->token = $token; } /** * Gets the token. * * @return string */ public function getToken() { return $this->token; } /** * Sets the parent token. */ public function setParent(self $parent) { $this->parent = $parent; } /** * Returns the parent profile. * * @return self|null */ public function getParent() { return $this->parent; } /** * Returns the parent token. * * @return string|null */ public function getParentToken() { return $this->parent ? $this->parent->getToken() : null; } /** * Returns the IP. * * @return string|null */ public function getIp() { return $this->ip; } public function setIp(?string $ip) { $this->ip = $ip; } /** * Returns the request method. * * @return string|null */ public function getMethod() { return $this->method; } public function setMethod(string $method) { $this->method = $method; } /** * Returns the URL. * * @return string|null */ public function getUrl() { return $this->url; } public function setUrl(?string $url) { $this->url = $url; } /** * @return int */ public function getTime() { return $this->time ?? 0; } public function setTime(int $time) { $this->time = $time; } public function setStatusCode(int $statusCode) { $this->statusCode = $statusCode; } /** * @return int|null */ public function getStatusCode() { return $this->statusCode; } /** * Finds children profilers. * * @return self[] */ public function getChildren() { return $this->children; } /** * Sets children profiler. * * @param Profile[] $children */ public function setChildren(array $children) { $this->children = []; foreach ($children as $child) { $this->addChild($child); } } /** * Adds the child token. */ public function addChild(self $child) { $this->children[] = $child; $child->setParent($this); } public function getChildByToken(string $token): ?self { foreach ($this->children as $child) { if ($token === $child->getToken()) { return $child; } } return null; } /** * Gets a Collector by name. * * @return DataCollectorInterface * * @throws \InvalidArgumentException if the collector does not exist */ public function getCollector(string $name) { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); } return $this->collectors[$name]; } /** * Gets the Collectors associated with this profile. * * @return DataCollectorInterface[] */ public function getCollectors() { return $this->collectors; } /** * Sets the Collectors associated with this profile. * * @param DataCollectorInterface[] $collectors */ public function setCollectors(array $collectors) { $this->collectors = []; foreach ($collectors as $collector) { $this->addCollector($collector); } } /** * Adds a Collector. */ public function addCollector(DataCollectorInterface $collector) { $this->collectors[$collector->getName()] = $collector; } /** * @return bool */ public function hasCollector(string $name) { return isset($this->collectors[$name]); } /** * @return array */ public function __sleep() { return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode']; } } PK­¡[/ì! ÂÂProfilerStorageInterface.phpnu„[µü¤ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\HttpKernel\Profiler; /** * ProfilerStorageInterface. * * This interface exists for historical reasons. The only supported * implementation is FileProfilerStorage. * * As the profiler must only be used on non-production servers, the file storage * is more than enough and no other implementations will ever be supported. * * @internal * * @author Fabien Potencier */ interface ProfilerStorageInterface { /** * Finds profiler tokens for the given criteria. * * @param int|null $limit The maximum number of tokens to return * @param int|null $start The start date to search from * @param int|null $end The end date to search to */ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null): array; /** * Reads data associated with the given token. * * The method returns false if the token does not exist in the storage. */ public function read(string $token): ?Profile; /** * Saves a Profile. */ public function write(Profile $profile): bool; /** * Purges all data from the database. */ public function purge(); } PK­¡[û %##FileProfilerStorage.phpnu„[µü¤ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\HttpKernel\Profiler; /** * Storage for profiler using files. * * @author Alexandre Salomé */ class FileProfilerStorage implements ProfilerStorageInterface { /** * Folder where profiler data are stored. * * @var string */ private $folder; /** * Constructs the file storage using a "dsn-like" path. * * Example : "file:/path/to/the/storage/folder" * * @throws \RuntimeException */ public function __construct(string $dsn) { if (!str_starts_with($dsn, 'file:')) { throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); } $this->folder = substr($dsn, 5); if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); } } /** * {@inheritdoc} */ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null): array { $file = $this->getIndexFilename(); if (!file_exists($file)) { return []; } $file = fopen($file, 'r'); fseek($file, 0, \SEEK_END); $result = []; while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { $values = str_getcsv($line); [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values; $csvTime = (int) $csvTime; if ($ip && !str_contains($csvIp, $ip) || $url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) { continue; } if (!empty($start) && $csvTime < $start) { continue; } if (!empty($end) && $csvTime > $end) { continue; } $result[$csvToken] = [ 'token' => $csvToken, 'ip' => $csvIp, 'method' => $csvMethod, 'url' => $csvUrl, 'time' => $csvTime, 'parent' => $csvParent, 'status_code' => $csvStatusCode, ]; } fclose($file); return array_values($result); } /** * {@inheritdoc} */ public function purge() { $flags = \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); foreach ($iterator as $file) { if (is_file($file)) { unlink($file); } else { rmdir($file); } } } /** * {@inheritdoc} */ public function read(string $token): ?Profile { if (!$token || !file_exists($file = $this->getFilename($token))) { return null; } if (\function_exists('gzcompress')) { $file = 'compress.zlib://'.$file; } return $this->createProfileFromData($token, unserialize(file_get_contents($file))); } /** * {@inheritdoc} * * @throws \RuntimeException */ public function write(Profile $profile): bool { $file = $this->getFilename($profile->getToken()); $profileIndexed = is_file($file); if (!$profileIndexed) { // Create directory $dir = \dirname($file); if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); } } $profileToken = $profile->getToken(); // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; $childrenToken = array_filter(array_map(function (Profile $p) use ($profileToken) { return $profileToken !== $p->getToken() ? $p->getToken() : null; }, $profile->getChildren())); // Store profile $data = [ 'token' => $profileToken, 'parent' => $parentToken, 'children' => $childrenToken, 'data' => $profile->getCollectors(), 'ip' => $profile->getIp(), 'method' => $profile->getMethod(), 'url' => $profile->getUrl(), 'time' => $profile->getTime(), 'status_code' => $profile->getStatusCode(), ]; $context = stream_context_create(); if (\function_exists('gzcompress')) { $file = 'compress.zlib://'.$file; stream_context_set_option($context, 'zlib', 'level', 3); } if (false === file_put_contents($file, serialize($data), 0, $context)) { return false; } if (!$profileIndexed) { // Add to index if (false === $file = fopen($this->getIndexFilename(), 'a')) { return false; } fputcsv($file, [ $profile->getToken(), $profile->getIp(), $profile->getMethod(), $profile->getUrl(), $profile->getTime(), $profile->getParentToken(), $profile->getStatusCode(), ]); fclose($file); } return true; } /** * Gets filename to store data, associated to the token. * * @return string */ protected function getFilename(string $token) { // Uses 4 last characters, because first are mostly the same. $folderA = substr($token, -2, 2); $folderB = substr($token, -4, 2); return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; } /** * Gets the index filename. * * @return string */ protected function getIndexFilename() { return $this->folder.'/index.csv'; } /** * Reads a line in the file, backward. * * This function automatically skips the empty lines and do not include the line return in result value. * * @param resource $file The file resource, with the pointer placed at the end of the line to read * * @return mixed */ protected function readLineFromFile($file) { $line = ''; $position = ftell($file); if (0 === $position) { return null; } while (true) { $chunkSize = min($position, 1024); $position -= $chunkSize; fseek($file, $position); if (0 === $chunkSize) { // bof reached break; } $buffer = fread($file, $chunkSize); if (false === ($upTo = strrpos($buffer, "\n"))) { $line = $buffer.$line; continue; } $position += $upTo; $line = substr($buffer, $upTo + 1).$line; fseek($file, max(0, $position), \SEEK_SET); if ('' !== $line) { break; } } return '' === $line ? null : $line; } protected function createProfileFromData(string $token, array $data, Profile $parent = null) { $profile = new Profile($token); $profile->setIp($data['ip']); $profile->setMethod($data['method']); $profile->setUrl($data['url']); $profile->setTime($data['time']); $profile->setStatusCode($data['status_code']); $profile->setCollectors($data['data']); if (!$parent && $data['parent']) { $parent = $this->read($data['parent']); } if ($parent) { $profile->setParent($parent); } foreach ($data['children'] as $token) { if (!$token || !file_exists($file = $this->getFilename($token))) { continue; } if (\function_exists('gzcompress')) { $file = 'compress.zlib://'.$file; } $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); } return $profile; } } PK­¡[6i¤ÉÉ Profiler.phpnu„[µü¤ * * 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 */ 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(); } } PK­¡[†+   Profile.phpnu„[µü¤PK­¡[/ì! ÂÂÛProfilerStorageInterface.phpnu„[µü¤PK­¡[û %##éFileProfilerStorage.phpnu„[µü¤PK­¡[6i¤ÉÉ 1=Profiler.phpnu„[µü¤PKJ6W