Completed
Push — master ( 3b415d...9c7552 )
by Alejandro
02:33
created

MailServiceAbstractFactory   C

Complexity

Total Complexity 46

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 46
c 2
b 0
f 1
lcom 1
cbo 17
dl 0
loc 258
ccs 134
cts 134
cp 1
rs 6.16

8 Methods

Rating   Name   Duplication   Size   Complexity  
C createServiceWithName() 0 59 11
B createMessage() 0 28 6
C createTransport() 0 32 8
A setupTransportConfig() 0 10 3
B createRenderer() 0 35 6
A createHelperPluginManager() 0 9 1
A getSpecificConfig() 0 5 3
C attachMailListeners() 0 22 8

How to fix   Complexity   

Complex Class

Complex classes like MailServiceAbstractFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MailServiceAbstractFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace AcMailer\Service\Factory;
3
4
use AcMailer\Event\MailListenerAwareInterface;
5
use AcMailer\Event\MailListenerInterface;
6
use AcMailer\Factory\AbstractAcMailerFactory;
7
use AcMailer\Options\Factory\MailOptionsAbstractFactory;
8
use AcMailer\View\DefaultLayout;
9
use Zend\Mail\Transport\File;
10
use Zend\Mail\Transport\TransportInterface;
11
use Zend\Mvc\Service\ViewHelperManagerFactory;
12
use Zend\ServiceManager\Config;
13
use Zend\ServiceManager\Exception\ServiceNotFoundException;
14
use Zend\Mail\Message;
15
use Zend\Mail\Transport\Smtp;
16
use AcMailer\Service\MailService;
17
use AcMailer\Options\MailOptions;
18
use AcMailer\Exception\InvalidArgumentException;
19
use Zend\ServiceManager\ServiceLocatorInterface;
20
use Zend\View\HelperPluginManager;
21
use Zend\View\Renderer\PhpRenderer;
22
use Zend\View\Renderer\RendererInterface;
23
use Zend\View\Resolver\AggregateResolver;
24
use Zend\View\Resolver\TemplateMapResolver;
25
use Zend\View\Resolver\TemplatePathStack;
26
27
class MailServiceAbstractFactory extends AbstractAcMailerFactory
28
{
29
    const SPECIFIC_PART = 'mailservice';
30
31
    /**
32
     * @var MailOptions
33
     */
34
    protected $mailOptions;
35
36
    /**
37
     * Create service with name
38
     *
39
     * @param ServiceLocatorInterface $sm
40
     * @param $name
41
     * @param $requestedName
42
     * @return mixed
43
     */
44 13
    public function createServiceWithName(ServiceLocatorInterface $sm, $name, $requestedName)
45
    {
46 13
        $specificServiceName = explode('.', $name)[2];
47 13
        $this->mailOptions = $sm->get(
0 ignored issues
show
Documentation Bug introduced by
It seems like $sm->get(sprintf('%s.%s.... $specificServiceName)) can also be of type array. However, the property $mailOptions is declared as type object<AcMailer\Options\MailOptions>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
48 13
            sprintf('%s.%s.%s', self::ACMAILER_PART, MailOptionsAbstractFactory::SPECIFIC_PART, $specificServiceName)
49 13
        );
50
51
        // Create the service
52 13
        $message        = $this->createMessage();
53 13
        $transport      = $this->createTransport($sm);
54 11
        $renderer       = $this->createRenderer($sm);
55 11
        $mailService    = new MailService($message, $transport, $renderer);
56
57
        // Set subject
58 11
        $mailService->setSubject($this->mailOptions->getMessageOptions()->getSubject());
0 ignored issues
show
Deprecated Code introduced by
The method AcMailer\Service\MailService::setSubject() has been deprecated with message: Use $mailService->getMessage()->setSubject() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
59
60
        // Set body, either by using a template or a raw body
61 11
        $body = $this->mailOptions->getMessageOptions()->getBody();
62 11
        if ($body->getUseTemplate()) {
63 2
            $defaultLayoutConfig = $body->getTemplate()->getDefaultLayout();
64 2
            if (isset($defaultLayoutConfig['path'])) {
65 1
                $params = isset($defaultLayoutConfig['params']) ? $defaultLayoutConfig['params'] : [];
66 1
                $captureTo = isset($defaultLayoutConfig['template_capture_to'])
67 1
                    ? $defaultLayoutConfig['template_capture_to']
68 1
                    : 'content';
69 1
                $mailService->setDefaultLayout(new DefaultLayout($defaultLayoutConfig['path'], $params, $captureTo));
70 1
            }
71 2
            $mailService->setTemplate($body->getTemplate()->toViewModel(), ['charset' => $body->getCharset()]);
72 2
        } else {
73 9
            $mailService->setBody($body->getContent(), $body->getCharset());
74
        }
75
76
        // Attach files
77 11
        $files = $this->mailOptions->getMessageOptions()->getAttachments()->getFiles();
78 11
        $mailService->addAttachments($files);
79
80
        // Attach files from dir
81 11
        $dir = $this->mailOptions->getMessageOptions()->getAttachments()->getDir();
82 11
        if ($dir['iterate'] === true && is_string($dir['path']) && is_dir($dir['path'])) {
83 1
            $files = $dir['recursive'] === true ?
84 1
                new \RecursiveIteratorIterator(
85 1
                    new \RecursiveDirectoryIterator($dir['path'], \RecursiveDirectoryIterator::SKIP_DOTS),
86
                    \RecursiveIteratorIterator::CHILD_FIRST
87 1
                ):
88 1
                new \DirectoryIterator($dir['path']);
89
90
            /* @var \SplFileInfo $fileInfo */
91 1
            foreach ($files as $fileInfo) {
92 1
                if ($fileInfo->isDir()) {
93 1
                    continue;
94
                }
95 1
                $mailService->addAttachment($fileInfo->getPathname());
96 1
            }
97 1
        }
98
99
        // Attach mail listeners
100 11
        $this->attachMailListeners($mailService, $sm);
101 10
        return $mailService;
102
    }
103
104
    /**
105
     * @return Message
106
     */
107 13
    protected function createMessage()
108
    {
109 13
        $options = $this->mailOptions->getMessageOptions();
110
        // Prepare Mail Message
111 13
        $message = new Message();
112 13
        $from = $options->getFrom();
113 13
        if (! empty($from)) {
114 1
            $message->setFrom($from, $options->getFromName());
115 1
        }
116 13
        $replyTo = $options->getReplyTo();
117 13
        if (! empty($replyTo)) {
118 1
            $message->setReplyTo($replyTo, $options->getReplyToName());
119 1
        }
120 13
        $to = $options->getTo();
121 13
        if (! empty($to)) {
122 1
            $message->setTo($to);
123 1
        }
124 13
        $cc = $options->getCc();
125 13
        if (! empty($cc)) {
126 1
            $message->setCc($cc);
127 1
        }
128 13
        $bcc = $options->getBcc();
129 13
        if (! empty($bcc)) {
130 1
            $message->setBcc($bcc);
131 1
        }
132
133 13
        return $message;
134
    }
135
136
    /**
137
     * @param ServiceLocatorInterface $sm
138
     * @return TransportInterface
139
     */
140 13
    protected function createTransport(ServiceLocatorInterface $sm)
141
    {
142 13
        $adapter = $this->mailOptions->getMailAdapter();
143
        // A transport instance can be returned as is
144 13
        if ($adapter instanceof TransportInterface) {
145 1
            return $this->setupTransportConfig($adapter);
146
        }
147
148
        // Check if the adapter is a service
149 12
        if (is_string($adapter) && $sm->has($adapter)) {
150
            /** @var TransportInterface $transport */
151 2
            $transport = $sm->get($adapter);
152 2
            if ($transport instanceof TransportInterface) {
153 2
                return $this->setupTransportConfig($transport);
154
            } else {
155 1
                throw new InvalidArgumentException(
156
                    'Provided mail_adapter service does not return a "Zend\Mail\Transport\TransportInterface" instance'
157 1
                );
158
            }
159
        }
160
161
        // Check if the adapter is one of Zend's default adapters
162 10
        if (is_string($adapter) && is_subclass_of($adapter, 'Zend\Mail\Transport\TransportInterface')) {
163 9
            return $this->setupTransportConfig(new $adapter());
164
        }
165
166
        // The adapter is not valid. Throw an exception
167 1
        throw new InvalidArgumentException(sprintf(
168 1
            'mail_adapter must be an instance of "Zend\Mail\Transport\TransportInterface" or string, "%s" provided',
169 1
            is_object($adapter) ? get_class($adapter) : gettype($adapter)
170 1
        ));
171
    }
172
173
    /**
174
     * @param TransportInterface $transport
175
     * @return TransportInterface
176
     */
177 11
    protected function setupTransportConfig(TransportInterface $transport)
178
    {
179 11
        if ($transport instanceof Smtp) {
180 1
            $transport->setOptions($this->mailOptions->getSmtpOptions());
181 11
        } elseif ($transport instanceof File) {
182 1
            $transport->setOptions($this->mailOptions->getFileOptions());
0 ignored issues
show
Bug introduced by
It seems like $this->mailOptions->getFileOptions() can be null; however, setOptions() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
183 1
        }
184
185 11
        return $transport;
186
    }
187
188
    /**
189
     * @param ServiceLocatorInterface $sm
190
     * @return RendererInterface
191
     */
192 11
    protected function createRenderer(ServiceLocatorInterface $sm)
193
    {
194
        // Try to return the configured renderer. If it points to an undefined service, create a renderer on the fly
195
        try {
196 11
            $renderer = $sm->get('mailviewrenderer');
0 ignored issues
show
Bug Compatibility introduced by
The expression $sm->get('mailviewrenderer'); of type object|array adds the type array to the return on line 197 which is incompatible with the return type documented by AcMailer\Service\Factory...Factory::createRenderer of type Zend\View\Renderer\RendererInterface.
Loading history...
197 3
            return $renderer;
198 9
        } catch (ServiceNotFoundException $e) {
199
            // In case the renderer service is not defined, try to construct it
200 9
            $vmConfig = $this->getSpecificConfig($sm, 'view_manager');
201 9
            $renderer = new PhpRenderer();
202
203
            // Check what kind of view_manager configuration has been defined
204 9
            if (isset($vmConfig['template_map']) && isset($vmConfig['template_path_stack'])) {
205
                // If both a template_map and a template_path_stack have been defined, create an AggregateResolver
206 1
                $pathStackResolver = new TemplatePathStack();
207 1
                $pathStackResolver->setPaths($vmConfig['template_path_stack']);
208 1
                $resolver = new AggregateResolver();
209 1
                $resolver->attach($pathStackResolver)
210 1
                    ->attach(new TemplateMapResolver($vmConfig['template_map']));
211 1
                $renderer->setResolver($resolver);
212 9
            } elseif (isset($vmConfig['template_map'])) {
213
                // Create a TemplateMapResolver in case only the template_map has been defined
214 1
                $renderer->setResolver(new TemplateMapResolver($vmConfig['template_map']));
215 9
            } elseif (isset($vmConfig['template_path_stack'])) {
216
                // Create a TemplatePathStack resolver in case only the template_path_stack has been defined
217 9
                $pathStackResolver = new TemplatePathStack();
218 9
                $pathStackResolver->setPaths($vmConfig['template_path_stack']);
219 9
                $renderer->setResolver($pathStackResolver);
220 9
            }
221
222
            // Create a HelperPluginManager with default view helpers and user defined view helpers
223 9
            $renderer->setHelperPluginManager($this->createHelperPluginManager($sm));
224 9
            return $renderer;
225
        }
226
    }
227
228
    /**
229
     * Creates a view helper manager
230
     * @param ServiceLocatorInterface $sm
231
     * @return HelperPluginManager
232
     */
233 9
    protected function createHelperPluginManager(ServiceLocatorInterface $sm)
234
    {
235 9
        $factory = new ViewHelperManagerFactory();
236
        /** @var HelperPluginManager $helperManager */
237 9
        $helperManager = $factory->createService($sm);
238 9
        $config = new Config($this->getSpecificConfig($sm, 'view_helpers'));
239 9
        $config->configureServiceManager($helperManager);
240 9
        return $helperManager;
241
    }
242
243
    /**
244
     * Returns a specific configuration defined by provided key
245
     * @param ServiceLocatorInterface $sm
246
     * @param $configKey
247
     * @return array
248
     */
249 9
    protected function getSpecificConfig(ServiceLocatorInterface $sm, $configKey)
250
    {
251 9
        $config = $sm->get('Config');
252 9
        return ! empty($config) && isset($config[$configKey]) ? $config[$configKey] : [];
253
    }
254
255
    /**
256
     * Attaches the preconfigured mail listeners to the mail service
257
     *
258
     * @param MailListenerAwareInterface $service
259
     * @param ServiceLocatorInterface $sm
260
     * @throws InvalidArgumentException
261
     */
262 11
    protected function attachMailListeners(MailListenerAwareInterface $service, ServiceLocatorInterface $sm)
263
    {
264 11
        $listeners = $this->mailOptions->getMailListeners();
265 11
        foreach ($listeners as $listener) {
266
            // Try to fetch the listener from the ServiceManager or lazily create an instance
267 2
            if (is_string($listener) && $sm->has($listener)) {
268 1
                $listener = $sm->get($listener);
269 2
            } elseif (is_string($listener) && class_exists($listener)) {
270 1
                $listener = new $listener();
271 1
            }
272
273
            // At this point, the listener should be an instance of MailListenerInterface, otherwise it is invalid
274 2
            if (! $listener instanceof MailListenerInterface) {
275 1
                throw new InvalidArgumentException(sprintf(
276
                    'Provided listener of type "%s" is not valid. '
277 1
                    . 'Expected "string" or "AcMailer\Listener\MailListenerInterface"',
278 1
                    is_object($listener) ? get_class($listener) : gettype($listener)
279 1
                ));
280
            }
281 1
            $service->attachMailListener($listener);
282 10
        }
283 10
    }
284
}
285