Integrácia do frameworkov
Hermes nemá bundle pre framework a ani ho nepotrebuje. Jeho triedy dostávajú závislosti v konštruktore, takže ich vytvorí akýkoľvek kontajner. Stránky v tejto sekcii ukazujú tú istú malú integráciu v troch frameworkoch:
- Nette 3.2: služby v súbore NEON, Tracy ako PSR-3 logger, contributte/console pre príkaz workera.
- Laravel 11, 12 a 13: service provider,
config/hermes.phpa príkazy Artisan. - Symfony 7.4:
services.yamls autowiringom, Monolog a príkazy Symfony Console.
Každý príklad je súbor súborov v examples/frameworks a kód na týchto stránkach sa načítava z nich. Každý bol nainštalovaný do novo vytvoreného projektu a spustený proti Redisu (ext-redis aj Predis), RabbitMQ a SQS (lokálny server moto).
Čo má každá integrácia
| Časť | Čo to je | Kto ju používa |
|---|---|---|
DriverFactory | Vytvorí DriverInterface z jednoduchej konfigurácie: redis (ext-redis), predis, rabbitmq alebo sqs, plus názov fronty a limit správ. | kontajner |
EmitterInterface | Zdieľaný Emitter s PSR-3 loggerom frameworku. | controllery, príkazy, joby |
DispatcherInterface | Dispatcher s loggerom, ukončením a handlermi zaregistrovanými cez registerHandler(). | len príkaz workera |
ShutdownInterface | SharedFileShutdown nad súborom v dočasnom priečinku alebo v storage aplikácie. | worker a hermes:restart |
hermes:work | Volá $dispatcher->handle(). Blokuje až do ukončenia alebo limitu správ. --priority ho obmedzí na niektoré priority. | Supervisor alebo systemd |
hermes:restart | Volá $shutdown->shutdown(). Bežiace workery sa zastavia po aktuálnej správe. | skript nasadenia |
app:register-user | Ukážkový producent, ktorý vkladá user.registered. | vy, na vyskúšanie |
Tá istá DriverFactory a UserRegisteredHandler sa používajú vo všetkých troch frameworkoch:
<?php
declare(strict_types=1);
namespace App\Hermes;
use Aws\Sqs\SqsClient;
use InvalidArgumentException;
use PhpAmqpLib\Connection\AMQPLazyConnection;
use Predis\Client;
use Redis;
use Tomaj\Hermes\Driver\AmazonSqsDriver;
use Tomaj\Hermes\Driver\DriverInterface;
use Tomaj\Hermes\Driver\LazyRabbitMqDriver;
use Tomaj\Hermes\Driver\PredisSetDriver;
use Tomaj\Hermes\Driver\RedisSetDriver;
/**
* Builds the Hermes driver from plain configuration, so the web process and the worker
* always use the same driver, queue name and connection.
*/
final class DriverFactory
{
/**
* @param array{
* driver: string,
* queue: string,
* max_items: int,
* redis: array{host: string, port: int},
* rabbitmq: array{host: string, port: int, user: string, password: string, vhost: string},
* sqs: array{region: string, endpoint: ?string, key: string, secret: string},
* } $config
*/
public static function create(array $config): DriverInterface
{
$queue = $config['queue'];
$driver = match ($config['driver']) {
'redis' => new RedisSetDriver(self::redis($config['redis']), $queue),
'predis' => new PredisSetDriver(new Client(['host' => $config['redis']['host'], 'port' => $config['redis']['port']]), $queue),
'rabbitmq' => new LazyRabbitMqDriver(
new AMQPLazyConnection(
$config['rabbitmq']['host'],
$config['rabbitmq']['port'],
$config['rabbitmq']['user'],
$config['rabbitmq']['password'],
$config['rabbitmq']['vhost'],
),
$queue,
[],
1, // seconds to sleep when the queue is empty
),
'sqs' => new AmazonSqsDriver(self::sqs($config['sqs']), $queue),
default => throw new InvalidArgumentException("Unknown Hermes driver \"{$config['driver']}\""),
};
// Start a fresh worker process after this many messages.
$driver->setMaxProcessItems($config['max_items']);
return $driver;
}
/**
* @param array{host: string, port: int} $config
*/
private static function redis(array $config): Redis
{
$redis = new Redis();
$redis->connect($config['host'], $config['port']);
return $redis;
}
/**
* @param array{region: string, endpoint: ?string, key: string, secret: string} $config
*/
private static function sqs(array $config): SqsClient
{
$options = [
'region' => $config['region'],
'version' => 'latest',
'credentials' => ['key' => $config['key'], 'secret' => $config['secret']],
];
if ($config['endpoint'] !== null && $config['endpoint'] !== '') {
$options['endpoint'] = $config['endpoint']; // a local SQS such as moto or ElasticMQ
}
return new SqsClient($options);
}
}Driver sa vytvorí raz na proces. Webový proces aj worker musia dostať rovnakú triedu drivera a názov fronty; factory ich číta z jednej konfigurácie, takže sa nerozídu. Čo ktorý driver dokáže, nájdete v Prehľade driverov.
<?php
declare(strict_types=1);
namespace App\Hermes;
use Psr\Log\LoggerInterface;
use Tomaj\Hermes\Handler\RetryableHandlerInterface;
use Tomaj\Hermes\Handler\RetryTrait;
use Tomaj\Hermes\MessageInterface;
final class UserRegisteredHandler implements RetryableHandlerInterface
{
use RetryTrait;
public function __construct(private readonly LoggerInterface $logger) {}
#[\Override]
public function handle(MessageInterface $message): bool
{
$email = $message->getPayload()['email'] ?? null;
if (!is_string($email)) {
return false; // a broken message stays broken, do not retry it
}
// Send the welcome email here. Throw an exception to retry later.
$this->logger->info("Welcome email sent to {$email}", ['message_id' => $message->getId()]);
return true;
}
}Pravidlá platné v každom frameworku
- Producenti závisia od
EmitterInterface, nikdy odEmitter. Od Hermesu 6.0 sú triedyfinal, takže PHPUnit nevieEmitterzamockovať. Mockujte rozhranie; každá stránka frameworku má test. - Dispatcher si vytvára len worker. Vytvoriť ho vo webovej požiadavke neuškodí, ale nikdy tam nie je potrebný.
- Každá aplikácia nech má vlastný názov fronty. Dve aplikácie, ktoré zdieľajú jeden Redis a predvolený kľúč
hermes, si navzájom berú správy. Worker zahodí správu, pre ktorú nemá handler. - Po nasadení reštartujte workery. Worker drží starý kód, kým neskončí. Ako posledný krok nasadenia spustite
hermes:restarta nové workery nechajte spustiť správcu procesov. Pozrite Ukončenie a limit správ a Nasadenie do produkcie. - Zastavenie signálom potrebuje
ext-pcntl. S nímSIGTERMod Supervisora, systemd alebo Kubernetes nechá workera dokončiť správu a skončiť, aj vo vnútrihermes:work(Hermes počas behuhandle()preberá obsluhy signálov konzoly). Bez neho signál ukončí PHP okamžite, aj uprostred handlera, preto najprv zavolajtehermes:restart. Pozrite Ukončenie a limit správ.
Náhodná zložka opakovania a zaregistrovaný Random\Randomizer
Hermes 6.0 (#103) pridáva do Dispatchera voliteľný štvrtý argument konštruktora: ?Random\Randomizer $randomizer = null. Bez neho náhodná zložka opakovania používa nový Randomizer s bezpečným enginom. Pozrite Opakovanie.
Kontajnery, ktoré dispatcher autowirujú, vložia službu Random\Randomizer, ak ju aplikácia zaregistrovala na niečo iné, napríklad Mt19937 so seedom pre fixtures. Časy opakovania potom pochádzajú z tohto enginu. Každá stránka frameworku ukazuje, ako namiesto toho odovzdať null. Tento riadok pridajte len pri Hermese 6.0 alebo novšom: vo verzii 5.x a vo vydaniach pred #103 argument neexistuje a kontajner konfiguráciu odmietne.
Provider pre Laravel vytvára dispatcher cez new Dispatcher(...), takže sa ho to netýka.
Overené s
| Framework | Verzie | Drivery | Ďalej overené |
|---|---|---|---|
| Nette | nette/web-project, nette/application 3.3.0, nette/di 3.2.6, contributte/console 0.11.0 | ext-redis, Predis 3.7, RabbitMQ (php-amqplib 3.7.5), SQS na moto | POST na presenter, PHPUnit test, Supervisor |
| Laravel | 11.57.0, 12.70.0 a 13.38.0 | tie isté štyri | feature testy, Supervisor |
| Symfony | 7.4.21, monolog-bundle 4.1.0 | tie isté štyri | POST na controller, test WebTestCase, Supervisor |
PHP 8.4, Hermes dev-master. Každý framework bol spustený aj na Hermese 5.0.0 a na vetvách #103 a #105.
Časté otázky
Existuje bundle alebo balík Hermesu pre môj framework?
Nie. Integrácia je factory, niekoľko definícií služieb a dva konzolové príkazy, ktoré ukazujú stránky Nette, Laravel a Symfony. Skopírujte ich a zmeňte, čo potrebujete.
Môžem Hermes používať vedľa Laravel queues alebo Symfony Messenger?
Áno. Hermes sa ich nedotýka. Pre Hermes použite iný kľúč v Redise alebo inú frontu v RabbitMQ, aby si navzájom nečítali správy.
Ktorý driver nakonfigurovať?
Predis alebo ext-redis, ak chcete priority a odložené správy, RabbitMQ alebo SQS, ak ich už prevádzkujete. Čo ktorý driver podporuje, uvádza porovnanie.
Potrebujem jedného workera na handler?
Nie. Jeden proces hermes:work spúšťa všetky handlery zaregistrované v dispatcheri. Pre vyššiu priepustnosť spustite viac procesov a cez --priority si niektoré nechajte na urgentné správy. Pozrite Priorita.
Ako testovať kód, ktorý vkladá správy?
Typujte EmitterInterface a v teste ho nahraďte PHPUnit mockom. Každá stránka frameworku má funkčný test a handlery pokrýva Testovanie.