1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-log for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-log/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-log/blob/master/LICENSE.md New BSD License
7 */
8namespace Laminas\Log\Writer;
9
10use Laminas\Log\Exception;
11use Laminas\Log\Filter\FilterInterface;
12use Laminas\Log\Filter\Priority as PriorityFilter;
13use Laminas\Log\Formatter\FormatterInterface;
14use Laminas\Log\Logger;
15use Laminas\Log\WriterPluginManager;
16use Laminas\ServiceManager\ServiceManager;
17use Laminas\Stdlib\ArrayUtils;
18use Traversable;
19
20/**
21 * Buffers all events until the strategy determines to flush them.
22 *
23 * @see        http://packages.python.org/Logbook/api/handlers.html#logbook.FingersCrossedHandler
24 */
25class FingersCrossed extends AbstractWriter
26{
27    /**
28     * The wrapped writer
29     *
30     * @var WriterInterface
31     */
32    protected $writer;
33
34    /**
35     * Writer plugins
36     *
37     * @var WriterPluginManager
38     */
39    protected $writerPlugins;
40
41    /**
42     * Flag if buffering is enabled
43     *
44     * @var bool
45     */
46    protected $buffering = true;
47
48    /**
49     * Oldest entries are removed from the buffer if bufferSize is reached.
50     * 0 is infinte buffer size.
51     *
52     * @var int
53     */
54    protected $bufferSize;
55
56    /**
57     * array of log events
58     *
59     * @var array
60     */
61    protected $buffer = [];
62
63    /**
64     * Constructor
65     *
66     * @param WriterInterface|string|array|Traversable $writer Wrapped writer or array of configuration options
67     * @param FilterInterface|int $filterOrPriority Filter or log priority which determines buffering of events
68     * @param int $bufferSize Maximum buffer size
69     */
70    public function __construct($writer, $filterOrPriority = null, $bufferSize = 0)
71    {
72        $this->writer = $writer;
73
74        if ($writer instanceof Traversable) {
75            $writer = ArrayUtils::iteratorToArray($writer);
76        }
77
78        if (is_array($writer)) {
79            $filterOrPriority = isset($writer['priority']) ? $writer['priority'] : null;
80            $bufferSize       = isset($writer['bufferSize']) ? $writer['bufferSize'] : null;
81            $writer           = isset($writer['writer']) ? $writer['writer'] : null;
82        }
83
84        if (null === $filterOrPriority) {
85            $filterOrPriority = new PriorityFilter(Logger::WARN);
86        } elseif (! $filterOrPriority instanceof FilterInterface) {
87            $filterOrPriority = new PriorityFilter($filterOrPriority);
88        }
89
90        if (is_array($writer) && isset($writer['name'])) {
91            $this->setWriter($writer['name'], $writer['options']);
92        } else {
93            $this->setWriter($writer);
94        }
95        $this->addFilter($filterOrPriority);
96        $this->bufferSize = $bufferSize;
97    }
98
99    /**
100     * Set a new writer
101     *
102     * @param  string|WriterInterface $writer
103     * @param  array|null $options
104     * @return self
105     * @throws Exception\InvalidArgumentException
106     */
107    public function setWriter($writer, array $options = null)
108    {
109        if (is_string($writer)) {
110            $writer = $this->writerPlugin($writer, $options);
111        }
112
113        if (! $writer instanceof WriterInterface) {
114            throw new Exception\InvalidArgumentException(sprintf(
115                'Writer must implement %s\WriterInterface; received "%s"',
116                __NAMESPACE__,
117                is_object($writer) ? get_class($writer) : gettype($writer)
118            ));
119        }
120
121        $this->writer = $writer;
122        return $this;
123    }
124
125    /**
126     * Get writer plugin manager
127     *
128     * @return WriterPluginManager
129     */
130    public function getWriterPluginManager()
131    {
132        if (null === $this->writerPlugins) {
133            $this->setWriterPluginManager(new WriterPluginManager(new ServiceManager()));
134        }
135        return $this->writerPlugins;
136    }
137
138    /**
139     * Set writer plugin manager
140     *
141     * @param  string|WriterPluginManager $plugins
142     * @return FingersCrossed
143     * @throws Exception\InvalidArgumentException
144     */
145    public function setWriterPluginManager($plugins)
146    {
147        if (is_string($plugins)) {
148            $plugins = new $plugins;
149        }
150        if (! $plugins instanceof WriterPluginManager) {
151            throw new Exception\InvalidArgumentException(sprintf(
152                'Writer plugin manager must extend %s\WriterPluginManager; received %s',
153                __NAMESPACE__,
154                is_object($plugins) ? get_class($plugins) : gettype($plugins)
155            ));
156        }
157
158        $this->writerPlugins = $plugins;
159        return $this;
160    }
161
162    /**
163     * Get writer instance
164     *
165     * @param string $name
166     * @param array|null $options
167     * @return WriterInterface
168     */
169    public function writerPlugin($name, array $options = null)
170    {
171        return $this->getWriterPluginManager()->get($name, $options);
172    }
173
174    /**
175     * Log a message to this writer.
176     *
177     * @param array $event log data event
178     * @return void
179     */
180    public function write(array $event)
181    {
182        $this->doWrite($event);
183    }
184
185    /**
186     * Check if buffered data should be flushed
187     *
188     * @param array $event event data
189     * @return bool true if buffered data should be flushed
190     */
191    protected function isActivated(array $event)
192    {
193        foreach ($this->filters as $filter) {
194            if (! $filter->filter($event)) {
195                return false;
196            }
197        }
198        return true;
199    }
200
201    /**
202     * Write message to buffer or delegate event data to the wrapped writer
203     *
204     * @param array $event event data
205     * @return void
206     */
207    protected function doWrite(array $event)
208    {
209        if (! $this->buffering) {
210            $this->writer->write($event);
211            return;
212        }
213
214        $this->buffer[] = $event;
215
216        if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
217            array_shift($this->buffer);
218        }
219
220        if (! $this->isActivated($event)) {
221            return;
222        }
223
224        $this->buffering = false;
225
226        foreach ($this->buffer as $bufferedEvent) {
227            $this->writer->write($bufferedEvent);
228        }
229    }
230
231    /**
232     * Resets the state of the handler.
233     * Stops forwarding records to the wrapped writer
234     */
235    public function reset()
236    {
237        $this->buffering = true;
238    }
239
240    /**
241     * Stub in accordance to parent method signature.
242     * Fomatters must be set on the wrapped writer.
243     *
244     * @param string|FormatterInterface $formatter
245     * @param array|null $options (unused)
246     * @return WriterInterface
247     */
248    public function setFormatter($formatter, array $options = null)
249    {
250        return $this->writer;
251    }
252
253    /**
254     * Record shutdown
255     *
256     * @return void
257     */
258    public function shutdown()
259    {
260        $this->writer->shutdown();
261        $this->buffer = null;
262    }
263}
264