Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
CompositeErrorReporter
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 report
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 add
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of fast-forward/defer.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/defer
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Defer\ErrorReporter;
20
21use Countable;
22use Throwable;
23use FastForward\Defer\ErrorReporterInterface;
24
25/**
26 * Aggregates multiple error reporters and delegates error reporting to all of them.
27 * This class MUST be used when you want to ensure that all registered reporters are notified of an error.
28 * If any reporter throws an exception, this class SHALL log the failure using error_log and MUST continue reporting to the remaining reporters.
29 */
30final class CompositeErrorReporter implements ErrorReporterInterface, Countable
31{
32    /**
33     * List of error reporters. This property MUST contain only valid ErrorReporterInterface implementations.
34     *
35     * @var ErrorReporterInterface[]
36     */
37    private array $reporters;
38
39    /**
40     * Constructs a new CompositeErrorReporter instance.
41     *
42     * This constructor SHALL accept any number of ErrorReporterInterface implementations.
43     * The reporters MUST be stored in the order provided.
44     *
45     * @param ErrorReporterInterface ...$reporters One or more error reporters to aggregate.
46     */
47    public function __construct(ErrorReporterInterface ...$reporters)
48    {
49        $this->reporters = $reporters;
50    }
51
52    /**
53     * Reports a throwable to all registered reporters.
54     *
55     * This method MUST attempt to report the throwable to each reporter in sequence.
56     * If a reporter throws an exception, the failure MUST be logged using error_log,
57     * and reporting SHALL continue for the remaining reporters.
58     *
59     * @param Throwable $throwable the exception or error to report
60     * @param callable|null $callback the related callback, if available
61     * @param array $args arguments passed to the callback, if any
62     *
63     * @return void
64     */
65    public function report(Throwable $throwable, ?callable $callback = null, array $args = []): void
66    {
67        foreach ($this->reporters as $reporter) {
68            try {
69                $reporter->report($throwable, $callback, $args);
70            } catch (Throwable $reportingFailure) {
71                error_log(
72                    \sprintf(
73                        '[%s] Composite reporter failed: %s: %s in %s:%d',
74                        self::class,
75                        $reportingFailure::class,
76                        $reportingFailure->getMessage(),
77                        $reportingFailure->getFile(),
78                        $reportingFailure->getLine()
79                    )
80                );
81            }
82        }
83    }
84
85    /**
86     * Adds a new reporter to the composite.
87     *
88     * This method MUST append the reporter to the internal list. The reporter MUST implement ErrorReporterInterface.
89     *
90     * @param ErrorReporterInterface $reporter the reporter to add
91     *
92     * @return self returns the current instance for chaining
93     */
94    public function add(ErrorReporterInterface $reporter): self
95    {
96        $this->reporters[] = $reporter;
97
98        return $this;
99    }
100
101    /**
102     * Determines if the composite contains no reporters.
103     *
104     * This method MUST return true if no reporters are registered, and false otherwise.
105     *
106     * @return bool true if no reporters are registered; otherwise, false
107     */
108    public function isEmpty(): bool
109    {
110        return [] === $this->reporters;
111    }
112
113    /**
114     * Returns the number of registered reporters.
115     *
116     * This method MUST return the count of reporters currently registered in the composite.
117     *
118     * @return int the number of reporters
119     */
120    public function count(): int
121    {
122        return \count($this->reporters);
123    }
124}