|
7 | 7 | [](https://github.com/Thavarshan/matrix/actions/workflows/laravel-pint.yml)
|
8 | 8 | [](https://packagist.org/packages/jerome/matrix)
|
9 | 9 |
|
10 |
| -**Matrix** is a PHP library that brings asynchronous, non-blocking task execution to PHP. Inspired by the JavaScript `async`/`await` pattern, Matrix leverages `pcntl_fork()` and ReactPHP promises to run tasks in parallel child processes. The result is a clean, promise-based API enabling concurrency without blocking your main code. |
| 10 | +Matrix is a PHP library that provides asynchronous, non-blocking functionality inspired by JavaScript's `async`/`await` syntax. With Matrix, you can handle asynchronous tasks and manage concurrency in PHP using promises and a familiar, intuitive API. |
11 | 11 |
|
12 |
| -Matrix’s `async()` helper returns a promise-like object (thanks to `AsyncPromise`) which provides familiar `then()` and `catch()` methods, making asynchronous tasks feel as natural as JavaScript promises. |
| 12 | +## Why Matrix? |
13 | 13 |
|
14 |
| ---- |
15 |
| - |
16 |
| -## **Why Matrix?** |
17 |
| - |
18 |
| -Matrix aims to simplify parallel execution of potentially long-running or CPU-intensive operations in PHP. Instead of waiting for each task to complete sequentially, you can spawn child processes, run tasks concurrently, and handle their results asynchronously. |
| 14 | +Matrix simplifies the execution of asynchronous tasks in PHP by combining promises with ReactPHP's event loop. It allows for non-blocking execution, error propagation, and easy integration with existing PHP projects. |
19 | 15 |
|
20 |
| -- **JavaScript-like async API**: The `async()` function returns promise-like objects with `then()` and `catch()`. |
21 |
| -- **Built on `pcntl_fork()` and ReactPHP**: Achieves true parallelism by using multiple processes, integrated with React’s event loop for non-blocking IO. |
22 |
| -- **Error Propagation**: Exceptions thrown in child processes are serialized and rethrown as exceptions in the parent, simplifying error handling. |
23 |
| -- **Non-blocking Concurrency**: Your main code continues running while tasks proceed in parallel, improving efficiency for CPU-bound or blocking tasks. |
| 16 | +### Key Features |
24 | 17 |
|
25 |
| -### **Key Features** |
| 18 | +- **JavaScript-like API**: Use `async()` and `await()` for straightforward asynchronous programming. |
| 19 | +- **Built with ReactPHP**: Ensures non-blocking execution using ReactPHP's event loop. |
| 20 | +- **Error Handling**: Catch and handle exceptions seamlessly with `.catch()` or `try-catch`. |
| 21 | +- **Automatic Loop Management**: The event loop runs automatically to handle promise resolution. |
26 | 22 |
|
27 |
| -- **Familiar `then()` and `catch()` interface** for handling asynchronous results. |
28 |
| -- **Parallel execution via processes**: Offload heavy tasks to separate child processes. |
29 |
| -- **Automatic error forwarding**: Rethrow child exceptions in the parent for consistent error handling. |
30 |
| -- **Easy integration with existing code**: Just wrap your function calls with `async()` and chain handlers as needed. |
31 |
| - |
32 |
| ---- |
33 |
| - |
34 |
| -## **Installation** |
| 23 | +## Installation |
35 | 24 |
|
36 |
| -Install Matrix via Composer: |
| 25 | +Install via Composer: |
37 | 26 |
|
38 | 27 | ```bash
|
39 | 28 | composer require jerome/matrix
|
40 | 29 | ```
|
41 | 30 |
|
42 |
| -Ensure the following PHP extensions are enabled: |
| 31 | +Ensure the following extensions are enabled: |
43 | 32 |
|
44 |
| -- `pcntl` |
45 | 33 | - `sockets`
|
46 | 34 |
|
47 |
| -Matrix also relies on ReactPHP promises and event loop, installed automatically via Composer. |
48 |
| - |
49 |
| ---- |
50 |
| - |
51 |
| -## ⚠️ **Important Notice: Avoid Using `pcntl_fork()` in Web Server Environments** |
52 |
| - |
53 |
| -**Matrix PHP** leverages `pcntl_fork()` to achieve asynchronous, parallel task execution. However, it's crucial to understand the limitations and best practices when deploying Matrix PHP, especially concerning the use of `pcntl_fork()` within different server environments. |
54 |
| - |
55 |
| -### 🚫 **Why Avoid `pcntl_fork()` in Web Servers?** |
56 |
| - |
57 |
| -- **PHP Thread Safety:** |
58 |
| - PHP is **not inherently thread-safe**. Utilizing `pcntl_fork()` within a multi-threaded web server environment, such as Apache configured with the **Worker MPM**, can lead to unpredictable behavior, resource leaks, and potential server instability. |
59 |
| - |
60 |
| -- **Apache Worker MPM Issues:** |
61 |
| - Running PHP as a module in an Apache installation configured with the **Worker MPM** is **not recommended**. The **Worker MPM** uses multiple threads to handle requests, which conflicts with PHP's thread safety limitations. |
62 |
| - |
63 |
| -### ✅ **Recommended Setup** |
64 |
| - |
65 |
| -- **Use Prefork MPM with Apache:** |
66 |
| - For a stable and compatible environment, it's advised to configure Apache with the **Prefork MPM** module when integrating with PHP. The **Prefork MPM** handles each request in a separate process rather than threads, aligning with PHP's operational model and avoiding thread safety issues. |
67 |
| - |
68 |
| - **References:** |
69 |
| - - [PHP Manual: Installing PHP on Unix systems with Apache](http://php.net/install.unix.apache2.php) |
70 |
| - - [Steve Kallestad's Guide on Apache Worker MPM with PHP](http://www.stevekallestad.com/blog/apache_worker_mpm_with_php.html) |
| 35 | +Matrix relies on ReactPHP promises and event loop, which are installed automatically via Composer. |
71 | 36 |
|
72 |
| -### 🔧 **Best Practices** |
| 37 | +## API Overview |
73 | 38 |
|
74 |
| -- **Command-Line and Background Processes:** |
75 |
| - Matrix PHP is best suited for **command-line interfaces (CLI)**, background workers, or daemonized applications where process forking is safe and manageable. |
| 39 | +### `async(callable $callable): PromiseInterface` |
76 | 40 |
|
77 |
| -- **Web Applications:** |
78 |
| - For web-based deployments, prefer using **event-driven asynchronous models** provided by libraries like **ReactPHP**, which do not rely on process forking and are more compatible with typical web server configurations. |
| 41 | +Wraps a callable into an asynchronous function that returns a promise. |
79 | 42 |
|
80 |
| -### 🛡️ **Conclusion** |
81 |
| - |
82 |
| -To ensure the stability, performance, and reliability of your PHP applications using **Matrix PHP**, adhere to the following guidelines: |
83 |
| - |
84 |
| -1. **Avoid using `pcntl_fork()` in web server contexts**, especially with multi-threaded server modules like Apache's Worker MPM. |
85 |
| -2. **Configure Apache with Prefork MPM** when integrating with PHP to prevent thread safety issues. |
86 |
| -3. **Leverage event-driven asynchronous approaches** (e.g., ReactPHP) for web applications to achieve concurrency without the complexities of process forking. |
87 |
| - |
88 |
| -By following these best practices, you can harness the full potential of **Matrix PHP** while maintaining a robust and stable server environment. |
89 |
| - |
90 |
| ---- |
91 |
| - |
92 |
| -## **Asynchronous API Inspired by JavaScript** |
93 |
| - |
94 |
| -Matrix provides a helper called `async()` that returns an `AsyncPromise`, mimicking JavaScript’s promise usage. |
95 |
| - |
96 |
| -**Example:** |
| 43 | +Example: |
97 | 44 |
|
98 | 45 | ```php
|
99 | 46 | use function async;
|
100 | 47 |
|
101 |
| -async(fn () => 'Task result') |
102 |
| - ->then(fn($result) => print($result . PHP_EOL)) |
103 |
| - ->catch(fn($e) => print("Error: " . $e->getMessage())); |
104 |
| -``` |
| 48 | +$func = async(fn () => 'Success'); |
105 | 49 |
|
106 |
| -**What happens here?** |
| 50 | +$func->then(fn ($value) => echo $value) // Outputs: Success |
| 51 | + ->catch(fn ($e) => echo 'Error: ' . $e->getMessage()); |
| 52 | +``` |
107 | 53 |
|
108 |
| -- `async()` forks a new child process using `AsyncProcessManager`. |
109 |
| -- The child runs your callable and serializes the result or error. |
110 |
| -- The parent listens on a socket via ReactPHP’s event loop. |
111 |
| -- When the child finishes, the parent promise is resolved or rejected. |
112 |
| -- The `AsyncPromise` provides a `.then()` for success and `.catch()` for errors, similar to JS promises. |
| 54 | +### `await(PromiseInterface $promise): mixed` |
113 | 55 |
|
114 |
| -### **Error Handling** |
| 56 | +Awaits the resolution of a promise and returns its value. |
115 | 57 |
|
116 |
| -If the child task throws an exception, `catch()` handles it gracefully: |
| 58 | +Example: |
117 | 59 |
|
118 | 60 | ```php
|
119 |
| -async(fn () => throw new RuntimeException('Something went wrong')) |
120 |
| - ->then(fn($res) => print("Not called")) |
121 |
| - ->catch(fn($e) => print("Caught error: " . $e->getMessage() . PHP_EOL)); |
| 61 | +use function await; |
| 62 | + |
| 63 | +try { |
| 64 | + $result = await(async(fn () => 'Success')); |
| 65 | + echo $result; // Outputs: Success |
| 66 | +} catch (\Throwable $e) { |
| 67 | + echo 'Error: ' . $e->getMessage(); |
| 68 | +} |
122 | 69 | ```
|
123 | 70 |
|
124 |
| ---- |
125 |
| - |
126 |
| -## **Under the Hood** |
127 |
| - |
128 |
| -- **Forked Processes**: Calling `async()` triggers `AsyncProcessManager::fork()`, which spawns a new child process. This child executes your given callable in isolation. |
129 |
| -- **IPC (Inter-Process Communication)**: Results or errors are serialized and sent back to the parent via a socket pair, using `stream_socket_pair()`. |
130 |
| -- **ReactPHP Integration**: The parent uses `React\EventLoop` to add a read stream listener. This ensures your code never blocks, waiting for the child process’s response asynchronously. |
131 |
| -- **Promises**: The `AsyncPromise` class wraps React’s `PromiseInterface` to provide a `.then()` and `.catch()` interface, making asynchronous code more intuitive. |
132 |
| - |
133 |
| ---- |
134 |
| - |
135 |
| -## **Examples** |
| 71 | +## Examples |
136 | 72 |
|
137 |
| -**Running Multiple Tasks in Parallel:** |
| 73 | +### Running Asynchronous Tasks |
138 | 74 |
|
139 | 75 | ```php
|
140 |
| -async(function () { |
141 |
| - usleep(500000); // Simulate half-second work |
142 |
| - return "Task A done"; |
143 |
| -})->then(fn($res) => print("$res\n")); |
| 76 | +$promise = async(fn () => 'Task Completed'); |
144 | 77 |
|
145 |
| -async(function () { |
146 |
| - usleep(500000); // Another half-second task |
147 |
| - return "Task B done"; |
148 |
| -})->then(fn($res) => print("$res\n")); |
149 |
| - |
150 |
| -// Both tasks start around the same time, taking ~0.5s total if concurrent, rather than ~1s if sequential. |
| 78 | +$promise->then(fn ($value) => echo $value) // Outputs: Task Completed |
| 79 | + ->catch(fn ($e) => echo 'Error: ' . $e->getMessage()); |
151 | 80 | ```
|
152 | 81 |
|
153 |
| -**Transforming Results with Thenable Chain:** |
| 82 | +### Await Syntax |
154 | 83 |
|
155 | 84 | ```php
|
156 |
| -async(fn() => 21) |
157 |
| - ->then(fn($val) => $val * 2) // 42 |
158 |
| - ->then(fn($val) => "The answer is $val") // "The answer is 42" |
159 |
| - ->then(fn($str) => print($str . PHP_EOL)) |
160 |
| - ->catch(fn($e) => print("Error: " . $e->getMessage())); |
| 85 | +try { |
| 86 | + $result = await(async(fn () => 'Finished Task')); |
| 87 | + echo $result; // Outputs: Finished Task |
| 88 | +} catch (\Throwable $e) { |
| 89 | + echo 'Error: ' . $e->getMessage(); |
| 90 | +} |
161 | 91 | ```
|
162 | 92 |
|
163 |
| ---- |
164 |
| - |
165 |
| -## **Testing and Integration** |
166 |
| - |
167 |
| -You can write integration tests to confirm asynchronous behavior. For example, test that two half-second tasks complete in ~0.5s total, ensuring concurrency works as intended: |
| 93 | +### Handling Errors |
168 | 94 |
|
169 | 95 | ```php
|
170 |
| -it('runs tasks concurrently', function () { |
171 |
| - $start = microtime(true); |
172 |
| - |
173 |
| - $p1 = async(fn() => (usleep(500000), 'Done A')); |
174 |
| - $p2 = async(fn() => (usleep(500000), 'Done B')); |
| 96 | +$promise = async(fn () => throw new \RuntimeException('Task Failed')); |
175 | 97 |
|
176 |
| - $bothDone = React\Promise\all([ |
177 |
| - $p1->then(fn($r) => $r), |
178 |
| - $p2->then(fn($r) => $r), |
179 |
| - ]); |
180 |
| - |
181 |
| - $results = awaitPromise($bothDone); |
182 |
| - $elapsed = microtime(true) - $start; |
183 |
| - |
184 |
| - expect($results)->toEqual(['Done A', 'Done B']); |
185 |
| - expect($elapsed)->toBeLessThan(1.0); // Confirms concurrency |
186 |
| -}); |
| 98 | +$promise->then(fn ($value) => echo $value) |
| 99 | + ->catch(fn ($e) => echo 'Caught Error: ' . $e->getMessage()); // Outputs: Caught Error: Task Failed |
187 | 100 | ```
|
188 | 101 |
|
189 |
| -*(`awaitPromise()` is a helper test function that runs the event loop until the given promise resolves.)* |
| 102 | +## How It Works |
190 | 103 |
|
191 |
| ---- |
192 |
| - |
193 |
| -## **Common Issues and Troubleshooting** |
194 |
| - |
195 |
| -- **Sequential Execution Instead of Parallel**: |
196 |
| - Ensure `pcntl` and `sockets` extensions are enabled. Also, ensure you are not blocking the main thread. The event loop must run for asynchronous behavior. |
197 |
| - |
198 |
| -- **No Interleaved Output**: |
199 |
| - Console output might be buffered. If you rely on interleaving stdout, consider using `fflush(STDOUT)` in the child, or rely on timing tests to confirm concurrency. |
200 |
| - |
201 |
| -- **Exception Types**: |
202 |
| - If the child throws an unknown exception type not autoloaded in the parent, it defaults to `RuntimeException`. Basic details (message, file, line, trace) are preserved. |
| 104 | +1. **Event Loop Management**: The `async()` function ensures the event loop runs until the promise is resolved or rejected. |
| 105 | +2. **Promise Interface**: Promises provide `then` and `catch` for handling success and errors. |
| 106 | +3. **Synchronous Await**: The `await()` function allows synchronous-style code for promise resolution. |
203 | 107 |
|
204 | 108 | ---
|
205 |
| - |
206 |
| -## **API Reference** |
207 | 109 |
|
208 |
| -### Global `async()` function |
209 |
| - |
210 |
| -```php |
211 |
| -function async(callable $callable): AsyncPromise |
212 |
| -``` |
| 110 | +## Testing |
213 | 111 |
|
214 |
| -- Accepts a callable to run asynchronously. |
215 |
| -- Returns `AsyncPromise`, providing `.then()` and `.catch()`. |
| 112 | +Run the tests to ensure everything is working as expected: |
216 | 113 |
|
217 |
| -### `Matrix\AsyncProcessManager` |
218 |
| - |
219 |
| -- `fork(callable $callable)`: Forks a child process to run the callable. Returns a `React\Promise\PromiseInterface`. |
220 |
| - |
221 |
| -### `Matrix\AsyncPromise` |
222 |
| - |
223 |
| -- `then(callable $onFulfilled): self` |
224 |
| - Attaches a success handler. |
225 |
| -- `catch(callable $onRejected): self` |
226 |
| - Attaches an error handler (alias to React’s `otherwise()`). |
227 |
| - |
228 |
| ---- |
229 |
| - |
230 |
| -## **Contributing** |
231 |
| - |
232 |
| -We welcome contributions. To contribute: |
233 |
| - |
234 |
| -1. Fork the repository. |
235 |
| -2. Create a new feature branch (`git checkout -b feature/my-feature`). |
236 |
| -3. Commit your changes (`git commit -m 'Add new feature'`). |
237 |
| -4. Push to the branch (`git push origin feature/my-feature`). |
238 |
| -5. Open a pull request against `main`. |
239 |
| - |
240 |
| ---- |
241 |
| - |
242 |
| -## **License** |
| 114 | +```bash |
| 115 | +composer test |
| 116 | +``` |
243 | 117 |
|
244 |
| -Matrix is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for more information. |
| 118 | +## Contributing |
245 | 119 |
|
246 |
| ---- |
247 |
| - |
248 |
| -## **Authors** |
| 120 | +Contributions are welcome! Fork the repository and create a pull request. |
249 | 121 |
|
250 |
| -- **[Jerome Thayananthajothy]** - *Initial Work* - [Thavarshan](https://github.com/Thavarshan) |
| 122 | +## License |
251 | 123 |
|
252 |
| -See the [contributors](https://github.com/Thavarshan/matrix/contributors) who participated in this project. |
| 124 | +Matrix is licensed under the MIT License. |
0 commit comments