├── src ├── Exceptions │ └── CouldNotManageTask.php ├── Connection.php ├── Task.php └── Fork.php ├── LICENSE.md ├── .php_cs.php ├── composer.json ├── CHANGELOG.md └── README.md /src/Exceptions/CouldNotManageTask.php: -------------------------------------------------------------------------------- 1 | pid()}"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.php_cs.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'method_argument_space' => [ 30 | 'on_multiline' => 'ensure_fully_multiline', 31 | 'keep_multiple_spaces_after_comma' => true, 32 | ], 33 | 'single_trait_insert_per_statement' => true, 34 | ]) 35 | ->setFinder($finder); 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/fork", 3 | "description": "A lightweight solution for running code concurrently in PHP", 4 | "keywords": [ 5 | "spatie", 6 | "fork" 7 | ], 8 | "homepage": "https://github.com/spatie/fork", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Brent Roose", 13 | "email": "brent@spatie.be", 14 | "role": "Developer" 15 | }, 16 | { 17 | "name": "Freek Van der Herten", 18 | "email": "freek@spatie.be", 19 | "role": "Developer" 20 | } 21 | ], 22 | "require": { 23 | "php": "^8.0", 24 | "ext-sockets": "*", 25 | "ext-pcntl": "*" 26 | }, 27 | "require-dev": { 28 | "nesbot/carbon": "^2.66", 29 | "pestphp/pest": "^1.23", 30 | "phpunit/phpunit": "^9.5", 31 | "spatie/ray": "^1.10" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Spatie\\Fork\\": "src" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Spatie\\Fork\\Tests\\": "tests" 41 | } 42 | }, 43 | "scripts": { 44 | "test": "vendor/bin/pest", 45 | "test-coverage": "vendor/bin/pest --coverage-html coverage", 46 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes --config=.php_cs.php" 47 | }, 48 | "config": { 49 | "sort-packages": true, 50 | "allow-plugins": { 51 | "pestphp/pest-plugin": true 52 | } 53 | }, 54 | "minimum-stability": "dev", 55 | "prefer-stable": true 56 | } 57 | -------------------------------------------------------------------------------- /src/Connection.php: -------------------------------------------------------------------------------- 1 | socket); 19 | 20 | $this->timeoutSeconds = floor($this->timeout); 21 | 22 | $this->timeoutMicroseconds = ($this->timeout * 1_000_000) - ($this->timeoutSeconds * 1_000_000); 23 | } 24 | 25 | /** 26 | * @return self[] 27 | */ 28 | public static function createPair(): array 29 | { 30 | socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets); 31 | 32 | [$socketToParent, $socketToChild] = $sockets; 33 | 34 | return [ 35 | new self($socketToParent), 36 | new self($socketToChild), 37 | ]; 38 | } 39 | 40 | public function close(): self 41 | { 42 | socket_close($this->socket); 43 | 44 | return $this; 45 | } 46 | 47 | public function write(string $payload): self 48 | { 49 | socket_set_nonblock($this->socket); 50 | 51 | while ($payload !== '') { 52 | $write = [$this->socket]; 53 | 54 | $read = null; 55 | 56 | $except = null; 57 | 58 | $selectResult = @socket_select($read, $write, $except, $this->timeoutSeconds, $this->timeoutMicroseconds); 59 | 60 | if ($selectResult === false && socket_last_error() === SOCKET_EINTR) { 61 | continue; 62 | } 63 | 64 | if ($selectResult === false) { 65 | break; 66 | } 67 | 68 | if ($selectResult <= 0) { 69 | break; 70 | } 71 | 72 | $length = strlen($payload); 73 | 74 | $amountOfBytesSent = socket_write($this->socket, $payload, $length); 75 | 76 | if ($amountOfBytesSent === false || $amountOfBytesSent === $length) { 77 | break; 78 | } 79 | 80 | $payload = substr($payload, $amountOfBytesSent); 81 | } 82 | 83 | return $this; 84 | } 85 | 86 | public function read(): Generator 87 | { 88 | socket_set_nonblock($this->socket); 89 | 90 | while (true) { 91 | $read = [$this->socket]; 92 | 93 | $write = null; 94 | 95 | $except = null; 96 | 97 | $selectResult = @socket_select($read, $write, $except, $this->timeoutSeconds, $this->timeoutMicroseconds); 98 | 99 | if ($selectResult === false && socket_last_error() === SOCKET_EINTR) { 100 | continue; 101 | } 102 | 103 | if ($selectResult === false) { 104 | break; 105 | } 106 | 107 | if ($selectResult <= 0) { 108 | break; 109 | } 110 | 111 | $outputFromSocket = socket_read($this->socket, $this->bufferSize); 112 | 113 | if ($outputFromSocket === false || $outputFromSocket === '') { 114 | break; 115 | } 116 | 117 | yield $outputFromSocket; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Task.php: -------------------------------------------------------------------------------- 1 | callable = Closure::fromCallable($callable); 38 | 39 | $this->order = $order; 40 | } 41 | 42 | public function setName(string $name): self 43 | { 44 | $this->name = $name; 45 | 46 | return $this; 47 | } 48 | 49 | public function name(): string 50 | { 51 | return $this->name; 52 | } 53 | 54 | public function order(): int 55 | { 56 | return $this->order; 57 | } 58 | 59 | public function pid(): int 60 | { 61 | return $this->pid; 62 | } 63 | 64 | public function setPid(int $pid): self 65 | { 66 | $this->pid = $pid; 67 | 68 | return $this; 69 | } 70 | 71 | public function setConnection(Connection $connection): self 72 | { 73 | $this->connection = $connection; 74 | 75 | return $this; 76 | } 77 | 78 | public function startTime(): int 79 | { 80 | return $this->startTime; 81 | } 82 | 83 | public function setStartTime($startTime): self 84 | { 85 | $this->startTime = $startTime; 86 | 87 | return $this; 88 | } 89 | 90 | public function execute(): string | bool 91 | { 92 | $output = ($this->callable)(); 93 | 94 | if (is_string($output)) { 95 | return $output; 96 | } 97 | 98 | return self::SERIALIZATION_TOKEN . serialize($output); 99 | } 100 | 101 | public function output(): mixed 102 | { 103 | foreach ($this->connection->read() as $output) { 104 | $this->output .= $output; 105 | } 106 | 107 | $this->connection->close(); 108 | 109 | $this->triggerSuccessCallback(); 110 | 111 | $output = $this->output; 112 | 113 | if (str_starts_with($output, self::SERIALIZATION_TOKEN)) { 114 | $output = unserialize( 115 | substr($output, strlen(self::SERIALIZATION_TOKEN)) 116 | ); 117 | } 118 | 119 | return $output; 120 | } 121 | 122 | public function onSuccess(callable $callback): self 123 | { 124 | $this->successCallback = $callback; 125 | 126 | return $this; 127 | } 128 | 129 | public function isFinished(): bool 130 | { 131 | $this->output .= $this->connection->read()->current(); 132 | 133 | if (pcntl_waitpid($this->pid(), $status, WNOHANG | WUNTRACED) === $this->pid) { 134 | 135 | if (pcntl_wexitstatus($status) !== 0) { 136 | throw CouldNotManageTask::make($this); 137 | } 138 | 139 | return true; 140 | } 141 | 142 | return false; 143 | } 144 | 145 | public function triggerSuccessCallback(): mixed 146 | { 147 | if (! $this->successCallback) { 148 | return null; 149 | } 150 | 151 | return call_user_func_array($this->successCallback, [$this]); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `fork` will be documented in this file. 4 | 5 | ## 1.2.5 - 2025-04-24 6 | 7 | ### What's Changed 8 | 9 | * Fix task exit status handling, uniformize process exit method and tests by @jchambon-equativ in https://github.com/spatie/fork/pull/74 10 | 11 | ### New Contributors 12 | 13 | * @jchambon-equativ made their first contribution in https://github.com/spatie/fork/pull/74 14 | 15 | **Full Changelog**: https://github.com/spatie/fork/compare/1.2.4...1.2.5 16 | 17 | ## 1.2.4 - 2025-01-27 18 | 19 | ### What's Changed 20 | 21 | * [REMOVE] PHP 8.4 deprecation warnings by @yabooodya in https://github.com/spatie/fork/pull/66 22 | 23 | ### New Contributors 24 | 25 | * @yabooodya made their first contribution in https://github.com/spatie/fork/pull/66 26 | 27 | **Full Changelog**: https://github.com/spatie/fork/compare/1.2.3...1.2.4 28 | 29 | ## 1.2.3 - 2024-12-11 30 | 31 | ### What's Changed 32 | 33 | * Add a test to verify that child processes are isolated by @ahmadreza1383 in https://github.com/spatie/fork/pull/64 34 | * Interrupted system call is not handled well by @jochem-blok in https://github.com/spatie/fork/pull/61 35 | 36 | ### New Contributors 37 | 38 | * @ahmadreza1383 made their first contribution in https://github.com/spatie/fork/pull/64 39 | * @jochem-blok made their first contribution in https://github.com/spatie/fork/pull/61 40 | 41 | **Full Changelog**: https://github.com/spatie/fork/compare/1.2.2...1.2.3 42 | 43 | ## 1.2.2 - 2023-12-07 44 | 45 | ### What's Changed 46 | 47 | * Switch from kill-all-exit to a normal exit when a child is done by @puggan in https://github.com/spatie/fork/pull/51 48 | 49 | ### New Contributors 50 | 51 | * @puggan made their first contribution in https://github.com/spatie/fork/pull/51 52 | 53 | **Full Changelog**: https://github.com/spatie/fork/compare/1.2.1...1.2.2 54 | 55 | ## 1.2.1 - 2023-11-23 56 | 57 | ### What's Changed 58 | 59 | - Kill any forked tasks when force closed in terminal by @stevebauman in https://github.com/spatie/fork/pull/48 60 | 61 | ### New Contributors 62 | 63 | - @stevebauman made their first contribution in https://github.com/spatie/fork/pull/48 64 | 65 | **Full Changelog**: https://github.com/spatie/fork/compare/1.2.0...1.2.1 66 | 67 | ## 1.2.0 - 2023-05-29 68 | 69 | ### What's Changed 70 | 71 | - Listen signals for terminating process by @zKoz210 in https://github.com/spatie/fork/pull/42 72 | 73 | **Full Changelog**: https://github.com/spatie/fork/compare/1.1.3...1.2.0 74 | 75 | ## 1.1.3 - 2023-05-16 76 | 77 | ### What's Changed 78 | 79 | - Refactor tests to Pest by @alexmanase in https://github.com/spatie/fork/pull/33 80 | - Fixed php-cs-fixer by @zKoz210 in https://github.com/spatie/fork/pull/40 81 | - Fixed tests for PHP 8.2 by @zKoz210 in https://github.com/spatie/fork/pull/38 82 | - Fixed #36 by @zKoz210 in https://github.com/spatie/fork/pull/39 83 | 84 | ### New Contributors 85 | 86 | - @alexmanase made their first contribution in https://github.com/spatie/fork/pull/33 87 | - @zKoz210 made their first contribution in https://github.com/spatie/fork/pull/40 88 | 89 | **Full Changelog**: https://github.com/spatie/fork/compare/1.1.2...1.1.3 90 | 91 | ## 1.1.2 - 2022-10-03 92 | 93 | ### What's Changed 94 | 95 | - fixes socket_select interrupted exception by @henzeb in https://github.com/spatie/fork/pull/32 96 | 97 | ### New Contributors 98 | 99 | - @henzeb made their first contribution in https://github.com/spatie/fork/pull/32 100 | 101 | **Full Changelog**: https://github.com/spatie/fork/compare/1.1.1...1.1.2 102 | 103 | ## 1.1.1 - 2021-12-09 104 | 105 | - Proper handling of timeout microseconds 106 | 107 | ## 1.1.1 - 2021-12-09 108 | 109 | - Proper handling of timeout microseconds 110 | 111 | ## 1.1.0 - 2021-05-04 112 | 113 | - Add `Fork::concurrent(int $concurrent)` 114 | 115 | ## 1.0.1 - 2021-05-03 116 | 117 | - Add check for pcntl support 118 | 119 | ## 1.0.0 - 2021-04-30 120 | 121 | - Initial release 122 | -------------------------------------------------------------------------------- /src/Fork.php: -------------------------------------------------------------------------------- 1 | toExecuteBeforeInChildTask = $child; 41 | $this->toExecuteBeforeInParentTask = $parent; 42 | 43 | return $this; 44 | } 45 | 46 | public function after(?callable $child = null, ?callable $parent = null): self 47 | { 48 | $this->toExecuteAfterInChildTask = $child; 49 | $this->toExecuteAfterInParentTask = $parent; 50 | 51 | return $this; 52 | } 53 | 54 | public function concurrent(int $concurrent): self 55 | { 56 | $this->concurrent = $concurrent; 57 | 58 | return $this; 59 | } 60 | 61 | public function run(callable ...$callables): array 62 | { 63 | $tasks = []; 64 | 65 | foreach ($callables as $order => $callable) { 66 | $tasks[] = Task::fromCallable($callable, $order); 67 | } 68 | 69 | return $this->waitFor(...$tasks); 70 | } 71 | 72 | protected function waitFor(Task ...$queue): array 73 | { 74 | $output = []; 75 | 76 | $this->listenForSignals(); 77 | $this->startRunning(...$queue); 78 | 79 | while ($this->isRunning()) { 80 | foreach ($this->runningTasks as $task) { 81 | if (! $task->isFinished()) { 82 | continue; 83 | } 84 | 85 | $output[$task->order()] = $this->finishTask($task); 86 | 87 | $this->shiftTaskFromQueue(); 88 | } 89 | 90 | if ($this->isRunning()) { 91 | usleep(1_000); 92 | } 93 | } 94 | 95 | return $output; 96 | } 97 | 98 | protected function runTask(Task $task): Task 99 | { 100 | if ($this->toExecuteBeforeInParentTask) { 101 | ($this->toExecuteBeforeInParentTask)(); 102 | } 103 | 104 | return $this->forkForTask($task); 105 | } 106 | 107 | protected function finishTask(Task $task): mixed 108 | { 109 | $output = $task->output(); 110 | 111 | if ($this->toExecuteAfterInParentTask) { 112 | ($this->toExecuteAfterInParentTask)($output); 113 | } 114 | 115 | unset($this->runningTasks[$task->order()]); 116 | 117 | return $output; 118 | } 119 | 120 | protected function forkForTask(Task $task): ?Task 121 | { 122 | [$socketToParent, $socketToChild] = Connection::createPair(); 123 | 124 | $processId = pcntl_fork(); 125 | 126 | if ($this->currentlyInChildTask($processId)) { 127 | $socketToChild->close(); 128 | 129 | try { 130 | $this->executeInChildTask($task, $socketToParent); 131 | } finally { 132 | if (! extension_loaded('posix')) { 133 | exit(); 134 | } 135 | 136 | posix_kill(getmypid(), SIGKILL); 137 | } 138 | 139 | } 140 | 141 | $socketToParent->close(); 142 | 143 | return $task 144 | ->setStartTime(time()) 145 | ->setPid($processId) 146 | ->setConnection($socketToChild); 147 | } 148 | 149 | /** 150 | * Enable async signals for the process. 151 | * 152 | * @return void 153 | */ 154 | protected function listenForSignals(): void 155 | { 156 | pcntl_async_signals(true); 157 | 158 | pcntl_signal(SIGQUIT, fn () => $this->exit()); 159 | pcntl_signal(SIGTERM, fn () => $this->exit()); 160 | pcntl_signal(SIGINT, fn () => $this->exit()); 161 | } 162 | 163 | protected function exit(): void 164 | { 165 | if (! extension_loaded('posix')) { 166 | exit(); 167 | } 168 | 169 | foreach ($this->runningTasks as $task) { 170 | posix_kill($task->pid(), SIGKILL); 171 | } 172 | 173 | posix_kill(getmypid(), SIGKILL); 174 | } 175 | 176 | protected function currentlyInChildTask(int $pid): bool 177 | { 178 | return $pid === 0; 179 | } 180 | 181 | protected function executeInChildTask( 182 | Task $task, 183 | Connection $connectionToParent, 184 | ): void { 185 | if ($this->toExecuteBeforeInChildTask) { 186 | ($this->toExecuteBeforeInChildTask)(); 187 | } 188 | 189 | $output = $task->execute(); 190 | 191 | $connectionToParent->write($output); 192 | 193 | if ($this->toExecuteAfterInChildTask) { 194 | ($this->toExecuteAfterInChildTask)($output); 195 | } 196 | 197 | $connectionToParent->close(); 198 | } 199 | 200 | protected function shiftTaskFromQueue(): void 201 | { 202 | if (! count($this->queue)) { 203 | return; 204 | } 205 | 206 | $firstTask = array_shift($this->queue); 207 | 208 | $this->runningTasks[] = $this->runTask($firstTask); 209 | } 210 | 211 | protected function startRunning( 212 | Task ...$queue 213 | ): void { 214 | $this->queue = $queue; 215 | 216 | foreach ($this->queue as $task) { 217 | $this->runningTasks[$task->order()] = $this->runTask($task); 218 | 219 | unset($this->queue[$task->order()]); 220 | 221 | if ($this->concurrencyLimitReached()) { 222 | break; 223 | } 224 | } 225 | } 226 | 227 | protected function isRunning(): bool 228 | { 229 | return count($this->runningTasks) > 0; 230 | } 231 | 232 | protected function concurrencyLimitReached(): bool 233 | { 234 | return $this->concurrent && count($this->runningTasks) >= $this->concurrent; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A lightweight solution for running PHP code concurrently 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/fork.svg?style=flat-square)](https://packagist.org/packages/spatie/fork) 4 | [![Tests](https://github.com/spatie/fork/actions/workflows/run-tests.yml/badge.svg)](https://github.com/spatie/fork/actions/workflows/run-tests.yml) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/spatie/fork/php-cs-fixer.yml?label=code%20style)](https://github.com/spatie/fork/actions/workflows/php-cs-fixer.yml) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/fork.svg?style=flat-square)](https://packagist.org/packages/spatie/fork) 7 | 8 | This package makes it easy to run PHP concurrently. Behind the scenes, concurrency is achieved by forking the main PHP process to one or more child tasks. 9 | 10 | In this example, where we are going to call an imaginary slow API, all three closures will run at the same time. 11 | 12 | ```php 13 | use Spatie\Fork\Fork; 14 | 15 | $results = Fork::new() 16 | ->run( 17 | fn () => (new Api)->fetchData(userId: 1), 18 | fn () => (new Api)->fetchData(userId: 2), 19 | fn () => (new Api)->fetchData(userId: 3), 20 | ); 21 | 22 | $results[0]; // fetched data of user 1 23 | $results[1]; // fetched data of user 2 24 | $results[2]; // fetched data of user 3 25 | ``` 26 | ## How it works under the hood 27 | 28 | ✨ In this [video on YouTube](https://www.youtube.com/watch?v=IJXzc46MFPM), we explain how the package works internally. 29 | 30 | ## Support us 31 | 32 | [](https://spatie.be/github-ad-click/fork) 33 | 34 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can 35 | support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 36 | 37 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 38 | You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards 39 | on [our virtual postcard wall](https://spatie.be/open-source/postcards). 40 | 41 | ## Requirements 42 | 43 | This package requires PHP 8 and the [pcntl](https://www.php.net/manual/en/intro.pcntl.php) extensions which is installed in many Unix and Mac systems by default. 44 | 45 | ❗️ [pcntl](https://www.php.net/manual/en/intro.pcntl.php) only works in CLI processes, not in a web context. 46 | ❗️ [posix](https://www.php.net/manual/en/book.posix.php) required for correct handling of process termination for Alpine Linux. 47 | 48 | ## Installation 49 | 50 | You can install the package via composer: 51 | 52 | ```bash 53 | composer require spatie/fork 54 | ``` 55 | 56 | ## Usage 57 | 58 | You can pass as many closures as you want to `run`. They will be run concurrently. The `run` function will return an array with the return values of the executed closures. 59 | 60 | ```php 61 | use Spatie\Fork\Fork; 62 | 63 | $results = Fork::new() 64 | ->run( 65 | function () { 66 | sleep(1); 67 | 68 | return 'result from task 1'; 69 | }, 70 | function () { 71 | sleep(1); 72 | 73 | return 'result from task 2'; 74 | }, 75 | function () { 76 | sleep(1); 77 | 78 | return 'result from task 3'; 79 | }, 80 | ); 81 | 82 | // this code will be reached this point after 1 second 83 | $results[0]; // contains 'result from task 1' 84 | $results[1]; // contains 'result from task 2' 85 | $results[2]; // contains 'result from task 3' 86 | ``` 87 | 88 | ### Running code before and after each closure 89 | 90 | If you need to execute some code before or after each callable passed to `run`, you can pass a callable to `before` or `after` methods. This callable passed will be executed in the child process right before or after the execution of the callable passed to `run`. 91 | 92 | ### Using `before` and `after` in the child task 93 | 94 | Here's an example where we are going to get a value from the database using a Laravel Eloquent model. In order to let the child task use the DB, it is necessary to reconnect to the DB. The closure passed to `before` will run in both child tasks that are created for the closures passed to `run`. 95 | 96 | ```php 97 | use App\Models\User; 98 | use Illuminate\Support\Facades\DB; 99 | use Spatie\Fork\Fork; 100 | 101 | Fork::new() 102 | ->before(fn () => DB::connection('mysql')->reconnect()) 103 | ->run( 104 | fn () => User::find(1)->someLongRunningFunction(), 105 | fn () => User::find(2)->someLongRunningFunction(), 106 | ); 107 | ``` 108 | 109 | If you need to perform some cleanup in the child task after the callable has run, you can use the `after` method on a `Spatie\Fork\Fork` instance. 110 | 111 | ### Using `before` and `after` in the parent task. 112 | 113 | If you need to let the callable passed to `before` or `after` run in the parent task, then you need to pass that callable to the `parent` argument. 114 | 115 | ```php 116 | use App\Models\User; 117 | use Illuminate\Support\Facades\DB; 118 | use Spatie\Fork\Fork; 119 | 120 | Fork::new() 121 | ->before( 122 | parent: fn() => echo 'this runs in the parent task' 123 | ) 124 | ->run( 125 | fn () => User::find(1)->someLongRunningFunction(), 126 | fn () => User::find(2)->someLongRunningFunction(), 127 | ); 128 | ``` 129 | 130 | You can also pass different closures, to be run in the child and the parent task 131 | 132 | ```php 133 | use Spatie\Fork\Fork; 134 | 135 | Fork::new() 136 | ->before( 137 | child: fn() => echo 'this runs in the child task', 138 | parent: fn() => echo 'this runs in the parent task', 139 | ) 140 | ->run( 141 | fn () => User::find(1)->someLongRunningFunction(), 142 | fn () => User::find(2)->someLongRunningFunction(), 143 | ); 144 | ``` 145 | 146 | ### Returning data 147 | 148 | All output data is gathered in an array and available as soon as all children are done. In this example, `$results` will contain three items: 149 | 150 | ```php 151 | $results = Fork::new() 152 | ->run( 153 | fn () => (new Api)->fetchData(userId: 1), 154 | fn () => (new Api)->fetchData(userId: 2), 155 | fn () => (new Api)->fetchData(userId: 3), 156 | ); 157 | ``` 158 | 159 | The output is also available in the `after` callbacks, which are called whenever a child is done and not at the very end: 160 | 161 | ```php 162 | $results = Fork::new() 163 | ->after( 164 | child: fn (int $i) => echo $i, // 1, 2 and 3 165 | parent: fn (int $i) => echo $i, // 1, 2 and 3 166 | ) 167 | ->run( 168 | fn () => 1, 169 | fn () => 2, 170 | fn () => 3, 171 | ); 172 | ``` 173 | 174 | Finally, return values from child tasks are serialized using PHP's built-in `serialize` method. This means that you can return anything you can normally serialize in PHP, including objects: 175 | 176 | ```php 177 | $result = Fork::new() 178 | ->run( 179 | fn () => new DateTime('2021-01-01'), 180 | fn () => new DateTime('2021-01-02'), 181 | ); 182 | ``` 183 | 184 | ### Configuring concurrency 185 | 186 | By default, all callables will be run in parallel. You can however configure a maximum amount of concurrent processes: 187 | 188 | ```php 189 | $results = Fork::new() 190 | ->concurrent(2) 191 | ->run( 192 | fn () => 1, 193 | fn () => 2, 194 | fn () => 3, 195 | ); 196 | ``` 197 | 198 | In this case, the first two functions will be run immediately and as soon as one of them finishes, the last one will start as well. 199 | 200 | ## Testing 201 | 202 | ```bash 203 | composer test 204 | ``` 205 | 206 | ## Changelog 207 | 208 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 209 | 210 | ## Contributing 211 | 212 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 213 | 214 | ## Security Vulnerabilities 215 | 216 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 217 | 218 | ## Credits 219 | 220 | - [Brent Roose](https://github.com/brendt) 221 | - [Freek Van der Herten](https://github.com/freekmurze) 222 | - [All Contributors](../../contributors) 223 | 224 | ## License 225 | 226 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 227 | --------------------------------------------------------------------------------