Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
15 / 16
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Mail
93.75% covered (success)
93.75%
15 / 16
75.00% covered (warning)
75.00%
3 / 4
14.05
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 transports
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
7.10
 list
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 string
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Config;
6
7use Cosray\Exception\RuntimeException;
8
9final class Mail
10{
11    /** @var array<non-empty-string, non-empty-string>|null */
12    private ?array $transportsCache = null;
13
14    public function __construct(
15        private readonly \Cosray\Config $config,
16    ) {}
17
18    /**
19     * Symfony Mailer DSNs by name; the first one is the default.
20     *
21     * @var array<non-empty-string, non-empty-string>
22     */
23    public array $transports {
24        get => $this->transportsCache ??= self::transports($this->config->get('mail.transports'));
25    }
26
27    /** The sender of mails that name none, like `Example <noreply@example.com>`. */
28    public ?string $from {
29        get => self::string($this->config->get('mail.from'));
30    }
31
32    /**
33     * Addresses that receive every mail instead of its recipients, for
34     * staging and development.
35     *
36     * @var list<non-empty-string>
37     */
38    public array $redirect {
39        get => self::list($this->config->get('mail.redirect'));
40    }
41
42    /** @var list<non-empty-string> template directories, searched in order */
43    public array $views {
44        get => self::list($this->config->get('mail.views'))
45            ?: [
46                rtrim($this->config->path->root, '/') . '/' . trim($this->config->path->views, '/') . '/mail',
47            ];
48    }
49
50    /** @return array<non-empty-string, non-empty-string> */
51    private static function transports(mixed $transports): array
52    {
53        if (!is_array($transports)) {
54            throw new RuntimeException('`mail.transports` takes DSNs by transport name.');
55        }
56
57        $valid = [];
58
59        foreach ($transports as $name => $dsn) {
60            if (!is_string($name) || $name === '' || !is_string($dsn) || $dsn === '') {
61                throw new RuntimeException('`mail.transports` takes DSNs by transport name.');
62            }
63
64            $valid[$name] = $dsn;
65        }
66
67        return $valid;
68    }
69
70    /** @return list<non-empty-string> */
71    private static function list(mixed $value): array
72    {
73        $values = is_string($value) ? explode(',', $value) : (array) $value;
74
75        return array_values(array_filter(
76            array_map(static fn(mixed $item): string => is_string($item) ? trim($item) : '', $values),
77            static fn(string $item): bool => $item !== '',
78        ));
79    }
80
81    /** @return ?non-empty-string */
82    private static function string(mixed $value): ?string
83    {
84        $value = is_string($value) ? trim($value) : '';
85
86        return $value === '' ? null : $value;
87    }
88}