Completed
Push — master ( b780b9...dbf744 )
by Terry
02:21
created

Test::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 5
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
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 $suiteName
30
     * @return Suite
31
     */
32
    public static function suite(string $suiteName='') : Suite
33
    {
34
        $suiteName                  = $suiteName ?: static::$currentSuite;
35
        static::$suites[$suiteName] = new Suite();
36
37
        return static::$suites[$suiteName];
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
     * @return Suite
48
     * @throws AssertionFailedException
49
     */
50
    public static function test(string $testName, Closure $test, string $suiteName='', string $successMessage='', int $exceptionCode=0, string $exceptionClass='') : Suite
51
    {
52
        Assert::that($successMessage)->notEmpty();
53
        Assert::that($test)->isCallable();
54
        Assert::that($suiteName)->notEmpty();
55
56
        return static::suite($suiteName)->test($testName, $test, $successMessage, $exceptionCode, $exceptionClass);
57
    }
58
59
    /**
60
     * @param string $suiteName
61
     * @param string $testName
62
     */
63
    public static function run(string $suiteName='', string $testName='')
64
    {
65
        $suites         = static::$suites;
66
        if ( ! empty($suiteName) )
67
        {
68
            Assert::that($suites)->keyExists($suiteName, "The test suite ({$suiteName}) has not been loaded");
69
            $suites         = [$suites[$suiteName]];
70
        }
71
        foreach ( $suites as $suite )
72
        {
73
            $suite->run($testName);
74
        }
75
    }
76
77
    /**
78
     * @return Logger
79
     */
80
    public static function getLogger() : Logger
81
    {
82
        if ( ! static::$logger )
83
        {
84
            static::$logger = new Logger();
85
        }
86
87
        return static::$logger;
88
    }
89
90
    /**
91
     * @param string $suiteName
92
     * @return Suite
93
     */
94
    protected static function getSuite(string $suiteName='') : Suite
95
    {
96
        $suiteName                  = $suiteName ?: static::$currentSuite;
97
        if ( ! array_key_exists($suiteName, static::$suites) )
98
        {
99
            return static::suite($suiteName);
100
        }
101
102
        return static::$suites[$suiteName];
103
    }
104
}
105
106
107
class Suite
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
108
{
109
    /** @var Test[] */
110
    protected $tests        = [];
111
112
    /** @var mixed[] */
113
    protected $fixtures     = [];
114
115
    /** @var Logger */
116
    protected $logger       = null;
117
118
    /**
119
     * @param string $filter
120
     */
121
    public function run(string $filter='')
122
    {
123
        foreach ( $this->tests as $test => $testCase )
124
        {
125
            $testName   = $testCase->getTestName();
126
            if ( $filter && $test !== $filter )
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $test (integer) and $filter (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
127
            {
128
                continue;
129
            }
130
            try
131
            {
132
                $this->getLogger()->info("[{$testName}] - Starting...");
133
                $testCase->runTest($this);
134
                $this->getLogger()->info("[{$testName}] - " . $testCase->getSuccessMessage());
135
            }
136
            catch ( \Exception $e )
137
            {
138
                $expectedCode       = $testCase->getExceptionCode();
139
                $expectedClass      = $testCase->getExceptionType();
140
                $code               = $e->getCode();
141
                $exception          = get_class($e);
142
                if ( ! $expectedClass &&  ! $expectedCode )
143
                {
144
                    $this->getLogger()->error($e->getMessage(), [compact('testName'), $e]);
145
146
                    continue;
147
                }
148
                if ( $expectedCode && $expectedCode !== $code )
149
                {
150
                    $this->getLogger()->error("Exception code({$code}) was expected to be ({$expectedCode})", [compact('testName'), $e]);
151
152
                    continue;
153
                }
154
                if ( $expectedClass && $expectedClass !== $exception )
155
                {
156
                    $this->getLogger()->error("Exception class({$exception}) was expected to be ({$expectedClass})", [compact('testName'), $e]);
157
158
                    continue;
159
                }
160
                $this->getLogger()->info("[{$test}] - " . $testCase->getSuccessMessage());
161
            }
162
        }
163
    }
164
165
    /**
166
     * @param string   $testName
167
     * @param Closure  $test
168
     * @param string   $successMessage
169
     * @param int|null $exceptionCode
170
     * @param string   $exceptionClass
171
     * @return Suite
172
     * @throws AssertionFailedException
173
     */
174
    public function test(string $testName, Closure $test, string $successMessage='', int $exceptionCode=0, string $exceptionClass='') : Suite
175
    {
176
        $this->tests[]  = new Test($testName, $test, $successMessage, $exceptionCode, $exceptionClass);
177
178
        return $this;
179
    }
180
181
    /**
182
     * @param string $fixtureName
183
     * @param        $value
184
     * @return Suite
185
     */
186
    public function fixture(string $fixtureName, $value) : Suite
187
    {
188
        $this->fixtures[$fixtureName]  = $value;
189
190
        return $this;
191
    }
192
193
    /**
194
     * @param string $fixtureName
195
     * @return mixed
196
     * @throws AssertionFailedException
197
     */
198
    public function getFixture(string $fixtureName)
199
    {
200
        Assert::that($this->fixtures)->keyExists($fixtureName, "The fixture ({$fixtureName}) does not exist.");
201
202
        return $this->fixtures[$fixtureName];
203
    }
204
205
206
    /**
207
     * @param Logger $logger
208
     * @return $this
209
     */
210
    public function setLogger(Logger $logger) : Suite
211
    {
212
        $this->logger = $logger;
213
214
        return $this;
215
    }
216
217
    /**
218
     * @return Logger
219
     */
220
    public function getLogger() : Logger
221
    {
222
        if ( ! $this->logger )
223
        {
224
            $this->logger = new Logger();
225
        }
226
227
        return $this->logger;
228
    }
229
}
230
231
class Test
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
232
{
233
    /** @var string  */
234
    public $testName        = '';
235
236
    /** @var string  */
237
    public $successMessage  = '';
238
239
    /** @var Closure  */
240
    public $test            = null;
241
242
    /** @var string */
243
    public $exceptionType   = null;
244
245
    /** @var int */
246
    public $exceptionCode   = null;
247
248
    /**
249
     * Test constructor.
250
     *
251
     * @param string   $testName
252
     * @param Closure  $test
253
     * @param string   $successMessage
254
     * @param int|null $exceptionCode
255
     * @param string   $exceptionClass
256
     * @throws AssertionFailedException
257
     */
258
    public function __construct(string $testName, Closure $test, string $successMessage='', int $exceptionCode=0, string $exceptionClass='')
259
    {
260
        $this->setTestName($testName);
261
        $this->setTest($test);
262
        $this->setSuccessMessage($successMessage);
263
        $this->setExceptionCode($exceptionCode);
264
        $this->setExceptionType($exceptionClass);
265
    }
266
267
    /**
268
     * @return string
269
     */
270
    public function getTestName() : string
271
    {
272
        return $this->testName;
273
    }
274
275
    /**
276
     * @param string $testName
277
     * @return Test
278
     */
279
    public function setTestName(string $testName) : Test
280
    {
281
        Assert($testName)->notEmpty();
0 ignored issues
show
Bug introduced by
The method notEmpty cannot be called on Assert($testName) (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
282
283
        $this->testName = $testName;
284
285
        return $this;
286
    }
287
288
    /**
289
     * @return string
290
     */
291
    public function getSuccessMessage() : string
292
    {
293
        if ( ! $this->successMessage )
294
        {
295
            return "Successfully run {$this->testName}";
296
        }
297
298
        return $this->successMessage;
299
    }
300
301
    /**
302
     * @param string $successMessage
303
     * @return Test
304
     * @throws AssertionFailedException
305
     */
306
    public function setSuccessMessage(string $successMessage) : Test
307
    {
308
        $this->successMessage = $successMessage;
309
310
        return $this;
311
    }
312
313
    /**
314
     * @return Closure
315
     */
316
    public function getTest() : Closure
317
    {
318
        return $this->test;
319
    }
320
321
    /**
322
     * @param Closure $test
323
     * @return Test
324
     */
325
    public function setTest(Closure $test) : Test
326
    {
327
        $this->test = $test;
328
329
        return $this;
330
    }
331
332
    /**
333
     * @return string
334
     */
335
    public function getExceptionType() : string
336
    {
337
        return $this->exceptionType;
338
    }
339
340
    /**
341
     * @param string $exceptionType
342
     * @return Test
343
     */
344
    public function setExceptionType(string $exceptionType) : Test
345
    {
346
        $this->exceptionType = $exceptionType;
347
348
        return $this;
349
    }
350
351
    /**
352
     * @return int
353
     */
354
    public function getExceptionCode() : int
355
    {
356
        return $this->exceptionCode;
357
    }
358
359
    /**
360
     * @param int $exceptionCode
361
     * @return Test
362
     */
363
    public function setExceptionCode(int $exceptionCode) : Test
364
    {
365
        $this->exceptionCode = $exceptionCode;
366
367
        return $this;
368
    }
369
370
    /**
371
     * @param Suite $suite
372
     * @return mixed
373
     */
374
    public function runTest(Suite $suite)
375
    {
376
        return $this->getTest()->__invoke($suite);
377
    }
378
}
379
380
/**
381
 * Class Logger
382
 *
383
 * @package Terah\Assert
384
 */
385
class Logger
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
386
{
387
    const EMERGENCY     = 'emergency';
388
    const ALERT         = 'alert';
389
    const CRITICAL      = 'critical';
390
    const ERROR         = 'error';
391
    const WARNING       = 'warning';
392
    const NOTICE        = 'notice';
393
    const INFO          = 'info';
394
    const DEBUG         = 'debug';
395
396
    const BLACK         = 'black';
397
    const DARK_GRAY     = 'dark_gray';
398
    const BLUE          = 'blue';
399
    const LIGHT_BLUE    = 'light_blue';
400
    const GREEN         = 'green';
401
    const LIGHT_GREEN   = 'light_green';
402
    const CYAN          = 'cyan';
403
    const LIGHT_CYAN    = 'light_cyan';
404
    const RED           = 'red';
405
    const LIGHT_RED     = 'light_red';
406
    const PURPLE        = 'purple';
407
    const LIGHT_PURPLE  = 'light_purple';
408
    const BROWN         = 'brown';
409
    const YELLOW        = 'yellow';
410
    const MAGENTA       = 'magenta';
411
    const LIGHT_GRAY    = 'light_gray';
412
    const WHITE         = 'white';
413
    const DEFAULT       = 'default';
414
    const BOLD          = 'bold';
415
416
    /**  @var resource $resource The file handle */
417
    protected $resource         = null;
418
419
    /** @var string $level */
420
    protected $level            = self::INFO;
421
422
    /** @var bool $closeLocally */
423
    protected $closeLocally     = false;
424
425
    /** @var bool */
426
    protected $addDate          = true;
427
428
    /** @var string  */
429
    protected $separator        = ' | ';
430
431
    /** @var \Closure */
432
    protected $formatter        = null;
433
434
    /** @var string  */
435
    protected $lastLogEntry     = '';
436
437
    /** @var bool|null  */
438
    protected $gzipFile         = null;
439
440
    /** @var bool  */
441
    protected $useLocking       = false;
442
443
    /**
444
     * @var array $logLevels List of supported levels
445
     */
446
    static protected $logLevels       = [
447
        self::EMERGENCY => [1, self::WHITE,       self::RED,      self::DEFAULT,  'EMERG'],
448
        self::ALERT     => [2, self::WHITE,       self::YELLOW,   self::DEFAULT,  'ALERT'],
449
        self::CRITICAL  => [3, self::RED,         self::DEFAULT,  self::BOLD ,    'CRIT'],
450
        self::ERROR     => [4, self::RED,         self::DEFAULT,  self::DEFAULT,  'ERROR'],
451
        self::WARNING   => [5, self::YELLOW,      self::DEFAULT,  self::DEFAULT,  'WARN'],
452
        self::NOTICE    => [6, self::CYAN,        self::DEFAULT,  self::DEFAULT,  'NOTE'],
453
        self::INFO      => [7, self::GREEN,       self::DEFAULT,  self::DEFAULT,  'INFO'],
454
        self::DEBUG     => [8, self::LIGHT_GRAY,  self::DEFAULT,  self::DEFAULT,  'DEBUG'],
455
    ];
456
457
    /**
458
     * @var array
459
     */
460
    static protected $colours   = [
461
        'fore' => [
462
            self::BLACK         => '0;30',
463
            self::DARK_GRAY     => '1;30',
464
            self::BLUE          => '0;34',
465
            self::LIGHT_BLUE    => '1;34',
466
            self::GREEN         => '0;32',
467
            self::LIGHT_GREEN   => '1;32',
468
            self::CYAN          => '0;36',
469
            self::LIGHT_CYAN    => '1;36',
470
            self::RED           => '0;31',
471
            self::LIGHT_RED     => '1;31',
472
            self::PURPLE        => '0;35',
473
            self::LIGHT_PURPLE  => '1;35',
474
            self::BROWN         => '0;33',
475
            self::YELLOW        => '1;33',
476
            self::MAGENTA       => '0;35',
477
            self::LIGHT_GRAY    => '0;37',
478
            self::WHITE         => '1;37',
479
        ],
480
        'back'  => [
481
            self::DEFAULT       => '49',
482
            self::BLACK         => '40',
483
            self::RED           => '41',
484
            self::GREEN         => '42',
485
            self::YELLOW        => '43',
486
            self::BLUE          => '44',
487
            self::MAGENTA       => '45',
488
            self::CYAN          => '46',
489
            self::LIGHT_GRAY    => '47',
490
        ],
491
        self::BOLD => [],
492
    ];
493
494
    /**
495
     * @param mixed  $resource
496
     * @param string $level
497
     * @param bool   $useLocking
498
     * @param bool   $gzipFile
499
     * @param bool   $addDate
500
     */
501
    public function __construct($resource=STDOUT, string $level=self::INFO, bool $useLocking=false, bool $gzipFile=false, bool $addDate=true)
502
    {
503
        $this->resource     = $resource;
504
        $this->setLogLevel($level);
505
        $this->useLocking   = $useLocking;
506
        $this->gzipFile     = $gzipFile;
507
        $this->addDate      = $addDate;
508
    }
509
510
    /**
511
     * System is unusable.
512
     *
513
     * @param string $message
514
     * @param array $context
515
     */
516
    public function emergency(string $message, array $context=[])
517
    {
518
        $this->log(self::EMERGENCY, $message, $context);
519
    }
520
521
    /**
522
     * Action must be taken immediately.
523
     *
524
     * Example: Entire website down, database unavailable, etc. This should
525
     * trigger the SMS alerts and wake you up.
526
     *
527
     * @param string $message
528
     * @param array $context
529
     */
530
    public function alert(string $message, array $context=[])
531
    {
532
        $this->log(self::ALERT, $message, $context);
533
    }
534
535
    /**
536
     * Critical conditions.
537
     *
538
     * Example: Application component unavailable, unexpected exception.
539
     *
540
     * @param string $message
541
     * @param array $context
542
     */
543
    public function critical(string $message, array $context=[])
544
    {
545
        $this->log(self::CRITICAL, $message, $context);
546
    }
547
548
    /**
549
     * Runtime errors that do not require immediate action but should typically
550
     * be logged and monitored.
551
     *
552
     * @param string $message
553
     * @param array $context
554
     */
555
    public function error(string $message, array $context=[])
556
    {
557
        $this->log(self::ERROR, $message, $context);
558
    }
559
560
    /**
561
     * Exceptional occurrences that are not errors.
562
     *
563
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
564
     * that are not necessarily wrong.
565
     *
566
     * @param string $message
567
     * @param array $context
568
     */
569
    public function warning(string $message, array $context=[])
570
    {
571
        $this->log(self::WARNING, $message, $context);
572
    }
573
574
    /**
575
     * Normal but significant events.
576
     *
577
     * @param string $message
578
     * @param array $context
579
     */
580
    public function notice(string $message, array $context=[])
581
    {
582
        $this->log(self::NOTICE, $message, $context);
583
    }
584
585
    /**
586
     * Interesting events.
587
     *
588
     * Example: User logs in, SQL logs.
589
     *
590
     * @param string $message
591
     * @param array $context
592
     */
593
    public function info(string $message, array $context=[])
594
    {
595
        $this->log(self::INFO, $message, $context);
596
    }
597
598
    /**
599
     * Detailed debug information.
600
     *
601
     * @param string $message
602
     * @param array $context
603
     */
604
    public function debug(string $message, array $context=[])
605
    {
606
        $this->log(self::DEBUG, $message, $context);
607
    }
608
609
    /**
610
     * @param $resource
611
     * @return Logger
612
     */
613
    public function setLogFile($resource) : Logger
614
    {
615
        $this->resource     = $resource;
616
617
        return $this;
618
    }
619
620
    /**
621
     * @param string $string
622
     * @param string $foregroundColor
623
     * @param string $backgroundColor
624
     * @param bool $bold
625
     * @return string
626
     */
627
    public static function addColour(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string
628
    {
629
        // todo: support bold
630
        unset($bold);
631
        $coloredString = '';
632
        // Check if given foreground color found
633 View Code Duplication
        if ( isset(static::$colours['fore'][$foregroundColor]) )
0 ignored issues
show
Duplication introduced by
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.

Loading history...
634
        {
635
            $coloredString .= "\033[" . static::$colours['fore'][$foregroundColor] . "m";
636
        }
637
        // Check if given background color found
638 View Code Duplication
        if ( isset(static::$colours['back'][$backgroundColor]) )
0 ignored issues
show
Duplication introduced by
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.

Loading history...
639
        {
640
            $coloredString .= "\033[" . static::$colours['back'][$backgroundColor] . "m";
641
        }
642
        // Add string and end coloring
643
        $coloredString .=  $string . "\033[0m";
644
645
        return $coloredString;
646
    }
647
648
    /**
649
     * @param string    $string
650
     * @param string    $foregroundColor
651
     * @param string    $backgroundColor
652
     * @param bool      $bold
653
     * @return string
654
     */
655
    public function colourize(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string
656
    {
657
        return static::addColour($string, $foregroundColor, $backgroundColor, $bold);
658
    }
659
660
    /**
661
     * @param string $level Ignore logging attempts at a level less the $level
662
     * @return Logger
663
     */
664
    public function setLogLevel(string $level) : Logger
665
    {
666
        if ( ! isset(static::$logLevels[$level]) )
667
        {
668
            throw new \InvalidArgumentException("Log level is invalid");
669
        }
670
        $this->level = static::$logLevels[$level][0];
671
672
        return $this;
673
    }
674
675
    /**
676
     * @return Logger
677
     */
678
    public function lock() : Logger
679
    {
680
        $this->useLocking = true;
681
682
        return $this;
683
    }
684
685
    /**
686
     * @return Logger
687
     */
688
    public function gzipped() : Logger
689
    {
690
        $this->gzipFile = true;
691
692
        return $this;
693
    }
694
695
    /**
696
     * @param callable $fnFormatter
697
     *
698
     * @return Logger
699
     */
700
    public function formatter(callable $fnFormatter) : Logger
701
    {
702
        $this->formatter = $fnFormatter;
0 ignored issues
show
Documentation Bug introduced by
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..

Loading history...
703
704
        return $this;
705
    }
706
707
    /**
708
     * Log messages to resource
709
     *
710
     * @param mixed          $level    The level of the log message
711
     * @param string|object  $message  If an object is passed it must implement __toString()
712
     * @param array          $context  Placeholders to be substituted in the message
713
     */
714
    public function log($level, $message, array $context=[])
715
    {
716
        $level = isset(static::$logLevels[$level]) ? $level : self::INFO;
717
        list($logLevel, $fore, $back, $style) = static::$logLevels[$level];
718
        unset($style);
719
        if ( $logLevel > $this->level )
720
        {
721
            return ;
722
        }
723
        if ( is_callable($this->formatter) )
724
        {
725
            $message = $this->formatter->__invoke(static::$logLevels[$level][4], $message, $context);
726
        }
727
        else
728
        {
729
            $message = $this->formatMessage($level, $message, $context);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type object; however, Terah\Assert\Logger::formatMessage() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
730
        }
731
        $this->lastLogEntry = $message;
732
        $this->write($this->colourize($message, $fore, $back) . PHP_EOL);
733
    }
734
735
    /**
736
     * @param string $style
737
     * @param string $message
738
     * @return string
739
     */
740
    public static function style(string $style, string $message) : string
741
    {
742
        $style = isset(static::$logLevels[$style]) ? $style : self::INFO;
743
        list($logLevel, $fore, $back, $style) = static::$logLevels[$style];
744
        unset($logLevel, $style);
745
746
        return static::addColour($message, $fore, $back);
747
    }
748
749
    /**
750
     * @param string $level
751
     * @param string $message
752
     * @param array  $context
753
     * @return string
754
     */
755
    protected function formatMessage(string $level, string $message, array $context=[]) : string
756
    {
757
        # Handle objects implementing __toString
758
        $message            = (string) $message;
759
        $message            .= empty($context) ? '' : PHP_EOL . json_encode($context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
760
        $data               = $this->addDate ? ['date' => date('Y-m-d H:i:s')] : [];
761
        $data['level']      = strtoupper(str_pad(static::$logLevels[$level][4], 5, ' ', STR_PAD_RIGHT));
762
        $data['message']    = $message;
763
764
        return implode($this->separator, $data);
765
    }
766
767
    /**
768
     * Write the content to the stream
769
     *
770
     * @param  string $content
771
     */
772
    public function write(string $content)
773
    {
774
        $resource = $this->getResource();
775
        if ( $this->useLocking )
776
        {
777
            flock($resource, LOCK_EX);
778
        }
779
        gzwrite($resource, $content);
780
        if ( $this->useLocking )
781
        {
782
            flock($resource, LOCK_UN);
783
        }
784
    }
785
786
    /**
787
     * @return mixed|resource
788
     * @throws \Exception
789
     */
790
    protected function getResource()
791
    {
792
        if ( is_resource($this->resource) )
793
        {
794
            return $this->resource;
795
        }
796
        $fileName               = $this->resource;
797
        $this->closeLocally     = true;
798
        $this->resource         = $this->openResource();
799
        if ( ! is_resource($this->resource) )
800
        {
801
            throw new \Exception("The resource ({$fileName}) could not be opened");
802
        }
803
804
        return $this->resource;
805
    }
806
807
    /**
808
     * @return string
809
     */
810
    public function getLastLogEntry() : string
811
    {
812
        return $this->lastLogEntry;
813
    }
814
815
    /**
816
     * @return resource
817
     */
818
    protected function openResource()
819
    {
820
        if ( $this->gzipFile )
821
        {
822
            return gzopen($this->resource, 'a');
823
        }
824
825
        return fopen($this->resource, 'a');
826
    }
827
828
    public function __destruct()
829
    {
830
        if ($this->closeLocally)
831
        {
832
            gzclose($this->getResource());
833
        }
834
    }
835
}
836