Yet another runtime for Symfony and RoadRunner.
composer require fluffydiscord/roadrunner-symfony-bundle
Define the environment variable APP_RUNTIME
in .rr.yaml
and set up rpc
plugin:
.rr.yaml
server:
env:
APP_RUNTIME: FluffyDiscord\RoadRunnerBundle\Runtime\Runtime
rpc:
listen: tcp://127.0.0.1:6001
Don't forget to add the RR_RPC
to your .env
:
RR_RPC=tcp://127.0.0.1:6001
fluffy_discord_road_runner.yaml
fluffy_discord_road_runner:
# Optional
# Specify relative path from "kernel.project_dir" to your RoadRunner config file
# if you want to run cache:warmup without having your RoadRunner running in background,
# e.g. when building Docker images. Default is ".rr.yaml"
rr_config_path: ".rr.yaml"
# https://docs.roadrunner.dev/http/http
http:
# Optional
# -----------
# This decides when to boot the Symfony kernel.
#
# false (default) - before first request (worker takes some time to be ready, but app has consistent response times)
# true - once first request arrives (worker is ready immediately, but inconsistent response times due to kernel boot time spikes)
#
# If you use large amount of workers, you might want to set this to true or else the RR boot up might
# take a lot of time or just boot up using only a few "emergency" workers
# and then use dynamic worker scaling as described here https://docs.roadrunner.dev/php-worker/scaling
lazy_boot: false
# https://docs.roadrunner.dev/plugins/centrifuge
centrifugo:
# Optional
# -----------
# See http section
lazy_boot: false
# https://docs.roadrunner.dev/key-value/overview-kv
kv:
# Optional
# -----------
# If true (default), bundle will automatically register all "kv" adapters in your .rr.yaml.
# Registered services have alias "cache.adapter.rr_kv.NAME"
auto_register: true
# Optional
# -----------
# Which serializer should be used.
# By default, "IgbinarySerializer" will be used if "igbinary" php extension
# is installed (recommended), otherwise "DefaultSerializer".
# You are free to create your own serializer, it needs to implement
# Spiral\RoadRunner\KeyValue\Serializer\SerializerInterface
serializer: null
# Optional
# -----------
# Specify relative path from "kernel.project_dir" to a keypair file
# for end-to-end encryption. "sodium" php extension is required.
# https://docs.roadrunner.dev/key-value/overview-kv#end-to-end-value-encryption
keypair_path: bin/keypair.key
If you want to use REMOTE_ADDR
as trusted proxy, replace it with 0.0.0.0/0
instead
or else your trusted headers will not work.
Symfony is using the $_SERVER['REMOTE_ADDR']
to find out the proxy address,
but in the context of RoadRunner, $_SERVER
contains only environment
variables and the REMOTE_ADDS
is missing.
Build-in full support for Symfony's BinaryFileResponse
and StreamedJsonResponse
. The StreamedResponse
needs one little
change to be fully streamable - you have to change the callback
to a \Generator
, replacing all echo
with yield
. Look at the example:
use Symfony\Component\HttpFoundation\StreamedResponse;
class MyController
{
#[Route("/stream")]
public function myStreamResponse()
{
return new StreamedResponse(
function () {
// replace all 'echo' or any outputs with 'yield'
// echo "data";
yield "data";
}
);
}
}
Built in support for Sentry. Just install & configure it as you normally do.
composer require sentry/sentry-symfony
To enable Centrifugo you need to add roadrunner-php/centrifugo
package.
composer require roadrunner-php/centrifugo
Bundle is using Symfony's Event dispatcher. You can create event listener for any event extending FluffyDiscord\RoadRunnerBundle\Event\Centrifugo\CentrifugoEventInterface
:
ConnectEvent
required :)InvalidEvent
PublishEvent
RefreshEvent
RPCEvent
SubRefreshEvent
SubscribeEvent
Example usage:
<?php
namespace App\EventListener;
use App\Centrifuge\Event\ConnectEvent;
use RoadRunner\Centrifugo\Payload\ConnectResponse;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: ConnectEvent::class, method: "handleConnect")]
readonly class ChatListener
{
public function handleConnect(ConnectEvent $event): void
{
// original Centrifugo request passed from RoadRunner
$request = $event->getRequest();
// auth your user or whatever you want
$authToken = $request->getData()["authToken"] ?? null;
$user = ...
// stop propagating to other listeners,
// you have successfully connected your user
$event->stopPropagation();
// send response using the $event->setResponse($myResponse)
$event->setResponse(new ConnectResponse(
user: $user->getId(),
data: [
"messages" => ... // initial data client receives when connected
],
));
}
}
Be aware that if you do not set any response, bundle will send DisconnectResponse
back by default.
- If possible, stop using lazy loading in your services, inject services immediately.
- It is no longer needed and might potentially bring issues to you like memory leaks.
- Do not use/create local class/array caches in your services. Try to make them stateless or if they cannot be, add ResetInterface to clean up before each request.
- Symfony forms might leak data across requests due to local caching it uses. Make sure your form
defaultOptions
are stateless. Do not store anything sensitive/important as it will be leaked in the following requests. - Simplify your
User
session serialization by taking advantage ofEquatableInterface
and custom de/serialization logic. This will prevent errors because of detached Doctrine entities and, as a side bonus, will speed up loading user from sessions.
<?php
namespace App\Entity\User;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface
{
#[ORM\Id]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT, unique: true)]
private ?string $email = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $password = null;
// serialize ony these three fields
public function __serialize(): array
{
return [
"id" => $this->id,
"email" => $this->email,
"password" => $this->password,
];
}
// unserialize ony these three fields
public function __unserialize(array $data): void
{
$this->id = $data["id"] ?? null;
$this->email = $data["email"] ?? null;
$this->password = $data["password"] ?? null;
}
// check only the three serialized fields
public function isEqualTo(mixed $user): bool
{
if (!$user instanceof self) {
return false;
}
return $this->id === $user->getId()
&&
$this->password === $user->getPassword()
&&
$this->email === $user->getEmail()
;
}
}
With RoadRunner you cannot simply dump and die, because nothing will be printed. I would like to introduce Buggregator to work around that. As a bonus it can also work as a mailtrap or testing Sentry locally
Inspiration taken from existing solutions like Baldinof's Bundle and Nyholm's Runtime