This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php declare(strict_types=1); |
||
2 | |||
3 | namespace Terah\Assert; |
||
4 | |||
5 | use Closure; |
||
6 | |||
7 | class Tester |
||
8 | { |
||
9 | const DEFAULT_SUITE = 'default'; |
||
10 | |||
11 | /** @var string */ |
||
12 | protected static $currentSuite = self::DEFAULT_SUITE; |
||
13 | |||
14 | /** @var Suite[] */ |
||
15 | protected static $suites = []; |
||
16 | |||
17 | /** @var Logger $logger */ |
||
18 | public static $logger = null; |
||
19 | |||
20 | /** |
||
21 | * @return bool |
||
22 | */ |
||
23 | public static function init() : bool |
||
24 | { |
||
25 | return true; |
||
26 | } |
||
27 | |||
28 | /** |
||
29 | * @param string $name |
||
30 | * @return Suite |
||
31 | */ |
||
32 | public static function suite(string $name='') : Suite |
||
33 | { |
||
34 | $name = $name ?: static::$currentSuite; |
||
35 | static::$suites[$name] = new Suite(); |
||
36 | |||
37 | return static::$suites[$name]; |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * @param string $testName |
||
42 | * @param Closure $test |
||
43 | * @param string $suiteName |
||
44 | * @param string $successMessage |
||
45 | * @param int|null $exceptionCode |
||
46 | * @param string $exceptionClass |
||
47 | * @param string $exceptionMsg |
||
48 | * @return Suite |
||
49 | * @throws AssertionFailedException |
||
50 | */ |
||
51 | public static function test(string $testName, Closure $test, string $suiteName='', string $successMessage='', int $exceptionCode=0, string $exceptionClass='', string $exceptionMsg='') : Suite |
||
52 | { |
||
53 | Assert::that($successMessage)->notEmpty(); |
||
54 | Assert::that($test)->isCallable(); |
||
55 | Assert::that($suiteName)->notEmpty(); |
||
56 | |||
57 | return static::suite($suiteName)->test($testName, $test, $successMessage, $exceptionCode, $exceptionClass, $exceptionMsg); |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * @param string $suiteName |
||
62 | * @param string $testName |
||
63 | * @return array |
||
64 | */ |
||
65 | public static function run(string $suiteName='', string $testName='') : array |
||
66 | { |
||
67 | $totalFailed = 0; |
||
68 | $totalTests = 0; |
||
69 | $suites = static::$suites; |
||
70 | if ( ! empty($suiteName) ) |
||
71 | { |
||
72 | Assert::that($suites)->keyExists($suiteName, "The test suite ({$suiteName}) has not been loaded"); |
||
73 | $suites = [$suites[$suiteName]]; |
||
74 | } |
||
75 | foreach ( $suites as $suite ) |
||
76 | { |
||
77 | $totalFailed += $suite->run($testName); |
||
78 | $totalTests += $suite->totalTestsCount(); |
||
79 | } |
||
80 | |||
81 | return compact('totalFailed', 'totalTests'); |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * @return Logger |
||
86 | */ |
||
87 | public static function getLogger() : Logger |
||
88 | { |
||
89 | if ( ! static::$logger ) |
||
90 | { |
||
91 | static::$logger = new Logger(); |
||
92 | } |
||
93 | |||
94 | return static::$logger; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * @param string $suiteName |
||
99 | * @return Suite |
||
100 | */ |
||
101 | protected static function getSuite(string $suiteName='') : Suite |
||
102 | { |
||
103 | $suiteName = $suiteName ?: static::$currentSuite; |
||
104 | if ( ! array_key_exists($suiteName, static::$suites) ) |
||
105 | { |
||
106 | return static::suite($suiteName); |
||
107 | } |
||
108 | |||
109 | return static::$suites[$suiteName]; |
||
110 | } |
||
111 | |||
112 | |||
113 | /** |
||
114 | * @param string $inputFile |
||
115 | * @param string $outputPath |
||
116 | * @return bool |
||
117 | */ |
||
118 | public static function generateTest(string $inputFile, string $outputPath) : bool |
||
119 | { |
||
120 | $declaredClasses = get_declared_classes(); |
||
121 | require $inputFile; //one or more classes in file, contains class class1, class2, etc... |
||
122 | |||
123 | $className = array_values(array_diff_key(get_declared_classes(), $declaredClasses)); |
||
124 | |||
125 | $reflectionClass = new \ReflectionClass($className[0]); |
||
126 | $publicMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC); |
||
127 | $fullClassName = $reflectionClass->getName(); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
128 | $className = $reflectionClass->getShortName(); |
||
129 | $namespace = $reflectionClass->getNamespaceName(); |
||
130 | $constructorParams = ''; |
||
131 | foreach ( $publicMethods as $method ) |
||
132 | { |
||
133 | if ( $method->isConstructor() ) |
||
134 | { |
||
135 | $constructorParams = static::getMethodParams($method); |
||
136 | } |
||
137 | } |
||
138 | $objectInit = "new {$fullClassName}({$constructorParams})"; |
||
139 | $output = []; |
||
140 | $output[] = <<<PHP |
||
141 | <?php declare(strict_types=1); |
||
142 | |||
143 | namespace {$namespace}\Test; |
||
144 | |||
145 | use Terah\Assert\Assert; |
||
146 | use Terah\Assert\Tester; |
||
147 | use Terah\Assert\Suite; |
||
148 | |||
149 | Tester::suite('AssertSuite') |
||
150 | |||
151 | ->fixture('testSubject', {$objectInit}) |
||
152 | PHP; |
||
153 | |||
154 | foreach ( $publicMethods as $method ) |
||
155 | { |
||
156 | $methodName = $method->getName(); |
||
0 ignored issues
–
show
![]() |
|||
157 | $methodParams = static::getMethodParams($method); |
||
158 | $testName = 'test' . ucfirst($methodName); |
||
159 | $successArgs = static::getMethodArgs($method); |
||
160 | $failArgs = static::getMethodArgs($method, ' '); |
||
161 | $returnVal = static::getReturnVal($method); |
||
162 | $methodSignature = "\$suite->getFixture('testSubject')->{$methodName}({$methodParams})"; |
||
163 | |||
164 | if ( $method->isStatic() ) |
||
165 | { |
||
166 | $methodSignature = "{$className}::{$methodName}({$methodParams})"; |
||
167 | } |
||
168 | |||
169 | $output[] = <<<PHP |
||
170 | |||
171 | ->test('{$testName}Success', function(Suite \$suite) { |
||
172 | |||
173 | {$successArgs} |
||
174 | \$actual = {$methodSignature}; |
||
175 | \$expected = {$returnVal}; |
||
176 | |||
177 | Assert::that(\$actual))->eq(\$expected, 'The method ({$methodName}) did not produce the correct output'); |
||
178 | }) |
||
179 | |||
180 | ->test('{$testName}Failure', function(Suite \$suite) { |
||
181 | |||
182 | {$failArgs} |
||
183 | \$actual = {$methodSignature}; |
||
184 | \$expected = {$returnVal}; |
||
185 | |||
186 | Assert::that(\$actual))->eq(\$expected, 'The method ({$methodName}) did not produce the correct output'); |
||
187 | |||
188 | }, '', Assert::INVALID_INTEGER, AssertionFailedException::class) |
||
189 | PHP; |
||
190 | |||
191 | } |
||
192 | |||
193 | $output[] = " ;"; |
||
194 | |||
195 | return static::createDirectoriesAndSaveFile($outputPath, implode("\n", $output)); |
||
196 | } |
||
197 | |||
198 | |||
199 | /** |
||
200 | * @param string $filePath |
||
201 | * @param string $data |
||
202 | * @param int $flags |
||
203 | * @param int $dirMode |
||
204 | * @return bool |
||
205 | */ |
||
206 | protected static function createDirectoriesAndSaveFile(string $filePath, $data, $flags=0, $dirMode=0755) : bool |
||
207 | { |
||
208 | static::createParentDirectories($filePath, $dirMode); |
||
209 | Assert::that(file_put_contents($filePath, $data, $flags))->notFalse("Failed to put contents in file ({$filePath})"); |
||
210 | |||
211 | return true; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * @param string $filePath |
||
216 | * @param int $mode |
||
217 | * @return bool |
||
218 | */ |
||
219 | protected static function createParentDirectories(string $filePath, $mode=0755) : bool |
||
220 | { |
||
221 | $directoryPath = preg_match('/.*\//', $filePath); |
||
222 | Assert::that($filePath) |
||
223 | ->notEmpty("Failed to identify path ({$directoryPath}) to create") |
||
224 | ->notEq(DIRECTORY_SEPARATOR, "Failed to identify path ({$directoryPath}) to create"); |
||
225 | if ( file_exists($directoryPath) ) |
||
226 | { |
||
227 | Assert::that(is_dir($directoryPath))->notFalse("Failed to create parent directories.. files exists and is not a directory({$directoryPath})"); |
||
228 | |||
229 | return true; |
||
230 | } |
||
231 | Assert::that(mkdir($directoryPath, $mode, true))->notFalse("Failed to create parent directories ({$directoryPath})"); |
||
232 | Assert::that($directoryPath)->directory(); |
||
233 | |||
234 | return true; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * @param \ReflectionMethod $method |
||
239 | * @return string |
||
240 | */ |
||
241 | protected static function getMethodParams(\ReflectionMethod $method) : string |
||
242 | { |
||
243 | $output = []; |
||
244 | foreach ( $method->getParameters() as $param ) |
||
245 | { |
||
246 | $output[] = '$' . $param->getName(); |
||
0 ignored issues
–
show
![]() |
|||
247 | } |
||
248 | |||
249 | return implode(', ', $output); |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * @param \ReflectionMethod $method |
||
254 | * @param string $extraPadding |
||
255 | * @return string |
||
256 | */ |
||
257 | protected static function getMethodArgs(\ReflectionMethod $method, string $extraPadding='') : string |
||
258 | { |
||
259 | $output = []; |
||
260 | $params = $method->getParameters(); |
||
261 | foreach ( $params as $param ) |
||
262 | { |
||
263 | $type = $param->hasType() ? $param->getType()->_toString() : ''; |
||
264 | $paramDef = str_pad('$' . $param->getName(), 32, ' ') . '= '; |
||
0 ignored issues
–
show
![]() |
|||
265 | $paramDef .= static::getDefaultValue($type); |
||
266 | $output[] = $paramDef . ';'; |
||
267 | } |
||
268 | |||
269 | return implode("\n {$extraPadding}", $output); |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * @param \ReflectionMethod $method |
||
274 | * @return string |
||
275 | */ |
||
276 | protected static function getReturnVal(\ReflectionMethod $method) : string |
||
277 | { |
||
278 | $returnType = $method->hasReturnType() ? $method->getReturnType()->_toString() : ''; |
||
279 | |||
280 | return static::getDefaultValue($returnType); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * @param string $type |
||
285 | * @param string $default |
||
286 | * @return string |
||
287 | */ |
||
288 | protected static function getDefaultValue(string $type='', string $default='null') : string |
||
289 | { |
||
290 | $typeMap = [ |
||
291 | 'int' => "0", |
||
292 | 'float' => "0.0", |
||
293 | 'string' => "''", |
||
294 | 'bool' => "false", |
||
295 | 'stdClass' => "new stdClass", |
||
296 | 'array' => "[]", |
||
297 | ]; |
||
298 | |||
299 | return $typeMap[$type] ?? $default; |
||
300 | } |
||
301 | } |
||
302 | |||
303 | |||
304 | class Suite |
||
0 ignored issues
–
show
|
|||
305 | { |
||
306 | /** @var Closure[] */ |
||
307 | protected $setUps = []; |
||
308 | |||
309 | /** @var Closure[] */ |
||
310 | protected $tearDowns = []; |
||
311 | |||
312 | /** @var Test[] */ |
||
313 | protected $tests = []; |
||
314 | |||
315 | /** @var mixed[] */ |
||
316 | protected $fixtures = []; |
||
317 | |||
318 | /** @var Logger */ |
||
319 | protected $logger = null; |
||
320 | |||
321 | /** @var int **/ |
||
322 | protected $failedCount = 0; |
||
323 | |||
324 | /** |
||
325 | * @param string $filter |
||
326 | * @return int |
||
327 | */ |
||
328 | public function run(string $filter='') : int |
||
329 | { |
||
330 | foreach ( $this->tests as $test => $testCase ) |
||
331 | { |
||
332 | $testName = $testCase->getTestName(); |
||
333 | if ( $filter && $testName !== $filter ) |
||
334 | { |
||
335 | continue; |
||
336 | } |
||
337 | try |
||
338 | { |
||
339 | $this->getLogger()->info("[{$testName}] - Starting..."); |
||
340 | foreach ( $this->setUps as $idx => $closure ) |
||
341 | { |
||
342 | $closure->__invoke($this); |
||
343 | } |
||
344 | $testCase->runTest($this); |
||
345 | foreach ( $this->tearDowns as $idx => $closure ) |
||
346 | { |
||
347 | $closure->__invoke($this); |
||
348 | } |
||
349 | $this->getLogger()->info("[{$testName}] - " . $testCase->getSuccessMessage()); |
||
350 | } |
||
351 | catch ( \Exception $e ) |
||
352 | { |
||
353 | $expectedCode = $testCase->getExceptionCode(); |
||
354 | $expectedClass = $testCase->getExceptionType(); |
||
355 | $expectedMsg = $testCase->getExceptionMsg(); |
||
356 | $code = $e->getCode(); |
||
357 | $message = $e->getMessage(); |
||
358 | $exception = get_class($e); |
||
359 | if ( ! $expectedClass && ! $expectedCode ) |
||
360 | { |
||
361 | $this->getLogger()->error($e->getMessage(), [compact('testName'), $e]); |
||
362 | $this->failedCount++; |
||
363 | |||
364 | continue; |
||
365 | } |
||
366 | View Code Duplication | if ( $expectedCode && $expectedCode !== $code ) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
367 | { |
||
368 | $this->getLogger()->error("Exception code({$code}) was expected to be ({$expectedCode})", [compact('testName'), $e]); |
||
369 | $this->failedCount++; |
||
370 | |||
371 | continue; |
||
372 | } |
||
373 | View Code Duplication | if ( $expectedMsg && $expectedMsg !== $message ) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
374 | { |
||
375 | $this->getLogger()->error("Exception message({$message}) was expected to be ({$expectedMsg})", [compact('testName'), $e]); |
||
376 | $this->failedCount++; |
||
377 | |||
378 | continue; |
||
379 | } |
||
380 | View Code Duplication | if ( $expectedClass && $expectedClass !== $exception ) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
381 | { |
||
382 | $this->getLogger()->error("Exception class({$exception}) was expected to be ({$expectedClass})", [compact('testName'), $e]); |
||
383 | $this->failedCount++; |
||
384 | |||
385 | continue; |
||
386 | } |
||
387 | $this->getLogger()->info("[{$test}] - " . $testCase->getSuccessMessage()); |
||
388 | } |
||
389 | } |
||
390 | |||
391 | return $this->failedTestsCount(); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * @return int |
||
396 | */ |
||
397 | public function totalTestsCount() : int |
||
398 | { |
||
399 | return count($this->tests); |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * @return int |
||
404 | */ |
||
405 | public function failedTestsCount() : int |
||
406 | { |
||
407 | return $this->failedCount; |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * @param Closure $callback |
||
412 | * @return Suite |
||
413 | */ |
||
414 | public function setUp(Closure $callback) : Suite |
||
415 | { |
||
416 | $this->setUps[] = $callback; |
||
417 | |||
418 | return $this; |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * @param Closure $callback |
||
423 | * @return Suite |
||
424 | */ |
||
425 | public function tearDown(Closure $callback) : Suite |
||
426 | { |
||
427 | $this->tearDowns[] = $callback; |
||
428 | |||
429 | return $this; |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * @param string $testName |
||
434 | * @param Closure $test |
||
435 | * @param string $successMessage |
||
436 | * @param int|null $exceptionCode |
||
437 | * @param string $exceptionClass |
||
438 | * @return Suite |
||
439 | * @throws AssertionFailedException |
||
440 | */ |
||
441 | public function test(string $testName, Closure $test, string $successMessage='', int $exceptionCode=0, string $exceptionClass='', string $exceptionMsg='') : Suite |
||
442 | { |
||
443 | $this->tests[] = new Test($testName, $test, $successMessage, $exceptionCode, $exceptionClass, $exceptionMsg); |
||
444 | |||
445 | return $this; |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * @param string $fixtureName |
||
450 | * @param $value |
||
451 | * @return Suite |
||
452 | */ |
||
453 | public function fixture(string $fixtureName, $value) : Suite |
||
454 | { |
||
455 | $this->fixtures[$fixtureName] = $value; |
||
456 | |||
457 | return $this; |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * @param string $fixtureName |
||
462 | * @return mixed |
||
463 | * @throws AssertionFailedException |
||
464 | */ |
||
465 | public function getFixture(string $fixtureName) |
||
466 | { |
||
467 | Assert::that($this->fixtures)->keyExists($fixtureName, "The fixture ({$fixtureName}) does not exist."); |
||
468 | |||
469 | if ( is_callable($this->fixtures[$fixtureName]) ) |
||
470 | { |
||
471 | $this->fixtures[$fixtureName] = $this->fixtures[$fixtureName]->__invoke($this); |
||
472 | } |
||
473 | |||
474 | return $this->fixtures[$fixtureName]; |
||
475 | } |
||
476 | |||
477 | /** |
||
478 | * @param Closure $callback |
||
479 | * @return $this |
||
480 | */ |
||
481 | public function execute(Closure $callback) |
||
482 | { |
||
483 | $callback->__invoke($this); |
||
484 | |||
485 | return $this; |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * @param Logger $logger |
||
490 | * @return $this |
||
491 | */ |
||
492 | public function setLogger(Logger $logger) : Suite |
||
493 | { |
||
494 | $this->logger = $logger; |
||
495 | |||
496 | return $this; |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * @return Logger |
||
501 | */ |
||
502 | public function getLogger() : Logger |
||
503 | { |
||
504 | if ( ! $this->logger ) |
||
505 | { |
||
506 | $this->logger = new Logger(); |
||
507 | } |
||
508 | |||
509 | return $this->logger; |
||
510 | } |
||
511 | |||
512 | } |
||
513 | |||
514 | class Test |
||
0 ignored issues
–
show
|
|||
515 | { |
||
516 | /** @var string */ |
||
517 | public $testName = ''; |
||
518 | |||
519 | /** @var string */ |
||
520 | public $successMessage = ''; |
||
521 | |||
522 | /** @var Closure */ |
||
523 | public $test = null; |
||
524 | |||
525 | /** @var string */ |
||
526 | public $exceptionType = null; |
||
527 | |||
528 | /** @var int */ |
||
529 | public $exceptionCode = null; |
||
530 | |||
531 | /** @var string */ |
||
532 | public $exceptionMsg = null; |
||
533 | |||
534 | /** |
||
535 | * Test constructor. |
||
536 | * |
||
537 | * @param string $testName |
||
538 | * @param Closure $test |
||
539 | * @param string $successMessage |
||
540 | * @param int $exceptionCode |
||
541 | * @param string $exceptionClass |
||
542 | * @param string $exceptionMsg |
||
543 | * @throws AssertionFailedException |
||
544 | */ |
||
545 | public function __construct(string $testName, Closure $test, string $successMessage='', int $exceptionCode=0, string $exceptionClass='', string $exceptionMsg='') |
||
546 | { |
||
547 | $this->setTestName($testName); |
||
548 | $this->setTest($test); |
||
549 | $this->setSuccessMessage($successMessage); |
||
550 | $this->setExceptionCode($exceptionCode); |
||
551 | $this->setExceptionType($exceptionClass); |
||
552 | $this->setExceptionMsg($exceptionMsg); |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * @return string |
||
557 | */ |
||
558 | public function getTestName() : string |
||
559 | { |
||
560 | return $this->testName; |
||
561 | } |
||
562 | |||
563 | /** |
||
564 | * @param string $testName |
||
565 | * @return Test |
||
566 | */ |
||
567 | public function setTestName(string $testName) : Test |
||
568 | { |
||
569 | Assert::that($testName)->notEmpty(); |
||
570 | |||
571 | $this->testName = $testName; |
||
572 | |||
573 | return $this; |
||
574 | } |
||
575 | |||
576 | /** |
||
577 | * @return string |
||
578 | */ |
||
579 | public function getSuccessMessage() : string |
||
580 | { |
||
581 | if ( ! $this->successMessage ) |
||
582 | { |
||
583 | return "Successfully run {$this->testName}"; |
||
584 | } |
||
585 | |||
586 | return $this->successMessage; |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * @param string $successMessage |
||
591 | * @return Test |
||
592 | * @throws AssertionFailedException |
||
593 | */ |
||
594 | public function setSuccessMessage(string $successMessage) : Test |
||
595 | { |
||
596 | $this->successMessage = $successMessage; |
||
597 | |||
598 | return $this; |
||
599 | } |
||
600 | |||
601 | /** |
||
602 | * @return Closure |
||
603 | */ |
||
604 | public function getTest() : Closure |
||
605 | { |
||
606 | return $this->test; |
||
607 | } |
||
608 | |||
609 | /** |
||
610 | * @param Closure $test |
||
611 | * @return Test |
||
612 | */ |
||
613 | public function setTest(Closure $test) : Test |
||
614 | { |
||
615 | $this->test = $test; |
||
616 | |||
617 | return $this; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * @return string |
||
622 | */ |
||
623 | public function getExceptionType() : string |
||
624 | { |
||
625 | return $this->exceptionType; |
||
626 | } |
||
627 | |||
628 | /** |
||
629 | * @param string $exceptionType |
||
630 | * @return Test |
||
631 | */ |
||
632 | public function setExceptionType(string $exceptionType) : Test |
||
633 | { |
||
634 | $this->exceptionType = $exceptionType; |
||
635 | |||
636 | return $this; |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * @return string |
||
641 | */ |
||
642 | public function getExceptionMsg() : string |
||
643 | { |
||
644 | return $this->exceptionMsg; |
||
645 | } |
||
646 | |||
647 | /** |
||
648 | * @param string $exceptionMsg |
||
649 | * @return Test |
||
650 | */ |
||
651 | public function setExceptionMsg(string $exceptionMsg) : Test |
||
652 | { |
||
653 | $this->exceptionMsg = $exceptionMsg; |
||
654 | |||
655 | return $this; |
||
656 | } |
||
657 | |||
658 | /** |
||
659 | * @return int |
||
660 | */ |
||
661 | public function getExceptionCode() : int |
||
662 | { |
||
663 | return $this->exceptionCode; |
||
664 | } |
||
665 | |||
666 | /** |
||
667 | * @param int $exceptionCode |
||
668 | * @return Test |
||
669 | */ |
||
670 | public function setExceptionCode(int $exceptionCode) : Test |
||
671 | { |
||
672 | $this->exceptionCode = $exceptionCode; |
||
673 | |||
674 | return $this; |
||
675 | } |
||
676 | |||
677 | /** |
||
678 | * @param Suite $suite |
||
679 | * @return mixed |
||
680 | */ |
||
681 | public function runTest(Suite $suite) |
||
682 | { |
||
683 | return $this->getTest()->__invoke($suite); |
||
684 | } |
||
685 | } |
||
686 | |||
687 | /** |
||
688 | * Class Logger |
||
689 | * |
||
690 | * @package Terah\Assert |
||
691 | */ |
||
692 | class Logger |
||
0 ignored issues
–
show
|
|||
693 | { |
||
694 | const EMERGENCY = 'emergency'; |
||
695 | const ALERT = 'alert'; |
||
696 | const CRITICAL = 'critical'; |
||
697 | const ERROR = 'error'; |
||
698 | const WARNING = 'warning'; |
||
699 | const NOTICE = 'notice'; |
||
700 | const INFO = 'info'; |
||
701 | const DEBUG = 'debug'; |
||
702 | |||
703 | const BLACK = 'black'; |
||
704 | const DARK_GRAY = 'dark_gray'; |
||
705 | const BLUE = 'blue'; |
||
706 | const LIGHT_BLUE = 'light_blue'; |
||
707 | const GREEN = 'green'; |
||
708 | const LIGHT_GREEN = 'light_green'; |
||
709 | const CYAN = 'cyan'; |
||
710 | const LIGHT_CYAN = 'light_cyan'; |
||
711 | const RED = 'red'; |
||
712 | const LIGHT_RED = 'light_red'; |
||
713 | const PURPLE = 'purple'; |
||
714 | const LIGHT_PURPLE = 'light_purple'; |
||
715 | const BROWN = 'brown'; |
||
716 | const YELLOW = 'yellow'; |
||
717 | const MAGENTA = 'magenta'; |
||
718 | const LIGHT_GRAY = 'light_gray'; |
||
719 | const WHITE = 'white'; |
||
720 | const DEFAULT = 'default'; |
||
721 | const BOLD = 'bold'; |
||
722 | |||
723 | /** @var resource $resource The file handle */ |
||
724 | protected $resource = null; |
||
725 | |||
726 | /** @var string $level */ |
||
727 | protected $level = self::INFO; |
||
728 | |||
729 | /** @var bool $closeLocally */ |
||
730 | protected $closeLocally = false; |
||
731 | |||
732 | /** @var bool */ |
||
733 | protected $addDate = true; |
||
734 | |||
735 | /** @var string */ |
||
736 | protected $separator = ' | '; |
||
737 | |||
738 | /** @var \Closure */ |
||
739 | protected $formatter = null; |
||
740 | |||
741 | /** @var string */ |
||
742 | protected $lastLogEntry = ''; |
||
743 | |||
744 | /** @var bool|null */ |
||
745 | protected $gzipFile = null; |
||
746 | |||
747 | /** @var bool */ |
||
748 | protected $useLocking = false; |
||
749 | |||
750 | /** |
||
751 | * @var array $logLevels List of supported levels |
||
752 | */ |
||
753 | static protected $logLevels = [ |
||
754 | self::EMERGENCY => [1, self::WHITE, self::RED, self::DEFAULT, 'EMERG'], |
||
755 | self::ALERT => [2, self::WHITE, self::YELLOW, self::DEFAULT, 'ALERT'], |
||
756 | self::CRITICAL => [3, self::RED, self::DEFAULT, self::BOLD , 'CRIT'], |
||
757 | self::ERROR => [4, self::RED, self::DEFAULT, self::DEFAULT, 'ERROR'], |
||
758 | self::WARNING => [5, self::YELLOW, self::DEFAULT, self::DEFAULT, 'WARN'], |
||
759 | self::NOTICE => [6, self::CYAN, self::DEFAULT, self::DEFAULT, 'NOTE'], |
||
760 | self::INFO => [7, self::GREEN, self::DEFAULT, self::DEFAULT, 'INFO'], |
||
761 | self::DEBUG => [8, self::LIGHT_GRAY, self::DEFAULT, self::DEFAULT, 'DEBUG'], |
||
762 | ]; |
||
763 | |||
764 | /** |
||
765 | * @var array |
||
766 | */ |
||
767 | static protected $colours = [ |
||
768 | 'fore' => [ |
||
769 | self::BLACK => '0;30', |
||
770 | self::DARK_GRAY => '1;30', |
||
771 | self::BLUE => '0;34', |
||
772 | self::LIGHT_BLUE => '1;34', |
||
773 | self::GREEN => '0;32', |
||
774 | self::LIGHT_GREEN => '1;32', |
||
775 | self::CYAN => '0;36', |
||
776 | self::LIGHT_CYAN => '1;36', |
||
777 | self::RED => '0;31', |
||
778 | self::LIGHT_RED => '1;31', |
||
779 | self::PURPLE => '0;35', |
||
780 | self::LIGHT_PURPLE => '1;35', |
||
781 | self::BROWN => '0;33', |
||
782 | self::YELLOW => '1;33', |
||
783 | self::MAGENTA => '0;35', |
||
784 | self::LIGHT_GRAY => '0;37', |
||
785 | self::WHITE => '1;37', |
||
786 | ], |
||
787 | 'back' => [ |
||
788 | self::DEFAULT => '49', |
||
789 | self::BLACK => '40', |
||
790 | self::RED => '41', |
||
791 | self::GREEN => '42', |
||
792 | self::YELLOW => '43', |
||
793 | self::BLUE => '44', |
||
794 | self::MAGENTA => '45', |
||
795 | self::CYAN => '46', |
||
796 | self::LIGHT_GRAY => '47', |
||
797 | ], |
||
798 | self::BOLD => [], |
||
799 | ]; |
||
800 | |||
801 | /** |
||
802 | * @param mixed $resource |
||
803 | * @param string $level |
||
804 | * @param bool $useLocking |
||
805 | * @param bool $gzipFile |
||
806 | * @param bool $addDate |
||
807 | */ |
||
808 | public function __construct($resource=STDOUT, string $level=self::INFO, bool $useLocking=false, bool $gzipFile=false, bool $addDate=true) |
||
809 | { |
||
810 | $this->resource = $resource; |
||
811 | $this->setLogLevel($level); |
||
812 | $this->useLocking = $useLocking; |
||
813 | $this->gzipFile = $gzipFile; |
||
814 | $this->addDate = $addDate; |
||
815 | } |
||
816 | |||
817 | /** |
||
818 | * System is unusable. |
||
819 | * |
||
820 | * @param string $message |
||
821 | * @param array $context |
||
822 | */ |
||
823 | public function emergency(string $message, array $context=[]) |
||
824 | { |
||
825 | $this->log(self::EMERGENCY, $message, $context); |
||
826 | } |
||
827 | |||
828 | /** |
||
829 | * Action must be taken immediately. |
||
830 | * |
||
831 | * Example: Entire website down, database unavailable, etc. This should |
||
832 | * trigger the SMS alerts and wake you up. |
||
833 | * |
||
834 | * @param string $message |
||
835 | * @param array $context |
||
836 | */ |
||
837 | public function alert(string $message, array $context=[]) |
||
838 | { |
||
839 | $this->log(self::ALERT, $message, $context); |
||
840 | } |
||
841 | |||
842 | /** |
||
843 | * Critical conditions. |
||
844 | * |
||
845 | * Example: Application component unavailable, unexpected exception. |
||
846 | * |
||
847 | * @param string $message |
||
848 | * @param array $context |
||
849 | */ |
||
850 | public function critical(string $message, array $context=[]) |
||
851 | { |
||
852 | $this->log(self::CRITICAL, $message, $context); |
||
853 | } |
||
854 | |||
855 | /** |
||
856 | * Runtime errors that do not require immediate action but should typically |
||
857 | * be logged and monitored. |
||
858 | * |
||
859 | * @param string $message |
||
860 | * @param array $context |
||
861 | */ |
||
862 | public function error(string $message, array $context=[]) |
||
863 | { |
||
864 | $this->log(self::ERROR, $message, $context); |
||
865 | } |
||
866 | |||
867 | /** |
||
868 | * Exceptional occurrences that are not errors. |
||
869 | * |
||
870 | * Example: Use of deprecated APIs, poor use of an API, undesirable things |
||
871 | * that are not necessarily wrong. |
||
872 | * |
||
873 | * @param string $message |
||
874 | * @param array $context |
||
875 | */ |
||
876 | public function warning(string $message, array $context=[]) |
||
877 | { |
||
878 | $this->log(self::WARNING, $message, $context); |
||
879 | } |
||
880 | |||
881 | /** |
||
882 | * Normal but significant events. |
||
883 | * |
||
884 | * @param string $message |
||
885 | * @param array $context |
||
886 | */ |
||
887 | public function notice(string $message, array $context=[]) |
||
888 | { |
||
889 | $this->log(self::NOTICE, $message, $context); |
||
890 | } |
||
891 | |||
892 | /** |
||
893 | * Interesting events. |
||
894 | * |
||
895 | * Example: User logs in, SQL logs. |
||
896 | * |
||
897 | * @param string $message |
||
898 | * @param array $context |
||
899 | */ |
||
900 | public function info(string $message, array $context=[]) |
||
901 | { |
||
902 | $this->log(self::INFO, $message, $context); |
||
903 | } |
||
904 | |||
905 | /** |
||
906 | * Detailed debug information. |
||
907 | * |
||
908 | * @param string $message |
||
909 | * @param array $context |
||
910 | */ |
||
911 | public function debug(string $message, array $context=[]) |
||
912 | { |
||
913 | $this->log(self::DEBUG, $message, $context); |
||
914 | } |
||
915 | |||
916 | /** |
||
917 | * @param $resource |
||
918 | * @return Logger |
||
919 | */ |
||
920 | public function setLogFile($resource) : Logger |
||
921 | { |
||
922 | $this->resource = $resource; |
||
923 | |||
924 | return $this; |
||
925 | } |
||
926 | |||
927 | /** |
||
928 | * @param string $string |
||
929 | * @param string $foregroundColor |
||
930 | * @param string $backgroundColor |
||
931 | * @param bool $bold |
||
932 | * @return string |
||
933 | */ |
||
934 | public static function addColour(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string |
||
935 | { |
||
936 | // todo: support bold |
||
937 | unset($bold); |
||
938 | $coloredString = ''; |
||
939 | // Check if given foreground color found |
||
940 | View Code Duplication | if ( isset(static::$colours['fore'][$foregroundColor]) ) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
941 | { |
||
942 | $coloredString .= "\033[" . static::$colours['fore'][$foregroundColor] . "m"; |
||
943 | } |
||
944 | // Check if given background color found |
||
945 | View Code Duplication | if ( isset(static::$colours['back'][$backgroundColor]) ) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
946 | { |
||
947 | $coloredString .= "\033[" . static::$colours['back'][$backgroundColor] . "m"; |
||
948 | } |
||
949 | // Add string and end coloring |
||
950 | $coloredString .= $string . "\033[0m"; |
||
951 | |||
952 | return $coloredString; |
||
953 | } |
||
954 | |||
955 | /** |
||
956 | * @param string $string |
||
957 | * @param string $foregroundColor |
||
958 | * @param string $backgroundColor |
||
959 | * @param bool $bold |
||
960 | * @return string |
||
961 | */ |
||
962 | public function colourize(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string |
||
963 | { |
||
964 | return static::addColour($string, $foregroundColor, $backgroundColor, $bold); |
||
965 | } |
||
966 | |||
967 | /** |
||
968 | * @param string $level Ignore logging attempts at a level less the $level |
||
969 | * @return Logger |
||
970 | */ |
||
971 | public function setLogLevel(string $level) : Logger |
||
972 | { |
||
973 | if ( ! isset(static::$logLevels[$level]) ) |
||
974 | { |
||
975 | throw new \InvalidArgumentException("Log level is invalid"); |
||
976 | } |
||
977 | $this->level = static::$logLevels[$level][0]; |
||
978 | |||
979 | return $this; |
||
980 | } |
||
981 | |||
982 | /** |
||
983 | * @return Logger |
||
984 | */ |
||
985 | public function lock() : Logger |
||
986 | { |
||
987 | $this->useLocking = true; |
||
988 | |||
989 | return $this; |
||
990 | } |
||
991 | |||
992 | /** |
||
993 | * @return Logger |
||
994 | */ |
||
995 | public function gzipped() : Logger |
||
996 | { |
||
997 | $this->gzipFile = true; |
||
998 | |||
999 | return $this; |
||
1000 | } |
||
1001 | |||
1002 | /** |
||
1003 | * @param callable $fnFormatter |
||
1004 | * |
||
1005 | * @return Logger |
||
1006 | */ |
||
1007 | public function formatter(callable $fnFormatter) : Logger |
||
1008 | { |
||
1009 | $this->formatter = $fnFormatter; |
||
0 ignored issues
–
show
It seems like
$fnFormatter of type callable is incompatible with the declared type object<Closure> of property $formatter .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
1010 | |||
1011 | return $this; |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * Log messages to resource |
||
1016 | * |
||
1017 | * @param mixed $level The level of the log message |
||
1018 | * @param string $message If an object is passed it must implement __toString() |
||
1019 | * @param array $context Placeholders to be substituted in the message |
||
1020 | */ |
||
1021 | public function log($level, $message, array $context=[]) |
||
1022 | { |
||
1023 | $level = isset(static::$logLevels[$level]) ? $level : self::INFO; |
||
1024 | list($logLevel, $fore, $back, $style) = static::$logLevels[$level]; |
||
1025 | unset($style); |
||
1026 | if ( $logLevel > $this->level ) |
||
1027 | { |
||
1028 | return ; |
||
1029 | } |
||
1030 | if ( is_callable($this->formatter) ) |
||
1031 | { |
||
1032 | $message = $this->formatter->__invoke(static::$logLevels[$level][4], $message, $context); |
||
1033 | } |
||
1034 | else |
||
1035 | { |
||
1036 | $message = $this->formatMessage($level, $message, $context); |
||
1037 | } |
||
1038 | $this->lastLogEntry = $message; |
||
1039 | $this->write($this->colourize($message, $fore, $back) . PHP_EOL); |
||
1040 | } |
||
1041 | |||
1042 | /** |
||
1043 | * @param string $style |
||
1044 | * @param string $message |
||
1045 | * @return string |
||
1046 | */ |
||
1047 | public static function style(string $style, string $message) : string |
||
1048 | { |
||
1049 | $style = isset(static::$logLevels[$style]) ? $style : self::INFO; |
||
1050 | list($logLevel, $fore, $back, $style) = static::$logLevels[$style]; |
||
1051 | unset($logLevel, $style); |
||
1052 | |||
1053 | return static::addColour($message, $fore, $back); |
||
1054 | } |
||
1055 | |||
1056 | /** |
||
1057 | * @param string $level |
||
1058 | * @param string $message |
||
1059 | * @param array $context |
||
1060 | * @return string |
||
1061 | */ |
||
1062 | protected function formatMessage(string $level, string $message, array $context=[]) : string |
||
1063 | { |
||
1064 | # Handle objects implementing __toString |
||
1065 | $message = (string) $message; |
||
1066 | $message .= empty($context) ? '' : PHP_EOL . json_encode($context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||
1067 | $data = $this->addDate ? ['date' => date('Y-m-d H:i:s')] : []; |
||
1068 | $data['level'] = strtoupper(str_pad(static::$logLevels[$level][4], 5, ' ', STR_PAD_RIGHT)); |
||
1069 | $data['message'] = $message; |
||
1070 | |||
1071 | return implode($this->separator, $data); |
||
1072 | } |
||
1073 | |||
1074 | /** |
||
1075 | * Write the content to the stream |
||
1076 | * |
||
1077 | * @param string $content |
||
1078 | */ |
||
1079 | public function write(string $content) |
||
1080 | { |
||
1081 | $resource = $this->getResource(); |
||
1082 | if ( $this->useLocking ) |
||
1083 | { |
||
1084 | flock($resource, LOCK_EX); |
||
1085 | } |
||
1086 | gzwrite($resource, $content); |
||
1087 | if ( $this->useLocking ) |
||
1088 | { |
||
1089 | flock($resource, LOCK_UN); |
||
1090 | } |
||
1091 | } |
||
1092 | |||
1093 | /** |
||
1094 | * @return mixed|resource |
||
1095 | * @throws \Exception |
||
1096 | */ |
||
1097 | protected function getResource() |
||
1098 | { |
||
1099 | if ( is_resource($this->resource) ) |
||
1100 | { |
||
1101 | return $this->resource; |
||
1102 | } |
||
1103 | $fileName = $this->resource; |
||
1104 | $this->closeLocally = true; |
||
1105 | $this->resource = $this->openResource(); |
||
1106 | if ( ! is_resource($this->resource) ) |
||
1107 | { |
||
1108 | throw new \Exception("The resource ({$fileName}) could not be opened"); |
||
1109 | } |
||
1110 | |||
1111 | return $this->resource; |
||
1112 | } |
||
1113 | |||
1114 | /** |
||
1115 | * @return string |
||
1116 | */ |
||
1117 | public function getLastLogEntry() : string |
||
1118 | { |
||
1119 | return $this->lastLogEntry; |
||
1120 | } |
||
1121 | |||
1122 | /** |
||
1123 | * @return resource |
||
1124 | */ |
||
1125 | protected function openResource() |
||
1126 | { |
||
1127 | if ( $this->gzipFile ) |
||
1128 | { |
||
1129 | return gzopen($this->resource, 'a'); |
||
1130 | } |
||
1131 | |||
1132 | return fopen($this->resource, 'a'); |
||
1133 | } |
||
1134 | |||
1135 | public function __destruct() |
||
1136 | { |
||
1137 | if ($this->closeLocally) |
||
1138 | { |
||
1139 | gzclose($this->getResource()); |
||
1140 | } |
||
1141 | } |
||
1142 | } |
||
1143 |