Initial commit

This commit is contained in:
2021-01-18 16:46:31 +03:00
commit f33a9dc226
9 changed files with 313 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace NsqPHP\NsqBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class NsqExtension extends Extension
{
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace NsqPHP\NsqBundle\Messenger;
use Nsq\Envelope;
use Symfony\Component\Messenger\Stamp\StampInterface;
/**
* @psalm-immutable
*/
final class NsqReceivedStamp implements StampInterface
{
public Envelope $envelope;
public function __construct(Envelope $envelope)
{
$this->envelope = $envelope;
}
}

View File

@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
namespace NsqPHP\NsqBundle\Messenger;
use Generator;
use JsonException;
use LogicException;
use Nsq\Connection;
use Nsq\Envelope as NsqEnvelope;
use Nsq\Reader;
use Nsq\Subscriber;
use Nsq\Writer;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
use function json_decode;
use function json_encode;
use const JSON_THROW_ON_ERROR;
final class NsqTransport implements TransportInterface
{
private Connection $connection;
private SerializerInterface $serializer;
private ?Generator $generator = null;
private string $topic;
private string $channel;
public function __construct(
Connection $connection,
string $topic,
string $channel,
SerializerInterface $serializer = null
) {
$this->connection = $connection;
$this->topic = $topic;
$this->channel = $channel;
$this->serializer = $serializer ?? new PhpSerializer();
}
/**
* {@inheritdoc}
*/
public function send(Envelope $envelope): Envelope
{
$nsqEnvelope = $this->getNsqEnvelope($envelope);
$encodedMessage = $this->serializer->encode($envelope->withoutAll(NsqReceivedStamp::class));
$this->getPublisher()->pub($this->topic, json_encode($encodedMessage, JSON_THROW_ON_ERROR));
if (null !== $nsqEnvelope) {
$nsqEnvelope->ack();
}
return $envelope;
}
/**
* {@inheritdoc}
*/
public function get(): iterable
{
$generator = $this->generator;
if (null === $generator) {
$this->generator = $generator = $this->getSubscriber()->subscribe($this->topic, $this->channel);
} else {
$generator->next();
}
/** @var NsqEnvelope|null $nsqEnvelope */
$nsqEnvelope = $generator->current();
if (null === $nsqEnvelope) {
return [];
}
try {
$encodedEnvelope = json_decode($nsqEnvelope->message->body, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$nsqEnvelope->ack();
throw new MessageDecodingFailedException('', 0, $e);
}
try {
$envelope = $this->serializer->decode($encodedEnvelope);
} catch (MessageDecodingFailedException $e) {
$nsqEnvelope->ack();
throw $e;
}
return [
$envelope->with(
new NsqReceivedStamp($nsqEnvelope),
new TransportMessageIdStamp($nsqEnvelope->message->id),
),
];
}
/**
* {@inheritdoc}
*/
public function ack(Envelope $envelope): void
{
$message = $this->getNsqEnvelope($envelope);
if (!$message instanceof NsqEnvelope) {
throw new LogicException('Returned envelop doesn\'t related to NsqMessage.');
}
$message->ack();
}
/**
* {@inheritdoc}
*/
public function reject(Envelope $envelope): void
{
$message = $this->getNsqEnvelope($envelope);
if (!$message instanceof NsqEnvelope) {
throw new LogicException('Returned envelop doesn\'t related to NsqMessage.');
}
$message->ack();
}
private function getNsqEnvelope(Envelope $envelope): ?NsqEnvelope
{
$stamp = $envelope->last(NsqReceivedStamp::class);
if (!$stamp instanceof NsqReceivedStamp) {
return null;
}
return $stamp->envelope;
}
private function getPublisher(): Writer
{
return $this->publisher ??= new Writer($this->connection);
}
private function getSubscriber(): Subscriber
{
return $this->publisher ??= new Subscriber(new Reader($this->connection));
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace NsqPHP\NsqBundle\Messenger;
use Nsq\Config;
use Nsq\Connection;
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
use function dump;
use function parse_str;
use function parse_url;
use function sprintf;
use function strpos;
final class NsqTransportFactory implements TransportFactoryInterface
{
/**
* {@inheritdoc}
*/
public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
{
if (false === $parsedUrl = parse_url($dsn)) {
throw new InvalidArgumentException(sprintf('The given Nsq DSN "%s" is invalid.', $dsn));
}
$nsqOptions = [];
if (isset($parsedUrl['query'])) {
parse_str($parsedUrl['query'], $nsqOptions);
}
$address = sprintf('tcp://%s:%s', $parsedUrl['host'] ?? 'nsqd', $parsedUrl['port'] ?? 4150);
return new NsqTransport(
Connection::connect(new Config($address)),
$nsqOptions['topic'] ?? 'symfony-messenger',
$nsqOptions['channel'] ?? 'default',
$serializer
);
}
/**
* {@inheritdoc}
*/
public function supports(string $dsn, array $options): bool
{
return 0 === strpos($dsn, 'nsq://');
}
}

10
src/NsqBundle.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace NsqPHP\NsqBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class NsqBundle extends Bundle
{
}

View File

@@ -0,0 +1,3 @@
services:
NsqPHP\NsqBundle\Messenger\NsqTransportFactory:
tags: [ 'messenger.transport_factory' ]