Skip to content

PHP & event

PHP syntax doesn't offer anything special for events. Most modern PHP libraries therefore implement something to provide such functionality. WordPress has its add_filter and Symfony/Laravel have event listeners.

In .NET/C#, we have event class members.

Goal

When extending a .NET application with PHP code, we would like to take advantage of C# events. It doesn't make sense to declare a new event in PHP code, but it would be useful to register a PHP function to an existing event.

Instead of the += operator used in C#, we need to take a different approach. Also, we need to alter the PeachPie/PHP runtime, so it all works seamlessly.

We decided to implement an API similar to TypeScript's events. We'll have a "fake" method add( callback ) on the event class member, which returns a disposable object. Upon disposal, the registered callback will be removed.

C# Example

In C#, event (i.e. OnMessage) is defined as depicted below:

class MyListener {
    public event EventHandler OnMessage;
}

In order to register a callback to the event, we use the += operator:

var obj = new MyListener();
obj.OnMessage += (sender, arg) => {
    Console.WriteLine("Got a message!");
};

The event can be invoked by its owner only. This means only in the C# class:

class MyListener {
    public event EventHandler OnMessage;

    void GotMessage(string message) {
        this.OnMessage(this, new MessageEventArgs(message));
    }
}

Note: of course it's possible to use reflection to invoke an event outside its class context.

PHP Example

Having the same C# class MyListener as in the example above, we can register a PHP callable as depicted below:

$obj = new MyListener;
$obj->OnMessage->add(
    function ($sender, $arg) {
        echo "Got a message!";
    }
);

How It Works

The runtime knows that OnMessage is not a property, but an event. Before PeachPie version 1.2, it simply ignored the event class members. Now it sees them, but it fakes accessing them.

When the PeachPie runtime generates a read access, it returns an instance of ClrEvent class (implemented in Peachpie.Runtime.dll). This class has a method add( callback ) which converts the given callback argument to a matching CLR delegate. Then, using .NET reflection, it registers it as an event handler of the original CLR event.

The implementation (on GitHub) is straightforward.

It allows PHP code to register its callbacks to C# events without altering syntax, and C# code to invoke PHP functions without even noticing it's PHP. It all happens under the cover.

Unregistering Callback

Unregistering a callback cannot be done with something like remove( callback ). Simply because the callback is always a different object, and by default, the CLR event remembers its registered callbacks as a list of delegate instances.

PHP allows to have almost anything as a callable value (usually a string or an array or a class with the __invoke method) - this is converted to a CLR delegate instance - but always a new one - so removing such newly created delegate would fail.

Instead, we chose the approach from the TypeScript language. add( callback ) returns a disposable object, which remembers the delegate instance used originally, and safely unregisters that one upon disposal.

$obj = new MyListener;
$hook = $obj->OnMessage->add(function ($sender, $arg) {});

// remove the generated `delegate` to anonymous PHP `function`
// from the `OnMessage` event:
$hook->dispose();

Step Through the Code

Let's see how the debugging experience looks like when stepping through PHP code using C# events:

debugging C# event used from PHP

See More

Become a sponsor on Patreon

This new feature, and much more, is currently available only to our sponsors on Patreon. Please consider becoming a member to help us with the development of PeachPie and get early access to features such as this one, as well as release builds and nightly builds, and much more.