Skip to content

Instantly share code, notes, and snippets.

@ptondereau
Created December 18, 2025 08:26
Show Gist options
  • Select an option

  • Save ptondereau/5e2a7693fe4a3b05a12455e9e13f0712 to your computer and use it in GitHub Desktop.

Select an option

Save ptondereau/5e2a7693fe4a3b05a12455e9e13f0712 to your computer and use it in GitHub Desktop.
<?php
namespace App\Profiler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FrankenPhpProfiler
{
private ?\BlackfireProbe $probe = null;
private ?Request $request = null;
public function start(Request $request): bool
{
if (!method_exists(\BlackfireProbe::class, 'setAttribute')) {
return false;
}
if (!$request->headers->has('x-blackfire-query')) {
return false;
}
if ($this->probe) {
return false;
}
$this->probe = new \BlackfireProbe($request->headers->get('x-blackfire-query'));
$this->request = $request;
if (!$this->probe->enable()) {
\BlackfireProbe::setAttribute('profileTitle', $request->getUri());
$this->reset();
throw new \UnexpectedValueException('Cannot enable Blackfire profiler');
}
return true;
}
public function stop(Request $request, ?Response $response = null): bool
{
if (!class_exists(\BlackfireProbe::class, false)) {
return false;
}
if (!$this->probe) {
return false;
}
if (!$this->probe->isEnabled()) {
return false;
}
if ($this->request !== $request) {
return false;
}
$this->probe->close();
if ($response) {
$responseLine = $this->probe->getResponseLine();
if ($responseLine) {
list($probeHeaderName, $probeHeaderValue) = explode(':', $responseLine, 2);
$response->headers->set('x-' . strtolower($probeHeaderName), trim($probeHeaderValue));
}
}
$this->reset();
return true;
}
public function reset(): void
{
if ($this->probe && $this->probe->isEnabled()) {
$this->probe->close();
}
$this->probe = null;
$this->request = null;
}
}
<?php
namespace App\EventSubscriber;
use App\Profiler\FrankenPhpProfiler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class FrankenPhpProfilerSubscriber implements EventSubscriberInterface
{
private FrankenPhpProfiler $profiler;
public function __construct()
{
$this->profiler = new FrankenPhpProfiler();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 1024],
KernelEvents::RESPONSE => ['onKernelResponse', -1024],
KernelEvents::TERMINATE => ['onKernelTerminate', -1024],
KernelEvents::EXCEPTION => ['onKernelException', -1024],
];
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
if (!method_exists(\BlackfireProbe::class, 'setAttribute')) {
return;
}
try {
$this->profiler->start($event->getRequest());
} catch (\UnexpectedValueException $e) {
}
}
public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
if (!class_exists(\BlackfireProbe::class, false)) {
return;
}
try {
$response = $event->getResponse();
if (method_exists(\BlackfireProbe::class, 'setAttribute')) {
\BlackfireProbe::setAttribute('http.status_code', $response->getStatusCode());
}
$this->profiler->stop($event->getRequest(), $response);
} catch (\Throwable $e) {
$this->profiler->reset();
}
}
public function onKernelTerminate(TerminateEvent $event): void
{
$this->profiler->reset();
}
public function onKernelException(ExceptionEvent $event): void
{
$this->profiler->reset();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment