{"id":13823871,"url":"https://github.com/tpunt/phactor","last_synced_at":"2025-04-15T04:24:23.828Z","repository":{"id":58936730,"uuid":"75304165","full_name":"tpunt/phactor","owner":"tpunt","description":"An implementation of the Actor model for PHP","archived":false,"fork":false,"pushed_at":"2018-04-21T15:21:50.000Z","size":397,"stargazers_count":62,"open_issues_count":8,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-28T15:51:49.008Z","etag":null,"topics":["actor-model","concurrency","php","php-extension","reactive-programming"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tpunt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-01T15:11:43.000Z","updated_at":"2025-03-07T20:07:23.000Z","dependencies_parsed_at":"2022-09-19T01:10:44.246Z","dependency_job_id":null,"html_url":"https://github.com/tpunt/phactor","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpunt%2Fphactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpunt%2Fphactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpunt%2Fphactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpunt%2Fphactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tpunt","download_url":"https://codeload.github.com/tpunt/phactor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249004871,"owners_count":21196970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actor-model","concurrency","php","php-extension","reactive-programming"],"created_at":"2024-08-04T09:00:47.648Z","updated_at":"2025-04-15T04:24:23.810Z","avatar_url":"https://github.com/tpunt.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# The Phactor Extension\n\nThis extension seeks to provide an implementation of the [Actor model](https://en.wikipedia.org/wiki/Actor_model) in PHP. Due to the size and complexity of this project, I am making an early-stage release of it, in hopes that it will attract the help of other developers.\n\nRelevant reading:\n - [Actor model (Wikipedia)](https://en.wikipedia.org/wiki/Actor_model)\n - [The Reactive Manifesto](https://www.reactivemanifesto.org)\n\nQuick details:\n - Uses an N:M threading model\n - Cooperative scheduling of actors\n - Actors have an initial cost of ~500 bytes\n\nRequirements:\n - A ZTS version of PHP 7.2 (master branch is currently unsupported)\n - An x86-64 Unix-based OS\n - The Pthread library\n\nMajor goals:\n - Stabilise things (ongoing)\n - Implement supervision trees (in progress)\n - Implement remote actors (to do)\n - Implement internal non-blocking APIs (to do)\n\nHow you can help:\n - Resolve any of the open issues of this repo\n - Open new issues for: bugs, general feedback, support for a platform, ideas discussion, etc\n\nThis extension has been tested on OS X (Yosemite and Sierra), Centos 7 (64bit), and Ubuntu 14.04 and 16.04 (64bit).\n\n## The Basics\n\nEach actorised application will have its own actor system. This actor system is responsible for managing the actors within its system, along with configuring it.\n\nEach actor has a single entry point: `Actor::receive()`. This method will be automatically invoked upon actor creation, where the actor will then be able to begin handling messages from its mailbox. Once the `Actor::receive()` method has finished executing, the actor will be destroyed. This means that in order to keep an actor alive, the `Actor::receive()` method needs to keep executing. This can be achieved by invoking the `Actor::receiveBlock()` method (perhaps in tandem with looping) to voluntarily interrupt the actor, placing it in an idle state where it will wait for new messages to arrive.\n\nTo shut down an actor system, the `ActorSystem::shutdown()` static method must be invoked (it can be called from anywhere in the application).\n\nThe following script demonstrates the basic flow of execution when using an actor:\n```php\n\u003c?php\n\nuse phactor\\{ActorSystem, Actor, ActorRef};\n\n$actorSystem = new ActorSystem();\n\nclass Test extends Actor\n{\n    // internal actor state\n    private $str;\n\n    public function __construct(string $param)\n    {\n        $this-\u003estr = $param;\n        // send a message to itself\n        $this-\u003esend('actor name', \"{$param} {$param}\");\n    }\n\n    // automatically invoked (at an arbitrary time after __construct)\n    public function receive()\n    {\n        var_dump($this-\u003estr); // string(5) \"arg 1\"\n\n        $message = $this-\u003ereceiveBlock(); // wait here for a new message\n\n        var_dump($message); // string(11) \"arg 1 arg 1\"\n\n        ActorSystem::shutdown(); // shut down the actor system\n\n        // end of receive() method - the actor will be destroyed (asynchronously)\n    }\n}\n\n// spawn the new actor - executes the actor's __construct() and receive() methods\nnew ActorRef(Test::class, ['arg 1'], 'actor name');\n```\n\n## API\n\n```php\n\u003c?php\n\nnamespace Phactor;\n\nfinal class ActorSystem\n{\n    public function __construct(int $threadCount = $coreCount + 10); // 10 additional threads\n\n    public static function shutdown(void) : void;\n\n    public function block(void) : void; // should be ignored (used internally)\n}\n\nfinal class ActorRef\n{\n    private $ref;\n    private $name = '';\n\n    public function __construct(string $actorClassName[, array $ctorArgs[, string $actorName]]);\n\n    // for debugging only - do not use this for the sender (since it will be treated as the actor name)\n    public function getRef(void) : string;\n\n    public function getName(void) : string;\n\n    // for retrieving a reference to an actor object\n    public static function fromActor(Actor $actor) : ActorRef;\n}\n\nabstract class Actor\n{\n    // the public API and entry point of an actor\n    public abstract function receive(void) : void;\n\n    // send a message (asynchronously) to an actor\n    protected final function send(ActorRef $toActorRef, mixed $message) : void;\n    protected final function send(string $toActorName, mixed $message) : void;\n\n    // wait for a message (cooperatively yields the actor)\n    protected final function receiveBlock(void) : mixed;\n}\n\nfinal class Supervisor\n{\n    // supervision strategy\n    public const ONE_FOR_ONE = 0;\n\n    public function __construct(ActorRef|string $supervisor[, int $strategy = Supervisor::ONE_FOR_ONE]);\n\n    // Add a pre-existing actor to the group of supervised workers\n    public function addWorker(ActorRef|string $worker) : void;\n\n    // Creates a new actor, links it to the supervisor, and return its actor reference.\n    // This should be used if the actor's constructor may throw an exception\n    public function newWorker(string $actorClass[, array $ctorArgs[, string $actorName]]) : ActorRef;\n}\n```\n\nAll messages and actor constructor arguments (the second parameter of `ActorRef::__construct` and `Supervisor::newWorker`) will be serialised.\n\n## Examples\n\nAn anonymous actor sending a message to itself:\n```php\n\u003c?php\n\nuse phactor\\{ActorSystem, Actor, ActorRef};\n\n$actorSystem = new ActorSystem();\n\nclass Test extends Actor\n{\n    public function __construct()\n    {\n        // since this is an anonymous actor, its reference must be fetched in\n        // order to send a message to itself\n        $actorRef = ActorRef::fromActor($this);\n        $this-\u003esend($actorRef, 123);\n    }\n\n    public function receive()\n    {\n        var_dump($this-\u003ereceiveBlock()); // int(123)\n        ActorSystem::shutdown();\n    }\n}\n\n// spawn a new anonymous actor\nnew ActorRef(Test::class);\n```\n\nAn actor sending messages to itself in a synchronous execution style:\n```php\n\u003c?php\n\nuse phactor\\{ActorSystem, Actor, ActorRef};\n\n$actorSystem = new ActorSystem();\n\nclass Test extends Actor\n{\n    private $messages = ['something 1', 'something 2', 'something 3', 'shutdown'];\n\n    public function __construct(string $s)\n    {\n        $this-\u003esend('testing', $s);\n    }\n\n    public function receive()\n    {\n        $i = 0;\n\n        do {\n            // block here, waiting for a new message to be received\n            $message = $this-\u003ereceiveBlock();\n            var_dump($message);\n\n            if ($message === 'shutdown') {\n                break;\n            }\n\n            // send a message to itself again\n            $this-\u003esend('testing', $this-\u003emessages[$i++]);\n        } while (true);\n\n        // shut down the actor system - without this, the PHP process will not stop\n        ActorSystem::shutdown();\n    }\n}\n\n// spawn the new actor\nnew ActorRef(Test::class, ['something 0'], 'testing');\n```\n\nUsing a supervisor with the `ONE_FOR_ONE` supervision strategy to handle the crashing of an actor (by automatically restarting it):\n```php\n\u003c?php\n\nuse phactor\\{ActorSystem, Actor, ActorRef, Supervisor};\n\n$as = new ActorSystem(1);\n\n// the supervisor\nclass A extends Actor\n{\n    public function receive(){$this-\u003ereceiveBlock();}\n}\n\n// the worker\nclass B extends Actor\n{\n    private static $i = 0;\n\n    public function __construct()\n    {\n        if (self::$i === 0) {\n            ++self::$i; // make it crash once here\n            var_dump('Crashing B in __construct()');\n            throw new exception();\n        }\n    }\n\n    public function receive()\n    {\n        if (self::$i === 1) {\n            ++self::$i; // make it crash once here\n            var_dump('Crashing B in receive()');\n            throw new exception();\n        }\n\n        $this-\u003esend('b', 1); // send itself a message to resume after the following interrupt\n\n        $this-\u003ereceiveBlock(); // interrupt and wait for the int(1) message to arrive\n\n        if (self::$i === 2) {\n            ++self::$i; // make it crash once here\n            var_dump('Crashing B in receive() again');\n            throw new exception();\n        }\n\n        var_dump('Made it!');\n\n        ActorSystem::shutdown();\n    }\n}\n\n$a = new ActorRef(A::class, [], 'a');\n$s = new Supervisor($a);\n\n$b = $s-\u003enewWorker(B::class, [], 'b');\n```\n\n**Note:** whilst static variables have been used in the above example (to crash an actor a controlled number of times), static state is very unpredictable and should be avoided in such concurrent applications.\n\nWhenever an actor is restarted, a new actor object will be created (so the `__construct` and `receive` methods will both be invoked again). The restarted actor will have the same reference and (if provided) the same name.\n\nBecause the actor could crash inside of its constructor, we created the new actor using the `Supervisor::newWorker()` method. This enables for an actor to be automatically restarted if it failed to construct itself (currently, the restart limit is hard coded to `5` - this will become configurable in future).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpunt%2Fphactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftpunt%2Fphactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpunt%2Fphactor/lists"}