Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Stream
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
7 / 7
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 stdout
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stderr
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFormatter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeLog
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 __destruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Log\Writer;
16
17use Pop\Log\Formatter;
18
19/**
20 * Stream log writer class
21 *
22 * @category   Pop
23 * @package    Pop\Log
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    5.0.0
28 */
29class Stream extends AbstractWriter
30{
31
32    /**
33     * The underlying stream resource
34     * @var mixed
35     */
36    protected mixed $stream = null;
37
38    /**
39     * Whether this instance opened (and therefore owns/closes) the stream
40     * @var bool
41     */
42    protected bool $ownsStream = false;
43
44    /**
45     * Formatter used to shape each log entry
46     * @var Formatter\FormatterInterface
47     */
48    protected Formatter\FormatterInterface $formatter;
49
50    /**
51     * Constructor
52     *
53     * Instantiate the stream writer object. Accepts either a stream URL string (opened here with
54     * fopen($stream, 'a') and closed automatically when this instance is destroyed) or an already-open
55     * stream resource (e.g. the STDOUT/STDERR constants, or a caller-supplied fopen() handle) — in that
56     * case, the caller keeps ownership and this writer never closes it.
57     *
58     * @param  mixed                         $stream
59     * @param  ?Formatter\FormatterInterface $formatter
60     * @throws Exception
61     */
62    public function __construct(mixed $stream, ?Formatter\FormatterInterface $formatter = null)
63    {
64        if (is_string($stream)) {
65            $handle = @fopen($stream, 'a');
66            if ($handle === false) {
67                throw new Exception('Unable to open stream: ' . $stream);
68            }
69            $this->stream     = $handle;
70            $this->ownsStream = true;
71        } elseif (is_resource($stream) && get_resource_type($stream) === 'stream') {
72            $this->stream = $stream;
73        } else {
74            throw new Exception('Stream must be a resource or a valid stream URL string.');
75        }
76
77        $this->formatter = $formatter ?? new Formatter\NdJson();
78    }
79
80    /**
81     * Create a writer targeting php://stdout
82     *
83     * @param  ?Formatter\FormatterInterface $formatter
84     * @return static
85     */
86    public static function stdout(?Formatter\FormatterInterface $formatter = null): static
87    {
88        return new static('php://stdout', $formatter);
89    }
90
91    /**
92     * Create a writer targeting php://stderr
93     *
94     * @param  ?Formatter\FormatterInterface $formatter
95     * @return static
96     */
97    public static function stderr(?Formatter\FormatterInterface $formatter = null): static
98    {
99        return new static('php://stderr', $formatter);
100    }
101
102    /**
103     * Get the underlying stream resource
104     *
105     * @return mixed
106     */
107    public function getStream(): mixed
108    {
109        return $this->stream;
110    }
111
112    /**
113     * Get the formatter
114     *
115     * @return Formatter\FormatterInterface
116     */
117    public function getFormatter(): Formatter\FormatterInterface
118    {
119        return $this->formatter;
120    }
121
122    /**
123     * Write to the log
124     *
125     * @param  string $level
126     * @param  string $message
127     * @param  array  $context
128     * @throws Exception
129     * @return Stream
130     */
131    public function writeLog(string $level, string $message, array $context = []): Stream
132    {
133        if ($this->isWithinLogLimit($level)) {
134            if (!is_resource($this->stream)) {
135                throw new Exception('Stream is no longer open.');
136            }
137            $entry = $this->formatter->format($level, $message, $context) . PHP_EOL;
138            if (@fwrite($this->stream, $entry) === false) {
139                throw new Exception('Unable to write to stream.');
140            }
141        }
142
143        return $this;
144    }
145
146    /**
147     * Destructor
148     *
149     * Closes the underlying stream only if this instance opened it itself (i.e. was constructed from a
150     * string). A caller-supplied resource (including STDOUT/STDERR) is never closed here.
151     */
152    public function __destruct()
153    {
154        if ($this->ownsStream && is_resource($this->stream)) {
155            fclose($this->stream);
156        }
157    }
158
159}