承接 sympress/event-dispatcher 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

sympress/event-dispatcher

Composer 安装命令:

composer require sympress/event-dispatcher

包简介

MU plugin for class-based WordPress events, listeners, and subscribers.

README 文档

README

Standalone WordPress MU-plugin for Symfony-style class-based events, listeners, and subscribers on top of native WordPress hooks and filters.

Requirements

  • WordPress 6.9
  • PHP 8.5

Design Goals

  • Symfony-like listener and subscriber API
  • PSR-14 compliant dispatcher and listener provider contracts
  • Class-based WordPress action and filter events
  • Immutable event value objects
  • Native WordPress hook bridge with one callback registration per event class
  • Container-friendly bootstrap for SymPress\Kernel\App
  • Enterprise-focused QA with PHPUnit, PHPStan, PHPCS, and PHP CS Fixer

Installation

  1. Place the package in wp-content/mu-plugins/event-dispatcher/.
  2. Ensure Composer autoloading is available.
  3. Add a root MU loader file because WordPress does not autoload subdirectories.
<?php

declare(strict_types=1);

require WPMU_PLUGIN_DIR . '/event-dispatcher/event-dispatcher.php';

Registering Subscribers

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Contract\EventSubscriberInterface;
use SymPress\EventDispatcher\Event\AbstractFilterEvent;

final readonly class UploadMimesEvent extends AbstractFilterEvent
{
    public function __construct(
        array $mimes,
        public int $userId,
    )
    {
        parent::__construct($mimes, [$mimes, $userId]);
    }

    public static function hookName(): string
    {
        return 'upload_mimes';
    }

    public static function acceptedArgs(): int
    {
        return 2;
    }

    public static function fromHookArguments(array $arguments): static
    {
        return new self(
            (array) ($arguments[0] ?? []),
            (int) ($arguments[1] ?? 0),
        );
    }

    /**
     * @return array<string, string>
     */
    public function mimes(): array
    {
        /** @var array<string, string> $mimes */
        $mimes = $this->value();

        return $mimes;
    }

    public function withAllowed(string $extension, string $mimeType): self
    {
        $mimes = $this->mimes();
        $mimes[$extension] = $mimeType;

        return new self($mimes, $this->userId);
    }
}

final class UploadSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            UploadMimesEvent::class => ['allowSvg', 100],
        ];
    }

    public function allowSvg(UploadMimesEvent $event): UploadMimesEvent
    {
        return $event->withAllowed('svg', 'image/svg+xml');
    }
}

add_action(
    EventSystem::REGISTER_HOOK,
    static function ($dispatcher): void {
        $dispatcher->addSubscriber(new UploadSubscriber());
    },
);

Filter Strategy

Filter sind bereits implementiert.

  • Das erste native Filter-Argument ist der Payload des Events.
  • Filter-Events sind immutable Value Objects.
  • Listener mutieren nie in-place, sondern geben bei Änderungen eine neue Event-Instanz zurück.
  • Der letzte Event-Stand wird über toHookResult() zurück in den nativen WordPress-Filter geschrieben.

Kurz gesagt: apply_filters() bleibt nach außen WordPress-kompatibel, intern arbeitest du aber mit sauberen immutable Events.

Registering Attribute-Based Listeners

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Attribute\AsEventListener;
use SymPress\EventDispatcher\Attribute\AsEventSubscriber;

#[AsEventSubscriber(event: UploadMimesEvent::class, method: 'allowSvg', priority: 100)]
final class UploadListener
{
    public function allowSvg(UploadMimesEvent $event): UploadMimesEvent
    {
        return $event->withAllowed('svg', 'image/svg+xml');
    }

    #[AsEventListener(priority: 0)]
    public function allowWebp(UploadMimesEvent $event): UploadMimesEvent
    {
        return $event->withAllowed('webp', 'image/webp');
    }
}

add_action(
    EventSystem::REGISTER_HOOK,
    static function ($dispatcher): void {
        $dispatcher->register(new UploadListener());
    },
);

Use Cases

1. WordPress Filter: MIME Types erweitern

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Event\AbstractFilterEvent;

final readonly class UploadMimesEvent extends AbstractFilterEvent
{
    /**
     * @param array<string, string> $mimes
     */
    public function __construct(
        array $mimes,
        public int $userId,
    ) {
        parent::__construct($mimes, [$mimes, $userId]);
    }

    public static function hookName(): string
    {
        return 'upload_mimes';
    }

    public static function acceptedArgs(): int
    {
        return 2;
    }

    public static function fromHookArguments(array $arguments): static
    {
        return new self(
            (array) ($arguments[0] ?? []),
            (int) ($arguments[1] ?? 0),
        );
    }

    /**
     * @return array<string, string>
     */
    public function mimes(): array
    {
        /** @var array<string, string> $mimes */
        $mimes = $this->value();

        return $mimes;
    }

    public function withAllowed(string $extension, string $mimeType): self
    {
        $mimes = $this->mimes();
        $mimes[$extension] = $mimeType;

        return new self($mimes, $this->userId);
    }
}

add_action(
    EventSystem::REGISTER_HOOK,
    static function ($dispatcher): void {
        $dispatcher->addListener(
            UploadMimesEvent::class,
            static fn (UploadMimesEvent $event): UploadMimesEvent => $event->withAllowed(
                'svg',
                'image/svg+xml',
            ),
            100,
        );
    },
);

2. WordPress Action: Typed save_post

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Event\AbstractActionEvent;

final readonly class SavePostEvent extends AbstractActionEvent
{
    public function __construct(
        public int $postId,
        public bool $update,
    ) {
        parent::__construct([$postId, $update]);
    }

    public static function hookName(): string
    {
        return 'save_post';
    }

    public static function acceptedArgs(): int
    {
        return 2;
    }

    public static function fromHookArguments(array $arguments): static
    {
        return new self(
            (int) ($arguments[0] ?? 0),
            (bool) ($arguments[1] ?? false),
        );
    }
}

add_action(
    EventSystem::REGISTER_HOOK,
    static function ($dispatcher): void {
        $dispatcher->addListener(
            SavePostEvent::class,
            static function (SavePostEvent $event): void {
                if (!$event->update) {
                    return;
                }

                error_log('Updated post: ' . $event->postId);
            },
        );
    },
);

3. Attribute-Based Hook Service

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Attribute\AsEventListener;
use SymPress\EventDispatcher\Attribute\AsEventSubscriber;

#[AsEventSubscriber(event: SavePostEvent::class, method: 'onSavePost')]
final class EditorialWorkflowListener
{
    public function onSavePost(SavePostEvent $event): void
    {
        if (!$event->update) {
            return;
        }

        error_log('Editorial workflow for post ' . $event->postId);
    }

    #[AsEventListener(priority: 100)]
    public function allowSvg(UploadMimesEvent $event): UploadMimesEvent
    {
        return $event->withAllowed('svg', 'image/svg+xml');
    }
}

add_action(
    EventSystem::REGISTER_HOOK,
    static function ($dispatcher): void {
        $dispatcher->register(new EditorialWorkflowListener());
    },
);

4. Pure Domain Event Without WordPress Hook

use SymPress\EventDispatcher\Application\EventSystem;
use SymPress\EventDispatcher\Event\AbstractEvent;

final readonly class OrderPaidEvent extends AbstractEvent
{
    public function __construct(
        public int $orderId,
        public string $paymentReference,
    ) {
    }
}

$dispatcher = EventSystem::getInstance()->getDispatcher();

$dispatcher->addListener(
    OrderPaidEvent::class,
    static function (OrderPaidEvent $event): void {
        error_log(
            sprintf(
                'Order %d paid with reference %s',
                $event->orderId,
                $event->paymentReference,
            ),
        );
    },
);

$dispatcher->dispatch(new OrderPaidEvent(42, 'PAY-123'));

5. Invokable Attribute Listener for Domain Events

use SymPress\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: OrderPaidEvent::class, priority: 50)]
final class SendInvoiceListener
{
    public function __invoke(OrderPaidEvent $event): void
    {
        error_log('Send invoice for order ' . $event->orderId);
    }
}

add_action(
    EventSystem::READY_HOOK,
    static function ($dispatcher): void {
        $dispatcher->register(new SendInvoiceListener());
    },
);

Runtime Model

  • EventSystem owns the singleton bootstrap lifecycle.
  • HookEventDispatcher implements PSR-14 dispatching and listener resolution plus the WordPress hook bridge.
  • HookEventDispatcher::register() resolves AsEventListener and AsEventSubscriber on classes and methods.
  • AbstractActionEvent maps native actions to typed event objects.
  • AbstractFilterEvent maps native filters to immutable event value objects; listeners return a new event instance when they change data.
  • Filter listeners should return the same event unchanged or a new event instance of the same class.
  • Subscribers follow the Symfony event subscriber format.

Development

composer test
composer cs:analyze
composer cs

License

This package is licensed under GPL-2.0-or-later.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: GPL-2.0-or-later
  • 更新时间: 2026-06-10

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固