## Getting Started
### Minimal Setup
1. **Install the bundle**:
```bash
composer require baldinof/roadrunner-bundle
For non-Flex projects, manually register the bundle in config/bundles.php:
return [
// ...
Baldinof\RoadRunnerBundle\BaldinofRoadRunnerBundle::class => ['all' => true],
];
Install RoadRunner CLI tool:
composer require --dev spiral/roadrunner-cli
vendor/bin/rr get --location bin/
Copy default config files (if not using Flex):
cp vendor/baldinof/roadrunner-bundle/.rr.* .
Start RoadRunner (dev mode with auto-reload):
bin/rr serve -c .rr.dev.yaml
Access your app at http://localhost:8080.
RoadRunner replaces Symfony’s built-in web server. The bundle integrates seamlessly with Symfony’s kernel, so existing routes/controllers work out-of-the-box. No additional configuration is needed for basic HTTP traffic.
Key behaviors to note:
Leverage reboot strategies to balance performance and stability. Configure in config/packages/baldinof_road_runner.yaml:
baldinof_road_runner:
kernel_reboot:
strategy: [on_exception, max_jobs] # Combine strategies
allowed_exceptions:
- Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
max_jobs: 1000
memory_threshold_mb: 256
When to use:
on_exception: Default. Reboot only on uncaught exceptions (safe for most apps).max_jobs: Reboot after X requests to prevent memory leaks (e.g., for stateful services).memory: Reboot if RAM exceeds Y MB (critical for long-running workers).always: Force a fresh container per request (use sparingly; impacts performance).Pro Tip: Implement Symfony\Contracts\Service\ResetInterface for stateful services to auto-reset on reboot.
Add custom middleware to manipulate requests/responses outside Symfony’s Kernel::handle().
Define middleware classes implementing Baldinof\RoadRunnerBundle\Http\MiddlewareInterface:
// src/Middleware/CustomMiddleware.php
namespace App\Middleware;
use Baldinof\RoadRunnerBundle\Http\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CustomMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, callable $next): ResponseInterface
{
// Pre-processing logic
$response = $next($request);
// Post-processing logic
return $response;
}
}
Register in config:
baldinof_road_runner:
middlewares:
- App\Middleware\CustomMiddleware
Caveats:
The bundle auto-detects and enables integrations for common Symfony bundles:
| Integration | Config Key | Purpose |
|---|---|---|
| Sentry | sentry (auto-enabled) |
Attaches request context to Sentry events. |
| Doctrine ORM | doctrine.orm.enabled |
Clears entity managers and checks DB connections post-request. |
| Doctrine MongoDB | doctrine_mongodb.odm.enabled |
Clears opened managers after requests. |
| Blackfire | blackfire (auto-enabled) |
Enables profiling when BF_PROFILE header is present. |
| Xdebug | xdebug (auto-enabled) |
Allows Xdebug in trigger mode. |
| Sessions | framework.session.enabled |
Adds session cookie to responses. |
Disable all integrations (rarely needed):
baldinof_road_runner:
default_integrations: false
Track custom metrics via Prometheus. Define metrics in config:
# config/packages/baldinof_road_runner.yaml
baldinof_road_runner:
metrics:
enabled: true
collect:
api_calls:
type: counter
help: "Total API calls"
cache_hits:
type: gauge
help: "Current cache hits"
Usage in Controllers:
use Spiral\RoadRunner\MetricsInterface;
class ApiController
{
public function index(MetricsInterface $metrics): Response
{
$metrics->add('api_calls', 1);
// ...
}
}
RoadRunner Config (.rr.yaml):
metrics:
address: "0.0.0.0:9180" # Prometheus endpoint
Enable gRPC by configuring RoadRunner and implementing service interfaces.
RoadRunner Config (.rr.yaml):
grpc:
listen: "tcp://:9001"
proto:
- "path/to/calculator.proto"
Service Implementation:
// src/Grpc/CalculatorService.php
namespace App\Grpc;
use Spiral\RoadRunner\GRPC;
use App\Grpc\Generated\Calculator\Sum;
use App\Grpc\Generated\Calculator\Result;
use App\Grpc\Generated\Calculator\CalculatorInterface;
class CalculatorService implements CalculatorInterface
{
public function Sum(GRPC\ContextInterface $ctx, Sum $request): Result
{
return (new Result())->setResult($request->getA() + $request->getB());
}
}
Auto-registration: Services implementing generated interfaces are registered automatically.
Use RoadRunner’s KV store for request-scoped caching. Requires:
composer require spiral/roadrunner-kv spiral/goridge symfony/cache
Config:
# config/packages/baldinof_road_runner.yaml
baldinof_road_runner:
kv:
storages:
- app_cache
# .rr.yaml
kv:
app_cache:
driver: memory
config: {}
Cache Pool Setup:
# config/packages/cache.yaml
framework:
cache:
pools:
app.cache:
adapter: cache.adapter.roadrunner.kv_app_cache
Optimize Docker images for production:
FROM php:8.2-alpine
# Install dependencies
RUN apk add --no-cache linux-headers autoconf openssl-dev \
&& docker-php-ext-install pdo_mysql opcache sockets
# Install RoadRunner
RUN ./vendor/bin/rr get-binary --location /usr/local/bin
# Copy and optimize
COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY . .
RUN composer install --no-dev --optimize-autoloader
# Warm up cache
RUN php bin/console cache:warmup
EXPOSE 8080
CMD ["rr", "serve", "-c", ".rr.yaml"]
Key Optimizations:
--optimize-autoloader for faster boot times.ResetInterface:
use Symfony\Contracts\Service\ResetInterface;
class StatefulService implements ResetInterface
{
public function reset(): void
{
$this->cache = [];
}
}
Connection objects are stateless.memory reboot strategy if you suspect leaks.bin/console server:dump
How can I help you explore Laravel packages today?