├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── Async.php ├── Pool.php ├── Task.php ├── event ├── ErrorEvent.php ├── Event.php └── SuccessEvent.php └── runtime ├── AutoloadRuntime.php └── ParentRuntime.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========================== 3 | 4 | 1.0.3 5 | -------------------------- 6 | 7 | - Enh: Support `wait()` async method return results of waited tasks. 8 | 9 | 1.0.2 10 | -------------------------- 11 | 12 | - Enh: Support dynamic Yii application class in child process. 13 | 14 | 1.0.1 15 | -------------------------- 16 | 17 | - Enh #1: Run async events declare first. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Vuong Xuong Minh 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii2 Async 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/vxm/yii2-async/v/stable)](https://packagist.org/packages/vxm/yii2-async) 4 | [![Total Downloads](https://poser.pugx.org/vxm/yii2-async/downloads)](https://packagist.org/packages/vxm/yii2-async) 5 | [![Build Status](https://travis-ci.org/vuongxuongminh/yii2-async.svg?branch=master)](https://travis-ci.org/vuongxuongminh/yii2-async) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/vuongxuongminh/yii2-async/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/vuongxuongminh/yii2-async/?branch=master) 7 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/vuongxuongminh/yii2-async/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/vuongxuongminh/yii2-async/?branch=master) 8 | [![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](http://www.yiiframework.com/) 9 | 10 | ## About it 11 | 12 | An extension provide an easy way to run code asynchronous and parallel base on [spatie/async](https://github.com/spatie/async) wrapper for Yii2 application. 13 | 14 | ## Requirements 15 | 16 | * [PHP >= 7.1](http://php.net) 17 | * [yiisoft/yii2 >= 2.0.13](https://github.com/yiisoft/yii2) 18 | 19 | ## Installation 20 | 21 | Require Yii2 Async using [Composer](https://getcomposer.org): 22 | 23 | ```bash 24 | composer require vxm/yii2-async 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### Configure 30 | 31 | Add the component to your application configure file: 32 | 33 | ```php 34 | [ 35 | 'components' => [ 36 | 'async' => [ 37 | 'class' => 'vxm\async\Async', 38 | 'appConfigFile' => '@app/config/async.php' // optional when you need to use yii feature in async process. 39 | ] 40 | ] 41 | ] 42 | ``` 43 | 44 | Because async code run in difference process you need to setup yii environment to use 45 | components via property `appConfigFile`. Example of an async app config file: 46 | 47 | ```php 48 | define('YII_ENV', 'dev'); 49 | define('YII_DEBUG', true); 50 | 51 | return [ 52 | 'id' => 'async-app', 53 | 'basePath' => __DIR__, 54 | 'runtimePath' => __DIR__ . '/runtime', 55 | 'aliases' => [ 56 | '@frontend' => dirname(__DIR__, 2) . '/frontend', 57 | '@backend' => dirname(__DIR__, 2) . '/backend' 58 | ] 59 | ]; 60 | ``` 61 | 62 | Make sure all of your aliases define in it to support an autoload. 63 | 64 | ### Run async code 65 | 66 | After add it to application components, now you can run an async code: 67 | 68 | ```php 69 | 70 | Yii::$app->async->run(function() { 71 | // do a thing. 72 | }); 73 | 74 | ``` 75 | 76 | ### Async events 77 | 78 | When creating asynchronous processes, you can add the following event hooks on a process in the second parameter. 79 | 80 | ```php 81 | 82 | Yii::$app->async->run(function() { 83 | 84 | if (rand(1, 2) === 1) { 85 | 86 | throw new \YourException; 87 | } 88 | 89 | return 123; 90 | }, [ 91 | 'success' => function ($result) { 92 | 93 | echo $result; // 123 94 | 95 | }, 96 | 'catch' => function (\YourException $exception) { 97 | 98 | // catch only \YourException 99 | 100 | }, 101 | 'error' => function() { 102 | 103 | // catch all exceptions 104 | 105 | }, 106 | 'timeout' => function() { 107 | 108 | // call when task timeout default's 15s 109 | 110 | } 111 | ]); 112 | 113 | ``` 114 | 115 | ### Wait process 116 | 117 | Sometime you need to wait a code executed, just call `wait()` after `run()`: 118 | 119 | ```php 120 | 121 | Yii::$app->async->run(function() { 122 | // do a thing. 123 | })->wait(); 124 | 125 | ``` 126 | 127 | Or you can wait multi tasks executed: 128 | 129 | ```php 130 | 131 | Yii::$app->async->run([YourAsyncClass::class, 'handleA']); 132 | Yii::$app->async->run([YourAsyncClass::class, 'handleD']); 133 | Yii::$app->async->run([YourAsyncClass::class, 'handleC']); 134 | Yii::$app->async->run([YourAsyncClass::class, 'handleD']); 135 | 136 | $results = Yii::$app->async->wait(); // results return from tasks above. 137 | 138 | ``` 139 | 140 | ### Working with task 141 | 142 | Besides using closures, you can also work with a Task. A Task is useful in situations where you need more setup work in the child process. 143 | 144 | The Task class makes this easier to do. 145 | 146 | ```php 147 | 148 | use vxm\async\Task; 149 | 150 | class MyTask extends Task 151 | { 152 | 153 | public $productId; 154 | 155 | 156 | public function run() 157 | { 158 | // Do the real work here. 159 | 160 | } 161 | } 162 | 163 | // Do task async use like an anonymous above. 164 | 165 | Yii::$app->async->run(new MyTask([ 166 | 'productId' => 123 167 | 168 | ])); 169 | 170 | ``` 171 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vxm/yii2-async", 3 | "description": "Provide an easy way to run code asynchronously for Yii2 application", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","yii2-async","async","php-async"], 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Vuong Minh", 10 | "email": "vuongxuongminh@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^7.1", 15 | "yiisoft/yii2": "~2.0.13", 16 | "spatie/async": "~1.0.0" 17 | }, 18 | "require-dev": { 19 | "symfony/stopwatch": "^4.0" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "vxm\\async\\": "/src" 24 | } 25 | }, 26 | "repositories": [ 27 | { 28 | "type": "composer", 29 | "url": "https://asset-packagist.org" 30 | } 31 | ], 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.0.x-dev" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Async.php: -------------------------------------------------------------------------------- 1 | [ 28 | * 'async' => 'vxm\async\Async' 29 | * ] 30 | * 31 | * ``` 32 | * 33 | * And after that you can run an async code: 34 | * 35 | * ```php 36 | * 37 | * Yii::$app->async->run(function () { 38 | * 39 | * sleep(5); 40 | * }); 41 | * 42 | * ``` 43 | * 44 | * If you want to wait until task done, you just to call [[wait]]. 45 | * 46 | * ```php 47 | * 48 | * Yii::$app->async->run(function () { 49 | * 50 | * sleep(5); 51 | * })->wait(); 52 | * 53 | * ``` 54 | * 55 | * Run multi tasks: 56 | * 57 | * ```php 58 | * 59 | * Yii::$app->async->run(function () { 60 | * 61 | * sleep(5); 62 | * }); 63 | * 64 | * Yii::$app->async->run(function () { 65 | * 66 | * sleep(5); 67 | * }); 68 | * 69 | * Yii::$app->async->wait(); 70 | * ``` 71 | * 72 | * @property string $autoload path of autoload file for runtime task execute environment. 73 | * @property int $sleepTimeWait time to sleep on wait tasks execute. 74 | * @property int $concurrency tasks executable. 75 | * @property int $timeout of task executable. 76 | * 77 | * @author Vuong Minh 78 | * @since 1.0.0 79 | */ 80 | class Async extends Component 81 | { 82 | 83 | /** 84 | * @event SuccessEvent an event that is triggered when task done. 85 | */ 86 | const EVENT_SUCCESS = 'success'; 87 | 88 | /** 89 | * @event ErrorEvent an event that is triggered when task error. 90 | */ 91 | const EVENT_ERROR = 'error'; 92 | 93 | /** 94 | * @event \yii\base\Event an event that is triggered when task timeout. 95 | */ 96 | const EVENT_TIMEOUT = 'timeout'; 97 | 98 | /** 99 | * @var Pool handling tasks. 100 | */ 101 | protected $pool; 102 | 103 | /** 104 | * @var string a file config of an application run in child process. 105 | * Note: If an autoload file's set, it will not affect, you need to invoke an app in your autoload if needed. 106 | */ 107 | protected $appConfigFile; 108 | 109 | /** 110 | * Async constructor. 111 | * 112 | * @param array $config 113 | * @throws \yii\base\InvalidConfigException 114 | */ 115 | public function __construct($config = []) 116 | { 117 | $pool = $this->pool = Yii::createObject(Pool::class); 118 | $pool->autoload(__DIR__ . '/runtime/AutoloadRuntime.php'); 119 | 120 | parent::__construct($config); 121 | } 122 | 123 | /** 124 | * Execute async task. 125 | * 126 | * @param callable|\Spatie\Async\Task|Task $callable need to execute. 127 | * @param array $callbacks event. Have key is an event name, value is a callable triggered when event happen, 128 | * have three events `error`, `success`, `timeout`. 129 | * @return static 130 | */ 131 | public function run($callable, array $callbacks = []): self 132 | { 133 | $process = $this->createProcess($callable); 134 | $this->registerProcessEvents($callbacks, $process); 135 | $this->registerProcessGlobalEvents($process); 136 | $this->pool->add($process); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * This method is called when task executed success. 143 | * When overriding this method, make sure you call the parent implementation to ensure the 144 | * event is triggered. 145 | * 146 | * @param mixed $output of task executed. 147 | * @throws \yii\base\InvalidConfigException 148 | */ 149 | public function success($output): void 150 | { 151 | $event = Yii::createObject([ 152 | 'class' => SuccessEvent::class, 153 | 'output' => $output 154 | ]); 155 | 156 | $this->trigger(self::EVENT_SUCCESS, $event); 157 | } 158 | 159 | /** 160 | * This method is called when task executed error. 161 | * When overriding this method, make sure you call the parent implementation to ensure the 162 | * event is triggered. 163 | * 164 | * @param Throwable $throwable when executing task. 165 | * @throws \yii\base\InvalidConfigException 166 | */ 167 | public function error(Throwable $throwable): void 168 | { 169 | $event = Yii::createObject([ 170 | 'class' => ErrorEvent::class, 171 | 'throwable' => $throwable 172 | ]); 173 | 174 | $this->trigger(self::EVENT_ERROR, $event); 175 | } 176 | 177 | /** 178 | * This method is called when task executed timeout. 179 | * When overriding this method, make sure you call the parent implementation to ensure the 180 | * event is triggered. 181 | * 182 | * @throws \yii\base\InvalidConfigException 183 | */ 184 | public function timeout(): void 185 | { 186 | $event = Yii::createObject(Event::class); 187 | 188 | $this->trigger(self::EVENT_TIMEOUT, $event); 189 | } 190 | 191 | /** 192 | * Register events to process given. 193 | * 194 | * @param array $events register to process given. 195 | * @param Runnable $process need to add events. 196 | * @since 1.0.3 197 | */ 198 | protected function registerProcessEvents(array $events, Runnable $process): void 199 | { 200 | foreach ($events as $event => $callable) { 201 | switch (strtolower($event)) { 202 | case 'success': 203 | $process->then(Closure::fromCallable($callable)); 204 | break; 205 | case 'error': 206 | case 'catch': 207 | $process->catch(Closure::fromCallable($callable)); 208 | break; 209 | case 'timeout': 210 | $process->timeout(Closure::fromCallable($callable)); 211 | break; 212 | default: 213 | break; 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * Register global events to process given. 220 | * 221 | * @param Runnable $process need to add global events. 222 | * @since 1.0.3 223 | */ 224 | protected function registerProcessGlobalEvents(Runnable $process): void 225 | { 226 | $process->then(Closure::fromCallable([$this, 'success'])); 227 | $process->catch(Closure::fromCallable([$this, 'error'])); 228 | $process->timeout(Closure::fromCallable([$this, 'timeout'])); 229 | } 230 | 231 | /** 232 | * Wait until all tasks done. 233 | * 234 | * @since 1.0.3 return results of async processes. 235 | */ 236 | public function wait() 237 | { 238 | $results = $this->pool->wait(); 239 | $this->pool->flush(); 240 | 241 | return $results; 242 | } 243 | 244 | /** 245 | * Set concurrency process do tasks. 246 | * 247 | * @param int $concurrency 248 | */ 249 | public function setConcurrency(int $concurrency): void 250 | { 251 | $this->pool->concurrency($concurrency); 252 | } 253 | 254 | /** 255 | * Set timeout of task when execute. 256 | * 257 | * @param int $timeout 258 | */ 259 | public function setTimeout(int $timeout): void 260 | { 261 | $this->pool->timeout($timeout); 262 | } 263 | 264 | /** 265 | * Set sleep time when wait tasks execute. 266 | * 267 | * @param int $sleepTimeWait 268 | */ 269 | public function setSleepTimeWait(int $sleepTimeWait): void 270 | { 271 | $this->pool->sleepTime($sleepTimeWait); 272 | } 273 | 274 | /** 275 | * Set autoload for environment tasks execute. 276 | * @param string $autoload it can use at an alias. 277 | */ 278 | public function setAutoload(string $autoload): void 279 | { 280 | $this->pool->autoload(Yii::getAlias($autoload)); 281 | } 282 | 283 | /** 284 | * Set an application config file for invoke in child runtime process. 285 | * 286 | * @param string $appConfigFile it can use at an alias. 287 | */ 288 | public function setAppConfigFile(string $appConfigFile): void 289 | { 290 | $this->appConfigFile = Yii::getAlias($appConfigFile); 291 | } 292 | 293 | /** 294 | * Create an async process. 295 | * 296 | * @param callable|\Spatie\Async\Task|Task $callable need to execute. 297 | * @return Runnable process. 298 | */ 299 | protected function createProcess($callable): Runnable 300 | { 301 | return ParentRuntime::createProcess([$callable, $this->appConfigFile]); 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /src/Pool.php: -------------------------------------------------------------------------------- 1 | 16 | * @since 1.0.0 17 | */ 18 | class Pool extends BasePool 19 | { 20 | 21 | /** 22 | * Flush the pool. 23 | */ 24 | public function flush(): void 25 | { 26 | $this->results = []; 27 | $this->failed = []; 28 | $this->queue = []; 29 | $this->finished = []; 30 | $this->timeouts = []; 31 | $this->inProgress = []; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Task.php: -------------------------------------------------------------------------------- 1 | 16 | * @since 1.0.0 17 | */ 18 | abstract class Task extends BaseObject 19 | { 20 | 21 | /** 22 | * Task executable. 23 | * 24 | * @return mixed 25 | */ 26 | abstract public function run(); 27 | 28 | /** 29 | * Magic call in child runtime process. 30 | * 31 | * @return mixed result of this task. 32 | * @see [[run()]] 33 | */ 34 | public function __invoke() 35 | { 36 | return $this->run(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/event/ErrorEvent.php: -------------------------------------------------------------------------------- 1 | 14 | * @since 1.0.0 15 | */ 16 | class ErrorEvent extends Event 17 | { 18 | 19 | /** 20 | * @var \Throwable when executing task. 21 | */ 22 | public $throwable; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/event/Event.php: -------------------------------------------------------------------------------- 1 | 16 | * @since 1.0.0 17 | */ 18 | class Event extends BaseEvent 19 | { 20 | 21 | /** 22 | * @var \vxm\async\Async object triggered this. 23 | */ 24 | public $sender; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/event/SuccessEvent.php: -------------------------------------------------------------------------------- 1 | 14 | * @since 1.0.0 15 | */ 16 | class SuccessEvent extends Event 17 | { 18 | 19 | /** 20 | * @var mixed output of task executed. 21 | */ 22 | public $output; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/runtime/AutoloadRuntime.php: -------------------------------------------------------------------------------- 1 | 8 | * @since 1.0.0 9 | * 10 | * Autoload in environment task executable. 11 | */ 12 | new class 13 | { 14 | const AUTOLOAD_PATHS = [ 15 | [ 16 | __DIR__ . '/../../../../autoload.php', 17 | __DIR__ . '/../../../autoload.php', 18 | __DIR__ . '/../../vendor/autoload.php', 19 | __DIR__ . '/../../../vendor/autoload.php' 20 | ], 21 | [ 22 | __DIR__ . '/../../../../yiisoft/yii2/Yii.php', 23 | __DIR__ . '/../../../yiisoft/yii2/Yii.php', 24 | __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php', 25 | __DIR__ . '/../../../vendor/yiisoft/yii2/Yii.php' 26 | ] 27 | ]; 28 | 29 | /** 30 | * Constructor require an autoload files. 31 | */ 32 | public function __construct() 33 | { 34 | if ($appConfigFile = $_SERVER['argv'][3] ?? null) { 35 | 36 | $appConfig = require($appConfigFile); // require first for define YII_ENV and YII_DEBUG. 37 | } 38 | 39 | foreach (self::AUTOLOAD_PATHS as $paths) { 40 | 41 | foreach ($paths as $path) { 42 | 43 | if (file_exists($path)) { 44 | 45 | require($path); 46 | 47 | break; 48 | } 49 | 50 | } 51 | } 52 | 53 | if (isset($appConfig)) { 54 | 55 | $appConfig['class'] = $appConfig['class'] ?? 'yii\console\Application'; 56 | 57 | Yii::createObject($appConfig); 58 | } 59 | } 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /src/runtime/ParentRuntime.php: -------------------------------------------------------------------------------- 1 | 21 | * @since 1.0.0 22 | */ 23 | class ParentRuntime extends BaseParentRuntime 24 | { 25 | 26 | /** 27 | * @inheritDoc 28 | */ 29 | public static function createProcess($task): Runnable 30 | { 31 | if (!self::$isInitialised) { 32 | self::init(); 33 | } 34 | 35 | list($task, $appConfigFile) = $task; 36 | 37 | if (!Pool::isSupported()) { 38 | return SynchronousProcess::create($task, self::getId()); 39 | } 40 | 41 | $process = new Process(implode(' ', [ 42 | 'exec php', 43 | self::$childProcessScript, 44 | self::$autoloader, 45 | self::encodeTask($task), 46 | $appConfigFile 47 | ])); 48 | 49 | return ParallelProcess::create($process, self::getId()); 50 | } 51 | } 52 | --------------------------------------------------------------------------------