├── .codeclimate.yml
├── .phpcs
└── Barracuda
│ ├── Sniffs
│ ├── Commenting
│ │ ├── DisallowSingleLineMultiCommentsSniff.php
│ │ ├── DocCommentSniff.php
│ │ ├── FunctionCommentSniff.php
│ │ ├── SpaceAfterCommentSniff.php
│ │ └── VariableCommentSniff.php
│ ├── ControlStructures
│ │ ├── ControlSignatureSniff.php
│ │ └── NoInlineAssignmentSniff.php
│ ├── Formatting
│ │ └── SpaceUnaryOperatorSniff.php
│ └── Functions
│ │ └── FunctionDeclarationSniff.php
│ ├── bootstrap.php
│ └── ruleset.xml
├── LICENSE
├── README.md
├── composer.json
├── examples
├── blocking.php
├── bucket.php
├── identifier.php
├── nonblocking.php
├── result_callback.php
└── result_deferred.php
└── fork_daemon.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - ".phpcs/**/*"
3 | - "examples/**/*"
4 | engines:
5 | phpmd:
6 | enabled: false
7 | config:
8 | file_extensions: "php"
9 | rulesets: "unusedcode"
10 | phpcodesniffer:
11 | enabled: true
12 | config:
13 | file_extensions: "php"
14 | standard: "/code/.phpcs/Barracuda/ruleset.xml"
15 | ignore_warnings: true
16 | fixme:
17 | enabled: true
18 | ratings:
19 | paths:
20 | - "*.php"
21 | - "**/*.php"
22 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/DisallowSingleLineMultiCommentsSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
11 | * @version 1.0.00
12 | * @link http://pear.php.net/package/PHP_CodeSniffer
13 | */
14 |
15 | /**
16 | * This sniff prohibits the use of Perl style hash comments.
17 | *
18 | * An example of a hash comment is:
19 | *
20 | *
21 | * /* This is a single line multi line comment, which is prohibited. */
22 | /* $hello = 'hello';
23 | *
24 | *
25 | * @category PHP
26 | * @package PHP_CodeSniffer
27 | * @author Ryan Matthews
28 | * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
29 | * @version Release: 1.0.00
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 | class Barracuda_Sniffs_Commenting_DisallowSingleLineMultiCommentsSniff implements PHP_CodeSniffer_Sniff
33 | {
34 |
35 |
36 | /**
37 | * Returns the token types that this sniff is interested in.
38 | *
39 | * @return array(int)
40 | */
41 | public function register()
42 | {
43 | return array(T_COMMENT);
44 |
45 | }// end register()
46 |
47 |
48 | /**
49 | * Processes the tokens that this sniff is interested in.
50 | *
51 | * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found.
52 | * @param int $stackPtr The position in the stack where
53 | * the token was found.
54 | *
55 | * @return void
56 | */
57 | public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
58 | {
59 | $tokens = $phpcsFile->getTokens();
60 | if (preg_match('/\/\*[^\n]*\*\//', $tokens[$stackPtr]['content']))
61 | {
62 | $error = 'Multi line comments are prohibited on single lines; found %s';
63 | $data = array(trim($tokens[$stackPtr]['content']));
64 | $phpcsFile->addError($error, $stackPtr, 'Found', $data);
65 | }
66 |
67 | }// end process()
68 | }
69 | // end class
70 |
71 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/DocCommentSniff.php:
--------------------------------------------------------------------------------
1 | array(
13 | 'required' => false,
14 | 'allow_multiple' => false,
15 | ),
16 | '@package' => array(
17 | 'required' => false,
18 | 'allow_multiple' => false,
19 | ),
20 | '@subpackage' => array(
21 | 'required' => false,
22 | 'allow_multiple' => false,
23 | ),
24 | '@author' => array(
25 | 'required' => false,
26 | 'allow_multiple' => true,
27 | ),
28 | '@copyright' => array(
29 | 'required' => false,
30 | 'allow_multiple' => true,
31 | ),
32 | '@license' => array(
33 | 'required' => false,
34 | 'allow_multiple' => false,
35 | ),
36 | '@version' => array(
37 | 'required' => false,
38 | 'allow_multiple' => false,
39 | ),
40 | '@link' => array(
41 | 'required' => false,
42 | 'allow_multiple' => true,
43 | ),
44 | '@see' => array(
45 | 'required' => false,
46 | 'allow_multiple' => true,
47 | ),
48 | '@since' => array(
49 | 'required' => false,
50 | 'allow_multiple' => false,
51 | ),
52 | '@deprecated' => array(
53 | 'required' => false,
54 | 'allow_multiple' => false,
55 | ),
56 | );
57 |
58 |
59 | /**
60 | * Returns an array of tokens this test wants to listen for.
61 | *
62 | * @return array
63 | */
64 | public function register()
65 | {
66 | return array(T_OPEN_TAG);
67 |
68 | }//end register()
69 |
70 |
71 | /**
72 | * Processes this test, when one of its tokens is encountered.
73 | *
74 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
75 | * @param int $stackPtr The position of the current token
76 | * in the stack passed in $tokens.
77 | *
78 | * @return int
79 | */
80 | public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
81 | {
82 | $tokens = $phpcsFile->getTokens();
83 |
84 | // Find the next non whitespace token.
85 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
86 |
87 | // Allow declare() statements at the top of the file.
88 | if ($tokens[$commentStart]['code'] === T_DECLARE) {
89 | $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1));
90 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true);
91 | }
92 |
93 | // Ignore vim header.
94 | if ($tokens[$commentStart]['code'] === T_COMMENT) {
95 | if (strstr($tokens[$commentStart]['content'], 'vim:') !== false) {
96 | $commentStart = $phpcsFile->findNext(
97 | T_WHITESPACE,
98 | ($commentStart + 1),
99 | null,
100 | true
101 | );
102 | }
103 | }
104 |
105 | $errorToken = ($stackPtr + 1);
106 | if (isset($tokens[$errorToken]) === false) {
107 | $errorToken--;
108 | }
109 |
110 | if ($tokens[$commentStart]['code'] === T_CLOSE_TAG) {
111 | // We are only interested if this is the first open tag.
112 | return ($phpcsFile->numTokens + 1);
113 | } else if ($tokens[$commentStart]['code'] === T_COMMENT) {
114 | $error = 'You must use "/**" style comments for a file comment';
115 | $phpcsFile->addError($error, $errorToken, 'WrongStyle');
116 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');
117 | return ($phpcsFile->numTokens + 1);
118 | } else if ($commentStart === false
119 | || $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG
120 | ) {
121 | $phpcsFile->addError('Missing file doc comment', $errorToken, 'Missing');
122 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no');
123 | return ($phpcsFile->numTokens + 1);
124 | } else {
125 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');
126 | }
127 |
128 | // Check the PHP Version, which should be in some text before the first tag.
129 | $commentEnd = $tokens[$commentStart]['comment_closer'];
130 | $found = false;
131 | for ($i = ($commentStart + 1); $i < $commentEnd; $i++) {
132 | if ($tokens[$i]['code'] === T_DOC_COMMENT_TAG) {
133 | break;
134 | } else if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING
135 | && strstr(strtolower($tokens[$i]['content']), 'php version') !== false
136 | ) {
137 | $found = true;
138 | break;
139 | }
140 | }
141 |
142 | if ($found === false) {
143 | $error = 'PHP version not specified';
144 | $phpcsFile->addWarning($error, $commentEnd, 'MissingVersion');
145 | }
146 |
147 | // Check each tag.
148 | $this->processTags($phpcsFile, $stackPtr, $commentStart);
149 |
150 | // Ignore the rest of the file.
151 | return ($phpcsFile->numTokens + 1);
152 |
153 | }//end process()
154 |
155 |
156 | /**
157 | * Processes each required or optional tag.
158 | *
159 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
160 | * @param int $stackPtr The position of the current token
161 | * in the stack passed in $tokens.
162 | * @param int $commentStart Position in the stack where the comment started.
163 | *
164 | * @return void
165 | */
166 | protected function processTags(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
167 | {
168 | $tokens = $phpcsFile->getTokens();
169 |
170 | if (get_class($this) === 'PEAR_Sniffs_Commenting_FileCommentSniff') {
171 | $docBlock = 'file';
172 | } else {
173 | $docBlock = 'class';
174 | }
175 |
176 | $commentEnd = $tokens[$commentStart]['comment_closer'];
177 |
178 | $foundTags = array();
179 | $tagTokens = array();
180 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
181 | $name = $tokens[$tag]['content'];
182 | if (isset($this->tags[$name]) === false) {
183 | continue;
184 | }
185 |
186 | if ($this->tags[$name]['allow_multiple'] === false && isset($tagTokens[$name]) === true) {
187 | $error = 'Only one %s tag is allowed in a %s comment';
188 | $data = array(
189 | $name,
190 | $docBlock,
191 | );
192 | $phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data);
193 | }
194 |
195 | $foundTags[] = $name;
196 | $tagTokens[$name][] = $tag;
197 |
198 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd);
199 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) {
200 | $error = 'Content missing for %s tag in %s comment';
201 | $data = array(
202 | $name,
203 | $docBlock,
204 | );
205 | $phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data);
206 | continue;
207 | }
208 | }//end foreach
209 |
210 | // Check if the tags are in the correct position.
211 | $pos = 0;
212 | foreach ($this->tags as $tag => $tagData) {
213 | if (isset($tagTokens[$tag]) === false) {
214 | if ($tagData['required'] === true) {
215 | $error = 'Missing %s tag in %s comment';
216 | $data = array(
217 | $tag,
218 | $docBlock,
219 | );
220 | $phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data);
221 | }
222 |
223 | continue;
224 | } else {
225 | $method = 'process'.substr($tag, 1);
226 | if (method_exists($this, $method) === true) {
227 | // Process each tag if a method is defined.
228 | call_user_func(array($this, $method), $phpcsFile, $tagTokens[$tag]);
229 | }
230 | }
231 |
232 | if (isset($foundTags[$pos]) === false) {
233 | break;
234 | }
235 |
236 | if ($foundTags[$pos] !== $tag) {
237 | $error = 'The tag in position %s should be the %s tag';
238 | $data = array(
239 | ($pos + 1),
240 | $tag,
241 | );
242 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data);
243 | }
244 |
245 | // Account for multiple tags.
246 | $pos++;
247 | while (isset($foundTags[$pos]) === true && $foundTags[$pos] === $tag) {
248 | $pos++;
249 | }
250 | }//end foreach
251 |
252 | }//end processTags()
253 |
254 |
255 | /**
256 | * Process the category tag.
257 | *
258 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
259 | * @param array $tags The tokens for these tags.
260 | *
261 | * @return void
262 | */
263 | protected function processCategory(PHP_CodeSniffer_File $phpcsFile, array $tags)
264 | {
265 | $tokens = $phpcsFile->getTokens();
266 | foreach ($tags as $tag) {
267 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
268 | // No content.
269 | continue;
270 | }
271 |
272 | $content = $tokens[($tag + 2)]['content'];
273 | if (PHP_CodeSniffer::isUnderscoreName($content) !== true) {
274 | $newContent = str_replace(' ', '_', $content);
275 | $nameBits = explode('_', $newContent);
276 | $firstBit = array_shift($nameBits);
277 | $newName = ucfirst($firstBit).'_';
278 | foreach ($nameBits as $bit) {
279 | if ($bit !== '') {
280 | $newName .= ucfirst($bit).'_';
281 | }
282 | }
283 |
284 | $error = 'Category name "%s" is not valid; consider "%s" instead';
285 | $validName = trim($newName, '_');
286 | $data = array(
287 | $content,
288 | $validName,
289 | );
290 | $phpcsFile->addError($error, $tag, 'InvalidCategory', $data);
291 | }
292 | }//end foreach
293 |
294 | }//end processCategory()
295 |
296 |
297 | /**
298 | * Process the package tag.
299 | *
300 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
301 | * @param array $tags The tokens for these tags.
302 | *
303 | * @return void
304 | */
305 | protected function processPackage(PHP_CodeSniffer_File $phpcsFile, array $tags)
306 | {
307 | $tokens = $phpcsFile->getTokens();
308 | foreach ($tags as $tag) {
309 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
310 | // No content.
311 | continue;
312 | }
313 |
314 | $content = $tokens[($tag + 2)]['content'];
315 | if (PHP_CodeSniffer::isUnderscoreName($content) === true) {
316 | continue;
317 | }
318 |
319 | $newContent = str_replace(' ', '_', $content);
320 | $newContent = trim($newContent, '_');
321 | $newContent = preg_replace('/[^A-Za-z_]/', '', $newContent);
322 | $nameBits = explode('_', $newContent);
323 | $firstBit = array_shift($nameBits);
324 | $newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
325 | foreach ($nameBits as $bit) {
326 | if ($bit !== '') {
327 | $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
328 | }
329 | }
330 |
331 | $error = 'Package name "%s" is not valid; consider "%s" instead';
332 | $validName = trim($newName, '_');
333 | $data = array(
334 | $content,
335 | $validName,
336 | );
337 | $phpcsFile->addError($error, $tag, 'InvalidPackage', $data);
338 | }//end foreach
339 |
340 | }//end processPackage()
341 |
342 |
343 | /**
344 | * Process the subpackage tag.
345 | *
346 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
347 | * @param array $tags The tokens for these tags.
348 | *
349 | * @return void
350 | */
351 | protected function processSubpackage(PHP_CodeSniffer_File $phpcsFile, array $tags)
352 | {
353 | $tokens = $phpcsFile->getTokens();
354 | foreach ($tags as $tag) {
355 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
356 | // No content.
357 | continue;
358 | }
359 |
360 | $content = $tokens[($tag + 2)]['content'];
361 | if (PHP_CodeSniffer::isUnderscoreName($content) === true) {
362 | continue;
363 | }
364 |
365 | $newContent = str_replace(' ', '_', $content);
366 | $nameBits = explode('_', $newContent);
367 | $firstBit = array_shift($nameBits);
368 | $newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
369 | foreach ($nameBits as $bit) {
370 | if ($bit !== '') {
371 | $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
372 | }
373 | }
374 |
375 | $error = 'Subpackage name "%s" is not valid; consider "%s" instead';
376 | $validName = trim($newName, '_');
377 | $data = array(
378 | $content,
379 | $validName,
380 | );
381 | $phpcsFile->addError($error, $tag, 'InvalidSubpackage', $data);
382 | }//end foreach
383 |
384 | }//end processSubpackage()
385 |
386 | /**
387 | * Author tag must be 'Firstname Lastname '.
388 | *
389 | * @param int $errorPos The line number where the error occurs.
390 | *
391 | * @return void
392 | */
393 | protected function processAuthors($errorPos)
394 | {
395 | $authors = $this->commentParser->getAuthors();
396 | if (empty($authors) === false) {
397 | $author = $authors[0];
398 | $content = $author->getContent();
399 | if (empty($content) === true) {
400 | $error = 'Content missing for @author tag in file comment';
401 | $this->currentFile->addError($error, $errorPos, 'MissingAuthor');
402 | } else if (preg_match('/^(.*) \<.*\@.*\>$/', $content) === 0) {
403 | $error = 'Expected "Firstname Lastname " for author tag';
404 | $this->currentFile->addError($error, $errorPos, 'IncorrectAuthor');
405 | }
406 | }
407 | }//end processAuthors()
408 |
409 | /**
410 | * Copyright tag must be in the form 'xxxx-xxxx Barracuda Networks, Inc.'.
411 | *
412 | * @param int $errorPos The line number where the error occurs.
413 | *
414 | * @return void
415 | */
416 | protected function processCopyrights($errorPos)
417 | {
418 | $copyrights = $this->commentParser->getCopyrights();
419 | $copyright = $copyrights[0];
420 | if ($copyright !== null) {
421 | $content = $copyright->getContent();
422 | if (empty($content) === true) {
423 | $error = 'Content missing for @copyright tag in file comment';
424 | $this->currentFile->addError($error, $errorPos, 'MissingCopyright');
425 | } else if (preg_match('/^([0-9]{4})(-[0-9]{4})? (\.*\)$/', $content) === 0) {
426 | $error = 'Expected "xxxx-xxxx Barracuda Networks, Inc." for copyright declaration';
427 | $this->currentFile->addError($error, $errorPos, 'IncorrectCopyright');
428 | }
429 | }
430 | }//end processCopyrights()
431 |
432 | /**
433 | * Process the license tag.
434 | *
435 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
436 | * @param array $tags The tokens for these tags.
437 | *
438 | * @return void
439 | */
440 | protected function processLicense(PHP_CodeSniffer_File $phpcsFile, array $tags)
441 | {
442 | $tokens = $phpcsFile->getTokens();
443 | foreach ($tags as $tag) {
444 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
445 | // No content.
446 | continue;
447 | }
448 |
449 | $content = $tokens[($tag + 2)]['content'];
450 | $matches = array();
451 | preg_match('/^([^\s]+)\s+(.*)/', $content, $matches);
452 | if (count($matches) !== 3) {
453 | $error = '@license tag must contain a URL and a license name';
454 | $phpcsFile->addError($error, $tag, 'IncompleteLicense');
455 | }
456 | }
457 |
458 | }//end processLicense()
459 |
460 |
461 | /**
462 | * Process the version tag.
463 | *
464 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
465 | * @param array $tags The tokens for these tags.
466 | *
467 | * @return void
468 | */
469 | protected function processVersion(PHP_CodeSniffer_File $phpcsFile, array $tags)
470 | {
471 | $tokens = $phpcsFile->getTokens();
472 | foreach ($tags as $tag) {
473 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
474 | // No content.
475 | continue;
476 | }
477 |
478 | $content = $tokens[($tag + 2)]['content'];
479 | if (strstr($content, 'CVS:') === false
480 | && strstr($content, 'SVN:') === false
481 | && strstr($content, 'GIT:') === false
482 | && strstr($content, 'HG:') === false
483 | ) {
484 | $error = 'Invalid version "%s" in file comment; consider "CVS: " or "SVN: " or "GIT: " or "HG: " instead';
485 | $data = array($content);
486 | $phpcsFile->addWarning($error, $tag, 'InvalidVersion', $data);
487 | }
488 | }
489 |
490 | }//end processVersion()
491 |
492 |
493 | }//end class
494 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/FunctionCommentSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Marc McIntyre
11 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 | * @link http://pear.php.net/package/PHP_CodeSniffer
14 | */
15 |
16 | if (class_exists('PEAR_Sniffs_Commenting_FunctionCommentSniff', true) === false) {
17 | throw new PHP_CodeSniffer_Exception('Class PEAR_Sniffs_Commenting_FunctionCommentSniff not found');
18 | }
19 |
20 | /**
21 | * Parses and verifies the doc comments for functions.
22 | *
23 | * @category PHP
24 | * @package PHP_CodeSniffer
25 | * @author Greg Sherwood
26 | * @author Marc McIntyre
27 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 | * @version Release: @package_version@
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 | class Barracuda_Sniffs_Commenting_FunctionCommentSniff extends PEAR_Sniffs_Commenting_FunctionCommentSniff
33 | {
34 | protected $allowedTypes = array(
35 | 'int',
36 | 'bool',
37 | );
38 |
39 | public function __construct()
40 | {
41 | PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes);
42 | }
43 |
44 | /**
45 | * Process the return comment of this function comment.
46 | *
47 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
48 | * @param int $stackPtr The position of the current token
49 | * in the stack passed in $tokens.
50 | * @param int $commentStart The position in the stack where the comment started.
51 | *
52 | * @return void
53 | */
54 | protected function processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
55 | {
56 | $tokens = $phpcsFile->getTokens();
57 |
58 | // Skip constructor and destructor.
59 | $methodName = $phpcsFile->getDeclarationName($stackPtr);
60 | $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct');
61 |
62 | $return = null;
63 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
64 | if ($tokens[$tag]['content'] === '@return') {
65 | if ($return !== null) {
66 | $error = 'Only 1 @return tag is allowed in a function comment';
67 | $phpcsFile->addError($error, $tag, 'DuplicateReturn');
68 | return;
69 | }
70 |
71 | $return = $tag;
72 | }
73 | }
74 |
75 | if ($isSpecialMethod === true) {
76 | return;
77 | }
78 |
79 | if ($return !== null) {
80 | $content = $tokens[($return + 2)]['content'];
81 | if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) {
82 | $error = 'Return type missing for @return tag in function comment';
83 | $phpcsFile->addError($error, $return, 'MissingReturnType');
84 | } else {
85 | // Check return type (can be multiple, separated by '|').
86 | $typeNames = explode('|', $content);
87 | $suggestedNames = array();
88 | foreach ($typeNames as $i => $typeName) {
89 | $suggestedName = PHP_CodeSniffer::suggestType($typeName);
90 | if (in_array($suggestedName, $suggestedNames) === false) {
91 | $suggestedNames[] = $suggestedName;
92 | }
93 | }
94 |
95 | $suggestedType = implode('|', $suggestedNames);
96 | if ($content !== $suggestedType) {
97 | $error = 'Expected "%s" but found "%s" for function return type';
98 | $data = array(
99 | $suggestedType,
100 | $content,
101 | );
102 | $fix = $phpcsFile->addFixableError($error, $return, 'InvalidReturn', $data);
103 | if ($fix === true) {
104 | $phpcsFile->fixer->replaceToken(($return + 2), $suggestedType);
105 | }
106 | }
107 |
108 | // If the return type is void, make sure there is
109 | // no return statement in the function.
110 | if ($content === 'void') {
111 | if (isset($tokens[$stackPtr]['scope_closer']) === true) {
112 | $endToken = $tokens[$stackPtr]['scope_closer'];
113 | for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) {
114 | if ($tokens[$returnToken]['code'] === T_CLOSURE) {
115 | $returnToken = $tokens[$returnToken]['scope_closer'];
116 | continue;
117 | }
118 |
119 | if ($tokens[$returnToken]['code'] === T_RETURN
120 | || $tokens[$returnToken]['code'] === T_YIELD
121 | ) {
122 | break;
123 | }
124 | }
125 |
126 | if ($returnToken !== $endToken) {
127 | // If the function is not returning anything, just
128 | // exiting, then there is no problem.
129 | $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
130 | if ($tokens[$semicolon]['code'] !== T_SEMICOLON) {
131 | $error = 'Function return type is void, but function contains return statement';
132 | $phpcsFile->addError($error, $return, 'InvalidReturnVoid');
133 | }
134 | }
135 | }//end if
136 | } else if ($content !== 'mixed') {
137 | // If return type is not void, there needs to be a return statement
138 | // somewhere in the function that returns something.
139 | if (isset($tokens[$stackPtr]['scope_closer']) === true) {
140 | $endToken = $tokens[$stackPtr]['scope_closer'];
141 | $returnToken = $phpcsFile->findNext(array(T_RETURN, T_YIELD), $stackPtr, $endToken);
142 | if ($returnToken === false) {
143 | $error = 'Function return type is not void, but function has no return statement';
144 | $phpcsFile->addError($error, $return, 'InvalidNoReturn');
145 | } else {
146 | $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
147 | if ($tokens[$semicolon]['code'] === T_SEMICOLON) {
148 | $error = 'Function return type is not void, but function is returning void here';
149 | $phpcsFile->addError($error, $returnToken, 'InvalidReturnNotVoid');
150 | }
151 | }
152 | }
153 | }//end if
154 | }//end if
155 | } else {
156 | $error = 'Missing @return tag in function comment';
157 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn');
158 | }//end if
159 |
160 | }//end processReturn()
161 |
162 |
163 | /**
164 | * Process any throw tags that this function comment has.
165 | *
166 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
167 | * @param int $stackPtr The position of the current token
168 | * in the stack passed in $tokens.
169 | * @param int $commentStart The position in the stack where the comment started.
170 | *
171 | * @return void
172 | */
173 | protected function processThrows(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
174 | {
175 | $tokens = $phpcsFile->getTokens();
176 |
177 | $throws = array();
178 | foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
179 | if ($tokens[$tag]['content'] !== '@throws') {
180 | continue;
181 | }
182 |
183 | $exception = null;
184 | $comment = null;
185 | if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
186 | $matches = array();
187 | preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[($tag + 2)]['content'], $matches);
188 | $exception = $matches[1];
189 | if (isset($matches[2]) === true && trim($matches[2]) !== '') {
190 | $comment = $matches[2];
191 | }
192 | }
193 |
194 | if ($exception === null) {
195 | $error = 'Exception type and comment missing for @throws tag in function comment';
196 | $phpcsFile->addError($error, $tag, 'InvalidThrows');
197 | } else if ($comment === null) {
198 | $error = 'Comment missing for @throws tag in function comment';
199 | $phpcsFile->addError($error, $tag, 'EmptyThrows');
200 | } else {
201 | // Any strings until the next tag belong to this comment.
202 | if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
203 | $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
204 | } else {
205 | $end = $tokens[$commentStart]['comment_closer'];
206 | }
207 |
208 | for ($i = ($tag + 3); $i < $end; $i++) {
209 | if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
210 | $comment .= ' '.$tokens[$i]['content'];
211 | }
212 | }
213 |
214 | // Starts with a capital letter and ends with a fullstop.
215 | $firstChar = $comment{0};
216 | if (strtoupper($firstChar) !== $firstChar) {
217 | $error = '@throws tag comment must start with a capital letter';
218 | $phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital');
219 | }
220 |
221 | $lastChar = substr($comment, -1);
222 | if ($lastChar !== '.') {
223 | $error = '@throws tag comment must end with a full stop';
224 | $phpcsFile->addError($error, ($tag + 2), 'ThrowsNoFullStop');
225 | }
226 | }//end if
227 | }//end foreach
228 |
229 | }//end processThrows()
230 |
231 |
232 | /**
233 | * Process the function parameter comments.
234 | *
235 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
236 | * @param int $stackPtr The position of the current token
237 | * in the stack passed in $tokens.
238 | * @param int $commentStart The position in the stack where the comment started.
239 | *
240 | * @return void
241 | */
242 | protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
243 | {
244 | $tokens = $phpcsFile->getTokens();
245 |
246 | $params = array();
247 | $maxType = 0;
248 | $maxVar = 0;
249 | foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
250 | if ($tokens[$tag]['content'] !== '@param') {
251 | continue;
252 | }
253 |
254 | $type = '';
255 | $typeSpace = 0;
256 | $var = '';
257 | $varSpace = 0;
258 | $comment = '';
259 | $commentLines = array();
260 | if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
261 | $matches = array();
262 | preg_match('/([^$&]+)(?:((?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches);
263 |
264 | $typeLen = strlen($matches[1]);
265 | $type = trim($matches[1]);
266 | $typeSpace = ($typeLen - strlen($type));
267 | $typeLen = strlen($type);
268 | if ($typeLen > $maxType) {
269 | $maxType = $typeLen;
270 | }
271 |
272 | if (isset($matches[2]) === true) {
273 | $var = $matches[2];
274 | $varLen = strlen($var);
275 | if ($varLen > $maxVar) {
276 | $maxVar = $varLen;
277 | }
278 |
279 | if (isset($matches[4]) === true) {
280 | $varSpace = strlen($matches[3]);
281 | $comment = $matches[4];
282 | $commentLines[] = array(
283 | 'comment' => $comment,
284 | 'token' => ($tag + 2),
285 | 'indent' => $varSpace,
286 | );
287 |
288 | // Any strings until the next tag belong to this comment.
289 | if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
290 | $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
291 | } else {
292 | $end = $tokens[$commentStart]['comment_closer'];
293 | }
294 |
295 | for ($i = ($tag + 3); $i < $end; $i++) {
296 | if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
297 | $indent = 0;
298 | if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) {
299 | $indent = strlen($tokens[($i - 1)]['content']);
300 | }
301 |
302 | $comment .= ' '.$tokens[$i]['content'];
303 | $commentLines[] = array(
304 | 'comment' => $tokens[$i]['content'],
305 | 'token' => $i,
306 | 'indent' => $indent,
307 | );
308 | }
309 | }
310 | } else {
311 | $error = 'Missing parameter comment';
312 | $phpcsFile->addError($error, $tag, 'MissingParamComment');
313 | $commentLines[] = array('comment' => '');
314 | }//end if
315 | } else {
316 | $error = 'Missing parameter name';
317 | $phpcsFile->addError($error, $tag, 'MissingParamName');
318 | }//end if
319 | } else {
320 | $error = 'Missing parameter type';
321 | $phpcsFile->addError($error, $tag, 'MissingParamType');
322 | }//end if
323 |
324 | $params[] = array(
325 | 'tag' => $tag,
326 | 'type' => $type,
327 | 'var' => $var,
328 | 'comment' => $comment,
329 | 'commentLines' => $commentLines,
330 | 'type_space' => $typeSpace,
331 | 'var_space' => $varSpace,
332 | );
333 | }//end foreach
334 |
335 | $realParams = $phpcsFile->getMethodParameters($stackPtr);
336 | $foundParams = array();
337 |
338 | foreach ($params as $pos => $param) {
339 | // If the type is empty, the whole line is empty.
340 | if ($param['type'] === '') {
341 | continue;
342 | }
343 |
344 | // Check the param type value.
345 | $typeNames = explode('|', $param['type']);
346 | foreach ($typeNames as $typeName) {
347 | $suggestedName = PHP_CodeSniffer::suggestType($typeName);
348 | if ($typeName !== $suggestedName) {
349 | $error = 'Expected "%s" but found "%s" for parameter type';
350 | $data = array(
351 | $suggestedName,
352 | $typeName,
353 | );
354 |
355 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data);
356 | if ($fix === true) {
357 | $content = $suggestedName;
358 | $content .= str_repeat(' ', $param['type_space']);
359 | $content .= $param['var'];
360 | $content .= str_repeat(' ', $param['var_space']);
361 | if (isset($param['commentLines'][0]) === true) {
362 | $content .= $param['commentLines'][0]['comment'];
363 | }
364 |
365 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
366 | }
367 | } else if (count($typeNames) === 1) {
368 | // Check type hint for array and custom type.
369 | $suggestedTypeHint = '';
370 | if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') {
371 | $suggestedTypeHint = 'array';
372 | } else if (strpos($suggestedName, 'callable') !== false) {
373 | $suggestedTypeHint = 'callable';
374 | } else if (strpos($suggestedName, 'callback') !== false) {
375 | $suggestedTypeHint = 'callable';
376 | } else if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) {
377 | $suggestedTypeHint = $suggestedName;
378 | }
379 |
380 | if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) {
381 | $typeHint = $realParams[$pos]['type_hint'];
382 | if ($typeHint === '') {
383 | $error = 'Type hint "%s" missing for %s';
384 | $data = array(
385 | $suggestedTypeHint,
386 | $param['var'],
387 | );
388 | $phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data);
389 | } else if ($typeHint !== substr($suggestedTypeHint, (strlen($typeHint) * -1))) {
390 | $error = 'Expected type hint "%s"; found "%s" for %s';
391 | $data = array(
392 | $suggestedTypeHint,
393 | $typeHint,
394 | $param['var'],
395 | );
396 | $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data);
397 | }
398 | } else if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) {
399 | $typeHint = $realParams[$pos]['type_hint'];
400 | if ($typeHint !== '') {
401 | $error = 'Unknown type hint "%s" found for %s';
402 | $data = array(
403 | $typeHint,
404 | $param['var'],
405 | );
406 | $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data);
407 | }
408 | }//end if
409 | }//end if
410 | }//end foreach
411 |
412 | if ($param['var'] === '') {
413 | continue;
414 | }
415 |
416 | $foundParams[] = $param['var'];
417 |
418 | // Check number of spaces after the type.
419 | $spaces = ($maxType - strlen($param['type']) + 1);
420 | if ($param['type_space'] !== $spaces) {
421 | $error = 'Expected %s spaces after parameter type; %s found';
422 | $data = array(
423 | $spaces,
424 | $param['type_space'],
425 | );
426 |
427 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data);
428 | if ($fix === true) {
429 | $phpcsFile->fixer->beginChangeset();
430 |
431 | $content = $param['type'];
432 | $content .= str_repeat(' ', $spaces);
433 | $content .= $param['var'];
434 | $content .= str_repeat(' ', $param['var_space']);
435 | $content .= $param['commentLines'][0]['comment'];
436 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
437 |
438 | // Fix up the indent of additional comment lines.
439 | foreach ($param['commentLines'] as $lineNum => $line) {
440 | if ($lineNum === 0
441 | || $param['commentLines'][$lineNum]['indent'] === 0
442 | ) {
443 | continue;
444 | }
445 |
446 | $newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']);
447 | $phpcsFile->fixer->replaceToken(
448 | ($param['commentLines'][$lineNum]['token'] - 1),
449 | str_repeat(' ', $newIndent)
450 | );
451 | }
452 |
453 | $phpcsFile->fixer->endChangeset();
454 | }//end if
455 | }//end if
456 |
457 | // Make sure the param name is correct.
458 | if (isset($realParams[$pos]) === true) {
459 | $realName = $realParams[$pos]['name'];
460 | if ($realName !== $param['var']) {
461 | $code = 'ParamNameNoMatch';
462 | $data = array(
463 | $param['var'],
464 | $realName,
465 | );
466 |
467 | $error = 'Doc comment for parameter %s does not match ';
468 | if (strtolower($param['var']) === strtolower($realName)) {
469 | $error .= 'case of ';
470 | $code = 'ParamNameNoCaseMatch';
471 | }
472 |
473 | $error .= 'actual variable name %s';
474 |
475 | $phpcsFile->addError($error, $param['tag'], $code, $data);
476 | }
477 | } else if (substr($param['var'], -4) !== ',...') {
478 | // We must have an extra parameter comment.
479 | $error = 'Superfluous parameter comment';
480 | $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment');
481 | }//end if
482 |
483 | if ($param['comment'] === '') {
484 | continue;
485 | }
486 |
487 | // Check number of spaces after the var name.
488 | $spaces = ($maxVar - strlen($param['var']) + 1);
489 | if ($param['var_space'] !== $spaces) {
490 | $error = 'Expected %s spaces after parameter name; %s found';
491 | $data = array(
492 | $spaces,
493 | $param['var_space'],
494 | );
495 |
496 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data);
497 | if ($fix === true) {
498 | $phpcsFile->fixer->beginChangeset();
499 |
500 | $content = $param['type'];
501 | $content .= str_repeat(' ', $param['type_space']);
502 | $content .= $param['var'];
503 | $content .= str_repeat(' ', $spaces);
504 | $content .= $param['commentLines'][0]['comment'];
505 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
506 |
507 | // Fix up the indent of additional comment lines.
508 | foreach ($param['commentLines'] as $lineNum => $line) {
509 | if ($lineNum === 0
510 | || $param['commentLines'][$lineNum]['indent'] === 0
511 | ) {
512 | continue;
513 | }
514 |
515 | $newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['var_space']);
516 | $phpcsFile->fixer->replaceToken(
517 | ($param['commentLines'][$lineNum]['token'] - 1),
518 | str_repeat(' ', $newIndent)
519 | );
520 | }
521 |
522 | $phpcsFile->fixer->endChangeset();
523 | }//end if
524 | }//end if
525 |
526 | // Param comments must start with a capital letter and end with the full stop.
527 | $firstChar = $param['comment']{0};
528 | if (preg_match('|\p{Lu}|u', $firstChar) === 0) {
529 | $error = 'Parameter comment must start with a capital letter';
530 | $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital');
531 | }
532 |
533 | $lastChar = substr($param['comment'], -1);
534 | if ($lastChar !== '.') {
535 | $error = 'Parameter comment must end with a full stop';
536 | $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop');
537 | }
538 | }//end foreach
539 |
540 | $realNames = array();
541 | foreach ($realParams as $realParam) {
542 | $realNames[] = $realParam['name'];
543 | }
544 |
545 | // Report missing comments.
546 | $diff = array_diff($realNames, $foundParams);
547 | foreach ($diff as $neededParam) {
548 | $error = 'Doc comment for parameter "%s" missing';
549 | $data = array($neededParam);
550 | $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data);
551 | }
552 |
553 | }//end processParams()
554 |
555 |
556 | }//end class
557 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/SpaceAfterCommentSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
28 |
29 | $valid = false;
30 |
31 | if (preg_match('|//\s|', $tokens[$stackPtr]['content']))
32 | {
33 | $valid = true;
34 | }
35 |
36 | if (preg_match('|\*[\s/]|', $tokens[$stackPtr]['content']))
37 | {
38 | $valid = true;
39 | }
40 |
41 | if ($valid === false)
42 | {
43 | $error = 'A space is required at the start of the comment %s';
44 | $data = array(trim($tokens[$stackPtr]['content']));
45 | $phpcsFile->addError($error, $stackPtr, 'Found', $data);
46 | }
47 |
48 | }// end process()
49 | }
50 | // end class
51 |
52 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/VariableCommentSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Marc McIntyre
11 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 | * @link http://pear.php.net/package/PHP_CodeSniffer
14 | */
15 |
16 | if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) {
17 | throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found');
18 | }
19 |
20 | /**
21 | * Parses and verifies the variable doc comment.
22 | *
23 | * @category PHP
24 | * @package PHP_CodeSniffer
25 | * @author Greg Sherwood
26 | * @author Marc McIntyre
27 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 | * @version Release: @package_version@
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 |
33 | class Barracuda_Sniffs_Commenting_VariableCommentSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff
34 | {
35 | protected $allowedTypes = array(
36 | 'bool',
37 | 'int',
38 | );
39 |
40 | public function __construct()
41 | {
42 | PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes);
43 | }
44 |
45 | /**
46 | * Called to process class member vars.
47 | *
48 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
49 | * @param int $stackPtr The position of the current token
50 | * in the stack passed in $tokens.
51 | *
52 | * @return void
53 | */
54 | public function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
55 | {
56 | $tokens = $phpcsFile->getTokens();
57 | $commentToken = array(
58 | T_COMMENT,
59 | T_DOC_COMMENT_CLOSE_TAG,
60 | );
61 |
62 | $commentEnd = $phpcsFile->findPrevious($commentToken, $stackPtr);
63 | if ($commentEnd === false) {
64 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
65 | return;
66 | }
67 |
68 | if ($tokens[$commentEnd]['code'] === T_COMMENT) {
69 | $phpcsFile->addError('You must use "/**" style comments for a member variable comment', $stackPtr, 'WrongStyle');
70 | return;
71 | } else if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
72 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
73 | return;
74 | } else {
75 | // Make sure the comment we have found belongs to us.
76 | $commentFor = $phpcsFile->findNext(array(T_VARIABLE, T_CLASS, T_INTERFACE), ($commentEnd + 1));
77 | if ($commentFor !== $stackPtr) {
78 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
79 | return;
80 | }
81 | }
82 |
83 | $commentStart = $tokens[$commentEnd]['comment_opener'];
84 |
85 | $foundVar = null;
86 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
87 | if ($tokens[$tag]['content'] === '@var') {
88 | if ($foundVar !== null) {
89 | $error = 'Only one @var tag is allowed in a member variable comment';
90 | $phpcsFile->addError($error, $tag, 'DuplicateVar');
91 | } else {
92 | $foundVar = $tag;
93 | }
94 | } else if ($tokens[$tag]['content'] === '@see') {
95 | // Make sure the tag isn't empty.
96 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd);
97 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) {
98 | $error = 'Content missing for @see tag in member variable comment';
99 | $phpcsFile->addError($error, $tag, 'EmptySees');
100 | }
101 | } else {
102 | $error = '%s tag is not allowed in member variable comment';
103 | $data = array($tokens[$tag]['content']);
104 | $phpcsFile->addWarning($error, $tag, 'TagNotAllowed', $data);
105 | }//end if
106 | }//end foreach
107 |
108 | // The @var tag is the only one we require.
109 | if ($foundVar === null) {
110 | $error = 'Missing @var tag in member variable comment';
111 | $phpcsFile->addError($error, $commentEnd, 'MissingVar');
112 | return;
113 | }
114 |
115 | $firstTag = $tokens[$commentStart]['comment_tags'][0];
116 | if ($foundVar !== null && $tokens[$firstTag]['content'] !== '@var') {
117 | $error = 'The @var tag must be the first tag in a member variable comment';
118 | $phpcsFile->addError($error, $foundVar, 'VarOrder');
119 | }
120 |
121 | // Make sure the tag isn't empty and has the correct padding.
122 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $foundVar, $commentEnd);
123 | if ($string === false || $tokens[$string]['line'] !== $tokens[$foundVar]['line']) {
124 | $error = 'Content missing for @var tag in member variable comment';
125 | $phpcsFile->addError($error, $foundVar, 'EmptyVar');
126 | return;
127 | }
128 |
129 | $varType = $tokens[($foundVar + 2)]['content'];
130 | $suggestedType = PHP_CodeSniffer::suggestType($varType);
131 | if ($varType !== $suggestedType) {
132 | $error = 'Expected "%s" but found "%s" for @var tag in member variable comment';
133 | $data = array(
134 | $suggestedType,
135 | $varType,
136 | );
137 | $phpcsFile->addError($error, ($foundVar + 2), 'IncorrectVarType', $data);
138 | }
139 |
140 | }//end processMemberVar()
141 |
142 |
143 | /**
144 | * Called to process a normal variable.
145 | *
146 | * Not required for this sniff.
147 | *
148 | * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
149 | * @param int $stackPtr The position where the double quoted
150 | * string was found.
151 | *
152 | * @return void
153 | */
154 | protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
155 | {
156 |
157 | }//end processVariable()
158 |
159 |
160 | /**
161 | * Called to process variables found in double quoted strings.
162 | *
163 | * Not required for this sniff.
164 | *
165 | * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
166 | * @param int $stackPtr The position where the double quoted
167 | * string was found.
168 | *
169 | * @return void
170 | */
171 | protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
172 | {
173 |
174 | }//end processVariableInString()
175 |
176 |
177 | }//end class
178 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/ControlStructures/ControlSignatureSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
30 |
31 | if (isset($tokens[($stackPtr + 1)]) === false) {
32 | return;
33 | }
34 |
35 | // Single space after the keyword.
36 | if (in_array($tokens[$stackPtr]['code'], array(T_CATCH, T_IF, T_WHILE, T_FOR, T_FOREACH, T_ELSEIF, T_SWITCH))) {
37 | $found = 1;
38 | if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
39 | $found = 0;
40 | } else if ($tokens[($stackPtr + 1)]['content'] !== ' ') {
41 | if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) {
42 | $found = 'newline';
43 | } else {
44 | $found = strlen($tokens[($stackPtr + 1)]['content']);
45 | }
46 | }
47 |
48 | if ($found !== 1) {
49 | $error = 'Expected 1 space after %s keyword; %s found';
50 | $data = array(
51 | strtoupper($tokens[$stackPtr]['content']),
52 | $found,
53 | );
54 |
55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data);
56 | if ($fix === true) {
57 | if ($found === 0) {
58 | $phpcsFile->fixer->addContent($stackPtr, ' ');
59 | } else {
60 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
61 | }
62 | }
63 | }
64 | }
65 |
66 | // Single newline after the keyword.
67 | if (in_array($tokens[$stackPtr]['code'], array(T_TRY, T_DO, T_ELSE))
68 | && isset($tokens[$stackPtr]['scope_opener']) === true
69 | ) {
70 | $opener = $tokens[$stackPtr]['scope_opener'];
71 | $found = ($tokens[$opener]['line'] - $tokens[$stackPtr]['line']);
72 | if ($found !== 1) {
73 | $error = 'Expected 1 newline after % keyword; %s found';
74 | $data = array(
75 | strtoupper($tokens[$stackPtr]['content']),
76 | $found,
77 | );
78 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterKeyword', $data);
79 | if ($fix === true) {
80 | $phpcsFile->fixer->beginChangeset();
81 | for ($i = ($stackPtr + 1); $i < $opener; $i++) {
82 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) {
83 | break;
84 | }
85 |
86 | $phpcsFile->fixer->replaceToken($i, '');
87 | }
88 |
89 | $phpcsFile->fixer->addContent($stackPtr, $phpcsFile->eolChar);
90 | $phpcsFile->fixer->endChangeset();
91 | }
92 | }//end if
93 | }//end if
94 |
95 | // Single newline after closing parenthesis.
96 | if (isset($tokens[$stackPtr]['parenthesis_closer']) === true
97 | && isset($tokens[$stackPtr]['scope_opener']) === true
98 | ) {
99 | $closer = $tokens[$stackPtr]['parenthesis_closer'];
100 | $opener = $tokens[$stackPtr]['scope_opener'];
101 | $found = ($tokens[$opener]['line'] - $tokens[$closer]['line']);
102 | if ($found !== 1) {
103 | $error = 'Expected 1 newline after closing parenthesis; %s found';
104 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterCloseParenthesis', array($found));
105 | if ($fix === true) {
106 | $phpcsFile->fixer->beginChangeset();
107 | for ($i = ($closer + 1); $i < $opener; $i++) {
108 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) {
109 | break;
110 | }
111 |
112 | $phpcsFile->fixer->replaceToken($i, '');
113 | }
114 |
115 | $phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar);
116 | $phpcsFile->fixer->endChangeset();
117 | }
118 | }//end if
119 | }//end if
120 |
121 | // Single newline after opening brace.
122 | if (isset($tokens[$stackPtr]['scope_opener']) === true) {
123 | $opener = $tokens[$stackPtr]['scope_opener'];
124 | for ($next = ($opener + 1); $next < $phpcsFile->numTokens; $next++) {
125 | $code = $tokens[$next]['code'];
126 |
127 | if ($code === T_WHITESPACE) {
128 | continue;
129 | }
130 |
131 | // Skip all empty tokens on the same line as the opener.
132 | if ($tokens[$next]['line'] === $tokens[$opener]['line']
133 | && (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true
134 | || $code === T_CLOSE_TAG)
135 | ) {
136 | continue;
137 | }
138 |
139 | // We found the first bit of a code, or a comment on the
140 | // following line.
141 | break;
142 | }
143 |
144 | $found = ($tokens[$next]['line'] - $tokens[$opener]['line']);
145 | if ($found !== 1) {
146 | $error = 'Expected 1 newline after opening brace; %s found';
147 | $data = array($found);
148 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace', $data);
149 | if ($fix === true) {
150 | $phpcsFile->fixer->beginChangeset();
151 | for ($i = ($opener + 1); $i < $next; $i++) {
152 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) {
153 | break;
154 | }
155 |
156 | $phpcsFile->fixer->replaceToken($i, '');
157 | }
158 |
159 | $phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar);
160 | $phpcsFile->fixer->endChangeset();
161 | }
162 | }
163 | } else if ($tokens[$stackPtr]['code'] === T_WHILE) {
164 | // Zero spaces after parenthesis closer.
165 | $closer = $tokens[$stackPtr]['parenthesis_closer'];
166 | $found = 0;
167 | if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
168 | if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
169 | $found = 'newline';
170 | } else {
171 | $found = strlen($tokens[($closer + 1)]['content']);
172 | }
173 | }
174 |
175 | if ($found !== 0) {
176 | $error = 'Expected 0 spaces before semicolon; %s found';
177 | $data = array($found);
178 | $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data);
179 | if ($fix === true) {
180 | $phpcsFile->fixer->replaceToken(($closer + 1), '');
181 | }
182 | }
183 | }//end if
184 |
185 | // Only want to check multi-keyword structures from here on.
186 | if ($tokens[$stackPtr]['code'] === T_DO) {
187 | if (isset($tokens[$stackPtr]['scope_closer']) === false) {
188 | return;
189 | }
190 |
191 | $closer = $tokens[$stackPtr]['scope_closer'];
192 | } else if ($tokens[$stackPtr]['code'] === T_ELSE
193 | || $tokens[$stackPtr]['code'] === T_ELSEIF
194 | || $tokens[$stackPtr]['code'] === T_CATCH
195 | ) {
196 | $closer = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
197 | if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) {
198 | return;
199 | }
200 | } else {
201 | return;
202 | }
203 |
204 | // Single space after closing brace.
205 | if ($tokens[$stackPtr]['code'] === T_DO) {
206 | $found = 1;
207 | if ($tokens[($closer + 1)]['code'] !== T_WHITESPACE) {
208 | $found = 0;
209 | } else if ($tokens[($closer + 1)]['content'] !== ' ') {
210 | if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
211 | $found = 'newline';
212 | } else {
213 | $found = strlen($tokens[($closer + 1)]['content']);
214 | }
215 | }
216 |
217 | if ($found !== 1) {
218 | $error = 'Expected 1 space after closing brace; %s found';
219 | $data = array($found);
220 | $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseBrace', $data);
221 | if ($fix === true) {
222 | if ($found === 0) {
223 | $phpcsFile->fixer->addContent($closer, ' ');
224 | } else {
225 | $phpcsFile->fixer->replaceToken(($closer + 1), ' ');
226 | }
227 | }
228 | }
229 | }
230 |
231 | // Single newline after closing brace.
232 | if ($tokens[$stackPtr]['code'] !== T_DO) {
233 | for ($next = ($closer + 1); $next < $phpcsFile->numTokens; $next++) {
234 | $code = $tokens[$next]['code'];
235 |
236 | if ($code === T_WHITESPACE) {
237 | continue;
238 | }
239 |
240 | // Skip all empty tokens on the same line as the closer.
241 | if ($tokens[$next]['line'] === $tokens[$closer]['line']
242 | && (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true
243 | || $code === T_CLOSE_TAG)
244 | ) {
245 | continue;
246 | }
247 |
248 | // We found the first bit of a code, or a comment on the
249 | // following line.
250 | break;
251 | }
252 |
253 | $found = ($tokens[$next]['line'] - $tokens[$closer]['line']);
254 | if ($found !== 1) {
255 | $error = 'Expected 1 newline after closing brace; %s found';
256 | $data = array($found);
257 | $fix = $phpcsFile->addFixableError($error, $closer, 'NewlineAfterCloseBrace', $data);
258 | if ($fix === true) {
259 | $phpcsFile->fixer->beginChangeset();
260 | for ($i = ($closer + 1); $i < $next; $i++) {
261 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) {
262 | break;
263 | }
264 |
265 | $phpcsFile->fixer->replaceToken($i, '');
266 | }
267 |
268 | $phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar);
269 | $phpcsFile->fixer->endChangeset();
270 | }
271 | }
272 | }
273 |
274 | }//end process()
275 | }
276 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/ControlStructures/NoInlineAssignmentSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
32 |
33 | $end_position = $tokens[$stackPtr]['parenthesis_closer'];
34 |
35 | // states: -1 = normal, 0 = start function call (probably), 1 = in function
36 | $function = -1;
37 |
38 | for ($position = $stackPtr; $position < $end_position; $position++)
39 | {
40 | if ($tokens[$position]['type'] == 'T_STRING')
41 | {
42 | $function = 0;
43 | continue;
44 | }
45 |
46 | if ($function === 0)
47 | {
48 | if ($tokens[$position]['type'] == 'T_OPEN_PARENTHESIS')
49 | {
50 | $function = 1;
51 | continue;
52 | }
53 | }
54 | elseif ($function !== 1)
55 | {
56 | $function = -1;
57 | if ($tokens[$position]['type'] == 'T_EQUAL')
58 | {
59 | $error = 'Inline assignment not allowed in if statements';
60 | $phpcsFile->addError($error, $stackPtr, 'IncDecLeft');
61 | return;
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Formatting/SpaceUnaryOperatorSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
28 |
29 | // Check decrement / increment.
30 | if ($tokens[$stackPtr]['code'] === T_DEC || $tokens[$stackPtr]['code'] === T_INC)
31 | {
32 | $modifyLeft = substr($tokens[($stackPtr - 1)]['content'], 0, 1) === '$' ||
33 | $tokens[($stackPtr + 1)]['content'] === ';';
34 |
35 | if ($modifyLeft === true && $tokens[($stackPtr - 1)]['code'] === T_WHITESPACE)
36 | {
37 | $error = 'There must not be a single space before a unary operator statement';
38 | $phpcsFile->addError($error, $stackPtr, 'IncDecLeft');
39 | return;
40 | }
41 |
42 | if ($modifyLeft === false && !in_array(substr($tokens[($stackPtr + 1)]['content'], 0, 1), array('$', ',')))
43 | {
44 | $error = 'A unary operator statement must not be followed by a single space';
45 | $phpcsFile->addError($error, $stackPtr, 'IncDecRight');
46 | return;
47 | }
48 | }
49 |
50 | // Check "!" operator.
51 | if ($tokens[$stackPtr]['code'] === T_BOOLEAN_NOT && $tokens[$stackPtr + 1]['code'] === T_WHITESPACE)
52 | {
53 | $error = 'A unary operator statement must not be followed by a space';
54 | $phpcsFile->addError($error, $stackPtr, 'BooleanNot');
55 | return;
56 | }
57 |
58 | // Find the last syntax item to determine if this is an unary operator.
59 | $lastSyntaxItem = $phpcsFile->findPrevious(
60 | array(T_WHITESPACE),
61 | $stackPtr - 1,
62 | ($tokens[$stackPtr]['column']) * -1,
63 | true,
64 | null,
65 | true
66 | );
67 | $operatorSuffixAllowed = in_array(
68 | $tokens[$lastSyntaxItem]['code'],
69 | array(
70 | T_LNUMBER,
71 | T_DNUMBER,
72 | T_CLOSE_PARENTHESIS,
73 | T_CLOSE_CURLY_BRACKET,
74 | T_CLOSE_SQUARE_BRACKET,
75 | T_VARIABLE,
76 | T_STRING,
77 | )
78 | );
79 |
80 | // Check plus / minus value assignments or comparisons.
81 | if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS)
82 | {
83 | if ($operatorSuffixAllowed === false
84 | && $tokens[($stackPtr + 1)]['code'] === T_WHITESPACE
85 | )
86 | {
87 | $error = 'A unary operator statement must not be followed by a space';
88 | $phpcsFile->addError($error, $stackPtr);
89 | }
90 | }
91 |
92 | } // end process()
93 | // end class
94 | }
95 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Functions/FunctionDeclarationSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
11 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 | * @link http://pear.php.net/package/PHP_CodeSniffer
13 | */
14 |
15 | if (class_exists('PEAR_Sniffs_Functions_FunctionDeclarationSniff', true) === false) {
16 | $error = 'Class PEAR_Sniffs_Functions_FunctionDeclarationSniff not found';
17 | throw new PHP_CodeSniffer_Exception($error);
18 | }
19 |
20 | /**
21 | * Barracuda_Sniffs_Functions_MultiLineFunctionDeclarationSniff.
22 | *
23 | * Ensure single and multi-line function declarations are defined correctly.
24 | *
25 | * @category PHP
26 | * @package PHP_CodeSniffer
27 | * @author Greg Sherwood
28 | * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
29 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
30 | * @version Release: 1.5.0RC4
31 | * @link http://pear.php.net/package/PHP_CodeSniffer
32 | */
33 | class Barracuda_Sniffs_Functions_FunctionDeclarationSniff extends PEAR_Sniffs_Functions_FunctionDeclarationSniff
34 | {
35 |
36 |
37 | /**
38 | * Processes multi-line declarations.
39 | *
40 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
41 | * @param int $stackPtr The position of the current token
42 | * in the stack passed in $tokens.
43 | * @param array $tokens The stack of tokens that make up
44 | * the file.
45 | *
46 | * @return void
47 | */
48 | public function processMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
49 | {
50 | // We need to work out how far indented the function
51 | // declaration itself is, so we can work out how far to
52 | // indent parameters.
53 | $functionIndent = 0;
54 | for ($i = ($stackPtr - 1); $i >= 0; $i--) {
55 | if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
56 | $i++;
57 | break;
58 | }
59 | }
60 |
61 | if ($tokens[$i]['code'] === T_WHITESPACE) {
62 | $functionIndent = strlen($tokens[$i]['content']);
63 | }
64 |
65 | // The closing parenthesis must be on a new line, even
66 | // when checking abstract function definitions.
67 | $closeBracket = $tokens[$stackPtr]['parenthesis_closer'];
68 | $prev = $phpcsFile->findPrevious(
69 | T_WHITESPACE,
70 | ($closeBracket - 1),
71 | null,
72 | true
73 | );
74 |
75 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
76 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
77 | $error = 'The closing parenthesis of a multi-line function declaration must be on a new line';
78 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine');
79 | if ($fix === true) {
80 | $phpcsFile->fixer->addNewlineBefore($closeBracket);
81 | }
82 | }
83 | }
84 |
85 | // If this is a closure and is using a USE statement, the closing
86 | // parenthesis we need to look at from now on is the closing parenthesis
87 | // of the USE statement.
88 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
89 | $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
90 | if ($use !== false) {
91 | $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
92 | $closeBracket = $tokens[$open]['parenthesis_closer'];
93 |
94 | $prev = $phpcsFile->findPrevious(
95 | T_WHITESPACE,
96 | ($closeBracket - 1),
97 | null,
98 | true
99 | );
100 |
101 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
102 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
103 | $error = 'The closing parenthesis of a multi-line use declaration must be on a new line';
104 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine');
105 | if ($fix === true) {
106 | $phpcsFile->fixer->addNewlineBefore($closeBracket);
107 | }
108 | }
109 | }
110 | }//end if
111 | }//end if
112 |
113 | // Each line between the parenthesis should be indented 4 spaces.
114 | $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
115 | $lastLine = $tokens[$openBracket]['line'];
116 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
117 | if ($tokens[$i]['line'] !== $lastLine) {
118 | if ($i === $tokens[$stackPtr]['parenthesis_closer']
119 | || ($tokens[$i]['code'] === T_WHITESPACE
120 | && (($i + 1) === $closeBracket
121 | || ($i + 1) === $tokens[$stackPtr]['parenthesis_closer']))
122 | ) {
123 | // Closing braces need to be indented to the same level
124 | // as the function.
125 | $expectedIndent = $functionIndent;
126 | } else {
127 | $expectedIndent = ($functionIndent + $this->indent);
128 | }
129 |
130 | // We changed lines, so this should be a whitespace indent token.
131 | if ($tokens[$i]['code'] !== T_WHITESPACE) {
132 | $foundIndent = 0;
133 | } else {
134 | $foundIndent = strlen($tokens[$i]['content']);
135 | }
136 |
137 | if ($expectedIndent !== $foundIndent) {
138 | $error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s';
139 | $data = array(
140 | $expectedIndent,
141 | $foundIndent,
142 | );
143 |
144 | $fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data);
145 | if ($fix === true) {
146 | $spaces = str_repeat(' ', $expectedIndent);
147 | if ($foundIndent === 0) {
148 | $phpcsFile->fixer->addContentBefore($i, $spaces);
149 | } else {
150 | $phpcsFile->fixer->replaceToken($i, $spaces);
151 | }
152 | }
153 | }
154 |
155 | $lastLine = $tokens[$i]['line'];
156 | }//end if
157 |
158 | if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
159 | // Skip arrays as they have their own indentation rules.
160 | if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
161 | $i = $tokens[$i]['bracket_closer'];
162 | } else {
163 | $i = $tokens[$i]['parenthesis_closer'];
164 | }
165 |
166 | $lastLine = $tokens[$i]['line'];
167 | continue;
168 | }
169 | }//end for
170 |
171 | if (isset($tokens[$stackPtr]['scope_opener']) === true) {
172 |
173 | // any scope opener, we get the next token (should be EOL)
174 | $next = $tokens[($closeBracket + 1)];
175 |
176 | // if the token is EOL, then no error
177 | if ($next['content'] === $phpcsFile->eolChar)
178 | {
179 | $length = -1;
180 | } elseif ($next['code'] == T_OPEN_CURLY_BRACKET) {
181 | $length = 0;
182 | } else {
183 | $length = strlen($next['content']);
184 | }
185 |
186 | // any length means a problem, even zero
187 | if ($length >= 0) {
188 | $data = array($length);
189 | $code = 'NewLineBeforeOpenBrace';
190 |
191 | $error = 'There must be a newline before the opening brace of a multi-line function declaration; found ';
192 |
193 | // if whitespace, then report it
194 | if ($length > 0) {
195 | $error .= '%s spaces';
196 | $code = 'SpaceBeforeOpenBrace';
197 | }
198 | // otherwise, no space but still brace on same line
199 | else
200 | {
201 | $error .= ' opening brace';
202 | }
203 |
204 | $fix = $phpcsFile->addFixableError($error, ($closeBracket + 1), $code, $data);
205 | if ($fix === true) {
206 |
207 | // remove whitespace
208 | if ($length > 0)
209 | {
210 | $phpcsFile->fixer->replaceToken($closeBracket + 1, '');
211 | }
212 |
213 | // add the EOL token
214 | $phpcsFile->fixer->addContent($closeBracket, $phpcsFile->eolChar);
215 | }
216 |
217 | return;
218 | }//end if
219 |
220 | // And just in case they do something funny before the brace...
221 | $next = $phpcsFile->findNext(
222 | T_WHITESPACE,
223 | ($closeBracket + 1),
224 | null,
225 | true
226 | );
227 |
228 | if ($next !== false && $tokens[$next]['code'] !== T_OPEN_CURLY_BRACKET) {
229 | $error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration';
230 | $phpcsFile->addError($error, $next, 'NoSpaceBeforeOpenBrace');
231 | }
232 | }//end if
233 |
234 | $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
235 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'function');
236 |
237 | if ($tokens[$stackPtr]['code'] !== T_CLOSURE) {
238 | return;
239 | }
240 |
241 | $use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']);
242 | if ($use === false) {
243 | return;
244 | }
245 |
246 | $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null);
247 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'use');
248 |
249 | // Also check spacing.
250 | if ($tokens[($use - 1)]['code'] === T_WHITESPACE) {
251 | $gap = strlen($tokens[($use - 1)]['content']);
252 | } else {
253 | $gap = 0;
254 | }
255 |
256 | }//end processMultiLineDeclaration()
257 |
258 |
259 | /**
260 | * Processes the contents of a single set of brackets.
261 | *
262 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
263 | * @param int $openBracket The position of the open bracket
264 | * in the stack passed in $tokens.
265 | * @param array $tokens The stack of tokens that make up
266 | * the file.
267 | * @param string $type The type of the token the brackets
268 | * belong to (function or use).
269 | *
270 | * @return void
271 | */
272 | public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket, $tokens, $type='function')
273 | {
274 | $errorPrefix = '';
275 | if ($type === 'use') {
276 | $errorPrefix = 'Use';
277 | }
278 |
279 | $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
280 |
281 | // The open bracket should be the last thing on the line.
282 | if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) {
283 | $next = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true);
284 | if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) {
285 | $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket';
286 | $phpcsFile->addError($error, $next, $errorPrefix.'FirstParamSpacing');
287 | }
288 | }
289 |
290 | // Each line between the brackets should contain a single parameter.
291 | $lastCommaLine = null;
292 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
293 | // Skip brackets, like arrays, as they can contain commas.
294 | if (isset($tokens[$i]['parenthesis_opener']) === true) {
295 | $i = $tokens[$i]['parenthesis_closer'];
296 | continue;
297 | }
298 |
299 | if ($tokens[$i]['code'] === T_COMMA) {
300 | if ($lastCommaLine !== null && $lastCommaLine === $tokens[$i]['line']) {
301 | $error = 'Multi-line '.$type.' declarations must define one parameter per line';
302 | $phpcsFile->addError($error, $i, $errorPrefix.'OneParamPerLine');
303 | } else {
304 | // Comma must be the last thing on the line.
305 | $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true);
306 | if ($tokens[$next]['line'] !== ($tokens[$i]['line'] + 1)) {
307 | $error = 'Commas in multi-line '.$type.' declarations must be the last content on a line';
308 | $phpcsFile->addError($error, $next, $errorPrefix.'ContentAfterComma');
309 | }
310 | }
311 |
312 | $lastCommaLine = $tokens[$i]['line'];
313 | }
314 | }
315 |
316 | }//end processBracket()
317 |
318 | /**
319 | * Processes single-line declarations.
320 | *
321 | * Just uses the Generic BSD-Allman brace sniff.
322 | *
323 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
324 | * @param int $stackPtr The position of the current token
325 | * in the stack passed in $tokens.
326 | * @param array $tokens The stack of tokens that make up
327 | * the file.
328 | *
329 | * @return void
330 | */
331 | public function processSingleLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
332 | {
333 | if (class_exists('Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff', true) === false) {
334 | throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff not found');
335 | }
336 |
337 | $sniff = new Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff();
338 |
339 | $sniff->process($phpcsFile, $stackPtr);
340 |
341 | }//end processSingleLineDeclaration()
342 |
343 |
344 | }//end class
345 |
346 | ?>
347 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/bootstrap.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/barracudanetworks/forkdaemon-php/496e186daee40920482bcfb9f81c19ffaa6fd0a6/.phpcs/Barracuda/bootstrap.php
--------------------------------------------------------------------------------
/.phpcs/Barracuda/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PSR2 with Barracuda exceptions
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
48 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Barracuda Networks, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP Fork Daemon
2 | A library to make setup and management of forking daemons in PHP easy.
3 |
4 | ## Features
5 | - Easy management of PHP forks
6 | - Return result of children by callback or polling parent for results
7 | - Splitting work units into buckets
8 | - Preforking callbacks to manage resources before forking
9 | - Dynamic setting of number of children / work per child
10 |
11 | ## Usage
12 | Check out the examples in the examples directory
13 | ``php example/blocking.php``
14 |
15 | ## Caveats
16 | - You need to specify ``declare(ticks=1);`` before inclusion of the fork-daemon library, otherwise signals wont be handled. This *must* be done in the main PHP file, as ``declare(ticks=N);`` only works for the file in which it is declared and the files which that file includes. Reference: [PHP Documentation](http://php.net/manual/en/control-structures.declare.php#control-structures.declare.ticks)
17 |
18 | ## License
19 | Copyright 2013 Barracuda Networks, Inc.
20 | Licensed under the MIT License
21 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "barracuda/forkdaemon-php",
3 | "type": "library",
4 | "description": "A library to make setup and management of forking daemons in PHP easy.",
5 | "keywords": ["forking", "daemons", "php"],
6 | "homepage": "https://github.com/barracudanetworks/forkdaemon-php",
7 | "license": "MIT",
8 | "require": {
9 | "php": ">=5.3.0"
10 | },
11 | "autoload": {
12 | "classmap": [ "fork_daemon.php" ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/blocking.php:
--------------------------------------------------------------------------------
1 | max_children_set(5);
10 | $server->max_work_per_child_set(3);
11 | $server->register_child_run("process_child_run");
12 | $server->register_parent_child_exit("process_child_exit");
13 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
14 |
15 | test_blocking();
16 |
17 | function test_blocking()
18 | {
19 | global $server;
20 |
21 | echo "Adding 10 units of work\n";
22 |
23 | $data_set = array();
24 | for($i=0; $i<10; $i++) $data_set[] = $i;
25 | shuffle($data_set);
26 | $server->addwork($data_set);
27 |
28 | /* process work blocking mode */
29 | $server->process_work(true);
30 |
31 | echo "Adding 15 more units of work\n";
32 |
33 | $data_set = array();
34 | for($i=10; $i<25; $i++) $data_set[] = $i;
35 | shuffle($data_set);
36 | $server->addwork($data_set);
37 |
38 | /* process work blocking mode */
39 | $server->process_work(true);
40 | }
41 |
42 | /*
43 | * CALLBACK FUNCTIONS
44 | */
45 |
46 | /* registered call back function */
47 | function process_child_run($data_set, $identifier = "")
48 | {
49 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
50 | sleep(rand(4,8));
51 | }
52 |
53 | /* registered call back function */
54 | function process_child_exit($pid, $identifier = "")
55 | {
56 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
57 | }
58 |
59 | /* registered call back function */
60 | function logger($message)
61 | {
62 | echo "logger: " . $message . PHP_EOL;
63 | }
64 |
--------------------------------------------------------------------------------
/examples/bucket.php:
--------------------------------------------------------------------------------
1 | max_children_set(5);
10 | $server->max_work_per_child_set(3);
11 | $server->register_child_run("process_child_run");
12 | $server->register_parent_child_exit("process_child_exit");
13 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
14 |
15 | test_bucket();
16 |
17 | function test_bucket()
18 | {
19 | global $server;
20 |
21 | define("BUCKET1", 1);
22 | define("BUCKET2", 2);
23 |
24 | $server->add_bucket(BUCKET1);
25 | $server->add_bucket(BUCKET2);
26 | $server->max_children_set(2, BUCKET1);
27 | $server->max_children_set(5, BUCKET2);
28 |
29 | $data_set = array();
30 | for($i=0; $i<100; $i++) $data_set[] = $i;
31 |
32 | /* add work to bucket 1 */
33 | shuffle($data_set);
34 | $server->addwork($data_set, "", BUCKET1);
35 |
36 | /* add work to bucket 2 */
37 | shuffle($data_set);
38 | $server->addwork($data_set, "", BUCKET2);
39 |
40 | /* wait until all work allocated */
41 | while ($server->work_sets_count(BUCKET1) > 0 || $server->work_sets_count(BUCKET2) > 0)
42 | {
43 | echo "work set count(1): " . $server->work_sets_count(BUCKET1) . ", count(2): " . $server->work_sets_count(BUCKET2) . "\n";
44 | if ($server->work_sets_count(BUCKET1) > 0) $server->process_work(false, BUCKET1);
45 | if ($server->work_sets_count(BUCKET2) > 0) $server->process_work(false, BUCKET2);
46 | sleep(1);
47 | }
48 |
49 | /* wait until all children finish */
50 | while ($server->children_running() > 0)
51 | {
52 | echo "waiting for " . $server->children_running() . " children to finish\n";
53 | sleep(1);
54 | }
55 | }
56 |
57 | /*
58 | * CALLBACK FUNCTIONS
59 | */
60 |
61 | /* registered call back function */
62 | function process_child_run($data_set, $identifier = "")
63 | {
64 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
65 | sleep(rand(4,8));
66 | }
67 |
68 | /* registered call back function */
69 | function process_child_exit($pid, $identifier = "")
70 | {
71 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
72 | }
73 |
74 | /* registered call back function */
75 | function logger($message)
76 | {
77 | echo "logger: " . $message . PHP_EOL;
78 | }
79 |
--------------------------------------------------------------------------------
/examples/identifier.php:
--------------------------------------------------------------------------------
1 | max_children_set(5);
10 | $server->max_work_per_child_set(3);
11 | $server->register_child_run("process_child_run");
12 | $server->register_parent_child_exit("process_child_exit");
13 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
14 |
15 | test_identifier();
16 |
17 | function test_identifier()
18 | {
19 | global $server;
20 |
21 | $server->child_single_work_item_set(true);
22 | $server->max_work_per_child_set(1);
23 |
24 | echo "Adding 100 units of work\n";
25 |
26 | /* add work */
27 | $data_set = array();
28 | for($i=0; $i<100; $i++) $data_set[] = $i;
29 | shuffle($data_set);
30 | $data_set = array_chunk($data_set, 3);
31 |
32 | $i = 0;
33 | foreach ($data_set as $item)
34 | {
35 | $server->addwork($item, "IDn$i");
36 | $i++;
37 | }
38 |
39 | echo "Processing work in non-blocking mode\n";
40 |
41 | /* process work non blocking mode */
42 | $server->process_work(false);
43 |
44 | /* wait until all work allocated */
45 | while ($server->work_sets_count() > 0)
46 | {
47 | echo "work set count: " . $server->work_sets_count() . "\n";
48 | $server->process_work(false);
49 | sleep(1);
50 | }
51 |
52 | /* wait until all children finish */
53 | while ($server->children_running() > 0)
54 | {
55 | echo "waiting for " . $server->children_running() . " children to finish\n";
56 | sleep(1);
57 | }
58 | }
59 |
60 | /*
61 | * CALLBACK FUNCTIONS
62 | */
63 |
64 | /* registered call back function */
65 | function process_child_run($data_set, $identifier = "")
66 | {
67 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
68 | sleep(rand(4,8));
69 | }
70 |
71 | /* registered call back function */
72 | function process_child_exit($pid, $identifier = "")
73 | {
74 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
75 | }
76 |
77 | /* registered call back function */
78 | function logger($message)
79 | {
80 | echo "logger: " . $message . PHP_EOL;
81 | }
82 |
--------------------------------------------------------------------------------
/examples/nonblocking.php:
--------------------------------------------------------------------------------
1 | max_children_set(5);
10 | $server->max_work_per_child_set(3);
11 | $server->register_child_run("process_child_run");
12 | $server->register_parent_child_exit("process_child_exit");
13 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
14 |
15 | test_nonblocking();
16 |
17 | function test_nonblocking()
18 | {
19 | global $server;
20 |
21 | echo "Adding 100 units of work\n";
22 |
23 | /* add work */
24 | $data_set = array();
25 | for($i=0; $i<100; $i++) $data_set[] = $i;
26 | shuffle($data_set);
27 | $server->addwork($data_set);
28 |
29 | echo "Processing work in non-blocking mode\n";
30 |
31 | /* process work non blocking mode */
32 | $server->process_work(false);
33 |
34 | /* wait until all work allocated */
35 | while ($server->work_sets_count() > 0)
36 | {
37 | echo "work set count: " . $server->work_sets_count() . "\n";
38 | $server->process_work(false);
39 | sleep(1);
40 | }
41 |
42 | /* wait until all children finish */
43 | while ($server->children_running() > 0)
44 | {
45 | echo "waiting for " . $server->children_running() . " children to finish\n";
46 | sleep(1);
47 | }
48 | }
49 |
50 | /*
51 | * CALLBACK FUNCTIONS
52 | */
53 |
54 | /* registered call back function */
55 | function process_child_run($data_set, $identifier = "")
56 | {
57 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
58 | sleep(rand(4,8));
59 | }
60 |
61 | /* registered call back function */
62 | function process_child_exit($pid, $identifier = "")
63 | {
64 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
65 | }
66 |
67 | /* registered call back function */
68 | function logger($message)
69 | {
70 | echo "logger: " . $message . PHP_EOL;
71 | }
72 |
--------------------------------------------------------------------------------
/examples/result_callback.php:
--------------------------------------------------------------------------------
1 | max_children_set(5);
14 | $server->max_work_per_child_set(3);
15 | $server->register_child_run("process_child_run");
16 | $server->register_parent_child_exit("process_child_exit");
17 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
18 | $server->register_parent_results("process_results");
19 |
20 | test_nonblocking();
21 |
22 | function test_nonblocking()
23 | {
24 | global $server;
25 |
26 | echo "Adding 100 units of work\n";
27 |
28 | /* add work */
29 | $data_set = array();
30 | for($i=0; $i<100; $i++) $data_set[] = $i;
31 | shuffle($data_set);
32 | $server->addwork($data_set);
33 |
34 | echo "Processing work in non-blocking mode\n";
35 |
36 | /* process work non blocking mode */
37 | $server->process_work(false);
38 |
39 | /* wait until all work allocated */
40 | while ($server->work_sets_count() > 0)
41 | {
42 | echo "work set count: " . $server->work_sets_count() . "\n";
43 | $server->process_work(false);
44 | sleep(1);
45 | }
46 |
47 | /* wait until all children finish */
48 | while ($server->children_running() > 0)
49 | {
50 | echo "waiting for " . $server->children_running() . " children to finish\n";
51 | sleep(1);
52 | }
53 | }
54 |
55 | /*
56 | * CALLBACK FUNCTIONS
57 | */
58 |
59 | function process_results($results, $identifier = "")
60 | {
61 | echo "Results came back: " . implode(",", $results) . ($identifier == "" ? "" : " (id:$identifier)") . PHP_EOL;
62 | }
63 |
64 | /* registered call back function */
65 | function process_child_run($data_set, $identifier = "")
66 | {
67 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
68 |
69 | $result = array_sum($data_set);
70 | sleep(rand(1,3));
71 |
72 | // return results
73 | return $result;
74 | }
75 |
76 | /* registered call back function */
77 | function process_child_exit($pid, $identifier = "")
78 | {
79 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
80 | }
81 |
82 | /* registered call back function */
83 | function logger($message)
84 | {
85 | echo "logger: " . $message . PHP_EOL;
86 | }
87 |
--------------------------------------------------------------------------------
/examples/result_deferred.php:
--------------------------------------------------------------------------------
1 | max_children_set(100);
15 | $server->max_work_per_child_set(3);
16 | $server->store_result_set(true);
17 | $server->register_child_run("process_child_run");
18 | $server->register_parent_child_exit("process_child_exit");
19 | $server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL);
20 | // no callback with this method since we check results at the end
21 |
22 | test_nonblocking();
23 |
24 | // since deferred results, check at the end
25 | $results = $server->get_all_results();
26 | var_dump($results);
27 | echo "Sum: " . array_sum($results) . PHP_EOL;
28 | echo "Count: " . count($results) . PHP_EOL;
29 |
30 | function test_nonblocking()
31 | {
32 | global $server;
33 |
34 | echo "Adding 100 units of work\n";
35 |
36 | /* add work */
37 | $data_set = array();
38 | for($i=0; $i<100; $i++) $data_set[] = $i;
39 | shuffle($data_set);
40 | $server->addwork($data_set);
41 |
42 | echo "Processing work in non-blocking mode\n";
43 | echo "Sum: " . array_sum($data_set) . PHP_EOL;
44 |
45 | /* process work non blocking mode */
46 | $server->process_work(false);
47 |
48 | /* wait until all work allocated */
49 | while ($server->work_sets_count() > 0)
50 | {
51 | echo "work set count: " . $server->work_sets_count() . "\n";
52 | $server->process_work(false);
53 | sleep(1);
54 | }
55 |
56 | /* wait until all children finish */
57 | while ($server->children_running() > 0)
58 | {
59 | echo "waiting for " . $server->children_running() . " children to finish\n";
60 | sleep(1);
61 | }
62 | }
63 |
64 | /*
65 | * CALLBACK FUNCTIONS
66 | */
67 |
68 | /* registered call back function */
69 | function process_child_run($data_set, $identifier = "")
70 | {
71 | echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
72 |
73 | sleep(rand(1,3));
74 |
75 | // just do a sum and return it as the result
76 | $result = array_sum($data_set);
77 |
78 | // return results
79 | return $result;
80 | }
81 |
82 | /* registered call back function */
83 | function process_child_exit($pid, $identifier = "")
84 | {
85 | echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n";
86 | }
87 |
88 | /* registered call back function */
89 | function logger($message)
90 | {
91 | echo "logger: " . $message . PHP_EOL;
92 | }
93 |
--------------------------------------------------------------------------------
/fork_daemon.php:
--------------------------------------------------------------------------------
1 | 86400);
51 |
52 | /**
53 | * Whether the bucket is in persisent mode or not.
54 | *
55 | * Persistent mode will keep $max_children[$bucket] processes running permanently
56 | *
57 | * @var array
58 | */
59 | protected $child_persistent_mode = array(self::DEFAULT_BUCKET => false);
60 |
61 | /**
62 | * The data that is passed to the child functions when in persistent mode
63 | *
64 | * @var array
65 | */
66 | protected $child_persistent_mode_data = array(self::DEFAULT_BUCKET => null);
67 |
68 | /**
69 | * Function the child invokes with a set of worker units
70 | * @access protected
71 | * @var integer $child_function_run
72 | */
73 | protected $child_function_run = array(self::DEFAULT_BUCKET => '');
74 |
75 | /**
76 | * Function the parent invokes when a child finishes
77 | * @access protected
78 | * @var integer $parent_function_child_exited
79 | */
80 | protected $parent_function_child_exited = array(self::DEFAULT_BUCKET => '');
81 |
82 | /**
83 | * Function the child invokes when sigint/term is received
84 | * @access protected
85 | * @var integer $child_function_exit
86 | */
87 | protected $child_function_exit = array(self::DEFAULT_BUCKET => '');
88 |
89 | /**
90 | * Function the parent invokes when a child is killed due to exceeding the max runtime
91 | * @access protected
92 | * @var integer $child_function_timeout
93 | */
94 | protected $child_function_timeout = array(self::DEFAULT_BUCKET => '');
95 |
96 | /**
97 | * Function the parent invokes before forking a child
98 | * @access protected
99 | * @var integer $parent_function_prefork
100 | */
101 | protected $parent_function_prefork = '';
102 |
103 | /**
104 | * Function the parent invokes when a child is spawned
105 | * @access protected
106 | * @var integer $parent_function_fork
107 | */
108 | protected $parent_function_fork = array(self::DEFAULT_BUCKET => '');
109 |
110 | /**
111 | * Function the parent invokes when the parent receives a SIGHUP
112 | * @access protected
113 | * @var integer $parent_function_sighup
114 | */
115 | protected $parent_function_sighup = '';
116 |
117 | /**
118 | * Property of the parent sighup function. If true, the parent
119 | * will send sighup to all children when the parent receives a
120 | * sighup.
121 | * @access protected
122 | * @var integer $parent_function_sighup_cascade
123 | */
124 | protected $parent_function_sighup_cascade = true;
125 |
126 | /**
127 | * Function the child invokes when the child receives a SIGHUP
128 | * @access protected
129 | * @var integer $child_function_sighup
130 | */
131 | protected $child_function_sighup = array(self::DEFAULT_BUCKET => '');
132 |
133 | /**
134 | * Function the parent invokes when a child has results to post
135 | * @access protected
136 | * @var integer $parent_function_results
137 | */
138 | protected $parent_function_results = array(self::DEFAULT_BUCKET => '');
139 |
140 | /**
141 | * Stores whether results are stored for retrieval by the parent
142 | * @access protected
143 | * @var boolean $store_result
144 | */
145 | protected $store_result = false;
146 |
147 | /**
148 | * Max number of seconds to wait for a child process
149 | * exit once it has been requested to exit
150 | * @access protected
151 | * @var integer $children_kill_timeout
152 | */
153 | protected $children_max_timeout = 30;
154 |
155 | /**
156 | * Function the parent runs when the daemon is getting shutdown
157 | * @access protected
158 | * @var integer $parent_function_exit
159 | */
160 | protected $parent_function_exit = '';
161 |
162 | /**
163 | * Stores whether the daemon is in single item mode or not
164 | * @access protected
165 | * @var bool $child_single_work_item
166 | */
167 | protected $child_single_work_item = array(self::DEFAULT_BUCKET => false);
168 |
169 | /**
170 | * Function to call when there is a message to log
171 | * @access protected
172 | * @var array $log_function array of callables index by severity
173 | * called with call_user_func($log_function, $message)
174 | */
175 | protected $log_function = null;
176 |
177 | /**
178 | * Stores whether or not we have received an exit request
179 | * @access protected
180 | * @default false
181 | * @var bool $exit_request_status
182 | */
183 | protected $exit_request_status = false;
184 |
185 | // **************** SERVER CONTROLS ****************/
186 | /**
187 | * Upper limit on the number of children started.
188 | * @access protected
189 | * @var integer $max_children
190 | */
191 | protected $max_children = array(self::DEFAULT_BUCKET => 25);
192 |
193 | /**
194 | * Upper limit on the number of work units sent to each child.
195 | * @access protected
196 | * @var integer $max_work_per_child
197 | */
198 | protected $max_work_per_child = array(self::DEFAULT_BUCKET => 100);
199 |
200 | /**
201 | * Interval to do house keeping in seconds
202 | * @access protected
203 | * @var integer $housekeeping_check_interval
204 | */
205 | protected $housekeeping_check_interval = 20;
206 |
207 | // **************** TRACKING CONTROLS ****************/
208 |
209 | /**
210 | * track children of parent including their status and create time
211 | * @access protected
212 | * @var array $forked_children
213 | */
214 | protected $forked_children = array();
215 |
216 | /**
217 | * number of tracked children (not stopped)
218 | * @access protected
219 | * @var array $forked_children_count
220 | */
221 | protected $forked_children_count = 0;
222 |
223 | /**
224 | * track the work units to process
225 | * @access protected
226 | * @var array $work_units
227 | */
228 | protected $work_units = array(self::DEFAULT_BUCKET => array());
229 |
230 | /**
231 | * track the buckets
232 | * @access protected
233 | * @var array $buckets
234 | */
235 | protected $buckets = array(0 => self::DEFAULT_BUCKET);
236 |
237 | /**
238 | * for the parent the track the results received from chilren
239 | * @access protected
240 | * @var array $work_units
241 | */
242 | protected $results = array(self::DEFAULT_BUCKET => array());
243 |
244 | /**
245 | * within a child, track the bucket the child exists in. note,
246 | * this shouldn't be set or referenced in the parent process
247 | * @access protected
248 | * @var int $child_bucket
249 | */
250 | protected $child_bucket = null;
251 |
252 | // **************** MOST IMPORTANT CONTROLS ****************/
253 |
254 | /**
255 | * parent pid
256 | * @access protected
257 | * @var array $parent_pid
258 | */
259 | static protected $parent_pid;
260 |
261 | /**
262 | * last housekeeping check time
263 | * @access protected
264 | * @var array $housekeeping_last_check
265 | */
266 | protected $housekeeping_last_check = 0;
267 |
268 | /**
269 | * Whether to free unused memory in the child process after fork.
270 | * Setting this to false can avoid an expensive garbage collection
271 | * in situations with a short-lived child process and a large work
272 | * list. By default, free this memory.
273 | * @access protected
274 | * @var bool $free_unused_memory_in_child
275 | */
276 | protected $free_unused_memory_in_child = true;
277 |
278 | // **************** FUNCTION DEFINITIONS ****************/
279 |
280 | /**
281 | * Set and Get functions
282 | */
283 |
284 | /**
285 | * @return array The forked children array.
286 | */
287 | public function getForkedChildren()
288 | {
289 | return $this->forked_children;
290 | }
291 |
292 | /**
293 | * Calling this function will prevent the child from freeing
294 | * the work_list and other work tracking variables. Normally
295 | * these are freed immediately since the forked child process
296 | * has no need of them, but in some cases (like when the child
297 | * calls pcntl_exec after forking) the triggered garbage
298 | * collection may negatively impact performance with no benefit.
299 | */
300 | public function disable_child_memory_cleanup()
301 | {
302 | $this->free_unused_memory_in_child = false;
303 | }
304 |
305 | /**
306 | * Allows the app to set the max_children value
307 | * @access public
308 | * @param int $value The new max_children value.
309 | * @param int $bucket The bucket to use.
310 | * @return void
311 | */
312 | public function max_children_set($value, $bucket = self::DEFAULT_BUCKET)
313 | {
314 | if ($value < 1)
315 | {
316 | $value = 0;
317 | $this->log(($bucket === self::DEFAULT_BUCKET ? 'default' : $bucket) . ' bucket max_children set to 0, bucket will be disabled', self::LOG_LEVEL_WARN);
318 | }
319 |
320 | $old = $this->max_children[$bucket];
321 | $this->max_children[$bucket] = $value;
322 |
323 | if ($this->child_persistent_mode[$bucket] && $old > $this->max_children[$bucket])
324 | {
325 | $difference = $old - $this->max_children[$bucket];
326 | $killed = 0;
327 | // Kill some of the child processes
328 | foreach ($this->forked_children as $pid => $child)
329 | {
330 | if ($child['bucket'] == $bucket && $child['status'] == self::WORKER)
331 | {
332 | $this->safe_kill($pid, SIGINT, "max_children lowered for bucket $bucket, killing pid $pid");
333 | $killed++;
334 |
335 | if ($killed == $difference)
336 | {
337 | break;
338 | }
339 | }
340 | }
341 | }
342 | }
343 |
344 | /**
345 | * Allows the app to retrieve the current max_children value.
346 | * @access public
347 | * @param int $bucket The bucket to use.
348 | * @return int the max_children value
349 | */
350 | public function max_children_get($bucket = self::DEFAULT_BUCKET)
351 | {
352 | return($this->max_children[$bucket]);
353 | }
354 |
355 | /**
356 | * Allows the app to set the max_work_per_child value
357 | * @access public
358 | * @param int $value New max_work_per_child value.
359 | * @param int $bucket The bucket to use.
360 | * @return void
361 | */
362 | public function max_work_per_child_set($value, $bucket = self::DEFAULT_BUCKET)
363 | {
364 | if ($this->child_single_work_item[$bucket])
365 | {
366 | $value = 1;
367 | }
368 |
369 | if ($value < 1)
370 | {
371 | $value = 0;
372 | $this->log(($bucket === self::DEFAULT_BUCKET ? 'default' : $bucket) . ' bucket max_work_per_child set to 0, bucket will be disabled', self::LOG_LEVEL_WARN);
373 | }
374 |
375 | $this->max_work_per_child[$bucket] = $value;
376 | }
377 |
378 | /**
379 | * Allows the app to retrieve the current max_work_per_child value.
380 | * @access public
381 | * @param int $bucket The bucket to use.
382 | * @return int The max_work_per_child value.
383 | */
384 | public function max_work_per_child_get($bucket = self::DEFAULT_BUCKET)
385 | {
386 | return($this->max_work_per_child[$bucket]);
387 | }
388 |
389 | /**
390 | * Allows the app to set the child_max_run_time value
391 | * @access public
392 | * @param int $value New child_max_run_time value.
393 | * @param int $bucket The bucket to use.
394 | * @return void
395 | */
396 | public function child_max_run_time_set($value, $bucket = self::DEFAULT_BUCKET)
397 | {
398 | if ($value == 0)
399 | {
400 | $this->log(($bucket === self::DEFAULT_BUCKET ? 'default' : $bucket) . ' bucket child_max_run_time set to 0', self::LOG_LEVEL_WARN);
401 | }
402 | elseif ($value < 0)
403 | {
404 | $value = -1;
405 | $this->log(($bucket === self::DEFAULT_BUCKET ? 'default' : $bucket) . ' bucket child_max_run_time set to unlimited', self::LOG_LEVEL_WARN);
406 | }
407 |
408 | $this->child_max_run_time[$bucket] = $value;
409 | }
410 |
411 | /**
412 | * Allows the app to retrieve the current child_max_run_time value.
413 | * @access public
414 | * @param int $bucket The bucket to use.
415 | * @return int the child_max_run_time value
416 | */
417 | public function child_max_run_time_get($bucket = self::DEFAULT_BUCKET)
418 | {
419 | return($this->child_max_run_time[$bucket]);
420 | }
421 |
422 | /**
423 | * @param bool $persistent_mode Whether or not the bucket should be in persistent mode.
424 | * @param int $bucket The bucket to configure.
425 | * @return void
426 | */
427 | public function child_persistent_mode_set($persistent_mode, $bucket = self::DEFAULT_BUCKET)
428 | {
429 | $this->child_persistent_mode[$bucket] = $persistent_mode;
430 | }
431 |
432 | /**
433 | * @param mixed $data The data to be passed to the child processes while in persistent mode.
434 | * @param int $bucket The bucket to configure.
435 | * @return void
436 | */
437 | public function child_persistent_mode_data_set($data, $bucket = self::DEFAULT_BUCKET)
438 | {
439 | $this->child_persistent_mode_data[$bucket] = $data;
440 | }
441 |
442 | /**
443 | * Allows the app to set the child_single_work_item value
444 | * @access public
445 | * @param int $value New child_single_work_item value.
446 | * @param int $bucket The bucket to use.
447 | * @return void
448 | */
449 | public function child_single_work_item_set($value, $bucket = self::DEFAULT_BUCKET)
450 | {
451 | if ($value < 1)
452 | {
453 | $value = 0;
454 | $this->log(($bucket === self::DEFAULT_BUCKET ? 'default' : $bucket) . ' bucket child_single_work_item set to 0', self::LOG_LEVEL_WARN);
455 | }
456 |
457 | $this->child_single_work_item[$bucket] = $value;
458 | }
459 |
460 | /**
461 | * Allows the app to retrieve the current child_single_work_item value.
462 | * @access public
463 | * @param int $bucket The bucket to use.
464 | * @return int the child_single_work_item value
465 | */
466 | public function child_single_work_item_get($bucket = self::DEFAULT_BUCKET)
467 | {
468 | return($this->child_single_work_item[$bucket]);
469 | }
470 |
471 | /**
472 | * Allows the app to set the store_result value
473 | * @access public
474 | * @param int $value New store_result value.
475 | * @return void
476 | */
477 | public function store_result_set($value)
478 | {
479 | $this->store_result = $value;
480 | }
481 |
482 | /**
483 | * Allows the app to retrieve the current store_result value.
484 | * @access public
485 | * @return boolean the store_result value
486 | */
487 | public function store_result_get()
488 | {
489 | return $this->store_result;
490 | }
491 |
492 | /**
493 | * Allows the app to retrieve the current child_bucket value.
494 | * @access public
495 | * @return int the child_bucket value representing the bucket number of the child
496 | */
497 | public function child_bucket_get()
498 | {
499 | // this function does not apply to the parent
500 | if (self::$parent_pid == getmypid())
501 | {
502 | return false;
503 | }
504 |
505 | return($this->child_bucket);
506 | }
507 |
508 |
509 | /**
510 | * Creates a new bucket to house forking operations
511 | * @access public
512 | * @param int $bucket The bucket to create.
513 | * @return void
514 | */
515 | public function add_bucket($bucket)
516 | {
517 | // create the bucket by copying values from the default bucket
518 | $this->max_children[$bucket] = $this->max_children[self::DEFAULT_BUCKET];
519 | $this->child_single_work_item[$bucket] = $this->child_single_work_item[self::DEFAULT_BUCKET];
520 | $this->max_work_per_child[$bucket] = $this->max_work_per_child[self::DEFAULT_BUCKET];
521 | $this->child_max_run_time[$bucket] = $this->child_max_run_time[self::DEFAULT_BUCKET];
522 | $this->child_single_work_item[$bucket] = $this->child_single_work_item[self::DEFAULT_BUCKET];
523 | $this->child_function_run[$bucket] = $this->child_function_run[self::DEFAULT_BUCKET];
524 | $this->parent_function_fork[$bucket] = $this->parent_function_fork[self::DEFAULT_BUCKET];
525 | $this->child_function_sighup[$bucket] = $this->child_function_sighup[self::DEFAULT_BUCKET];
526 | $this->child_function_exit[$bucket] = $this->child_function_exit[self::DEFAULT_BUCKET];
527 | $this->child_function_timeout[$bucket] = $this->child_function_timeout[self::DEFAULT_BUCKET];
528 | $this->parent_function_child_exited[$bucket] = $this->parent_function_child_exited[self::DEFAULT_BUCKET];
529 | $this->child_persistent_mode[$bucket] = $this->child_persistent_mode[self::DEFAULT_BUCKET];
530 | $this->child_persistent_mode_data[$bucket] = $this->child_persistent_mode_data[self::DEFAULT_BUCKET];
531 | $this->work_units[$bucket] = array();
532 | $this->buckets[$bucket] = $bucket;
533 | $this->results[$bucket] = array();
534 | }
535 |
536 | /**
537 | * Allows the app to set the call back function for child processes
538 | * @access public
539 | * @param string $function_name Name of function to be called.
540 | * @param int $bucket The bucket to use.
541 | * @return bool true if the callback was successfully registered, false if it failed
542 | */
543 | public function register_child_run($function_name, $bucket = self::DEFAULT_BUCKET)
544 | {
545 | // call child function
546 | if (is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
547 | {
548 | $this->child_function_run[$bucket] = $function_name;
549 | return true;
550 | }
551 |
552 | return false;
553 | }
554 |
555 | /**
556 | * Allows the app to set call back functions to cleanup resources before forking
557 | * @access public
558 | * @param array $function_names Names of functions to be called.
559 | * @return bool true if the callback was successfully registered, false if it failed
560 | */
561 | public function register_parent_prefork(array $function_names)
562 | {
563 | $this->parent_function_prefork = $function_names;
564 | return true;
565 | }
566 |
567 | /**
568 | * Allows the app to set the call back function for when a child process is spawned
569 | * @access public
570 | * @param string $function_name Name of function to be called.
571 | * @param int $bucket The bucket to use.
572 | * @return bool true if the callback was successfully registered, false if it failed
573 | */
574 | public function register_parent_fork($function_name, $bucket = self::DEFAULT_BUCKET)
575 | {
576 | // call child function
577 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
578 | {
579 | $this->parent_function_fork[$bucket] = $function_name;
580 | return true;
581 | }
582 |
583 | return false;
584 | }
585 |
586 | /**
587 | * Allows the app to set the call back function for when a parent process receives a SIGHUP
588 | * @access public
589 | * @param string $function_name Name of function to be called.
590 | * @param bool $cascade_signal If true, the parent will send a sighup to all of it's children.
591 | * @return bool true if the callback was successfully registered, false if it failed
592 | */
593 | public function register_parent_sighup($function_name, $cascade_signal = true)
594 | {
595 | // call child function
596 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
597 | {
598 | $this->parent_function_sighup = $function_name;
599 | $this->parent_function_sighup_cascade = $cascade_signal;
600 | return true;
601 | }
602 |
603 | return false;
604 | }
605 |
606 | /**
607 | * Allows the app to set the call back function for when a child process receives a SIGHUP
608 | * @access public
609 | * @param string $function_name Name of function to be called.
610 | * @param int $bucket The bucket to use.
611 | * @return bool true if the callback was successfully registered, false if it failed
612 | */
613 | public function register_child_sighup($function_name, $bucket = self::DEFAULT_BUCKET)
614 | {
615 | // call child function
616 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
617 | {
618 | $this->child_function_sighup[$bucket] = $function_name;
619 | return true;
620 | }
621 |
622 | return false;
623 | }
624 |
625 | /**
626 | * Allows the app to set the call back function for when a child process exits
627 | * @access public
628 | * @param string $function_name Name of function to be called.
629 | * @param int $bucket The bucket to use.
630 | * @return bool true if the callback was successfully registered, false if it failed
631 | */
632 | public function register_child_exit($function_name, $bucket = self::DEFAULT_BUCKET)
633 | {
634 | // call child function
635 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
636 | {
637 | $this->child_function_exit[$bucket] = $function_name;
638 | return true;
639 | }
640 |
641 | return false;
642 | }
643 |
644 | /**
645 | * Allows the app to set the call back function for when a child process is killed to exceeding its max runtime
646 | * @access public
647 | * @param string $function_name Name of function to be called.
648 | * @param int $bucket The bucket to use.
649 | * @return bool true if the callback was successfully registered, false if it failed
650 | */
651 | public function register_child_timeout($function_name, $bucket = self::DEFAULT_BUCKET)
652 | {
653 | // call child function
654 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
655 | {
656 | $this->child_function_timeout[$bucket] = $function_name;
657 | return true;
658 | }
659 |
660 | return false;
661 | }
662 |
663 | /**
664 | * Allows the app to set the call back function for when the parent process exits
665 | * @access public
666 | * @param string $function_name Name of function to be called.
667 | * @return bool true if the callback was successfully registered, false if it failed
668 | */
669 | public function register_parent_exit($function_name)
670 | {
671 | // call parent function
672 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
673 | {
674 | $this->parent_function_exit = $function_name;
675 | return true;
676 | }
677 |
678 | return false;
679 | }
680 |
681 | /**
682 | * Allows the app to set the call back function for when a child exits in the parent
683 | * @access public
684 | * @param string $function_name Name of function to be called.
685 | * @param int $bucket The bucket to use.
686 | * @return bool true if the callback was successfully registered, false if it failed
687 | */
688 | public function register_parent_child_exit($function_name, $bucket = self::DEFAULT_BUCKET)
689 | {
690 | // call parent function
691 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
692 | {
693 | $this->parent_function_child_exited[$bucket] = $function_name;
694 | return true;
695 | }
696 |
697 | return false;
698 | }
699 |
700 | /**
701 | * Allows the app to set the call back function for when the a child has results
702 | * @access public
703 | * @param string $function_name Name of function to be called.
704 | * @param int $bucket The bucket to use.
705 | * @return bool true if the callback was successfully registered, false if it failed
706 | */
707 | public function register_parent_results($function_name, $bucket = self::DEFAULT_BUCKET)
708 | {
709 | // call parent function
710 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
711 | {
712 | $this->parent_function_results[$bucket] = $function_name;
713 | return true;
714 | }
715 |
716 | return false;
717 | }
718 |
719 | /**
720 | * Allows the app to set the call back function for logging
721 | * @access public
722 | * @param string $function_name Name of function to be called.
723 | * @param int $severity The severity level.
724 | * @return bool true if the callback was successfully registered, false if it failed
725 | */
726 | public function register_logging($function_name, $severity)
727 | {
728 | // call parent function
729 | if ( is_callable($function_name) || ( is_array($function_name) && method_exists($function_name[0], $function_name[1]) ) || method_exists($this, $function_name) || function_exists($function_name) )
730 | {
731 | $this->log_function[$severity] = $function_name;
732 | return true;
733 | }
734 |
735 | return false;
736 | }
737 |
738 | // ************ NORMAL FUNCTION DEFS ************/
739 |
740 | /**
741 | * This is the class constructor, initializes the object.
742 | * @access public
743 | */
744 | public function __construct()
745 | {
746 | // record pid of parent process
747 | self::$parent_pid = getmypid();
748 |
749 | // install signal handlers
750 | pcntl_async_signals(true);
751 | pcntl_signal(SIGHUP, array(&$this, 'signal_handler_sighup'));
752 | pcntl_signal(SIGCHLD, array(&$this, 'signal_handler_sigchild'));
753 | pcntl_signal(SIGTERM, array(&$this, 'signal_handler_sigint'));
754 | pcntl_signal(SIGINT, array(&$this, 'signal_handler_sigint'));
755 | pcntl_signal(SIGALRM, SIG_IGN);
756 | pcntl_signal(SIGUSR2, SIG_IGN);
757 | pcntl_signal(SIGBUS, SIG_IGN);
758 | pcntl_signal(SIGPIPE, SIG_IGN);
759 | pcntl_signal(SIGABRT, SIG_IGN);
760 | pcntl_signal(SIGFPE, SIG_IGN);
761 | pcntl_signal(SIGILL, SIG_IGN);
762 | pcntl_signal(SIGQUIT, SIG_IGN);
763 | pcntl_signal(SIGTRAP, SIG_IGN);
764 | pcntl_signal(SIGSYS, SIG_IGN);
765 |
766 | // add barracuda specific prefork functions (doesn't hurt anything)
767 | $this->parent_function_prefork = array('db_clear_connection_cache', 'memcache_clear_connection_cache');
768 | }
769 |
770 | /**
771 | * Destructor does not do anything.
772 | * @access public
773 | */
774 | public function __destruct()
775 | {
776 | }
777 |
778 | /**
779 | * Handle both parent and child registered sighup callbacks.
780 | *
781 | * @param int $signal_number Is the signal that called this function. (should be '1' for SIGHUP).
782 | * @return void
783 | * @access public
784 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) $signal_number is passed into all signal handlers.
785 | */
786 | public function signal_handler_sighup($signal_number)
787 | {
788 | if (self::$parent_pid == getmypid())
789 | {
790 | // parent received sighup
791 | $this->log('parent process [' . getmypid() . '] received sighup', self::LOG_LEVEL_DEBUG);
792 |
793 | // call parent's sighup registered callback
794 | $this->invoke_callback($this->parent_function_sighup, array(), true);
795 |
796 | // if cascading, send sighup to all child processes
797 | if ($this->parent_function_sighup_cascade === true)
798 | {
799 | foreach ($this->forked_children as $pid => $pid_info)
800 | {
801 | if ($pid_info['status'] == self::STOPPED)
802 | {
803 | continue;
804 | }
805 | $this->safe_kill($pid, SIGHUP, 'parent process [' . getmypid() . '] sending sighup to child ' . $pid, self::LOG_LEVEL_DEBUG);
806 | }
807 | }
808 | }
809 | else
810 | {
811 | // child received sighup. note a child is only in one bucket, do not loop through all buckets
812 | if (isset($this->child_bucket) && isset($this->child_function_sighup[$this->child_bucket]))
813 | {
814 | $this->log('child process [' . getmypid() . '] received sighup with bucket type [' . $this->child_bucket . ']', self::LOG_LEVEL_DEBUG);
815 | $this->invoke_callback(
816 | $this->child_function_sighup[$this->child_bucket],
817 | array($this->child_bucket),
818 | true
819 | );
820 | }
821 | }
822 | }
823 |
824 | /**
825 | * Handle parent registered sigchild callbacks.
826 | *
827 | * @param int $signal_number Is the signal that called this function.
828 | * @return void
829 | * @access public
830 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) $signal_number is passed into all signal handlers.
831 | */
832 | public function signal_handler_sigchild($signal_number)
833 | {
834 | // do not allow signals to interrupt this
835 | pcntl_async_signals(false);
836 | {
837 | // reap all child zombie processes
838 | if (self::$parent_pid == getmypid())
839 | {
840 | $status = '';
841 |
842 | do
843 | {
844 | // get child pid that exited
845 | $child_pid = pcntl_waitpid(0, $status, WNOHANG);
846 | if ($child_pid > 0)
847 | {
848 | // child exited
849 | $identifier = false;
850 | if (!isset($this->forked_children[$child_pid]))
851 | {
852 | $this->log("Cannot find $child_pid in array! (This may be a subprocess not in our fork)", self::LOG_LEVEL_INFO);
853 | continue;
854 | }
855 |
856 | $child = $this->forked_children[$child_pid];
857 | $identifier = $child['identifier'];
858 |
859 | // call exit function if and only if its declared */
860 | if ($child['status'] == self::WORKER)
861 | {
862 | $this->invoke_callback($this->parent_function_child_exited[ $this->forked_children[$child_pid]['bucket'] ], array($child_pid, $this->forked_children[$child_pid]['identifier']), true);
863 | }
864 |
865 | // stop the child pid
866 | $this->forked_children[$child_pid]['status'] = self::STOPPED;
867 | $this->forked_children_count--;
868 |
869 | // respawn helper processes
870 | if ($child['status'] == self::HELPER && $child['respawn'] === true)
871 | {
872 | $this->log('Helper process ' . $child_pid . ' died, respawning', self::LOG_LEVEL_INFO);
873 | $this->helper_process_spawn($child['function'], $child['arguments'], $child['identifier'], true);
874 | }
875 |
876 | // Poll for results from any children
877 | $this->post_results($child['bucket']);
878 | }
879 | elseif ($child_pid < 0)
880 | {
881 | // ignore acceptable error 'No child processes' given we force this signal to run potentially when no children exist
882 | if (pcntl_get_last_error() == 10)
883 | {
884 | continue;
885 | }
886 |
887 | // pcntl_wait got an error
888 | $this->log('pcntl_waitpid failed with error ' . pcntl_get_last_error() . ':' . pcntl_strerror((pcntl_get_last_error())), self::LOG_LEVEL_DEBUG);
889 | }
890 | } while ($child_pid > 0);
891 | }
892 | }
893 | }
894 |
895 | /**
896 | * Handle both parent and child registered sigint callbacks
897 | *
898 | * User terminated by CTRL-C (detected only by the parent)
899 | *
900 | * @param int $signal_number Is the signal that called this function.
901 | * @return void
902 | * @access public
903 | */
904 | public function signal_handler_sigint($signal_number)
905 | {
906 | // log that we received an exit request
907 | $this->received_exit_request(true);
908 |
909 | // kill child processes
910 | if (self::$parent_pid == getmypid())
911 | {
912 | foreach ($this->forked_children as $pid => &$pid_info)
913 | {
914 | if ($pid_info['status'] == self::STOPPED)
915 | {
916 | continue;
917 | }
918 |
919 | // tell helpers not to respawn
920 | if ($pid_info['status'] == self::HELPER)
921 | {
922 | $pid_info['respawn'] = false;
923 | }
924 |
925 | $this->safe_kill($pid, SIGINT, 'requesting child exit for pid: ' . $pid, self::LOG_LEVEL_INFO);
926 | }
927 |
928 | sleep(1);
929 |
930 | // checking for missed sigchild
931 | $this->signal_handler_sigchild(SIGCHLD);
932 |
933 | $start_time = time();
934 |
935 | // wait for child processes to go away
936 | while ($this->forked_children_count > 0)
937 | {
938 | if (time() > ($start_time + $this->children_max_timeout))
939 | {
940 | foreach ($this->forked_children as $pid => $child)
941 | {
942 | if ($child['status'] == self::STOPPED)
943 | {
944 | continue;
945 | }
946 |
947 | $this->safe_kill($pid, SIGKILL, 'force killing child pid: ' . $pid, self::LOG_LEVEL_INFO);
948 |
949 | // stop the child
950 | $this->forked_children[$pid]['status'] = self::STOPPED;
951 | $this->forked_children_count--;
952 | }
953 | }
954 | else
955 | {
956 | $this->log('waiting ' . ($start_time + $this->children_max_timeout - time()) . ' seconds for ' . $this->forked_children_count . ' children to clean up', self::LOG_LEVEL_INFO);
957 | sleep(1);
958 | $this->housekeeping_check();
959 | }
960 | }
961 |
962 | // make call back to parent exit function if it exists
963 | $this->invoke_callback($this->parent_function_exit, $parameters = array(self::$parent_pid, $signal_number), true);
964 | }
965 | else
966 | {
967 | // invoke child cleanup callback
968 | if (isset($this->child_bucket))
969 | {
970 | $this->invoke_callback($this->child_function_exit[$this->child_bucket], $parameters = array($this->child_bucket), true);
971 | }
972 | }
973 |
974 | exit(-1);
975 | }
976 |
977 | /**
978 | * Check or set if we have recieved an exit request
979 | *
980 | * @param bool $requested Have we received the request? (optional).
981 | * @return current exit request status
982 | */
983 | public function received_exit_request($requested = null)
984 | {
985 | // if we are retreiving the value of the exit request
986 | if ($requested === null)
987 | {
988 | return $this->exit_request_status;
989 | }
990 |
991 | // ensure we have good data, or set to false if not
992 | if (!is_bool($requested))
993 | {
994 | $requested = false;
995 | }
996 |
997 | // set and return the ne value
998 | return ($this->exit_request_status = $requested);
999 | }
1000 |
1001 | /**
1002 | * Add work to the group of work to be processed
1003 | *
1004 | * @param array $new_work_units Array of items to be handed back to child in chunks.
1005 | * @param string $identifier A unique identifier for this work.
1006 | * @param int $bucket The bucket to use.
1007 | * @param bool $sort_queue True to sort the work unit queue.
1008 | * @return void
1009 | */
1010 | public function addwork(array $new_work_units, $identifier = '', $bucket = self::DEFAULT_BUCKET, $sort_queue = false)
1011 | {
1012 | // ensure bucket is setup before we try to add data to it
1013 | if (!array_key_exists($bucket, $this->work_units))
1014 | {
1015 | $this->add_bucket($bucket);
1016 | }
1017 |
1018 | // add to queue to send
1019 | if ($this->child_single_work_item[$bucket])
1020 | {
1021 | // prepend identifier with 'id-' because array_splice() re-arranges numeric keys
1022 | $this->work_units[$bucket]['id-' . $identifier] = $new_work_units;
1023 | }
1024 | elseif ($new_work_units === null || sizeof($new_work_units) === 0)
1025 | {
1026 | // no work
1027 | }
1028 | else
1029 | {
1030 | // merge in the new work units
1031 | $this->work_units[$bucket] = array_merge($this->work_units[$bucket], $new_work_units);
1032 | }
1033 |
1034 | // sort the queue
1035 | if ($sort_queue)
1036 | {
1037 | ksort($this->work_units[$bucket]);
1038 | }
1039 |
1040 | return;
1041 | }
1042 |
1043 | /**
1044 | * Based on identifier and bucket is a child working on the work
1045 | *
1046 | * @param string $identifier Unique identifier for the work.
1047 | * @param int $bucket The bucket.
1048 | * @return bool true if child has work, false if not
1049 | */
1050 | public function is_work_running($identifier, $bucket = self::DEFAULT_BUCKET)
1051 | {
1052 | foreach ($this->forked_children as $info)
1053 | {
1054 | if (($info['status'] != self::STOPPED) && ($info['identifier'] == $identifier) && ($info['bucket'] == $bucket))
1055 | {
1056 | return true;
1057 | }
1058 | }
1059 |
1060 | return false;
1061 | }
1062 |
1063 | /**
1064 | * Return array of currently running children
1065 | *
1066 | * @param int $bucket The bucket.
1067 | * @return bool true if child has work, false if not
1068 | */
1069 | public function work_running($bucket = self::DEFAULT_BUCKET)
1070 | {
1071 | $results = array();
1072 | foreach ($this->forked_children as $pid => $child)
1073 | {
1074 | if ($child['status'] != self::STOPPED && $child['bucket'] === $bucket)
1075 | {
1076 | $results[$pid] = $child;
1077 | }
1078 | }
1079 |
1080 | return $results;
1081 | }
1082 |
1083 | /**
1084 | * Return a list of the buckets which have been created
1085 | *
1086 | * @param bool $include_default_bucket Optionally include self::DEFAULT_BUCKET in returned value (DEFAULT: true).
1087 | * @return array list of buckets
1088 | */
1089 | public function bucket_list($include_default_bucket = true)
1090 | {
1091 | $bucket_list = array();
1092 |
1093 | foreach ($this->buckets as $bucket_id)
1094 | {
1095 | // skip the default bucket if ignored
1096 | if ( ($include_default_bucket === false) && ($bucket_id === self::DEFAULT_BUCKET) )
1097 | {
1098 | continue;
1099 | }
1100 |
1101 | $bucket_list[] = $bucket_id;
1102 | }
1103 |
1104 | return $bucket_list;
1105 | }
1106 |
1107 | /**
1108 | * Check to see if a bucket exists
1109 | *
1110 | * @param int $bucket_id The bucket id.
1111 | * @return bool true if the bucket exists, false if it does not
1112 | */
1113 | public function bucket_exists($bucket_id)
1114 | {
1115 | return (array_key_exists($bucket_id, $this->buckets));
1116 | }
1117 |
1118 | /**
1119 | * Return the number of work sets queued
1120 | *
1121 | * A work set is a chunk of items to be worked on. A whole work set
1122 | * is handed off to a child processes. This size of the work sets can
1123 | * be controlled by $this->max_work_per_child_set()
1124 | *
1125 | * @param int $bucket The bucket to use.
1126 | * @param bool $process_all_buckets If set to true, return the count of all buckets.
1127 | * @return int the number of work sets queued
1128 | */
1129 | public function work_sets_count($bucket = self::DEFAULT_BUCKET, $process_all_buckets = false)
1130 | {
1131 | // if asked to process all buckets, count all of them and return the count
1132 | if ($process_all_buckets === true)
1133 | {
1134 | $count = 0;
1135 | foreach ($this->buckets as $bucket_slot)
1136 | {
1137 | $count += count($this->work_units[$bucket_slot]);
1138 | }
1139 | return $count;
1140 | }
1141 |
1142 | return count($this->work_units[$bucket]);
1143 | }
1144 |
1145 | /**
1146 | * Return the contents of work sets queued
1147 | *
1148 | * A work set is a chunk of items to be worked on. A whole work set
1149 | * is handed off to a child processes. This size of the work sets can
1150 | * be controlled by $this->max_work_per_child_set()
1151 | *
1152 | * @param int $bucket The bucket to use.
1153 | * @return array contents of the bucket
1154 | */
1155 | public function work_sets($bucket = self::DEFAULT_BUCKET)
1156 | {
1157 | return $this->work_units[$bucket];
1158 | }
1159 |
1160 | /**
1161 | * Return the number of children running
1162 | *
1163 | * @param int $bucket The bucket to use.
1164 | * @param bool $show_pending True to show children that are done,
1165 | * but not yet had their results retrieved.
1166 | * @return int the number of children running
1167 | */
1168 | public function children_running($bucket = self::DEFAULT_BUCKET, $show_pending = false)
1169 | {
1170 | // force reaping of children
1171 | $this->signal_handler_sigchild(SIGCHLD);
1172 |
1173 | // return global count if bucket is default
1174 | if ($bucket == self::DEFAULT_BUCKET)
1175 | {
1176 | return ($show_pending ? count($this->forked_children) : $this->forked_children_count);
1177 | }
1178 |
1179 | // count within the specified bucket
1180 | $count = 0;
1181 | foreach ($this->forked_children as $child)
1182 | {
1183 | if ($show_pending)
1184 | {
1185 | if ($child['bucket'] == $bucket)
1186 | {
1187 | $count++;
1188 | }
1189 | }
1190 | else if (($child['bucket'] == $bucket) && ($child['status'] != self::STOPPED))
1191 | {
1192 | $count++;
1193 | }
1194 | }
1195 |
1196 | return $count;
1197 | }
1198 |
1199 | /**
1200 | * Returns the number of pending child items, including running children and
1201 | * work sets that have not been allocated. Children running includes those
1202 | * that have not had their results retrieved yet.
1203 | *
1204 | * @param int $bucket The bucket to check for pending children items.
1205 | * @return int Number of pending children items
1206 | */
1207 | public function children_pending($bucket = self::DEFAULT_BUCKET)
1208 | {
1209 | return $this->children_running($bucket, true) + $this->work_sets_count($bucket);
1210 | }
1211 |
1212 | /**
1213 | * Check if the current processes is a child
1214 | *
1215 | * @return bool true if the current PID is a child PID, false otherwise
1216 | */
1217 | public static function is_child()
1218 | {
1219 | return (isset(self::$parent_pid) ? (self::$parent_pid != getmypid()) : false);
1220 | }
1221 |
1222 | /**
1223 | * Try to kill a given process and make sure it is safe to kill
1224 | *
1225 | * @param int $pid The PID to check if it is our child.
1226 | * @param int $signal The kill signal to send to the given pid if possible.
1227 | * @param string $log_message The message to log out upon success.
1228 | * @param int $log_level The level at which to display the log_message.
1229 | * @return bool true on successful kill, false if not our child or not able to kill
1230 | */
1231 | public function safe_kill($pid, $signal, $log_message = '', $log_level = self::LOG_LEVEL_INFO)
1232 | {
1233 | if (!array_key_exists($pid, $this->forked_children))
1234 | {
1235 | return false;
1236 | }
1237 |
1238 | $stat_pid_file = '/proc/' . $pid . '/stat';
1239 | if (!file_exists($stat_pid_file))
1240 | {
1241 | $this->log('Unable to find info for PID ' . $pid . ' from ' . $stat_pid_file, self::LOG_LEVEL_DEBUG);
1242 | return false;
1243 | }
1244 |
1245 | $stat_pid_info = file_get_contents($stat_pid_file);
1246 | if ($stat_pid_info === false)
1247 | {
1248 | $this->log('Unable to get info for PID ' . $pid, self::LOG_LEVEL_DEBUG);
1249 | return false;
1250 | }
1251 |
1252 | $stat_pid_info = explode(' ', $stat_pid_info);
1253 | if (!array_key_exists(3, $stat_pid_info))
1254 | {
1255 | $this->log('Unable to find parent PID for PID ' . $pid, self::LOG_LEVEL_DEBUG);
1256 | return false;
1257 | }
1258 |
1259 | // the parent pid is the fourth entry in /proc/PID/stat
1260 | if ($stat_pid_info[3] == getmypid())
1261 | {
1262 | if ($log_message)
1263 | {
1264 | $this->log($log_message, $log_level);
1265 | }
1266 |
1267 | posix_kill($pid, $signal);
1268 | return true;
1269 | }
1270 |
1271 | $this->log('Failed to kill PID ' . $pid . ' with signal ' . $signal, self::LOG_LEVEL_WARN);
1272 | return false;
1273 | }
1274 |
1275 | /**
1276 | * Spawns a helper process
1277 | *
1278 | * Spawns a new helper process to perform duties under the parent server
1279 | * process without accepting connections. Helper processes can optionally
1280 | * be respawned when they die.
1281 | *
1282 | * @access public
1283 | * @param string $function_name Helper function to call.
1284 | * @param array $arguments Function arguments.
1285 | * @param string $identifier Helper process unique identifier.
1286 | * @param bool $respawn Whether to respawn the helper process when it dies.
1287 | * @return void
1288 | */
1289 | public function helper_process_spawn($function_name, array $arguments = array(), $identifier = '', $respawn = true)
1290 | {
1291 | if ((is_array($function_name) && method_exists($function_name[0], $function_name[1])) || function_exists($function_name))
1292 | {
1293 | // init the IPC sockets
1294 | list($socket_child, $socket_parent) = $this->ipc_init();
1295 |
1296 | // do not process signals while we are forking
1297 | pcntl_async_signals(false);
1298 | $pid = pcntl_fork();
1299 |
1300 | if ($pid == -1)
1301 | {
1302 | die("Forking error!\n");
1303 | }
1304 | elseif ($pid == 0)
1305 | {
1306 | /*
1307 | * Child process
1308 | */
1309 |
1310 | // set child properties
1311 | $this->child_bucket = self::DEFAULT_BUCKET;
1312 |
1313 | pcntl_async_signals(true);
1314 |
1315 | // close our socket (we only need the one to the parent)
1316 | socket_close($socket_child);
1317 |
1318 | // execute the function
1319 | $result = call_user_func_array($function_name, $arguments);
1320 |
1321 | // send the response to the parent
1322 | self::socket_send($socket_parent, $result);
1323 |
1324 | exit(0);
1325 | }
1326 | else
1327 | {
1328 | /*
1329 | * Parent process
1330 | */
1331 |
1332 | pcntl_async_signals(true);
1333 | $this->log('Spawned new helper process with pid ' . $pid, self::LOG_LEVEL_INFO);
1334 |
1335 | // close our socket (we only need the one to the child)
1336 | socket_close($socket_parent);
1337 |
1338 | // track the child
1339 | $this->forked_children[$pid] = array(
1340 | 'ctime' => time(),
1341 | 'identifier' => $identifier,
1342 | 'status' => self::HELPER,
1343 | 'bucket' => self::DEFAULT_BUCKET,
1344 | 'respawn' => $respawn,
1345 | 'function' => $function_name,
1346 | 'arguments' => $arguments,
1347 | 'socket' => $socket_child,
1348 | 'last_active' => microtime(true),
1349 | );
1350 | $this->forked_children_count++;
1351 | }
1352 | }
1353 | else
1354 | {
1355 | $this->log("Unable to spawn undefined helper function '" . $function_name . "'", self::LOG_LEVEL_CRIT);
1356 | }
1357 | }
1358 |
1359 | /**
1360 | * Forces a helper process to respawn
1361 | *
1362 | * @param string $identifier Id of the helper process to respawn.
1363 | * @return bool|void
1364 | */
1365 | public function helper_process_respawn($identifier)
1366 | {
1367 | if ($identifier == '')
1368 | {
1369 | return false;
1370 | }
1371 |
1372 | foreach ($this->forked_children as $pid => $child)
1373 | {
1374 | if ($child['status'] == self::HELPER && $child['identifier'] == $identifier)
1375 | {
1376 | $this->safe_kill($pid, SIGKILL, 'Forcing helper process \'' . $identifier . '\' with pid ' . $pid . ' to respawn', self::LOG_LEVEL_INFO);
1377 | }
1378 | }
1379 | }
1380 |
1381 | /**
1382 | * Kill a specified child(ren) by pid
1383 | *
1384 | * Note: This method will block until all requested pids have exited
1385 | *
1386 | * @param int $pids The child pid to kill.
1387 | * @param int $kill_delay How many seconds to wait before sending sig kill on stuck processes.
1388 | * @param int $signal The signal to use to alert the child.
1389 | * @return void
1390 | * @access public
1391 | */
1392 | public function kill_child_pid($pids, $kill_delay = 30, $signal = SIGINT)
1393 | {
1394 | if (!is_array($pids))
1395 | {
1396 | $pids = array($pids);
1397 | }
1398 |
1399 | if ($signal == SIGINT)
1400 | {
1401 | $signame = 'sigint';
1402 | }
1403 | else
1404 | {
1405 | $signame = 'signal ' . $signal;
1406 | }
1407 |
1408 | // send int sigs to the children
1409 | foreach ($pids as $index => $pid)
1410 | {
1411 | // make sure we own this pid
1412 | if (!array_key_exists($pid, $this->forked_children) || $this->forked_children[$pid]['status'] == self::STOPPED)
1413 | {
1414 | $this->log('Skipping kill request on pid ' . $pid . ' because we dont own it', self::LOG_LEVEL_INFO);
1415 | unset($pids[$index]);
1416 | continue;
1417 | }
1418 |
1419 | $this->safe_kill($pid, $signal, 'Asking pid ' . $pid . ' to exit via ' . $signame, self::LOG_LEVEL_INFO);
1420 | }
1421 |
1422 | // store the requst time
1423 | $request_time = microtime(true);
1424 | $time = 0;
1425 |
1426 | // make sure the children exit
1427 | while ((count($pids) > 0) && ($time < $kill_delay))
1428 | {
1429 | foreach ($pids as $index => $pid)
1430 | {
1431 | // check if the pid exited gracefully
1432 | if (!array_key_exists($pid, $this->forked_children) || $this->forked_children[$pid]['status'] == self::STOPPED)
1433 | {
1434 | $this->log('Pid ' . $pid . ' has exited gracefully', self::LOG_LEVEL_INFO);
1435 | unset($pids[$index]);
1436 | continue;
1437 | }
1438 |
1439 | $time = microtime(true) - $request_time;
1440 | if ($time < $kill_delay)
1441 | {
1442 | $this->log('Waiting ' . ($kill_delay - round($time, 0)) . ' seconds for ' . count($pids) . ' to exit gracefully', self::LOG_LEVEL_INFO);
1443 | sleep(1);
1444 | continue;
1445 | }
1446 |
1447 | $this->safe_kill($pid, SIGKILL, 'Force killing pid ' . $pid, self::LOG_LEVEL_INFO);
1448 | }
1449 | }
1450 | }
1451 |
1452 | /**
1453 | * Process work on the work queue
1454 | *
1455 | * This function will take work sets and hand them off to children.
1456 | * Part of the process is calling invoking fork_work_unit to fork
1457 | * off the child. If $blocking is set to true, this function will
1458 | * process all work units and wait until the children are done until
1459 | * returning. If $blocking is set to false, this function will
1460 | * start as many work units as max_children allows and then return.
1461 | *
1462 | * Note, if $blocking is turned off, the caller has to handle when
1463 | * the children are done with their current load.
1464 | *
1465 | * @param bool $blocking True for blocking mode, false for immediate return.
1466 | * @param int $bucket The bucket to use.
1467 | * @param bool $process_all_buckets If we should process all the buckets.
1468 | * @return bool
1469 | */
1470 | public function process_work($blocking = true, $bucket = self::DEFAULT_BUCKET, $process_all_buckets = false)
1471 | {
1472 | $this->housekeeping_check();
1473 |
1474 | // process work on all buckets if desired
1475 | if ($process_all_buckets === true)
1476 | {
1477 | foreach ($this->buckets as $bucket_slot)
1478 | {
1479 | $this->process_work($blocking, $bucket_slot, false);
1480 | }
1481 | return true;
1482 | }
1483 |
1484 | // if room fork children
1485 | if ($blocking === true)
1486 | {
1487 | // process work until completed
1488 | while ($this->work_sets_count($bucket) > 0)
1489 | {
1490 | // check to make sure we have not hit or exceded the max children (globally or within the bucket)
1491 | while ( $this->children_running($bucket) >= $this->max_children[$bucket] )
1492 | {
1493 | $this->housekeeping_check();
1494 | $this->signal_handler_sigchild(SIGCHLD);
1495 | sleep(1);
1496 | }
1497 |
1498 | $this->process_work_unit($bucket);
1499 | }
1500 |
1501 | // wait until work finishes
1502 | while ($this->children_running($bucket) > 0)
1503 | {
1504 | sleep(1);
1505 | $this->housekeeping_check();
1506 | $this->signal_handler_sigchild(SIGCHLD);
1507 | }
1508 |
1509 | // make call back to parent exit function if it exists
1510 | $this->invoke_callback($this->parent_function_exit, array(self::$parent_pid), true);
1511 | }
1512 | else
1513 | {
1514 | // fork children until max
1515 | while ( $this->children_running($bucket) < $this->max_children[$bucket] )
1516 | {
1517 | if (!$this->child_persistent_mode[$bucket] && $this->work_sets_count($bucket) == 0)
1518 | {
1519 | return true;
1520 | }
1521 |
1522 | $this->process_work_unit($bucket);
1523 | }
1524 | }
1525 |
1526 | return true;
1527 | }
1528 |
1529 | /**
1530 | * Returns the first result available from the bucket. This will run
1531 | * a non-blocking poll of the children for updated results.
1532 | *
1533 | * @param string $bucket The bucket to check.
1534 | * @return mixed The data retrieved from a child process on the buckets
1535 | */
1536 | public function get_result($bucket = self::DEFAULT_BUCKET)
1537 | {
1538 | // check for additional results
1539 | $this->post_results($bucket);
1540 |
1541 | if (!$this->has_result($bucket))
1542 | {
1543 | return null;
1544 | }
1545 |
1546 | return array_shift($this->results[$bucket]);
1547 | }
1548 |
1549 | /**
1550 | * Returns all the results currently in the results queue. This will
1551 | * run a non-blocking poll of the children for updated results.
1552 | *
1553 | * @param string $bucket The bucket to retrieves results.
1554 | * @return mixed Array of results from each child that has finished.
1555 | */
1556 | public function get_all_results($bucket = self::DEFAULT_BUCKET)
1557 | {
1558 | // check for additional results
1559 | $this->post_results($bucket);
1560 |
1561 | if (!$this->has_result($bucket))
1562 | {
1563 | return array();
1564 | }
1565 |
1566 | $results = $this->results[$bucket];
1567 | $this->results[$bucket] = array();
1568 |
1569 | return $results;
1570 | }
1571 |
1572 | /**
1573 | * Checks if there is a result on the bucket. Before checking,
1574 | * runs a non-blocking poll of the children for updated results.
1575 | *
1576 | * @param string $bucket The bucket to check.
1577 | * @return int Returns true if there is a result
1578 | */
1579 | public function has_result($bucket = self::DEFAULT_BUCKET)
1580 | {
1581 | // check for additional results
1582 | $this->post_results($bucket);
1583 |
1584 | return (!empty($this->results[$bucket]));
1585 | }
1586 |
1587 | /**
1588 | * Checks if any changed child sockets are in the bucket.
1589 | *
1590 | * @param int $bucket The bucket to get results in.
1591 | * @param int $timeout The timeout for changed socket checking (default 0).
1592 | * @return int|void Returns the number of changed sockets for children workers in $bucket,
1593 | * or empty array if none.
1594 | */
1595 | protected function get_changed_sockets($bucket = self::DEFAULT_BUCKET, $timeout = 0)
1596 | {
1597 | $write_dummy = null;
1598 | $exception_dummy = null;
1599 |
1600 | // grab all the children sockets
1601 | $sockets = array();
1602 | foreach ($this->forked_children as $pid => $child)
1603 | {
1604 | if ($child['bucket'] == $bucket)
1605 | {
1606 | $sockets[$pid] = $child['socket'];
1607 | }
1608 | }
1609 |
1610 | if (!empty($sockets))
1611 | {
1612 | // find changed sockets and return the array of them
1613 | $result = @socket_select($sockets, $write_dummy, $exception_dummy, $timeout);
1614 | if ($result !== false && $result > 0)
1615 | {
1616 | return $sockets;
1617 | }
1618 | }
1619 |
1620 | return null;
1621 | }
1622 |
1623 | /**
1624 | * Returns any pending results from the child sockets. If a
1625 | * child has no results and it has status self::STOPPED, this will remove
1626 | * the child record from $this->forked_children.
1627 | *
1628 | * NOTE: This must be polled to check for changed sockets.
1629 | *
1630 | * @param bool $blocking Set to true to block until a result comes in.
1631 | * @param int $timeout The timeout for changed socket checking (default 0)..
1632 | * @param int $bucket The bucket to look in.
1633 | * @return type The result of the child worker
1634 | */
1635 | protected function fetch_results($blocking = true, $timeout = 0, $bucket = self::DEFAULT_BUCKET)
1636 | {
1637 | $start = microtime(true);
1638 | $results = array();
1639 |
1640 | // loop while there is pending children and pending sockets; this
1641 | // will break early on timeouts and when not blocking.
1642 | do
1643 | {
1644 | $ready_sockets = $this->get_changed_sockets($bucket, $timeout);
1645 | if (is_array($ready_sockets))
1646 | {
1647 | foreach ($ready_sockets as $pid => $socket)
1648 | {
1649 | // Ensure PID is still on forked_children -- may have been removed if a SIGCHILD occurred. The hope
1650 | // is that this fixes BNBS-23987.
1651 | if (!isset($this->forked_children[$pid]))
1652 | {
1653 | unset($ready_sockets[$pid]);
1654 | continue;
1655 | }
1656 |
1657 | $result = $this->socket_receive($socket);
1658 | if ($result !== false && (!is_null($result)))
1659 | {
1660 | $this->forked_children[$pid]['last_active'] = $start;
1661 | $results[$pid] = $result;
1662 | }
1663 | }
1664 | }
1665 |
1666 | // clean up forked children that have stopped and did not have recently
1667 | // active sockets.
1668 | foreach ($this->forked_children as $pid => &$child)
1669 | {
1670 | if (isset($child['last_active']) && ($child['last_active'] < $start) && ($child['status'] == self::STOPPED))
1671 | {
1672 | // close the socket from the parent
1673 | unset($this->forked_children[$pid]);
1674 | }
1675 | }
1676 | unset($child);
1677 |
1678 | // check if timed out
1679 | if ($timeout && (microtime(true) - $start > $timeout))
1680 | {
1681 | return $results;
1682 | }
1683 |
1684 | // return null if not blocking and we haven't seen results
1685 | if (!$blocking)
1686 | {
1687 | return $results;
1688 | }
1689 | } while (count($this->forked_children) > 0);
1690 |
1691 | return $results;
1692 | }
1693 |
1694 | /**
1695 | * Posts any new results to a callback function if one is available, or stores
1696 | * them to the internal results storage if not. This does not block and will
1697 | * post any results that are available, so call while children are running
1698 | * to check and post more results.
1699 | *
1700 | * NOTE: This should be polled to update results.
1701 | *
1702 | * @param int $bucket The bucket to post the results in.
1703 | * @return bool Returns true on successfully posting results, even if none
1704 | * to post. Returns false on error from this function or error from
1705 | * the $this->parent_function_results callback.
1706 | */
1707 | protected function post_results($bucket = self::DEFAULT_BUCKET)
1708 | {
1709 | // fetch all the results up to this point
1710 | $results = $this->fetch_results(false, 0, $bucket);
1711 | if (is_array($results) && empty($results))
1712 | {
1713 | return true;
1714 | }
1715 |
1716 | if (!empty($this->parent_function_results[$bucket]))
1717 | {
1718 | if ($this->invoke_callback($this->parent_function_results[$bucket], array($results), true) === false)
1719 | {
1720 | return false;
1721 | }
1722 | }
1723 | elseif ($this->store_result === true)
1724 | {
1725 | $this->results[$bucket] += $results;
1726 | }
1727 |
1728 | return true;
1729 | }
1730 |
1731 | /**
1732 | * Pulls items off the work queue for processing
1733 | *
1734 | * Process the work queue by taking up to max_work_per_child items
1735 | * off the queue. A new child is then spawned off to process the
1736 | * work.
1737 | *
1738 | * @param int $bucket The bucket to use.
1739 | * @return void
1740 | */
1741 | protected function process_work_unit($bucket = self::DEFAULT_BUCKET)
1742 | {
1743 | $child_work_units = array_splice($this->work_units[$bucket], 0, $this->max_work_per_child[$bucket]);
1744 |
1745 | if ($this->child_persistent_mode[$bucket])
1746 | {
1747 | $data = isset($this->child_persistent_mode_data[$bucket]) ? $this->child_persistent_mode_data[$bucket] : null;
1748 | $this->fork_work_unit($data, '', $bucket);
1749 | }
1750 | elseif (count($child_work_units) > 0)
1751 | {
1752 | if ($this->child_single_work_item[$bucket])
1753 | {
1754 | // break out identifier and unit
1755 | $child_identifier = key($child_work_units);
1756 | $child_work_unit = current($child_work_units);
1757 | next($child_work_units);
1758 |
1759 | // strip preceeding 'id-' from the identifier
1760 | if (strpos($child_identifier, 'id-') === 0)
1761 | {
1762 | $child_identifier = substr($child_identifier, 3);
1763 | }
1764 |
1765 | // process work unit
1766 | $this->fork_work_unit(array($child_work_unit, $child_identifier), $child_identifier, $bucket);
1767 | }
1768 | else
1769 | {
1770 | $this->fork_work_unit(array($child_work_units), '', $bucket);
1771 | }
1772 | }
1773 |
1774 | // Poll for results from children
1775 | $this->post_results($bucket);
1776 | }
1777 |
1778 | /**
1779 | * Fork one child with one unit of work
1780 | *
1781 | * Given a work unit array, fork a child and hand
1782 | * off the work unit to the child.
1783 | *
1784 | * @param array $work_unit An array of work to process.
1785 | * @param string $identifier A unique identifier for this work.
1786 | * @param int $bucket The bucket to use.
1787 | * @return mixed the child pid on success or boolean false on failure
1788 | */
1789 | protected function fork_work_unit(array $work_unit, $identifier = '', $bucket = self::DEFAULT_BUCKET)
1790 | {
1791 | // prefork callback
1792 | foreach ($this->parent_function_prefork as $function)
1793 | {
1794 | $this->invoke_callback($function, array(), true);
1795 | }
1796 |
1797 | // init the IPC sockets
1798 | list($socket_child, $socket_parent) = $this->ipc_init();
1799 |
1800 | // turn off signals temporarily to prevent a SIGCHLD from interupting the parent before $this->forked_children is updated
1801 | pcntl_async_signals(false);
1802 |
1803 | // spoon!
1804 | $pid = pcntl_fork();
1805 |
1806 | if ($pid == -1)
1807 | {
1808 | /**
1809 | * Fork Error
1810 | */
1811 |
1812 | $this->log('failed to fork', self::LOG_LEVEL_CRIT);
1813 | return false;
1814 | }
1815 | elseif ($pid)
1816 | {
1817 | /**
1818 | * Parent Process
1819 | */
1820 |
1821 | // keep track of this pid in the parent
1822 | $this->forked_children[$pid] = array(
1823 | 'ctime' => time(),
1824 | 'identifier' => $identifier,
1825 | 'bucket' => $bucket,
1826 | 'status' => self::WORKER,
1827 | 'socket' => $socket_child,
1828 | 'last_active' => microtime(true),
1829 | );
1830 | $this->forked_children_count++;
1831 |
1832 | // turn back on signals now that $this->forked_children has been updated
1833 | pcntl_async_signals(true);
1834 |
1835 | // close our socket (we only need the one to the child)
1836 | socket_close($socket_parent);
1837 |
1838 | // debug logging
1839 | $this->log('forking child ' . $pid . ' for bucket ' . $bucket, self::LOG_LEVEL_DEBUG);
1840 |
1841 | // parent spawned child callback
1842 | $this->invoke_callback($this->parent_function_fork[$bucket], array($pid, $identifier), true);
1843 | }
1844 | else
1845 | {
1846 | /**
1847 | * Child Process
1848 | */
1849 |
1850 | if($this->free_unused_memory_in_child)
1851 | {
1852 | // free up unneeded parent memory for child process
1853 | $this->work_units = null;
1854 | $this->forked_children = null;
1855 | $this->results = null;
1856 | }
1857 |
1858 | // set child properties
1859 | $this->child_bucket = $bucket;
1860 |
1861 | // turn signals on for the child
1862 | pcntl_async_signals(true);
1863 |
1864 | // close our socket (we only need the one to the parent)
1865 | socket_close($socket_child);
1866 |
1867 | // re-seed the random generator to prevent clone from parent
1868 | srand();
1869 | mt_srand();
1870 |
1871 | // child run callback
1872 | $result = $this->invoke_callback($this->child_function_run[$bucket], $work_unit, false);
1873 |
1874 | // send the result to the parent
1875 | self::socket_send($socket_parent, $result);
1876 |
1877 | // delay the child's exit slightly to avoid race conditions
1878 | usleep(500);
1879 |
1880 | // exit after we complete one unit of work
1881 | exit;
1882 | }
1883 |
1884 | return $pid;
1885 | }
1886 |
1887 | /**
1888 | * Performs house keeping every housekeeping_check_interval seconds
1889 | * @access protected
1890 | * @return void
1891 | */
1892 | protected function housekeeping_check()
1893 | {
1894 | if ((time() - $this->housekeeping_last_check) >= $this->housekeeping_check_interval)
1895 | {
1896 | // check to make sure no children are violating the max run time
1897 | $this->kill_maxtime_violators();
1898 |
1899 | // look for zombie children just in case
1900 | $this->signal_handler_sigchild(SIGCHLD);
1901 |
1902 | // update the last check time to now
1903 | $this->housekeeping_last_check = time();
1904 | }
1905 | }
1906 |
1907 | /**
1908 | * Kills any children that have been running for too long.
1909 | * @access protected
1910 | * @return void
1911 | */
1912 | protected function kill_maxtime_violators()
1913 | {
1914 | foreach ($this->forked_children as $pid => $pid_info)
1915 | {
1916 | if ($pid_info['status'] == self::STOPPED)
1917 | {
1918 | continue;
1919 | }
1920 |
1921 | if ($this->child_max_run_time[$pid_info['bucket']] >= 0 && (time() - $pid_info['ctime']) > $this->child_max_run_time[$pid_info['bucket']])
1922 | {
1923 | $this->log('Force kill ' . $pid . ' has run too long', self::LOG_LEVEL_INFO);
1924 |
1925 | // notify app that child process timed out
1926 | $this->invoke_callback($this->child_function_timeout{$pid_info['bucket']}, array($pid, $pid_info['identifier']), true);
1927 |
1928 | $this->safe_kill($pid, SIGKILL); // its probably stuck on something, kill it immediately.
1929 | sleep(3); // give the child time to die
1930 |
1931 | // force signal handling
1932 | $this->signal_handler_sigchild(SIGCHLD);
1933 | }
1934 | }
1935 | }
1936 |
1937 | /**
1938 | * Invoke a call back function with parameters
1939 | *
1940 | * Given the name of a function and parameters to send it, invoke the function.
1941 | * This function will try using the objects inherited function if it exists. If not,
1942 | * it'll look for a declared function of the given name.
1943 | *
1944 | * @access protected
1945 | * @param string $function_name The name of the function to invoke.
1946 | * @param array $parameters An array of parameters to pass to function.
1947 | * @param bool $optional Is set to true, don't error if function_name not available.
1948 | * @return mixed false on error, otherwise return of callback function
1949 | */
1950 | protected function invoke_callback($function_name, array $parameters, $optional = false)
1951 | {
1952 | // call child function
1953 | if (is_callable($function_name))
1954 | {
1955 | if (!is_array($parameters))
1956 | {
1957 | $parameters = array($parameters);
1958 | }
1959 |
1960 | return call_user_func_array($function_name, $parameters);
1961 | }
1962 | elseif (is_array($function_name) && method_exists($function_name[0], $function_name[1]))
1963 | {
1964 | if (!is_array($parameters))
1965 | {
1966 | $parameters = array($parameters);
1967 | }
1968 |
1969 | return call_user_func_array($function_name, $parameters);
1970 | }
1971 | elseif (method_exists($this, $function_name) )
1972 | {
1973 | if (!is_array($parameters))
1974 | {
1975 | $parameters = array($parameters);
1976 | }
1977 |
1978 | return call_user_func_array($this->$function_name, $parameters);
1979 | }
1980 | else if (function_exists($function_name))
1981 | {
1982 | if (!is_array($parameters))
1983 | {
1984 | $parameters = array($parameters);
1985 | }
1986 |
1987 | return call_user_func_array($function_name, $parameters);
1988 | }
1989 | else
1990 | {
1991 | if ($optional === false)
1992 | {
1993 | $this->log("Error there are no functions declared in scope to handle callback for function '" . $function_name . "'", self::LOG_LEVEL_CRIT);
1994 | }
1995 | }
1996 | }
1997 |
1998 | /**
1999 | * Initialize interprocess communication by setting up a pair
2000 | * of sockets and returning them as an array.
2001 | *
2002 | * @return type
2003 | */
2004 | protected function ipc_init()
2005 | {
2006 | // windows needs AF_INET
2007 | $domain = strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? AF_INET : AF_UNIX;
2008 |
2009 | // create a socket pair for IPC
2010 | $sockets = array();
2011 | if (socket_create_pair($domain, SOCK_STREAM, 0, $sockets) === false)
2012 | {
2013 | $this->log('socket_create_pair failed: ' . socket_strerror(socket_last_error()), self::LOG_LEVEL_CRIT);
2014 | return false;
2015 | }
2016 |
2017 | // return the sockets
2018 | return $sockets;
2019 | }
2020 |
2021 | /**
2022 | * Sends a serializable message to the socket.
2023 | *
2024 | * @param resource $socket The socket to send the message on.
2025 | * @param mixed $message The serializable message to send.
2026 | * @return type Returns true on success, false on failure
2027 | */
2028 | protected function socket_send($socket, $message)
2029 | {
2030 | $serialized_message = @serialize($message);
2031 | if ($serialized_message == false)
2032 | {
2033 | $this->log('socket_send failed to serialize message', self::LOG_LEVEL_CRIT);
2034 | return false;
2035 | }
2036 |
2037 | $header = pack('N', strlen($serialized_message));
2038 | $data = $header . $serialized_message;
2039 | $bytes_left = strlen($data);
2040 | while ($bytes_left > 0)
2041 | {
2042 | $bytes_sent = @socket_write($socket, $data);
2043 | if ($bytes_sent === false)
2044 | {
2045 | $this->log('socket_send error: ' . socket_strerror(socket_last_error()), self::LOG_LEVEL_CRIT);
2046 | return false;
2047 | }
2048 |
2049 | $bytes_left -= $bytes_sent;
2050 | $data = substr($data, $bytes_sent);
2051 | }
2052 |
2053 | return true;
2054 | }
2055 |
2056 | /**
2057 | * Receives a serialized message from the socket.
2058 | *
2059 | * @param resource $socket The socket to receive the message from.
2060 | * @return mixed Returns data on success, false on failure
2061 | */
2062 | protected function socket_receive($socket)
2063 | {
2064 | // initially read to the length of the header size, then
2065 | // expand to read more
2066 | $bytes_total = self::SOCKET_HEADER_SIZE;
2067 | $bytes_read = 0;
2068 | $have_header = false;
2069 | $socket_message = '';
2070 | while ($bytes_read < $bytes_total)
2071 | {
2072 | $read = @socket_read($socket, $bytes_total - $bytes_read);
2073 | if ($read === false)
2074 | {
2075 | $this->log('socket_receive error: ' . socket_strerror(socket_last_error()), self::LOG_LEVEL_CRIT);
2076 | return false;
2077 | }
2078 |
2079 | // blank socket_read means done
2080 | if ($read == '')
2081 | {
2082 | break;
2083 | }
2084 |
2085 | $bytes_read += strlen($read);
2086 | $socket_message .= $read;
2087 |
2088 | if (!$have_header && $bytes_read >= self::SOCKET_HEADER_SIZE)
2089 | {
2090 | $have_header = true;
2091 | list($bytes_total) = array_values(unpack('N', $socket_message));
2092 | $bytes_read = 0;
2093 | $socket_message = '';
2094 | }
2095 | }
2096 |
2097 | $message = @unserialize($socket_message);
2098 |
2099 | return $message;
2100 | }
2101 |
2102 | /**
2103 | * Log a message
2104 | *
2105 | * @access protected
2106 | * @param string $message The text to log.
2107 | * @param int $severity The severity of the message.
2108 | * @return bool true on success, false on error
2109 | */
2110 | protected function log($message, $severity)
2111 | {
2112 | if (!empty($this->log_function))
2113 | {
2114 | if (isset($this->log_function[$severity]))
2115 | {
2116 | return call_user_func_array($this->log_function[$severity], array($message, $severity));
2117 | }
2118 | elseif (isset($this->log_function[self::LOG_LEVEL_ALL]))
2119 | {
2120 | return call_user_func_array($this->log_function[self::LOG_LEVEL_ALL], array($message, $severity));
2121 | }
2122 | }
2123 | // Barracuda specific logging class, to keep internal code working
2124 | elseif (method_exists('Log', 'message'))
2125 | {
2126 | return Log::message($message, $severity);
2127 | }
2128 |
2129 | return true;
2130 | }
2131 | }
2132 |
--------------------------------------------------------------------------------