1 <?php
2 /**
3 * AsyncTask
4 *
5 * Copyright (c) 2015, Dmitry Mamontov <d.slonyara@gmail.com>.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * Neither the name of Dmitry Mamontov nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @package asynctask
38 * @author Dmitry Mamontov <d.slonyara@gmail.com>
39 * @copyright 2015 Dmitry Mamontov <d.slonyara@gmail.com>
40 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
41 * @since File available since Release 1.0.3
42 */
43
44 /**
45 * AsyncTask enables proper and easy use of the thread. This class allows to perform background operations and publish results on the thread without having to manipulate threads and/or handlers.
46 *
47 * @author Dmitry Mamontov <d.slonyara@gmail.com>
48 * @copyright 2015 Dmitry Mamontov <d.slonyara@gmail.com>
49 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
50 * @version Release: 1.0.3
51 * @link https://github.com/dmamontov/asynctask
52 * @since Class available since Release 1.0.3
53 * @todo Planned to write a method publishProgress
54 * @abstract
55 */
56
57 abstract class AsyncTask
58 {
59 /**
60 * A numeric shared memory segment ID
61 * @var integer
62 * @static
63 */
64 private static $shmId;
65
66 /**
67 * The line number in which the object has been initialized
68 * @var integer
69 * @static
70 */
71 private $line;
72
73 /**
74 * Creates a new asynchronous task
75 * @return void
76 * @access public
77 * @final
78 */
79 final public function __construct()
80 {
81 $error = "";
82 if (version_compare(PHP_VERSION, '5.3.3', '<') || defined('HHVM_VERSION')) {
83 $error .= "\n\e[0m\e[0;32mAsyncTask only officially supports PHP 5.3.3 and above,\e[0m";
84 }
85 if (!extension_loaded('pcntl')) {
86 $error .= "\n\e[0m\e[0;32mAsyncTask uses the extension \"pcntl\",\e[0m";
87 }
88 if (!extension_loaded('posix')) {
89 $error .= "\n\e[0m\e[0;32mAsyncTask uses the extension \"posix\",\e[0m";
90 }
91
92 if (mb_strlen($error) > 0) {
93 throw new RuntimeException(
94 $error . "\n\e[0m\e[0;32myou will most likely encounter problems with non-installed extensions,"
95 . "\n\e[0;31mupgrading is strongly recommended.\e[0m\n"
96 );
97 }
98
99 $line = debug_backtrace();
100 $this->line = $line[0]['line'];
101
102 self::$shmId = shm_attach((int) (ftok(__FILE__, 'A') . $this->line));
103 shm_put_var(self::$shmId, 11511697116117115, 'PENDING');
104 shm_put_var(self::$shmId, 112112105100, getmypid());
105 }
106
107 /**
108 * Finish create an asynchronous task
109 * @return void
110 * @access public
111 * @final
112 */
113 final public function __destruct()
114 {
115 if (
116 @shm_has_var(self::$shmId, 112112105100) &&
117 shm_get_var(self::$shmId, 112112105100) == getmypid()
118 ) {
119 shm_remove(self::$shmId);
120 }
121 }
122
123 /**
124 * Returns the variable with the given key
125 * @param string $key
126 * @return mixed
127 * @access public
128 * @static
129 * @final
130 */
131 final protected static function getProperty($key)
132 {
133 if (
134 in_array($key, array('shmId', 'pid', 'ppid', 'status')) === false &&
135 @shm_has_var(self::$shmId, self::getUid($key))
136 ) {
137 return shm_get_var(self::$shmId, self::getUid($key));
138 } else {
139 return false;
140 }
141 }
142
143 /**
144 * Inserts or updates a variable with the given key
145 * @param string $key
146 * @param string $value
147 * @return boolean
148 * @access public
149 * @static
150 * @final
151 */
152 final protected static function setProperty($key, $value)
153 {
154 if (in_array($key, array('shmId', 'pid', 'ppid', 'status')) === false) {
155 shm_put_var(self::$shmId, self::getUid($key), $value);
156 return true;
157 } else {
158 return false;
159 }
160 }
161
162 /**
163 * Returns a unique integer identifier for a given key
164 * @param string $key
165 * @return integer
166 * @access private
167 * @static
168 * @final
169 */
170 final private static function getUid($key)
171 {
172 $uid = '';
173 for ($char = 0; $char < strlen($key); $char++) {
174 $uid .= ord($key[ $char ]);
175 }
176
177 return (int) $uid;
178 }
179
180 /**
181 * Executes the task with the specified parameters
182 * @param mixed $parameters
183 * @return void
184 * @access public
185 * @final
186 */
187 final public function execute($parameters)
188 {
189 $pid = pcntl_fork();
190 if ($pid == -1) {
191 exit();
192 } elseif (!$pid) {
193 self::$shmId = shm_attach((int) (ftok(__FILE__, 'A') . $this->line));
194 shm_put_var(self::$shmId, 112105100, getmypid());
195 shm_put_var(self::$shmId, 11511697116117115, 'RUNNING');
196
197 $this->onPreExecute();
198
199 $result = $this->doInBackground($parameters);
200
201 $this->onPostExecute($result);
202
203 if (@shm_has_var(self::$shmId, 112105100)) {
204 shm_put_var(self::$shmId, 112105100, null);
205 shm_put_var(self::$shmId, 11511697116117115, 'FINISHED');
206 }
207 exit();
208 }
209 }
210
211 /**
212 * Attempts to cancel execution of this task
213 * @return boolean
214 * @access public
215 * @final
216 */
217 final public function cancel()
218 {
219 if (
220 @shm_has_var(self::$shmId, 112105100) &&
221 is_null(shm_get_var(self::$shmId, 112105100)) !== null
222 ) {
223 $this->onCancelled();
224 posix_kill(shm_get_var(self::$shmId, 112105100), SIGKILL);
225 shm_put_var(self::$shmId, 112105100, null);
226 shm_put_var(self::$shmId, 11511697116117115, 'CANCELED');
227 return true;
228 } else {
229 return false;
230 }
231 }
232
233 /**
234 * Returns the current status of this task
235 * @return string
236 * @access public
237 * @final
238 */
239 final public function getStatus()
240 {
241 return @shm_has_var(self::$shmId, 11511697116117115)
242 ? shm_get_var(self::$shmId, 11511697116117115)
243 : 'PENDING';
244 }
245
246 /**
247 * Returns true if this task was cancelled before it completed normally
248 * @return boolean
249 * @access public
250 * @final
251 */
252 final public function isCancelled()
253 {
254 return @shm_has_var(self::$shmId, 11511697116117115) == 'CANCELED' ? true : false;
255 }
256
257 /**
258 * Runs on the thread before doInBackground($parameters)
259 * @return void
260 * @access protected
261 */
262 protected function onPreExecute()
263 {
264 }
265
266 /**
267 * Override this method to perform a computation on a background thread
268 * @param mixed $parameters
269 * @return mixed
270 * @access protected
271 * @abstract
272 */
273 abstract protected function doInBackground($parameters);
274
275 /**
276 * Runs on the thread after doInBackground($parameters)
277 * @param mixed $result
278 * @return void
279 * @access protected
280 */
281 protected function onPostExecute($result)
282 {
283 }
284
285 /**
286 * Runs on the thread after cancel()
287 * @return void
288 * @access protected
289 */
290 protected function onCancelled()
291 {
292 }
293 }
294