Mercure laravel eloquent updates
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
Research direction
The payload provides a proposed PublishMercureUpdatesListener with handleSaved(), handleDeleted(), and postFlush() entry points, but names no repository files or tests. Start by locating the existing Mercure integration and Laravel event wiring; the issue does not define acceptance criteria or what completion should look like.
Written by the indexing model from the issue text.
Description
To continue:
<?php
namespace App\Listeners;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ItemNotFoundException;
use SplObjectStorage;
use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Serializer\SerializerInterface;
// Assuming you have these contracts for your application
use App\Contracts\Mercure\IriConverterInterface;
/**
* Publishes resource updates to the Mercure hub.
* This is a Laravel port of ApiPlatform's PublishMercureUpdatesListener.
*/
final class PublishMercureUpdatesListener
{
private SplObjectStorage $createdObjects;
private SplObjectStorage $updatedObjects;
private SplObjectStorage $deletedObjects;
private const ALLOWED_KEYS = [
'topics' => true,
'data' => true,
'private' => true,
'id' => true,
'type' => true,
'retry' => true,
'normalization_context' => true,
'hub' => true,
'enable_async_update' => true,
];
/**
* @param array<string, string> $formats
*/
public function __construct(
private readonly IriConverterInterface $iriConverter,
private readonly SerializerInterface $serializer,
private readonly array $formats,
private readonly Application $app,
private readonly ?HubRegistry $hubRegistry = null,
private readonly ?Dispatcher $bus = null
) {
if (null === $this->bus && null === $this->hubRegistry) {
throw new \InvalidArgumentException('A message bus (Dispatcher) or a HubRegistry must be provided.');
}
$this->reset();
}
/**
* Handle "saved" events.
*/
public function handleSaved(Model $model): void
{
$property = $model->wasRecentlyCreated ? 'createdObjects' : 'updatedObjects';
$this->storeObjectToPublish($model, $property);
}
/**
* Handle "deleted" events.
*/
public function handleDeleted(Model $model): void
{
$this->storeObjectToPublish($model, 'deletedObjects');
}
/**
* Publishes updates for changes collected, then resets.
* This method should be called by a service provider in the app's 'terminating' event.
*/
public function postFlush(): void
{
try {
foreach (clone $this->createdObjects as $object) {
$this->publishUpdate($object, $this->createdObjects[$object], 'create');
}
foreach (clone $this->updatedObjects as $object) {
$this->publishUpdate($object, $this->updatedObjects[$object], 'update');
}
foreach (clone $this->deletedObjects as $object) {
$this->publishUpdate($object, $this->deletedObjects[$object], 'delete');
}
} finally {
$this->reset();
}
}
private function storeObjectToPublish(Model $model, string $property): void
{
if (!method_exists($model, 'getMercureOptions')) {
return;
}
$options = $model->getMercureOptions();
if ($options === false) {
return;
}
if ($options === true) {
$options = [];
}
if (!is_array($options)) {
throw new \InvalidArgumentException(sprintf('The value of getMercureOptions() for model "%s" must be a boolean or an array.', get_class($model)));
}
foreach (array_keys($options) as $key) {
if (!isset(self::ALLOWED_KEYS[$key])) {
throw new \InvalidArgumentException(sprintf('The option "%s" from getMercureOptions() on model "%s" is not supported.', $key, get_class($model)));
}
}
$options['enable_async_update'] ??= true;
if ($property === 'deletedObjects') {
// For deleted objects, we must evaluate topics and store IRIs now,
// as the object will no longer exist when publishing.
$this->evaluateOptions($options, $model);
// Store a proxy object with the necessary information.
$proxy = (object) [
'id' => $this->iriConverter->getIriFromResource($model),
'iri' => $this->iriConverter->getIriFromResource($model, true), // absolute URL
'type' => class_basename($model), // A sensible default for type
];
$this->deletedObjects[$proxy] = $options;
return;
}
$this->{$property}[$model] = $options;
}
/**
* @param Model|\stdClass $object
*/
private function publishUpdate(object $object, array $options, string $type): void
{
if ($object instanceof \stdClass) { // Proxy object for a deleted entity
$iri = $options['topics'] ?? $object->iri;
$data = json_encode(['@id' => $object->id, '@type' => $object->type]);
} else {
// For create/update, evaluate options just-in-time
$this->evaluateOptions($options, $object);
$iri = $options['topics'] ?? $this->iriConverter->getIriFromResource($object, true);
$context = $options['normalization_context'] ?? [];
$data = $options['data'] ?? $this->serializer->serialize($object, key($this->formats), $context);
}
if (empty($iri) || empty($data)) {
return;
}
$update = $this->buildUpdate($iri, $data, $options);
if ($options['enable_async_update'] && $this->bus) {
// In Laravel, Mercure updates are often handled by a queued job.
// You would need to create a job that takes an Update object and publishes it.
// For this example, we'll dispatch the Update object directly if a bus is present.
// A real implementation might be: $this->bus->dispatch(new PublishMercureUpdateJob($update, $options['hub'] ?? null));
$this->bus->dispatch($update);
return;
}
if ($this->hubRegistry) {
$this->hubRegistry->getHub($options['hub'] ?? null)->publish($update);
}
}
/**
* Replaces callable options with their evaluated values.
*/
private function evaluateOptions(array &$options, object $object): void
{
foreach ($options as $key => &$value) {
if ($value instanceof \Closure) {
$value = $this->app->call($value, ['object' => $object]);
}
}
}
/**
* @param string|string[] $iri
*/
private function buildUpdate(string|array $iri, string $data, array $options): Update
{
return new Update(
$iri,
$data,
$options['private'] ?? false,
$options['id'] ?? null,
$options['type'] ?? null,
$options['retry'] ?? null
);
}
private function reset(): void
{
$this->createdObjects = new SplObjectStorage();
$this->updatedObjects = new SplObjectStorage();
$this->deletedObjects = new SplObjectStorage();
}
}
- Dominant language
- PHP
- Stars
- 2.6k
- Forks
- 988
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 105
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from api-platform/core
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
api-platform/core#8744 ·
Maintainers usually reply within 1 day
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`Possibly taken @ousamabenyounes claimed this 4 days ago. Open
Difficulty 2/5 Under an hour Newbie friendliness 80/100
api-platform/core#8701 ·
Maintainers usually reply within 1 day
-
[Json-Schema] Nested resources lost externalDoc and description dataPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 3/5 1-2 days Newbie friendliness 68/100
api-platform/core#8721 ·
Maintainers usually reply within 1 day
-
JSON:API: 404/405 router errors are empty, and error objects have a non-spec `type` memberPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 3/5 1-2 days Newbie friendliness 50/100
api-platform/core#8704 ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
api-platform/core#8703 ·
Maintainers usually reply within 1 day
All issues in api-platform/core
Similar issues
-
sync-en
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
[Sync EN] Document Error thrown by shm_* functions on a detached segment (#5975)Possibly taken A pull request linked to this issue is open or already merged. Opensync-en
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 3 days
-
Перевод устарел
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
新版升级一下对应的依赖可以么Open
Difficulty 2/5 1-3 hours Newbie friendliness 62/100