MailServiceAbstractFactory::createRenderer()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 17
c 1
b 0
f 0
rs 9.2
ccs 6
cts 6
cp 1
cc 4
eloc 10
nc 3
nop 2
crap 4
1
<?php
2
declare(strict_types=1);
3
4
namespace AcMailer\Service\Factory;
5
6
use AcMailer\Attachment\AttachmentParserManager;
7
use AcMailer\Event\MailEvent;
8
use AcMailer\Event\MailListenerInterface;
9
use AcMailer\Exception;
10
use AcMailer\Model\EmailBuilder;
11
use AcMailer\Service\MailService;
12
use AcMailer\View\MailViewRendererFactory;
13
use Interop\Container\ContainerInterface;
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Container\NotFoundExceptionInterface;
16
use Zend\EventManager\EventsCapableInterface;
17
use Zend\EventManager\Exception\InvalidArgumentException;
18
use Zend\EventManager\LazyListenerAggregate;
19
use Zend\Expressive\Template\TemplateRendererInterface;
20
use Zend\Mail\Transport;
21
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
22
use Zend\Stdlib\ArrayUtils;
23
24
class MailServiceAbstractFactory implements AbstractFactoryInterface
25
{
26
    const ACMAILER_PART = 'acmailer';
27
    const MAIL_SERVICE_PART = 'mailservice';
28
    const TRANSPORT_MAP = [
29
        'sendmail' => Transport\Sendmail::class,
30
        'smtp' => Transport\Smtp::class,
31
        'file' => Transport\File::class,
32
        'in_memory' => Transport\InMemory::class,
33
        'null' => Transport\InMemory::class,
34
    ];
35
36
    /**
37
     * Can the factory create an instance for the service?
38
     *
39
     * @param  ContainerInterface $container
40
     * @param  string $requestedName
41
     * @return bool
42
     * @throws ContainerExceptionInterface
43
     */
44
    public function canCreate(ContainerInterface $container, $requestedName): bool
45
    {
46
        $parts = \explode('.', $requestedName);
47
        if (\count($parts) !== 3) {
48
            return false;
49
        }
50 13
51
        if ($parts[0] !== self::ACMAILER_PART || $parts[1] !== static::MAIL_SERVICE_PART) {
52 13
            return false;
53 13
        }
54 13
55 13
        $specificServiceName = $parts[2];
56
        $config = $container->get('config')['acmailer_options']['mail_services'] ?? [];
57
        return \array_key_exists($specificServiceName, $config);
58 13
    }
59 13
60 11
    /**
61 11
     * Create an object
62
     *
63
     * @param  ContainerInterface $container
64 11
     * @param  string $requestedName
65
     * @param  null|array $options
66
     * @return MailService
67 11
     * @throws InvalidArgumentException
68 11
     * @throws Exception\InvalidArgumentException
69 2
     * @throws Exception\ServiceNotCreatedException
70 2
     * @throws ContainerExceptionInterface
71 1
     * @throws NotFoundExceptionInterface
72 1
     */
73 1
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null): MailService
74 1
    {
75 2
        $specificServiceName = \explode('.', $requestedName)[2] ?? null;
76 1
        $mailOptions = $container->get('config')['acmailer_options'] ?? [];
77 2
        $specificMailServiceOptions = $mailOptions['mail_services'][$specificServiceName] ?? null;
78 2
79 9
        if ($specificMailServiceOptions === null) {
80
            throw new Exception\ServiceNotCreatedException(\sprintf(
81
                'Requested MailService with name "%s" could not be found. Make sure you have registered it with name'
82
                . ' "%s" under the acmailer_options.mail_services config entry',
83 11
                $requestedName,
84 11
                $specificServiceName
85
            ));
86
        }
87 11
88 11
        // Recursively extend configuration
89 1
        $specificMailServiceOptions = $this->buildConfig($mailOptions, $specificMailServiceOptions);
90 1
91 1
        // Create the service
92
        $transport = $this->createTransport($container, $specificMailServiceOptions);
93 1
        $renderer = $this->createRenderer($container, $specificMailServiceOptions);
94 1
        $mailService = new MailService(
95
            $transport,
96
            $renderer,
97 1
            $container->get(EmailBuilder::class),
98 1
            $container->get(AttachmentParserManager::class)
99 2
        );
100
101 1
        // Attach mail listeners
102 1
        $this->attachMailListeners($mailService, $container, $specificMailServiceOptions);
103 1
        return $mailService;
104
    }
105
106 11
    private function buildConfig(array $mailOptions, array $specificOptions): array
107 10
    {
108
        if (! isset($specificOptions['extends'])) {
109
            return $specificOptions;
110
        }
111
112
        // Recursively extend
113 13
        $mailServices = $mailOptions['mail_services'];
114
        $processedExtends = [];
115 13
        do {
116
            $serviceToExtend = $specificOptions['extends'] ?? null;
117 13
            // Unset the extends value to allow recursive inheritance
118 13
            unset($specificOptions['extends']);
119 13
120 1
            // Prevent an infinite loop by self inheritance
121 1
            if (\in_array($serviceToExtend, $processedExtends, true)) {
122 13
                throw new Exception\ServiceNotCreatedException(
123 13
                    'It wasn\'t possible to create a mail service due to circular inheritance. Review "extends".'
124 1
                );
125 1
            }
126 13
            $processedExtends[] = $serviceToExtend;
127 13
128 1
            // Ensure the service from which we have to extend has been configured
129 1
            if (! isset($mailServices[$serviceToExtend])) {
130 13
                throw new Exception\InvalidArgumentException(\sprintf(
131 13
                    'Provided service "%s" to extend from is not configured inside acmailer_options.mail_services',
132 1
                    $serviceToExtend
133 1
                ));
134 13
            }
135 13
136 1
            $specificOptions = ArrayUtils::merge($mailServices[$serviceToExtend], $specificOptions);
137 1
        } while (isset($specificOptions['extends']));
138
139 13
        return $specificOptions;
140
    }
141
142
    /**
143
     * @param ContainerInterface $container
144
     * @param array $mailOptions
145
     * @return Transport\TransportInterface
146 13
     * @throws Exception\InvalidArgumentException
147
     * @throws ContainerExceptionInterface
148 13
     * @throws NotFoundExceptionInterface
149
     */
150 13
    private function createTransport(ContainerInterface $container, array $mailOptions): Transport\TransportInterface
151 1
    {
152
        $transport = $mailOptions['transport'] ?? Transport\Sendmail::class;
153
        if (! \is_string($transport) && ! $transport instanceof Transport\TransportInterface) {
154
            // The adapter is not valid. Throw an exception
155 12
            throw Exception\InvalidArgumentException::fromValidTypes(
156
                ['string', Transport\TransportInterface::class],
157 2
                $transport
158 2
            );
159 1
        }
160
161 1
        // A transport instance can be returned as is
162
        if ($transport instanceof Transport\TransportInterface) {
163 1
            return $this->setupTransportConfig($transport, $mailOptions);
164
        }
165
166
        // Check if the adapter is one of Zend's default adapters
167
        $transport = self::TRANSPORT_MAP[$transport] ?? $transport;
168 10
        if (\is_subclass_of($transport, Transport\TransportInterface::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Zend\Mail\Transport\TransportInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
169 9
            return $this->setupTransportConfig(new $transport(), $mailOptions);
170
        }
171
172
        // Check if the transport is a service
173 1
        if ($container->has($transport)) {
174 1
            /** @var Transport\TransportInterface $transport */
175 1
            $transportInstance = $container->get($transport);
0 ignored issues
show
Documentation introduced by
$transport is of type object<Zend\Mail\Transport\TransportInterface>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
176 1
            if ($transportInstance instanceof Transport\TransportInterface) {
177
                return $this->setupTransportConfig($transportInstance, $mailOptions);
178
            }
179
180
            throw new Exception\InvalidArgumentException(\sprintf(
181
                'Provided transport service with name "%s" does not return a "%s" instance',
182
                $transport,
183 11
                Transport\TransportInterface::class
184
            ));
185 11
        }
186 1
187 11
        // The adapter is not valid. Throw an exception
188 1
        throw new Exception\InvalidArgumentException(\sprintf(
189 1
            'Registered transport "%s" is not either one of ["%s"], a "%s" subclass or a registered service.',
190
            $transport,
191 11
            \implode('", "', \array_keys(self::TRANSPORT_MAP)),
192
            Transport\TransportInterface::class
193
        ));
194
    }
195
196
    /**
197
     * @param Transport\TransportInterface $transport
198 11
     * @param array $mailOptions
199
     * @return Transport\TransportInterface
200
     */
201 11
    private function setupTransportConfig(
202
        Transport\TransportInterface $transport,
203
        array $mailOptions
204 11
    ): Transport\TransportInterface {
205 3
        if ($transport instanceof Transport\Smtp) {
206 9
            $transport->setOptions(new Transport\SmtpOptions($mailOptions['transport_options'] ?? []));
207
        } elseif ($transport instanceof Transport\File) {
208 9
            $transportOptions = $mailOptions['transport_options'] ?? [];
209 9
            $transportOptions['path'] = $transportOptions['path'] ?? 'data/mail/output';
210
            $transport->setOptions(new Transport\FileOptions($transportOptions));
211
        }
212 9
213
        return $transport;
214 1
    }
215 1
216 1
    /**
217 1
     * @param ContainerInterface $container
218 1
     * @param array $mailOptions
219 1
     * @return TemplateRendererInterface
220 9
     * @throws Exception\InvalidArgumentException
221
     * @throws ContainerExceptionInterface
222 1
     * @throws NotFoundExceptionInterface
223 9
     */
224
    private function createRenderer(ContainerInterface $container, array $mailOptions): TemplateRendererInterface
225 9
    {
226 9
        if (! isset($mailOptions['renderer'])) {
227 9
            return $container->get(MailViewRendererFactory::SERVICE_NAME);
228 9
        }
229
230
        // Resolve renderer service and ensure it has proper type
231 9
        $renderer = $container->get($mailOptions['renderer']);
232 9
        if (! $renderer instanceof TemplateRendererInterface) {
233
            throw new Exception\InvalidArgumentException(\sprintf(
234
                'Defined renderer of type "%s" is not valid. The renderer must resolve to a "%s" instance',
235
                \is_object($renderer) ? \get_class($renderer) : \gettype($renderer),
236
                TemplateRendererInterface::class
237
            ));
238
        }
239
        return $renderer;
240
    }
241 9
242
    /**
243 9
     * Attaches the preconfigured mail listeners to the mail service
244
     *
245 9
     * @param EventsCapableInterface $service
246 9
     * @param ContainerInterface $container
247 9
     * @param array $mailOptions
248 9
     * @throws InvalidArgumentException
249
     * @throws Exception\InvalidArgumentException
250
     * @throws NotFoundExceptionInterface
251
     */
252
    private function attachMailListeners(
253
        EventsCapableInterface $service,
254
        ContainerInterface $container,
255
        array $mailOptions
256
    ) {
257 9
        $listeners = (array) ($mailOptions['mail_listeners'] ?? []);
258
        if (empty($listeners)) {
259 9
            return;
260 9
        }
261
262
        $definitions = [];
263
        foreach ($listeners as $listener) {
264
            $priority = 1;
265
            if (\is_array($listener) && array_key_exists('listener', $listener)) {
266
                $listener = $listener['listener'];
267
                $priority = $listener['priority'] ?? 1;
268
            }
269
270 11
            // If the listener is already an instance, just register it
271
            if ($listener instanceof MailListenerInterface) {
272 11
                $listener->attach($service->getEventManager(), $priority);
273 11
                continue;
274
            }
275 2
276 1
            // Ensure the listener is a string
277 2
            if (! \is_string($listener)) {
278 1
                throw Exception\InvalidArgumentException::fromValidTypes(
279 1
                    ['string', MailListenerInterface::class],
280
                    $listener
281
                );
282 2
            }
283 1
284
            $definitions[] = [
285 1
                'listener' => $listener,
286 1
                'method' => 'onPreSend',
287 1
                'event' => MailEvent::EVENT_MAIL_PRE_SEND,
288
                'priority' => $priority,
289 1
            ];
290 10
            $definitions[] = [
291 10
                'listener' => $listener,
292
                'method' => 'onPostSend',
293
                'event' => MailEvent::EVENT_MAIL_POST_SEND,
294
                'priority' => $priority,
295
            ];
296
            $definitions[] = [
297
                'listener' => $listener,
298
                'method' => 'onSendError',
299
                'event' => MailEvent::EVENT_MAIL_SEND_ERROR,
300
                'priority' => $priority,
301
            ];
302
        }
303
304
        // Attach lazy event listeners if any
305
        if (! empty($definitions)) {
306
            (new LazyListenerAggregate($definitions, $container))->attach($service->getEventManager());
307
        }
308
    }
309
}
310