├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── demo ├── demo.php └── demo2.php └── src └── TaskBalancer ├── Balancer.php ├── Driver.php ├── Task.php └── TaskBalancerException.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 lan tian peng 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/toplan/task-balancer.svg)](https://packagist.org/packages/toplan/task-balancer) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/toplan/task-balancer.svg)](https://packagist.org/packages/toplan/task-balancer) 5 | 6 | Lightweight and powerful task load balancing. 7 | 8 | > like the `nginx` load balancing :smile: 9 | 10 | # Features 11 | 12 | - Support multiple drives for every task. 13 | - Automatically choose a driver to execute task by drivers' weight value. 14 | - Support multiple backup drivers. 15 | - Task lifecycle and hooks system. 16 | 17 | # Install 18 | 19 | ```php 20 | composer require toplan/task-balancer:~0.5 21 | ``` 22 | 23 | # Usage 24 | 25 | ```php 26 | //define a task 27 | Balancer::task('task1', function($task){ 28 | //define a driver for current task like this: 29 | $task->driver('driver_1 100 backup', function ($driver, $data) { 30 | //do something here 31 | ... 32 | //set whether run success/failure at last 33 | if ($success) { 34 | $driver->success(); 35 | } else { 36 | $driver->failure(); 37 | } 38 | //return some data you need 39 | return 'some data you need'; 40 | }); 41 | 42 | //or like this: 43 | $task->driver('driver_2', 90, function ($driver, $data) { 44 | //...same as above.. 45 | })->data(['this is data 2']); 46 | 47 | //or like this: 48 | $task->driver('driver_3') 49 | ->weight(0)->backUp() 50 | ->data(['this is data 3']) 51 | ->work(function ($driver, $data) { 52 | //...same as above.. 53 | }); 54 | }); 55 | 56 | //run the task 57 | $result = Balancer::run('task1'); 58 | ``` 59 | 60 | The `$result` structure: 61 | ```php 62 | [ 63 | 'success' => true, 64 | 'time' => [ 65 | 'started_at' => timestamp, 66 | 'finished_at' => timestamp 67 | ], 68 | 'logs' => [ 69 | '0' => [ 70 | 'driver' => 'driver_1', 71 | 'success' => false, 72 | 'time' => [ 73 | 'started_at' => timestamp, 74 | 'finished_at' => timestamp 75 | ], 76 | 'result' => 'some data you need' 77 | ], 78 | ... 79 | ] 80 | ] 81 | ``` 82 | 83 | # API 84 | 85 | ## Balancer 86 | 87 | ### Balancer::task($name[, $data][, Closure $ready]); 88 | 89 | Create a task instance, and return it. 90 | The closure `$ready` immediately called with argument `$task`. 91 | 92 | ```php 93 | Balancer::task('taskName', $data, function($task){ 94 | //task's ready work, such as create drivers. 95 | }); 96 | ``` 97 | 98 | > `$data` will store in the task instance. 99 | 100 | ### Balancer::run($name[, array $options]) 101 | 102 | Run the task by name, and return the result data. 103 | 104 | The keys of `$options`: 105 | - `data` 106 | - `driver` 107 | 108 | ## Task 109 | 110 | ### name($name) 111 | 112 | set the name of task. 113 | 114 | ### data($data) 115 | 116 | Set the data of task. 117 | 118 | ### driver($config[, $weight][, 'backup'], Closure $work) 119 | 120 | Create a driver for the task. The closure `$work` will been called with arguments `$driver` and `$data`. 121 | 122 | > Expected `$weight` to be a integer, default `1`. 123 | 124 | ```php 125 | $task->driver('driverName 80 backup', function($driver, $data){ 126 | //driver's job content. 127 | }); 128 | ``` 129 | 130 | ### hasDriver($name) 131 | 132 | Whether has the specified driver. 133 | 134 | ### getDriver($name) 135 | 136 | Get driver by name. 137 | 138 | ### removeDriver($name) 139 | 140 | Remove driver from drivers' pool by name. 141 | 142 | ## Driver 143 | 144 | ### weight($weight) 145 | 146 | Set the weight value of driver. 147 | 148 | ### backup($is) 149 | 150 | Set whether backup driver. 151 | 152 | > Expected `$is` to be boolean, default `true`. 153 | 154 | ### data($data) 155 | 156 | Set the data of driver. 157 | 158 | > `$data` will store in driver instance. 159 | 160 | ### work(Closure $work); 161 | 162 | Set the job content of driver. 163 | 164 | > `$data` equals to `$driver->getData()` 165 | 166 | ### reset($config[, $weight][, 'backup'], Closure $work) 167 | 168 | Reset driver's weight value, job content and reset whether backup. 169 | 170 | ### destroy() 171 | 172 | Remove the driver from task which belongs to. 173 | 174 | ### failure() 175 | 176 | Set the driver running failure. 177 | 178 | ### success() 179 | 180 | Set the driver run successfully. 181 | 182 | ### getDriverData() 183 | 184 | Get the data which store in driver instance. 185 | 186 | ### getTaskData() 187 | 188 | Get the data which store in task instance. 189 | 190 | 191 | ## Lifecycle & Hooks 192 | 193 | > Support multiple handlers for every hooks! 194 | 195 | ### Hooks 196 | 197 | | Hook name | handler arguments | influence of the last handler's return value | 198 | | --------- | :----------------: | :-----: | 199 | | beforeCreateDriver | $task, $props, $index, &$handlers, $prevReturn | if an array will been merged into original props | 200 | | afterCreateDriver | $task, $driver, $index, &$handlers, $prevReturn | - | 201 | | beforeRun | $task, $index, &$handlers, $prevReturn | if `false` will stop run task and return `false` | 202 | | beforeDriverRun | $task, $driver, $index, &$handlers, $prevReturn | if `false` will stop to use current driver and try to use next backup driver | 203 | | afterDriverRun | $task, $driverResult, $index, &$handlers, $prevReturn | - | 204 | | afterRun | $task, $taskResult, $index, &$handlers, $prevReturn | if not boolean will override result value | 205 | 206 | ### Usage 207 | 208 | * $task->hook($hookName, $handler, $override) 209 | 210 | * $task->beforeCreateDriver($handler, $override) 211 | 212 | * $task->afterCreateDriver($handler, $override) 213 | 214 | * $task->beforeRun($handler, $override) 215 | 216 | * $task->beforeDriverRun($handler, $override) 217 | 218 | * $task->afterDriverRun($handler, $override) 219 | 220 | * $task->afterRun($handler, $override) 221 | 222 | > `$override` default `false`. 223 | 224 | ```php 225 | //example 226 | $task->beforeRun(function($task, $index, $handlers, $prevReturn){ 227 | //what is $prevReturn? 228 | echo $prevReturn == null; //true 229 | //what is $index? 230 | echo $index == 0; //true 231 | //what is $handlers? 232 | echo count($handlers); //2 233 | //do something.. 234 | return 'beforeRun_1'; 235 | }, false); 236 | 237 | $task->beforeRun(function($task, $index, $handlers, $prevReturn){ 238 | //what is $prevReturn? 239 | echo $prevReturn == 'beforeRun_1'; //true 240 | //what is $index? 241 | echo $index == 1; //true 242 | //what is $handlers? 243 | echo count($handlers); //2 244 | //do other something.. 245 | }, false); 246 | ``` 247 | 248 | # Dependents 249 | 250 | - [phpsms](https://github.com/toplan/phpsms) 251 | 252 | # License 253 | 254 | MIT 255 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toplan/task-balancer", 3 | "description": "lightweight and powerful task load balancing.", 4 | "license": "MIT", 5 | "keywords": ["task", "balance", "load balancing"], 6 | "authors": [ 7 | { 8 | "name": "toplan", 9 | "email": "toplan710@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Toplan\\TaskBalance\\": "src/TaskBalancer/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/demo.php: -------------------------------------------------------------------------------- 1 | 'top lan', 13 | 'age' => '20', 14 | ]; 15 | 16 | //define task: 17 | $t = Balancer::task('test1', $data, function ($task) { 18 | $task->driver('driver_1 90', 'backup', function ($driver, $data) { 19 | $person = new Person($data['name'], $data['age']); 20 | $driver->failure(); 21 | print_r('run work! by '.$driver->name.'
'); 22 | 23 | return ['test.driver1 working', $person->toString()]; 24 | }); 25 | 26 | $task->driver('driver_2', 90, function ($driver, $data) { 27 | $driver->failure(); 28 | print_r('run work! by '.$driver->name.'
'); 29 | 30 | return ['test.driver2 working', $data]; 31 | }) 32 | ->data(['this is data 2']); 33 | 34 | $task->driver('driver_3') 35 | ->weight(0)->backUp() 36 | ->data(['this is data 3']) 37 | ->work(function ($driver, $data) { 38 | $driver->success(); 39 | print_r('run work! by '.$driver->name.'
'); 40 | 41 | return ['test.driver3 working', $data]; 42 | }); 43 | 44 | $task->beforeRun(function ($task) { 45 | print_r('before run --------!
'); 46 | }); 47 | 48 | $task->afterRun(function ($task, $results) { 49 | print_r('after run --------!
'); 50 | }); 51 | }); 52 | 53 | $result = Balancer::run('test1', $data); 54 | var_dump($result); 55 | 56 | class Person 57 | { 58 | protected $name; 59 | 60 | protected $age; 61 | 62 | public function __construct($name, $age) 63 | { 64 | $this->name = $name; 65 | $this->age = $age; 66 | } 67 | 68 | public function toString() 69 | { 70 | return "hi, I am $this->name, and $this->age year old"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /demo/demo2.php: -------------------------------------------------------------------------------- 1 | driver('driver1 10 backup', function ($driver, $data) { 18 | $driver->failure(); 19 | print_r('run work! by '.$driver->name.'
'); 20 | }); 21 | 22 | $task->beforeRun(function ($task, $index, $handlers, $preReturn) { 23 | print_r("before run ---$preReturn-----$index
"); 24 | 25 | return 11; 26 | }); 27 | 28 | $task->beforeRun(function ($task, $index, $handlers, $preReturn) { 29 | print_r("before run ---$preReturn-----$index
"); 30 | 31 | return 22; 32 | }, false); 33 | 34 | $task->beforeRun(function ($task, $index, $handlers, $preReturn) { 35 | print_r("before run ---$preReturn-----$index
"); 36 | }); 37 | 38 | $task->hook('beforeDriverRun', function ($task, $driver, $index, $handlers, $preReturn) { 39 | print_r("before driver run ---$preReturn-----$index
"); 40 | 41 | return [1]; 42 | }); 43 | 44 | $task->hook('beforeDriverRun', function ($task, $driver, $index, $handlers, $preReturn) { 45 | print_r('before driver run ---'.implode('=', $preReturn ?: [])."-----$index
"); 46 | 47 | return [1, 2]; 48 | }, true); 49 | 50 | $task->hook('beforeDriverRun', function ($task, $driver, $index, $handlers, $preReturn) { 51 | print_r('before driver run ---'.implode('=', $preReturn)."-----$index
"); 52 | 53 | return [1, 2, 3]; 54 | }); 55 | 56 | $task->afterRun(function ($task, $results, $index, $handlers, $preReturn) { 57 | print_r('after run --------!
'); 58 | }); 59 | }); 60 | 61 | $data = [ 62 | 'some' => 'data' 63 | ]; 64 | 65 | //run task: 66 | $result = Balancer::run('task1', $data); 67 | 68 | print_r('
'); 69 | var_dump($result); 70 | -------------------------------------------------------------------------------- /src/TaskBalancer/Balancer.php: -------------------------------------------------------------------------------- 1 | data($opts['data']); 59 | } 60 | $driverName = isset($opts['driver']) ?: null; 61 | 62 | return $task->run($driverName); 63 | } 64 | 65 | /** 66 | * whether has task. 67 | * 68 | * @param string $name 69 | * 70 | * @return bool 71 | */ 72 | public static function hasTask($name) 73 | { 74 | if (!self::$tasks) { 75 | return false; 76 | } 77 | if (isset(self::$tasks[$name])) { 78 | return true; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | /** 85 | * get a task instance by name. 86 | * 87 | * @param string $name 88 | * 89 | * @return Task|null 90 | */ 91 | public static function getTask($name) 92 | { 93 | if (self::hasTask($name)) { 94 | return self::$tasks[$name]; 95 | } 96 | } 97 | 98 | /** 99 | * destroy a task. 100 | * 101 | * @param string|string[] $name 102 | */ 103 | public static function destroy($name) 104 | { 105 | if (is_array($name)) { 106 | foreach ($name as $v) { 107 | self::destroy($v); 108 | } 109 | } elseif (is_string($name) && self::hasTask($name)) { 110 | unset(self::$tasks[$name]); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/TaskBalancer/Driver.php: -------------------------------------------------------------------------------- 1 | 0, 66 | 'finished_at' => 0, 67 | ]; 68 | 69 | /** 70 | * constructor. 71 | * 72 | * @param Task $task 73 | * @param string $name 74 | * @param int $weight 75 | * @param \Closure $work 76 | * @param bool $backup 77 | * 78 | * @throws TaskBalancerException 79 | */ 80 | public function __construct(Task $task, $name, $weight = 1, $backup = false, \Closure $work = null) 81 | { 82 | if (!is_string($name) || empty($name)) { 83 | throw new TaskBalancerException('Expected the driver name to be a non-empty string.'); 84 | } 85 | if ($task->hasDriver($name)) { 86 | throw new TaskBalancerException("The driver name `$name` already exits."); 87 | } 88 | $weight = intval($weight); 89 | 90 | $this->task = $task; 91 | $this->name = $name; 92 | $this->weight = $weight >= 0 ? $weight : 0; 93 | $this->backup = (bool) $backup; 94 | $this->work = $work; 95 | } 96 | 97 | /** 98 | * create a driver instance. 99 | * 100 | * @param Task $task 101 | * @param string $name 102 | * @param int $weight 103 | * @param \Closure $work 104 | * @param bool $backup 105 | * 106 | * @return static 107 | */ 108 | public static function create(Task $task, $name, $weight = 1, $backup = false, \Closure $work = null) 109 | { 110 | return new self($task, $name, $weight, $backup, $work); 111 | } 112 | 113 | /** 114 | * before run driver work. 115 | * 116 | * @return bool 117 | */ 118 | protected function beforeRun() 119 | { 120 | $this->time['started_at'] = microtime(); 121 | 122 | return true; 123 | } 124 | 125 | /** 126 | * run driver`s work. 127 | * 128 | * @return mixed 129 | */ 130 | public function run() 131 | { 132 | if (!$this->beforeRun()) { 133 | return; 134 | } 135 | $result = null; 136 | if (is_callable($this->work)) { 137 | $result = call_user_func_array($this->work, [$this, $this->getData()]); 138 | } 139 | 140 | return $this->afterRun($result); 141 | } 142 | 143 | /** 144 | * after run driver work. 145 | * 146 | * @param mixed $result 147 | * 148 | * @return mixed 149 | */ 150 | protected function afterRun($result) 151 | { 152 | $this->time['finished_at'] = microtime(); 153 | 154 | return $result; 155 | } 156 | 157 | /** 158 | * set driver run succeed. 159 | */ 160 | public function success() 161 | { 162 | $this->success = true; 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * set driver run failed. 169 | */ 170 | public function failure() 171 | { 172 | $this->success = false; 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * set data of driver. 179 | * 180 | * @param mixed $data 181 | * 182 | * @return $this 183 | */ 184 | public function data($data) 185 | { 186 | $this->data = $data; 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * set weight value of driver. 193 | * 194 | * @param int $weight 195 | * 196 | * @return $this 197 | */ 198 | public function weight($weight) 199 | { 200 | $this->weight = intval($weight); 201 | 202 | return $this; 203 | } 204 | 205 | /** 206 | * set current driver to be a backup driver. 207 | * 208 | * @param bool $is 209 | * 210 | * @return $this 211 | */ 212 | public function backup($is = true) 213 | { 214 | $is = (bool) $is; 215 | if ($this->backup === $is) { 216 | return $this; 217 | } 218 | $this->backup = $is; 219 | if ($this->backup) { 220 | $this->task->appendToBackupDrivers($this); 221 | } else { 222 | $this->task->removeFromBackupDrivers($this); 223 | } 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * set the run logic of driver. 230 | * 231 | * @param \Closure $work 232 | * 233 | * @return $this 234 | */ 235 | public function work(\Closure $work) 236 | { 237 | $this->work = $work; 238 | 239 | return $this; 240 | } 241 | 242 | /** 243 | * reset driver's properties. 244 | */ 245 | public function reset() 246 | { 247 | $args = func_get_args(); 248 | extract(self::parseArgs($args)); 249 | if ($this->weight !== $weight) { 250 | $this->weight($weight); 251 | } 252 | if ($this->backup !== $backup) { 253 | $this->backup($backup); 254 | } 255 | if (is_callable($work) && $this->work !== $work) { 256 | $this->work($work); 257 | } 258 | 259 | return $this; 260 | } 261 | 262 | /** 263 | * get data. 264 | * 265 | * @return mixed 266 | */ 267 | public function getData() 268 | { 269 | return $this->getDriverData() ?: $this->getTaskData(); 270 | } 271 | 272 | /** 273 | * get driver data. 274 | * 275 | * @return mixed 276 | */ 277 | public function getDriverData() 278 | { 279 | return $this->data; 280 | } 281 | 282 | /** 283 | * get task data. 284 | * 285 | * @return mixed 286 | */ 287 | public function getTaskData() 288 | { 289 | return $this->task->data; 290 | } 291 | 292 | /** 293 | * remove driver from task. 294 | */ 295 | public function destroy() 296 | { 297 | $this->task->removeDriver($this->name); 298 | } 299 | 300 | /** 301 | * override. 302 | * 303 | * @param string $name 304 | * 305 | * @return mixed 306 | */ 307 | public function __get($name) 308 | { 309 | if ($name === 'data') { 310 | return $this->getData(); 311 | } 312 | if (isset($this->$name)) { 313 | return $this->$name; 314 | } 315 | } 316 | 317 | /** 318 | * parse arguments to driver properties. 319 | * 320 | * @param array $args 321 | * 322 | * @return array 323 | */ 324 | public static function parseArgs(array $args) 325 | { 326 | $result = [ 327 | 'name' => null, 328 | 'work' => null, 329 | 'weight' => 1, 330 | 'backup' => false, 331 | ]; 332 | foreach ($args as $arg) { 333 | //find work 334 | if (is_callable($arg)) { 335 | $result['work'] = $arg; 336 | } 337 | //find weight, backup, name 338 | if (is_string($arg) || is_numeric($arg)) { 339 | $arg = preg_replace('/\s+/', ' ', "$arg"); 340 | $subArgs = explode(' ', trim($arg)); 341 | foreach ($subArgs as $subArg) { 342 | if (preg_match('/^[0-9]+$/', $subArg)) { 343 | $result['weight'] = intval($subArg); 344 | } elseif (preg_match('/(backup)/', strtolower($subArg))) { 345 | $result['backup'] = true; 346 | } else { 347 | $result['name'] = $subArg; 348 | } 349 | } 350 | } 351 | } 352 | 353 | return $result; 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/TaskBalancer/Task.php: -------------------------------------------------------------------------------- 1 | 0, 70 | 'finished_at' => 0, 71 | ]; 72 | 73 | /** 74 | * data of task. 75 | * 76 | * @var mixed 77 | */ 78 | protected $data = null; 79 | 80 | /** 81 | * logs of drivers. 82 | * 83 | * @var array 84 | */ 85 | protected $results = []; 86 | 87 | /** 88 | * handlers of hooks. 89 | * 90 | * @var array 91 | */ 92 | protected $handlers = []; 93 | 94 | /** 95 | * constructor. 96 | * 97 | * @param mixed $data 98 | */ 99 | public function __construct($data = null) 100 | { 101 | $this->data($data); 102 | } 103 | 104 | /** 105 | * create a new task. 106 | * 107 | * @param string|null $name 108 | * @param mixed $data 109 | * @param \Closure|null $ready 110 | * 111 | * @return Task 112 | */ 113 | public static function create($name = null, $data = null, \Closure $ready = null) 114 | { 115 | $task = new self($data); 116 | $task->name($name); 117 | if (is_callable($ready)) { 118 | call_user_func($ready, $task); 119 | } 120 | 121 | return $task; 122 | } 123 | 124 | /** 125 | * run task. 126 | * 127 | * @param string|null $driverName 128 | * 129 | * @throws \Exception 130 | * 131 | * @return bool 132 | */ 133 | public function run($driverName = null) 134 | { 135 | if ($this->isRunning()) { 136 | return false; 137 | } 138 | if (!$this->beforeRun()) { 139 | return false; 140 | } 141 | if (!$driverName) { 142 | $driverName = $this->getDriverNameByWeight(); 143 | } 144 | $this->initBackupDrivers([$driverName]); 145 | $success = $this->runDriver($driverName); 146 | 147 | return $this->afterRun($success); 148 | } 149 | 150 | /** 151 | * before run task. 152 | * 153 | * @return bool 154 | */ 155 | protected function beforeRun() 156 | { 157 | $this->reset(); 158 | $pass = $this->callHookHandler('beforeRun'); 159 | if ($pass) { 160 | $this->status = static::RUNNING; 161 | $this->time['started_at'] = microtime(); 162 | } 163 | 164 | return $pass; 165 | } 166 | 167 | /** 168 | * reset states. 169 | * 170 | * @return $this 171 | */ 172 | protected function reset() 173 | { 174 | $this->status = null; 175 | $this->results = []; 176 | $this->currentDriver = null; 177 | $this->time['started_at'] = 0; 178 | $this->time['finished_at'] = 0; 179 | 180 | return $this; 181 | } 182 | 183 | /** 184 | * after run task. 185 | * 186 | * @param bool $success 187 | * 188 | * @return mixed 189 | */ 190 | protected function afterRun($success) 191 | { 192 | $this->status = static::FINISHED; 193 | $this->time['finished_at'] = microtime(); 194 | $return = []; 195 | $return['success'] = $success; 196 | $return['time'] = $this->time; 197 | $return['logs'] = $this->results; 198 | $data = $this->callHookHandler('afterRun', $return); 199 | 200 | return is_bool($data) ? $return : $data; 201 | } 202 | 203 | /** 204 | * run driver by name. 205 | * 206 | * @param string $name 207 | * 208 | * @throws TaskBalancerException 209 | * 210 | * @return bool 211 | */ 212 | public function runDriver($name) 213 | { 214 | // if not found driver by the name, throw exception. 215 | $driver = $this->getDriver($name); 216 | if (!$driver) { 217 | return false; 218 | } 219 | $this->currentDriver = $driver; 220 | 221 | // before run a driver, call 'beforeDriverRun' hooks, 222 | // and current driver has already changed. 223 | // If 'beforeDriverRun' hook return false, 224 | // stop to use current driver and try to use next driver. 225 | $currentDriverEnable = $this->callHookHandler('beforeDriverRun', $driver); 226 | if (!$currentDriverEnable) { 227 | return $this->tryNextDriver(); 228 | } 229 | 230 | // start run driver, and store the result. 231 | $result = $driver->run(); 232 | $success = $driver->success; 233 | $data = [ 234 | 'driver' => $driver->name, 235 | 'time' => $driver->time, 236 | 'success' => $success, 237 | 'result' => $result, 238 | ]; 239 | array_push($this->results, $data); 240 | 241 | // call 'afterDriverRun' hooks. 242 | $this->callHookHandler('afterDriverRun', $data); 243 | 244 | // if failed, try to use next backup driver. 245 | if (!$success) { 246 | return $this->tryNextDriver(); 247 | } 248 | 249 | return true; 250 | } 251 | 252 | /** 253 | * try to use next backup driver. 254 | * 255 | * @return bool 256 | */ 257 | public function tryNextDriver() 258 | { 259 | $backupDriverName = array_pop($this->backupDrivers); 260 | if ($backupDriverName) { 261 | return $this->runDriver($backupDriverName); 262 | } 263 | 264 | return false; 265 | } 266 | 267 | /** 268 | * get a driver's name from drivers by driver's weight. 269 | * 270 | * @return string|null 271 | */ 272 | public function getDriverNameByWeight() 273 | { 274 | $count = $base = 0; 275 | $map = []; 276 | foreach ($this->drivers as $driver) { 277 | $count += $driver->weight; 278 | if ($driver->weight) { 279 | $max = $base + $driver->weight; 280 | $map[] = [ 281 | 'min' => $base, 282 | 'max' => $max, 283 | 'driver' => $driver->name, 284 | ]; 285 | $base = $max; 286 | } 287 | } 288 | if ($count <= 0) { 289 | return; 290 | } 291 | $number = mt_rand(0, $count - 1); 292 | foreach ($map as $data) { 293 | if ($number >= $data['min'] && $number < $data['max']) { 294 | return $data['driver']; 295 | } 296 | } 297 | } 298 | 299 | /** 300 | * create a new driver instance for current task. 301 | * 302 | * @return Driver 303 | */ 304 | public function driver() 305 | { 306 | $args = func_get_args(); 307 | $props = Driver::parseArgs($args); 308 | $newProps = $this->callHookHandler('beforeCreateDriver', $props); 309 | if (is_array($newProps)) { 310 | $props = array_merge($props, $newProps); 311 | } 312 | extract($props); 313 | $driver = Driver::create($this, $name, $weight, $backup, $work); 314 | $this->drivers[$name] = $driver; 315 | $this->callHookHandler('afterCreateDriver', $driver); 316 | 317 | return $driver; 318 | } 319 | 320 | /** 321 | * has driver. 322 | * 323 | * @param string $name 324 | * 325 | * @return bool 326 | */ 327 | public function hasDriver($name) 328 | { 329 | if (!$this->drivers) { 330 | return false; 331 | } 332 | 333 | return isset($this->drivers[$name]); 334 | } 335 | 336 | /** 337 | * get a driver by name. 338 | * 339 | * @param string $name 340 | * 341 | * @return Driver|null 342 | */ 343 | public function getDriver($name) 344 | { 345 | if ($this->hasDriver($name)) { 346 | return $this->drivers[$name]; 347 | } 348 | } 349 | 350 | /** 351 | * remove driver. 352 | * 353 | * @param Driver|string $driver 354 | */ 355 | public function removeDriver($driver) 356 | { 357 | if ($driver instanceof Driver) { 358 | $driver = $driver->name; 359 | } 360 | if (!$this->hasDriver($driver)) { 361 | return; 362 | } 363 | $this->removeFromBackupDrivers($driver); 364 | unset($this->drivers[$driver]); 365 | } 366 | 367 | /** 368 | * initialize back up drivers. 369 | * 370 | * @param string[] excepted 371 | */ 372 | public function initBackupDrivers(array $excepted = []) 373 | { 374 | $this->backupDrivers = []; 375 | foreach ($this->drivers as $name => $driver) { 376 | if ($driver->backup && !in_array($name, $excepted)) { 377 | array_unshift($this->backupDrivers, $name); 378 | } 379 | } 380 | } 381 | 382 | /** 383 | * is task running. 384 | * 385 | * @return bool 386 | */ 387 | public function isRunning() 388 | { 389 | return $this->status == static::RUNNING; 390 | } 391 | 392 | /** 393 | * append driver to backup drivers. 394 | * 395 | * @param Driver|string $driver 396 | */ 397 | public function appendToBackupDrivers($driver) 398 | { 399 | if ($driver instanceof Driver) { 400 | $driver = $driver->name; 401 | } 402 | if (!in_array($driver, $this->backupDrivers)) { 403 | array_push($this->backupDrivers, $driver); 404 | } 405 | } 406 | 407 | /** 408 | * remove driver from backup drivers. 409 | * 410 | * @param Driver|string $driver 411 | */ 412 | public function removeFromBackupDrivers($driver) 413 | { 414 | if ($driver instanceof Driver) { 415 | $driver = $driver->name; 416 | } 417 | if (in_array($driver, $this->backupDrivers)) { 418 | $index = array_search($driver, $this->backupDrivers); 419 | array_splice($this->backupDrivers, $index, 1); 420 | } 421 | } 422 | 423 | /** 424 | * set the name of task. 425 | * 426 | * @param string $name 427 | * 428 | * @return $this 429 | * @throws TaskBalancerException 430 | */ 431 | public function name($name) 432 | { 433 | if (!is_string($name) || empty($name)) { 434 | throw new TaskBalancerException('Expected task name to be a non-empty string.'); 435 | } 436 | $this->name = $name; 437 | 438 | return $this; 439 | } 440 | 441 | /** 442 | * set the data of task. 443 | * 444 | * @param mixed $data 445 | * 446 | * @return $this 447 | */ 448 | public function data($data) 449 | { 450 | $this->data = $data; 451 | 452 | return $this; 453 | } 454 | 455 | /** 456 | * set hook's handlers. 457 | * 458 | * @param string|array $hookName 459 | * @param \Closure|bool $handler 460 | * @param bool $override 461 | * 462 | * @throws TaskBalancerException 463 | */ 464 | public function hook($hookName, $handler = null, $override = false) 465 | { 466 | if (is_callable($handler) && is_string($hookName)) { 467 | if (in_array($hookName, self::$hooks)) { 468 | if (!isset($this->handlers[$hookName])) { 469 | $this->handlers[$hookName] = []; 470 | } 471 | if ($override) { 472 | $this->handlers[$hookName] = [$handler]; 473 | } else { 474 | array_push($this->handlers[$hookName], $handler); 475 | } 476 | } else { 477 | throw new TaskBalancerException("Don't support hooks `$hookName`."); 478 | } 479 | } elseif (is_array($hookName)) { 480 | if (is_bool($handler) && $handler) { 481 | $this->handlers = []; 482 | } 483 | foreach ($hookName as $k => $v) { 484 | $this->hook($k, $v, false); 485 | } 486 | } 487 | } 488 | 489 | /** 490 | * call hook's handlers. 491 | * 492 | * @param string $hookName 493 | * @param mixed $data 494 | * 495 | * @return mixed 496 | */ 497 | protected function callHookHandler($hookName, $data = null) 498 | { 499 | if (array_key_exists($hookName, $this->handlers)) { 500 | $handlers = $this->handlers[$hookName] ?: []; 501 | $result = null; 502 | foreach ($handlers as $index => $handler) { 503 | $handlerArgs = $data === null ? 504 | [$this, $index, &$handlers, $result] : 505 | [$this, $data, $index, &$handlers, $result]; 506 | $result = call_user_func_array($handler, $handlerArgs); 507 | } 508 | if ($result === null) { 509 | return true; 510 | } 511 | 512 | return $result; 513 | } 514 | 515 | return true; 516 | } 517 | 518 | /** 519 | * properties overload. 520 | * 521 | * @param string $name 522 | * 523 | * @return mixed 524 | */ 525 | public function __get($name) 526 | { 527 | if (isset($this->$name)) { 528 | return $this->$name; 529 | } 530 | if (isset($this->drivers[$name])) { 531 | return $this->drivers[$name]; 532 | } 533 | } 534 | 535 | /** 536 | * correct methods 'isset' and 'empty' 537 | * 538 | * @param string $name 539 | * 540 | * @return bool 541 | */ 542 | public function __isset($name) 543 | { 544 | return isset($this->$name) ?: isset($this->drivers[$name]); 545 | } 546 | 547 | /** 548 | * method overload. 549 | * 550 | * @param string $name 551 | * @param array $args 552 | * 553 | * @throws TaskBalancerException 554 | */ 555 | public function __call($name, $args) 556 | { 557 | if (in_array($name, self::$hooks)) { 558 | if (isset($args[0]) && is_callable($args[0])) { 559 | $override = isset($args[1]) ? (bool) $args[1] : false; 560 | $this->hook($name, $args[0], $override); 561 | } 562 | } else { 563 | throw new TaskBalancerException("Not found methods `$name`."); 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /src/TaskBalancer/TaskBalancerException.php: -------------------------------------------------------------------------------- 1 |