jul6art/push-bundle

Symfony real-time notification bundle

Maintainers

Package info

github.com/jul6art/push-bundle

Type:symfony-bundle

pkg:composer/jul6art/push-bundle

Transparency log

Statistics

Installs: 24

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

v2.0.1 2026-08-17 22:45 UTC

README

logo dev in the hood

License Version

jul6art/push-bundle

Symfony real-time notification bundle

⚠️ Work in progress so keep calm. The good news: this is maintained!

Requirements

  • php ^8.5
  • symfony ^7.4 || ^8.0
  • jul6art/core-bundle ^2.0
  • mercure (symfony/mercure-bundle ^0.3)

Installation

composer require jul6art/push-bundle

Then Download the mercure hub depending on your operating system and install it in the root of your project. For each release, the assets section list operating systems implementations. The folder must contain the mercure bin. Rename this folder mercure.

EventSource Polyfill

npm install event-source-polyfill

and import it on client side to make push works on IE and Edge

Generate new JWT token (Optionnal)

Go to jwt.io and put your future mercure secret key (default it's !ChangeMe!) in the verify signature textarea and this array in the payload textarea

{
    "mercure": {
        "publish": []
    }
}

Because the array is empty, the Symfony app will only be authorized to publish public updates (see the authorization section of symfony/mercure-bundle for further information).

THen store the generated token in your .env file as MERCURE_JWT_TOKEN parameter

Start mercure server

The default token is signed with the secret key: !ChangeMe!

CORS_ALLOWED_ORIGINS is the client URL and port. It can be * or a list of domains ADDR is the server url and 3000 is the port for mercure server

JWT_KEY='!ChangeMe!' ADDR='localhost:3000' ALLOW_ANONYMOUS=1 CORS_ALLOWED_ORIGINS="http://localhost:80" ./mercure/mercure

⚠️ By default, push messages are async so you need to launch a crawler in a terminal to dequeue messages and send it

bin/console messenger:consume async_priority_high --time-limit 600

Using with api-platform

Server side

use ApiPlatform\Metadata\ApiResource;

#[ApiResource(mercure: true)]
class SomeTopic
{
}

Client side

import {EventSourcePolyfill} from "../polyfills/Polyfills";

export default class MercureProvider {
    provide = () => {
        const publishUrl = new URL('http://publish.url:3000/hub');
        publishUrl.searchParams.append("topic", "/some_topic");
        publishUrl.searchParams.append("topic", "/some_topic/{id}");

        const es = new EventSourcePolyfill(publishUrl, {
            headers: {
                'Authorization': 'Bearer ' + YOUR_MERCURE_JWT_TOKEN
            }
        });
        es.onmessage = e => {
            const data = JSON.parse(e.data);

            const regex = /\/api\/(?<type>\w+)\//gm;

            const match = regex.exec(data['@id']);

            if (null !== match) {
                const event = new CustomEvent(match.groups.type, { "data": data });
                document.dispatchEvent(event);
            }

        };
    }
};

// somewhere else
document.addEventListener('...', function() {
  // what you need
});

Using without api-platform

Server side

use Jul6Art\PushBundle\Service\Traits\PusherAwareTrait;

class SomeService
{
    use PusherAwareTrait;

    public function notify(): void
    {
        $this->pusher->push('/some/topic', ['test' => true]);
    }
}

Sync (Optionnal)

push:
    async: false

Other messenger messages (Optionnal)

push:
    routing:
        'PathToSomeAsyncMessage': async_priority_high

Can be async_priority_high or async_priority_low or sync

Asyncable Attribute (Optionnal)

The Asyncable annotation is now a PHP attribute: Jul6Art\PushBundle\Attribute\Asyncable.

My Entity

use App\Event\MyClassEvent;
use Doctrine\ORM\Mapping as ORM;
use Jul6Art\PushBundle\Attribute\Asyncable;

#[ORM\Entity(repositoryClass: MyClassRepository::class)]
#[Asyncable(eventClass: MyClassEvent::class)]
class MyClass
{
}

My EntityEvent

The event class must accept the entity as its only constructor argument and must extend Jul6Art\CoreBundle\Event\AbstractEvent.

<?php

declare(strict_types=1);

namespace App\Event;

use App\Entity\MyClass;
use Jul6Art\CoreBundle\Event\AbstractEvent;

class MyClassEvent extends AbstractEvent
{
    public const string CREATED = 'event.my_class.created';
    public const string DELETED = 'event.my_class.deleted';
    public const string EDITED = 'event.my_class.edited';
    public const string VIEWED = 'event.my_class.viewed';

    public function __construct(private MyClass $myClass)
    {
        parent::__construct();
    }

    public function getMyClass(): MyClass
    {
        return $this->myClass;
    }

    public function setMyClass(MyClass $myClass): static
    {
        $this->myClass = $myClass;

        return $this;
    }
}

All actions in listeners who listen these event class consts will be async

You can also specify which doctrine events you want to track

#[ORM\Entity(repositoryClass: MyClassRepository::class)]
#[Asyncable(eventClass: MyClassEvent::class, events: ['postLoad', 'postPersist'])]
class MyClass
{
}

Available events are

  • postLoad
  • postPersist
  • postUpdate
  • preRemove

Quality assurance

composer qa           # coding standards, Rector, static analysis and tests
composer test         # PHPUnit
composer phpstan      # PHPStan, level max
composer cs           # PHP-CS-Fixer, writes the fixes
composer rector       # Rector, writes the fixes

License

The Push Bundle is open-sourced software licensed under the MIT license.

© 2026 jul6art