Passed
Pull Request — feature/unit-tests (#37)
by Daniel
06:16
created

AbstractUserEmailFactory::getTokenUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Component Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentBundle\Factory\Mailer\User;
15
16
use Psr\Container\ContainerInterface;
17
use Silverback\ApiComponentBundle\Entity\User\AbstractUser;
18
use Silverback\ApiComponentBundle\Event\UserEmailMessageEvent;
19
use Silverback\ApiComponentBundle\Exception\BadMethodCallException;
20
use Silverback\ApiComponentBundle\Exception\InvalidArgumentException;
21
use Silverback\ApiComponentBundle\Exception\RfcComplianceException;
22
use Silverback\ApiComponentBundle\Exception\UnexpectedValueException;
23
use Silverback\ApiComponentBundle\Url\RefererUrlHelper;
24
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
25
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
use Symfony\Component\HttpFoundation\RequestStack;
27
use Symfony\Component\Mime\Address;
28
use Symfony\Component\Mime\Exception\RfcComplianceException as SymfonyRfcComplianceException;
29
use Symfony\Component\Mime\RawMessage;
30
use Symfony\Contracts\Service\ServiceSubscriberInterface;
31
use Twig\Environment;
32
33
/**
34
 * @author Daniel West <[email protected]>
35
 */
36
abstract class AbstractUserEmailFactory implements ServiceSubscriberInterface
37
{
38
    protected ContainerInterface $container;
39
    private EventDispatcherInterface $eventDispatcher;
40
    protected bool $enabled;
41
    protected string $template;
42
    protected string $subject;
43
    protected array $emailContext;
44
    protected ?string $defaultRedirectPath;
45
    protected ?string $redirectPathQueryKey;
46
    protected ?RawMessage $message;
47
    private ?AbstractUser $user;
48
49 16
    public function __construct(
50
        ContainerInterface $container,
51
        EventDispatcherInterface $eventDispatcher,
52
        string $subject,
53
        bool $enabled = true,
54
        ?string $defaultRedirectPath = null,
55
        ?string $redirectPathQueryKey = null,
56
        array $emailContext = []
57
    ) {
58 16
        $this->container = $container;
59 16
        $this->eventDispatcher = $eventDispatcher;
60 16
        $this->subject = $subject;
61 16
        $this->enabled = $enabled;
62 16
        $this->emailContext = $emailContext;
63 16
        $this->defaultRedirectPath = $defaultRedirectPath;
64 16
        $this->redirectPathQueryKey = $redirectPathQueryKey;
65 16
    }
66
67 2
    public static function getSubscribedServices(): array
68
    {
69
        return [
70 2
            RequestStack::class,
71
            RefererUrlHelper::class,
72
            Environment::class,
73
        ];
74
    }
75
76 7
    protected static function getRequiredContextKeys(): ?array
77
    {
78
        return [
79 7
            'website_name',
80
            'user',
81
        ];
82
    }
83
84 10
    protected function initUser(AbstractUser $user): void
85
    {
86 10
        if (!$user->getUsername()) {
87 1
            throw new InvalidArgumentException('The user must have a username set to send them any email');
88
        }
89
90 9
        if (!$user->getEmailAddress()) {
91 1
            throw new InvalidArgumentException('The user must have an email address set to send them any email');
92
        }
93
94 8
        $this->user = $user;
95 8
    }
96
97 9
    protected function createEmailMessage(array $context = []): ?TemplatedEmail
98
    {
99 9
        if (!$this->enabled) {
100
            return null;
101
        }
102
103 9
        if (!isset($this->user)) {
104 1
            throw new BadMethodCallException('You must call the method `initUser` before `createEmailMessage`');
105
        }
106
107
        try {
108 8
            $toEmailAddress = Address::fromString((string) $this->user->getEmailAddress());
0 ignored issues
show
Bug introduced by
The method getEmailAddress() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

108
            $toEmailAddress = Address::fromString((string) $this->user->/** @scrutinizer ignore-call */ getEmailAddress());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
109 1
        } catch (SymfonyRfcComplianceException $exception) {
110 1
            $exception = new RfcComplianceException($exception->getMessage());
111 1
            throw $exception;
112
        }
113
114 7
        $context = array_replace_recursive([
115 7
            'user' => $this->user,
116 7
        ], $this->emailContext, $context);
117 7
        $this->validateContext($context);
118
119 6
        $twig = $this->container->get(Environment::class);
120 6
        $template = $twig->createTemplate($this->subject);
121 6
        $subject = $template->render($context);
122
123 6
        $email = (new TemplatedEmail())
124 6
            ->to($toEmailAddress)
125 6
            ->subject($subject)
126 6
            ->htmlTemplate('@SilverbackApiComponent/emails/' . $this->getTemplate())
127 6
            ->context($context);
128
129 6
        $event = new UserEmailMessageEvent(static::class, $email);
130 6
        $this->eventDispatcher->dispatch($event);
131
132 6
        return $event->getEmail();
133
    }
134
135 6
    protected function getTokenUrl(string $token, string $username): string
136
    {
137 6
        $path = $this->populatePathVariables($this->getTokenPath(), [
138 4
            'token' => $token,
139 4
            'username' => $username,
140
        ]);
141
142 4
        $refererUrlHelper = $this->container->get(RefererUrlHelper::class);
143
144 4
        return $refererUrlHelper->getAbsoluteUrl($path);
145
    }
146
147 6
    private function getTokenPath(): string
148
    {
149 6
        if (null === $this->defaultRedirectPath && null === $this->redirectPathQueryKey) {
150 1
            throw new InvalidArgumentException('The `defaultRedirectPath` or `redirectPathQueryKey` must be set');
151
        }
152
153 5
        $requestStack = $this->container->get(RequestStack::class);
154 5
        $request = $requestStack->getMasterRequest();
155
156 5
        $path = ($request && $this->redirectPathQueryKey) ?
157 3
            $request->query->get($this->redirectPathQueryKey, $this->defaultRedirectPath) :
158 5
            $this->defaultRedirectPath;
159
160 5
        if (null === $path) {
161 1
            throw new UnexpectedValueException(sprintf('The querystring key `%s` could not be found in the request to generate a token URL', $this->redirectPathQueryKey));
162
        }
163
164 4
        return $path;
165
    }
166
167 4
    private function populatePathVariables(string $path, array $variables): string
168
    {
169 4
        preg_match_all('/{{[\s]*(\w+)[\s]*}}/', $path, $matches);
170 4
        foreach ($matches[0] as $matchIndex => $fullMatch) {
171 2
            if (\array_key_exists($varKey = $matches[1][$matchIndex], $variables)) {
172 2
                $path = str_replace($fullMatch, rawurlencode($variables[$varKey]), $path);
173
            }
174
        }
175
176 4
        return $path;
177
    }
178
179 7
    private function validateContext(array $context): void
180
    {
181 7
        $requiredKeys = self::getRequiredContextKeys();
182 7
        foreach ($requiredKeys as $requiredKey) {
183 7
            if (!\array_key_exists($requiredKey, $context)) {
184 1
                throw new InvalidArgumentException(sprintf('The context key `%s` is required to create an email message with the factory `%s`', $requiredKey, static::class));
185
            }
186
        }
187 6
    }
188
189
    abstract public function create(AbstractUser $user, array $context = []): ?RawMessage;
190
191
    abstract protected function getTemplate(): string;
192
}
193