| examples | 1 day ago | ||
| src | 1 day ago | ||
| tests | 1 day ago | ||
| .gitignore | 1 day ago | ||
| README.md | 1 day ago | ||
| composer.json | 1 day ago | ||
| phpunit.xml.dist | 1 day ago | ||
Тонкий PHP-клиент Synapse (централизованный хаб уведомлений Gnexus) для сервисов экосистемы: gnexus-auth (Laravel), Navi-инстансы и другие PHP-сервисы.
Клиент берёт на себя весь повторяемый шаблон: env-конфиг, сборку и локальную валидацию конверта, типизированные исключения по HTTP-статусам, fire-and-forget emit, batch, статус события. Ретраев и локальных очередей нет — Synapse принимает событие с ответом 202 мгновенно, всё остальное делает его воркер.
Транспорт инжектный (PSR-18 + PSR-17, например Guzzle — в require-dev для тестов и примеров, жёсткой зависимости нет).
В composer.json сервиса:
{
"repositories": [
{
"type": "vcs",
"url": "https://git.gnexus.space/git/root/gn-synapse-client-php.git"
}
]
}
composer require gnexus/synapse-client:^0.1.0
use GNexus\Synapse\Config\SynapseConfig;
use GNexus\Synapse\SynapseClient;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
$client = new SynapseClient(
SynapseConfig::fromGlobals(), // SYNAPSE_URL, SYNAPSE_API_KEY, ...
new Client(['timeout' => 10.0]), // PSR-18 (таймаут задаёт потребитель)
new HttpFactory(), // PSR-17 RequestFactory
new HttpFactory(), // PSR-17 StreamFactory
);
// Обычная отправка: событие обязательно дойти — ошибка бросает исключение
$event = $client->send('bugtrail', 'test', 'failed', 'high',
['user_id' => 'u-42'], dedupKey: "failed-42");
$event->id; // uuid
$event->status; // 'queued' — 202, воркер заберёт позже
// Fire-and-forget: упавший Synapse не должен ломать основной путь сервиса
$client->emit(null, 'user', 'password_changed', payload: ['user_id' => $sub]);
// → SentEvent|null; SynapseException и транспортные сбои ловятся,
// пишутся в $logger->warning('synapse emit failed: ...', ...) и глотаются.
// Статус и доставки
$status = $client->status($event->id);
foreach ($status->deliveries as $d) { /* channel, target, status, attempts, error */ }
| Переменная | Обязательна | По умолчанию | Что делает |
|---|---|---|---|
SYNAPSE_URL |
да | — | База Synapse (http://localhost:8013) |
SYNAPSE_API_KEY |
да (для send/emit) | — | Ключ источника (syn_...) |
SYNAPSE_TIMEOUT |
нет | 10.0 |
Секунды (используется как fromGlobals()->timeoutSeconds) |
SYNAPSE_DEFAULT_SOURCE |
нет | — | source по умолчанию: $client->send(null, ...) |
SynapseConfig валидирует всё на конструировании (fail fast): пустой URL, схема не http(s), таймаут ≤ 0, ключ-пустая-строка → ConfigurationException. Send/sendBatch при отсутствии ключа тоже бросает ConfigurationException до HTTP.
default_source — смягчение антивспуфинга сервера: 403 получают вызовы, где source конверта не совпадает с источником ключа; с SYNAPSE_DEFAULT_SOURCE это ошибка конфигурации, а не кода.
send(...): SentEvent — конверт валидируется локально (без HTTP): имена ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$ 1..64, priority ∈ low|normal|high|critical, ttlSeconds 1..604800, dedupKey ≤255 ('' → null), payload — assoc-массив. normal priority в конверт не попадает, null-поля не шлются (scheduledAt → UTC DATE_ATOM). Ошибки валидации — локальный ValidationException (statusCode = null: это ошибка кода, не Synapse).emit(..., bool $throw = false): ?SentEvent — то же, но любые SynapseException/транспортные ошибки ловятся → $logger->warning(...) → null (throw: true → перебрасывает).sendBatch(array $envelopes): BatchResult — каждый элемент прогоняется через EnvelopeBuilder::build(); локально битые — BatchRejection(index, detail) без HTTP; ответ сервера 202 и 422 («ни одного не принято») парсится одинаково — index серверного отклонения ремапится на исходную позицию; 5xx/транспорт — исключение целиком.status(string $eventId): EventStatus — uuid-проверка, 404 → NotFoundException.health(): array, ready(): array — /api/healthz и /api/readyz, без ключа.GNexus\Synapse\Exception\)| Класс | Код | Когда |
|---|---|---|
SynapseException |
— | База: .detail, .statusCode |
ConfigurationException |
— | Битый конфиг / нет ключа (до HTTP) |
TransportException |
— | Сеть упала (.transportError) |
AuthException |
401 | Ключ нет/отозван/источник архивен |
ForbiddenException |
403 | source ≠ источник ключа (антивспуфинг) |
NotFoundException |
404 | Статус чужого/несуществующего события |
ValidationException |
422 / null | Незарегистрированный тип или битый конверт (null = локально) |
ServerException |
5xx | Synapse упал |
Ловить — одну SynapseException.
deduplicated: true. Не строить бизнес-логику на отсутствии дублей.timeoutSeconds) — в Guzzle и аналогах явно задавайте таймаут (Laravel-binding с таймаутом — examples/laravel/README.md).scheduled_at — резерв MVP: принимается, не отправляется.payload.user_id — непустая строка (uuid gnexus-auth); остальное — предупреждение в лог (сервер запишет address-доставки со статусом skipped).composer install vendor/bin/phpunit
Интеграционный смок против живого стека — examples/plain-php/smoke.php.
Лицензия: проприетарная (внутри экосистемы Gnexus).