├── .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 | [](https://packagist.org/packages/toplan/task-balancer)
4 | [](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 |