Installation
composer require braune-digital/pitcher-bundle
Add to config/bundles.php (Symfony 4+):
return [
// ...
BrauneDigital\PitcherBundle\BrauneDigitalPitcherBundle::class => ['all' => true],
];
Configure via config/packages/braune_digital_pitcher.yaml
braune_digital_pitcher:
secret: '%env(PITCHER_SECRET)%' # Replace with your Pitcher secret
project_id: '%env(PITCHER_PROJECT_ID)%'
environment: '%kernel.environment%'
debug: '%kernel.debug%'
First Use Case: Logging an Exception
Inject the PitcherClient service and use it in an exception handler or controller:
use BrauneDigital\PitcherBundle\Client\PitcherClientInterface;
public function __construct(private PitcherClientInterface $pitcher)
{
}
public function handleException(Exception $e)
{
$this->pitcher->sendException($e);
}
Exception Logging
PitcherClientInterface in exception handlers (e.g., EventListener for KernelExceptionEvent).sendException() method’s optional arguments:
$this->pitcher->sendException($e, [
'user_id' => $user->id,
'request_data' => $request->request->all(),
]);
Configuration Management
config/packages/.debug: true (Pitcher ignores debug environments by default).Integration with Monolog
use Monolog\Handler\AbstractProcessingHandler;
use BrauneDigital\PitcherBundle\Client\PitcherClientInterface;
class PitcherHandler extends AbstractProcessingHandler
{
public function __construct(PitcherClientInterface $pitcher)
{
parent::__construct($pitcher, Monolog\Logger::ERROR);
}
protected function write(array $record): void
{
$this->pitcher->sendException(new \RuntimeException($record['message']));
}
}
services.yaml:
services:
Monolog\Handler\PitcherHandler:
arguments: ['@braune_digital_pitcher.client']
tags: ['monolog.handler']
Custom Metadata
['trace_id' => $request->headers->get('X-Trace-ID')]) to enrich Pitcher’s exception context.Secret Management
env() or Symfony’s %env() syntax.secret or project_id is missing. Validate in config/validator.yaml:
services:
BrauneDigital\PitcherBundle\Validator\Constraints\ValidPitcherConfig:
tags: [validator.constraint_validator]
Environment Filtering
debug: true environments. Explicitly set debug: false in production config.Rate Limiting
use Symfony\Contracts\HttpClient\HttpClientInterface;
class RetryPitcherClient implements PitcherClientInterface
{
public function sendException(Exception $e, array $context = []): void
{
$client = HttpClientInterface::create();
$response = $client->post('https://api.pitcher-app.com/exceptions', [
'json' => [
'secret' => $this->secret,
'exception' => $e->getMessage(),
'context' => $context,
],
]);
if ($response->getStatusCode() === 429) {
sleep(1); // Retry after 1 second
$this->sendException($e, $context);
}
}
}
Deprecated Bundle
Enable HTTP Debugging
Add VERBOSITY: 2 to your .env to log Pitcher API requests:
SYMFONY_VERBOSITY=2
Mock Pitcher in Tests
Use a mock PitcherClientInterface in PHPUnit:
$mockPitcher = $this->createMock(PitcherClientInterface::class);
$mockPitcher->expects($this->once())
->method('sendException')
->with($this->isInstanceOf(Exception::class));
$this->container->set(PitcherClientInterface::class, $mockPitcher);
Check Response Codes
Wrap sendException() calls to log HTTP errors:
try {
$this->pitcher->sendException($e);
} catch (\RuntimeException $e) {
$this->logger->error('Pitcher API failed', ['exception' => $e->getMessage()]);
}
Custom Exception Formatter
Override the default exception serialization by extending BrauneDigital\PitcherBundle\Formatter\ExceptionFormatter:
class CustomExceptionFormatter extends ExceptionFormatter
{
public function format(Exception $e): array
{
$data = parent::format($e);
$data['custom_field'] = $this->getCustomData($e);
return $data;
}
}
Register in services.yaml:
services:
BrauneDigital\PitcherBundle\Formatter\ExceptionFormatter:
class: App\CustomExceptionFormatter
Async Processing Use Symfony’s Messenger component to offload Pitcher calls to a queue:
use Symfony\Component\Messenger\MessageBusInterface;
class PitcherMessage
{
public function __construct(private Exception $exception, private array $context) {}
}
// Handler
class PitcherHandler
{
public function __invoke(PitcherMessage $message)
{
$this->pitcher->sendException($message->exception, $message->context);
}
}
Webhook Integration Trigger Pitcher from frontend errors via a Symfony controller:
#[Route('/api/errors', methods: ['POST'])]
public function reportError(Request $request, PitcherClientInterface $pitcher): JsonResponse
{
$data = json_decode($request->getContent(), true);
$pitcher->sendException(new \RuntimeException($data['message']), $data['context']);
return new JsonResponse(['status' => 'received']);
}
How can I help you explore Laravel packages today?