|
| 1 | +--- |
| 2 | +title: Child Processes |
| 3 | +order: 700 |
| 4 | +--- |
| 5 | + |
| 6 | +# Child Processes |
| 7 | + |
| 8 | +Child Processes allow your application to spin up managed processes, forked from your app's main process. This is great |
| 9 | +for long-running processes that you want to interact with repeatedly during the life of your application. |
| 10 | + |
| 11 | +Child Processes can be managed from your application using a straightforward API. When your app quits, these processes |
| 12 | +get shut down gracefully. |
| 13 | + |
| 14 | +"Spawning" a Child Process is like running a command from the CLI. Any command you can run in the terminal can be a |
| 15 | +Child Process. |
| 16 | + |
| 17 | +```php |
| 18 | +ChildProcess::start( |
| 19 | + cmd: 'tail -f storage/logs/laravel.log', |
| 20 | + alias: 'tail' |
| 21 | +); |
| 22 | +``` |
| 23 | + |
| 24 | +Any process invoked using the ChildProcess facade will be non-blocking and keep running in the background. Even if the request that triggered it has finished. |
| 25 | + |
| 26 | +**Bear in mind that your Child Process ("command line") arguments may need to differ depending on which platform your |
| 27 | +application is running on (Mac/Linux vs Windows).** |
| 28 | + |
| 29 | +**Where possible, you should explicitly reference binaries by their full path name, unless you can reliably assume that |
| 30 | +the executable you're trying to spawn is available in the user's `PATH`.** |
| 31 | + |
| 32 | +Child Processes are managed by the runtime (Electron/Tauri) but are fully accessible to the Laravel side of your |
| 33 | +application. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Alternatives |
| 38 | + |
| 39 | +Before deciding to use a Child Process, consider the alternatives available to you. You should pick the most |
| 40 | +appropriate for the problem you're trying to solve: |
| 41 | + |
| 42 | +### Queues |
| 43 | + |
| 44 | +The [queue runner](queues) is useful for very simply offloading _Laravel_ tasks to the background. Each task must be a |
| 45 | +Laravel queued [Job](https://laravel.com/docs/queues#creating-jobs). |
| 46 | + |
| 47 | +Any queued jobs that don't get processed before your app is quit, will get processed when your application (and the |
| 48 | +queue runner) starts again. |
| 49 | + |
| 50 | +### Scheduler |
| 51 | + |
| 52 | +The Laravel scheduler runs as normal (every minute) inside a NativePHP application. You can add |
| 53 | +[scheduled tasks](https://laravel.com/docs/scheduling) to your application just as you normally would, to have them run |
| 54 | +on a regular schedule. |
| 55 | + |
| 56 | +Any scheduled tasks that would have run while your application isn't running will be skipped. |
| 57 | + |
| 58 | +**The queue runner and the scheduler are tied to your _application_, not the operating system, so they will |
| 59 | +only be able to run while your application is running.** |
| 60 | + |
| 61 | +### `shell_exec`, `proc_open` etc |
| 62 | + |
| 63 | +PHP has good built-in support for running arbitrary programs in separate processes. For example: |
| 64 | + |
| 65 | +- [`shell_exec`](https://www.php.net/manual/en/function.shell-exec.php) allows you to run commands and return their |
| 66 | + output to your application. |
| 67 | +- [`proc_open`](https://www.php.net/manual/en/function.proc-open.php) allows you to spin up a command with more control |
| 68 | + over how its input and output streams are handled. |
| 69 | + |
| 70 | +While these can be used in your NativePHP application, consider that they: |
| 71 | + |
| 72 | +- May block the script that is executing them until the sub-process has finished. |
| 73 | +- May become orphaned from your application, allowing them to continue running after your app has quit. |
| 74 | + |
| 75 | +Runaway orphaned processes could negatively impact your user's system and can become tricky to manage without user |
| 76 | +intervention. You should be cautious about starting processes this way. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## Starting a Child Process |
| 81 | + |
| 82 | +Each Child Process must have a unique alias. This is the name you will use to reference and interact with this process |
| 83 | +throughout your application. |
| 84 | + |
| 85 | +You may start a process using the `ChildProcess` facade: |
| 86 | + |
| 87 | +```php |
| 88 | +use Native\Laravel\Facades\ChildProcess; |
| 89 | + |
| 90 | +ChildProcess::start( |
| 91 | + cmd: 'tail -f storage/logs/laravel.log', |
| 92 | + alias: 'tail' |
| 93 | +); |
| 94 | +``` |
| 95 | + |
| 96 | +The `start` method will return a `Native\Laravel\ChildProcess` instance, which represents the process. You may interact |
| 97 | +directly with this instance to make changes to that process, but this does not necessarily mean that the |
| 98 | +process was started. |
| 99 | + |
| 100 | +The timing of process initilization is controlled by the user's operating system and spawning |
| 101 | +may fail for a number of reasons. |
| 102 | + |
| 103 | +**To determine if the process has started successfully, you should listen for the |
| 104 | +[`ProcessSpawned` event](#codeprocessspawnedcode).** |
| 105 | + |
| 106 | +### Current Working Directory |
| 107 | + |
| 108 | +By default, the child process will use the working directory of your application as it's "current working directory" |
| 109 | +(`cwd`). However, you can explicitly change this if needed by passing a string path to the `$cwd` parameter of the |
| 110 | +`start` method: |
| 111 | + |
| 112 | +```php |
| 113 | +ChildProcess::start( |
| 114 | + cmd: ['tail', '-f', 'logs/laravel.log'], |
| 115 | + alias: 'tail', |
| 116 | + cwd: storage_path() |
| 117 | +); |
| 118 | +``` |
| 119 | + |
| 120 | +### Persistent Processes |
| 121 | + |
| 122 | +You may mark a process as `persistent` to indicate that the runtime should make sure that once it has been started it |
| 123 | +is always running. This works similarly to tools like [`supervisord`](http://supervisord.org/), ensuring that the |
| 124 | +process gets booted up again in case it crashes. |
| 125 | + |
| 126 | +```php |
| 127 | +ChildProcess::start( |
| 128 | + cmd: ['tail', '-f', 'logs/laravel.log'], |
| 129 | + alias: 'tail', |
| 130 | + persistent: true |
| 131 | +); |
| 132 | +``` |
| 133 | + |
| 134 | +**The only way to stop a persistent process is for your application to quit.** |
| 135 | + |
| 136 | +### PHP scripts |
| 137 | + |
| 138 | +For your convenience, NativePHP provides a simple method to execute PHP scripts in the background using NativePHP's packaged PHP binary: |
| 139 | + |
| 140 | +```php |
| 141 | +ChildProcess::php('path/to/script.php', alias: 'script'); |
| 142 | +``` |
| 143 | + |
| 144 | +### Artisan commands |
| 145 | + |
| 146 | +NativePHP provides a similar method convenience for Artisan commands: |
| 147 | + |
| 148 | +```php |
| 149 | +ChildProcess::artisan('smtp:serve', alias: 'smtp-server'); |
| 150 | +``` |
| 151 | + |
| 152 | +## Getting running processes |
| 153 | + |
| 154 | +### Getting a single process |
| 155 | + |
| 156 | +You can use the `ChildProcess` facade's `get` method to get a running process with a given alias: |
| 157 | + |
| 158 | +```php |
| 159 | +$tail = ChildProcess::get('tail'); |
| 160 | +``` |
| 161 | + |
| 162 | +This will return a `Native\Laravel\ChildProcess` instance. |
| 163 | + |
| 164 | +### Getting all processes |
| 165 | + |
| 166 | +You can use the `ChildProcess` facade's `all` method to get all running processes: |
| 167 | + |
| 168 | +```php |
| 169 | +$processes = ChildProcess::all(); |
| 170 | +``` |
| 171 | + |
| 172 | +This will return an array of `Native\Laravel\ChildProcess` instances. |
| 173 | + |
| 174 | +## Stopping a Child Process |
| 175 | + |
| 176 | +Your child processes will shut down when your application exits. However, you may also choose to stop them manually or |
| 177 | +provide this control to your user. |
| 178 | + |
| 179 | +If you have a `Native\Laravel\ChildProcess` instance, you may call the `stop` method on it: |
| 180 | + |
| 181 | +```php |
| 182 | +$tail->stop(); |
| 183 | +``` |
| 184 | + |
| 185 | +Alternatively, you may use the `ChildProcess` facade to stop a process via its alias: |
| 186 | + |
| 187 | +```php |
| 188 | +ChildProcess::stop('tail'); |
| 189 | +``` |
| 190 | + |
| 191 | +This will attempt to stop the process gracefully. The [`ProcessExited`](#codeprocessexitedcode) event will be |
| 192 | +dispatched if the process exits. |
| 193 | + |
| 194 | +Note that [persistent processes](/docs/1/digging-deeper/child-process#persistent-processes) will restart even when you |
| 195 | +stop them manually. The only way to stop a persistent process is by quitting the application. |
| 196 | + |
| 197 | +## Restarting a Child Process |
| 198 | + |
| 199 | +As a convenience, you may simply restart a Child Process using the `restart` method. This may be useful in cases where |
| 200 | +the program has become unresponsive and you simply need to "reboot" it. |
| 201 | + |
| 202 | +If you have a `Native\Laravel\ChildProcess` instance, you may call the `restart` method on it: |
| 203 | + |
| 204 | +```php |
| 205 | +$tail->stop(); |
| 206 | +``` |
| 207 | + |
| 208 | +Alternatively, you may use the `ChildProcess` facade to restart a process via its alias: |
| 209 | + |
| 210 | +```php |
| 211 | +ChildProcess::restart('tail'); |
| 212 | +``` |
| 213 | + |
| 214 | +## Sending input |
| 215 | + |
| 216 | +There are multiple ways to provide input to your Child Process: |
| 217 | + |
| 218 | +- The environment. |
| 219 | +- Arguments to the command. |
| 220 | +- Its standard input stream (`STDIN`). |
| 221 | +- A custom interface, e.g. a network socket. |
| 222 | + |
| 223 | +Which you use will depend on what the program is capable of handling. |
| 224 | + |
| 225 | +### Environment |
| 226 | + |
| 227 | +Child Processes will inherit the environment available to your application by default. If needed, you can provide extra |
| 228 | +environment variables when starting the process via the `$env` parameter of the `start` method: |
| 229 | + |
| 230 | +```php |
| 231 | +ChildProcess::start( |
| 232 | + cmd: 'tail ...', |
| 233 | + alias: 'tail', |
| 234 | + env: [ |
| 235 | + 'CUSTOM_ENV_VAR' => 'custom value', |
| 236 | + ] |
| 237 | +); |
| 238 | +``` |
| 239 | + |
| 240 | +### Command line arguments |
| 241 | + |
| 242 | +You can pass arguments to the program via the `$cmd` parameter of the `start` method. This accepts a `string` or an |
| 243 | +`array`, whichever you prefer to use: |
| 244 | + |
| 245 | +```php |
| 246 | +ChildProcess::start( |
| 247 | + cmd: ['tail', '-f', 'storage/logs/laravel.log'], |
| 248 | + alias: 'tail' |
| 249 | +); |
| 250 | +``` |
| 251 | + |
| 252 | +### Messaging a Child Process |
| 253 | + |
| 254 | +You may send messages to a running child process's standard input stream (`STDIN`) using the `message` method: |
| 255 | + |
| 256 | +```php |
| 257 | +$tail->message('Hello, world!'); |
| 258 | +``` |
| 259 | + |
| 260 | +Alternatively, you may use the `ChildProcess` facade to message a process via its alias: |
| 261 | + |
| 262 | +```php |
| 263 | +ChildProcess::message('Hello, world!', 'tail'); |
| 264 | +``` |
| 265 | + |
| 266 | +The message format and how they are handled will be determined by the program you're running. |
| 267 | + |
| 268 | +## Handling output |
| 269 | + |
| 270 | +A Child Process may send output via any of the following interfaces: |
| 271 | + |
| 272 | +- Its standard output stream (`STDOUT`). |
| 273 | +- Its standard error stream (`STDERR`). |
| 274 | +- A custom interface, e.g. a network socket. |
| 275 | +- Broadcasting a Custom Event |
| 276 | + |
| 277 | +`STDOUT`, `STDERR` & [Custom Events](/docs/1/digging-deeper/broadcasting#custom-events) are dispatched using |
| 278 | +Laravel's event system. |
| 279 | + |
| 280 | +You may listen to these events by registering a listener in your app service provider, or on the front end |
| 281 | +using the [Native helper](/docs/1/digging-deeper/broadcasting#listening-with-javascript). |
| 282 | + |
| 283 | +Please see the [Events](#events) section for a full list of events. |
| 284 | + |
| 285 | +### Listening for Output (`STDOUT`) |
| 286 | + |
| 287 | +You may receive standard output for a process by registering an event listener for the |
| 288 | +[`MessageReceived`](#codemessagereceivedcode) event: |
| 289 | + |
| 290 | +### Listening for Errors (`STDERR`) |
| 291 | + |
| 292 | +You may receive standard errors for a process by registering an event listener for the |
| 293 | +[`ErrorReceived`](#codeerrorreceivedcode) event: |
| 294 | + |
| 295 | +## Events |
| 296 | + |
| 297 | +NativePHP provides a simple way to listen for Child Process events. |
| 298 | + |
| 299 | +All events get dispatched as regular Laravel events, so you may use your `AppServiceProvider` to register listeners. |
| 300 | + |
| 301 | +```php |
| 302 | +use Illuminate\Support\Facades\Event; |
| 303 | +use Native\Laravel\Events\ChildProcess\MessageReceived; |
| 304 | + |
| 305 | +/** |
| 306 | + * Bootstrap any application services. |
| 307 | + */ |
| 308 | +public function boot(): void |
| 309 | +{ |
| 310 | + Event::listen(MessageReceived::class, function(MessageReceived $event) { |
| 311 | + if ($event->alias === 'tail') { |
| 312 | + // |
| 313 | + } |
| 314 | + }); |
| 315 | +} |
| 316 | + |
| 317 | +``` |
| 318 | + |
| 319 | +Sometimes you may want to listen and react to these events in real-time, which is why NativePHP also broadcasts all |
| 320 | +Child Process events to the `nativephp` broadcast channel. Any events broadcasted this way also get dispatched over IPC, enabling you to react to them on the front-end without using websockets. |
| 321 | + |
| 322 | +```js |
| 323 | +Native.on("Native\\Laravel\\Events\\ChildProcess\\MessageReceived", (event) => { |
| 324 | + if (event.alias === "tail") { |
| 325 | + container.append(event.data); |
| 326 | + } |
| 327 | +}); |
| 328 | +``` |
| 329 | + |
| 330 | +To learn more about NativePHP's broadcasting capabilities, please refer to the |
| 331 | +[Broadcasting](/docs/digging-deeper/broadcasting) section. |
| 332 | + |
| 333 | +### `ProcessSpawned` |
| 334 | + |
| 335 | +This `Native\Laravel\Events\ChildProcess\ProcessSpawned` event will be dispatched when a Child Process has successfully |
| 336 | +been spawned. The payload of the event contains the `$alias` and the `$pid` of the process. |
| 337 | + |
| 338 | +**In Electron, the `$pid` here will be the Process ID of an Electron Helper process which spawns the underlying |
| 339 | +process.** |
| 340 | + |
| 341 | +### `ProcessExited` |
| 342 | + |
| 343 | +This `Native\Laravel\Events\ChildProcess\ProcessExited` event will be dispatched when a Child Process exits. The |
| 344 | +payload of the event contains the `$alias` of the process and its exit `$code`. |
| 345 | + |
| 346 | +### `MessageReceived` |
| 347 | + |
| 348 | +This `Native\Laravel\Events\ChildProcess\MessageReceived` event will be dispatched when the Child Process emits some |
| 349 | +output via its standard output stream (`STDOUT`). The payload of the event contains the `$alias` of the process and the |
| 350 | +message `$data`. |
| 351 | + |
| 352 | +### `ErrorReceived` |
| 353 | + |
| 354 | +This `Native\Laravel\Events\ChildProcess\ErrorReceived` event will be dispatched when the Child Process emits an error |
| 355 | +via its standard error stream (`STDERR`). The payload of the event contains the `$alias` of the process and the |
| 356 | +error `$data`. |
0 commit comments