PHP + Actors == Dapr
Tags: php, actors, dapr, tutorial
Published: 2021-02-26 20:33:12

> Disclosure: I'm the PHP SDK maintainer

PHP and actors?

![wait-what.gif](https://media.giphy.com/media/Su7Bo9MMnyl0ZE263z/giphy.gif)

> Dapr is a portable, event-driven runtime that makes it easy for any developer to build resilient, stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks.

Dapr (**D**istributed **Ap**plication **R**untime) supports PHP, and in my humble opinion, actors are the coolest part about Dapr. Dapr implements the [virtual actor pattern](https://www.microsoft.com/en-us/research/project/orleans-virtual-actors/). The virtual actor pattern essentially (and this is greatly simplified!) means that any instance of an actor will be "single-threaded" and doesn't exist until it's called. The single-threaded bit sounds exactly like what I'm used to with PHP, and the "doesn't exist until it's called" sounds like something I'd read in the PHP docs. 🤣

OK, it's a bit more than that, and I highly suggest reading Dapr's [documentation](https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/) about it to get a better overview. If you're curious what these actors look like in PHP, stick around.

## Setting up the project

With the [php-sdk](https://github.com/dapr/php-sdk), you create a new project with `composer init` and run `composer require dapr/php-sdk` to install it.

For the project, you'll create a simple actor that counts and call it `Count`, with two methods: a method to get the current count and increment the count by a given amount.

## State and stuff

The actor's state is pretty straightforward. Check it out:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=CountState.php %}

As you can see, the state has a single key: `$count` that is an integer with a default value of `0`. You only have to extend the `ActorState` abstract class to use it in an actor (to provide a transaction). That's all you need!

## Creating the interface

If you want to use a PHP client, you'll need to define an interface for the actor:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=CountInterface.php %}

You can see the two methods and an attribute. The `#[DaprType()]` attribute specifies what the name of the actor is according to Dapr, which need not match what we call it in our code.

## Implementing the interface

And now, to implement the interface, it's as simple as you'd expect:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=CountActor.php %}

Here you can see the familiar `#[DaprType()]` attribute, a constructor that takes the actor's id and its private state. From there, the actual implementation is straightforward as we're returning the state and/or incrementing it.

## Testing

Of course, the code only looks simple. We should verify that there aren't any shenanigans and it works exactly as you expect. To do that, install `phpunit` with `composer require --dev phpunit/phpunit` and write up some unit tests:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=CountTest.php %}

None of the underlying framework is initialized for unit tests, so you can assert that given a starting state, the resulting state is what you expect.

You can now test this with `phpunit`, which you can add as a script so that testing is as easy as `composer test`.

## Tying it all together

Now that there's an actor implementation, the runtime needs to be configured and initialized. To configure the runtime, create a `config.php` file:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=config.php %}

<small>You can find all the supported keys [in the docs too](https://docs.dapr.io/developing-applications/sdks/php/#configure-your-service).</small>

There's a relatively simple and opinionated runtime provided out-of-the-box that uses [PHP-DI](https://php-di.org/). However, you can bring your own DI container, router, or an entire framework.

Time to initialize the runtime:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=index.php %}

Here you can see a simple `error_log()` that outputs the current URI and then the `App::create()` call where we configure the DI container.

Finally, `$app->start()` is called, and the built-in routes are handled.

## Making a client

Of course, this is no fun if you can't remotely call the service. So, using `psysh`, a repl can be provided:

{% gist https://gist.github.com/withinboredom/a2977ca60a2616d7cf090ccef8da1027 file=repl.php %}

## Running it

### The server

{% asciinema 394730 %}

### The client

{% asciinema 394731 %}

## Errata

The code in this post lives here:

{% github withinboredom/example-actors %}

You can even fork and fiddle with the unit tests on the [php sandbox](https://phpsandbox.io/n/srjds).