juststeveking/cloudevents
A Laravel/PHP package for working with CloudEvents, the CNCF specification for describing event data consistently across services. Create standardized event objects and integrate them into event-driven Laravel applications and service-to-service workflows.
This is the initial 0.0.1 release of juststeveking/cloudevents, a PHP implementation of the CNCF CloudEvents specification with Laravel-friendly ergonomics. Treat this as a pre-1.0 preview: the value objects and public API can change without a major version bump, so pin the exact version ("juststeveking/cloudevents": "0.0.1") in composer.json while you evaluate.
Minimal steps:
composer require juststeveking/cloudevents. If a service provider/config is registered via laravel-package-tools, run php artisan vendor:publish --tag="cloudevents-config" and inspect the published config for defaults (source, spec version, serializer options).src/ first, not just the README. In a 0.0.1 there is usually one central value object (a CloudEvent type) plus serializer/transformer classes. Skim the constructor signature — required fields per the spec are id, source, specversion, and type; subject, time, datacontenttype, and data are optional.tests/) — this is the most reliable documentation of intended usage at this stage, since the changelog is an empty commit link.CloudEvent in a listener or job, serialize it to structured JSON, and push it to a queue, webhook, or broker. That single path exercises most of the package surface and tells you quickly whether the API fits your codebase.Build events at the boundary, not in your domain. Keep your domain events plain Laravel events and map them to CloudEvents in a dedicated listener or a small CloudEventFactory of your own. This insulates you from API churn in a 0.0.1 package and keeps the CNCF envelope out of your business logic.
final class CloudEventFactory
{
public static function fromOrderPlaced(OrderPlaced $event): CloudEvent
{
return new CloudEvent(
id: (string) Str::uuid(),
source: '/orders',
type: 'com.example.order.placed',
specVersion: '1.0',
subject: (string) $event->order->id,
time: now(),
data: $event->order->toArray(),
);
}
}
Prefer structured content mode for internal traffic. Structured mode (the whole envelope as one JSON body) is easier to log, replay, and debug than binary mode. Reserve binary mode (ce-* headers with the payload as body) for the specific brokers or HTTP receivers that demand it — and note that binary mode is only spec-compliant over HTTP, so don't hand-roll it for AMQP unless the package or broker client does it for you.
Wire serialization through Laravel's queue. A clean pattern is a SendCloudEvent job that accepts the value object, serializes it in handle(), and posts it with Http::withHeaders(...). Serialize inside the job rather than in the constructor so retries and failed() hooks see the same envelope, and so you avoid serializing objects into the queue payload.
Use extensions for tenant/correlation data. CloudEvents extensions (lowercase alphanumeric attribute names) are the sanctioned place for tenantid, correlationid, and traceparent. Avoid stuffing these into data — consumers filtering on envelope attributes can't see them there. If the package exposes an extensions array or a withExtension() style method, standardize on it early.
Test against the JSON shape, not the classes. Assert on the serialized envelope, since that is your actual contract with consumers:
$payload = CloudEventFactory::fromOrderPlaced($event)->toArray();
$this->assertSame('1.0', $payload['specversion']);
$this->assertSame('/orders', $payload['source']);
$this->assertArrayHasKey('id', $payload);
This keeps your tests valid even if the package renames methods.
Integration tip: one adapter, one place. If you consume CloudEvents too (webhook receiver, queue consumer), write a single controller/middleware that hydrates an incoming envelope and rejects anything missing required attributes. Don't scatter json_decode + array access across controllers.
0.0.1 there is no stability guarantee and the changelog entry is literally just a link to the commit list. Pin exactly, and re-read the diff before every bump.id/source/type if the constructor lacks guards. Add an explicit validation step (or a failing test) so malformed events never reach a broker. Per spec, id + source must be unique per event — generate a UUID or ULID per emission, never reuse one across retries of different events.time must be RFC3339. If you pass a Carbon instance, confirm what gets emitted (toRfc3339String(), not toDateTimeString()). Timezone offsets and microseconds are the usual source of consumer-side parse failures.datacontenttype to application/json when data is an array/object; omit it (or use application/octet-stream) for raw strings. Consumers deciding how to decode the payload rely on this field, not on sniffing.json_encode($data) as data, which then gets encoded again by the structured serializer, producing a JSON string instead of an object. Pass the PHP array and let the package encode it.correlation-id will be rejected by strict consumers.->toArray() / ->toJson()) rather than the object graph. Most issues are attribute naming, missing specversion, or a nested-encoding mistake, all visible in the JSON. If the package ships a config-driven default source or specversion, verify the resolved config isn't overriding the value you passed explicitly.CloudEventSerializer-style contract or a CloudEvent interface exists, bind your own implementation in a service provider rather than forking. That is the cheapest way to survive API changes — implement against the interface, not the concrete class, in your own application code.ce- prefix handling). If you must consume binary mode, wrap it with tests for those cases before trusting it in production.How can I help you explore Laravel packages today?