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

AbstractUserEmailFactory::populatePathVariables()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

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 3
nc 3
nop 2
crap 3
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 5
    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 5
        $this->container = $container;
59 5
        $this->eventDispatcher = $eventDispatcher;
60 5
        $this->subject = $subject;
61 5
        $this->enabled = $enabled;
62 5
        $this->emailContext = $emailContext;
63 5
        $this->defaultRedirectPath = $defaultRedirectPath;
64 5
        $this->redirectPathQueryKey = $redirectPathQueryKey;
65 5
    }
66
67 1
    public static function getSubscribedServices(): array
68
    {
69
        return [
70 1
            RequestStack::class,
71
            RefererUrlHelper::class,
72
            Environment::class,
73
        ];
74
    }
75
76
    abstract public function create(AbstractUser $user, array $context = []): ?RawMessage;
77
78
    abstract protected function getTemplate(): string;
79
80 5
    protected static function getRequiredContextKeys(): ?array
81
    {
82
        return [
83 5
            'website_name',
84
            'user',
85
        ];
86
    }
87
88 5
    protected function createEmailMessage(array $context = []): TemplatedEmail
89
    {
90 5
        if (!$this->user) {
91
            throw new BadMethodCallException('You must call the method `initUser` before `createEmailMessage`');
92
        }
93
94
        try {
95 5
            $toEmailAddress = Address::fromString((string) $this->user->getEmailAddress());
96
        } catch (SymfonyRfcComplianceException $exception) {
97
            $exception = new RfcComplianceException($exception->getMessage());
98
            throw $exception;
99
        }
100
101 5
        $context = array_replace_recursive([
102 5
            'user' => $this->user,
103 5
        ], $this->emailContext, $context);
104 5
        $this->validateContext($context);
105
106 5
        $twig = $this->container->get(Environment::class);
107 5
        $template = $twig->createTemplate($this->subject);
108 5
        $subject = $template->render($context);
109
110 5
        $email = (new TemplatedEmail())
111 5
            ->to($toEmailAddress)
112 5
            ->subject($subject)
113 5
            ->htmlTemplate('@SilverbackApiComponent/emails/' . $this->getTemplate())
114 5
            ->context($context);
115
116 5
        $event = new UserEmailMessageEvent(static::class, $email);
117 5
        $this->eventDispatcher->dispatch($event);
118
119 5
        return $event->getEmail();
120
    }
121
122 5
    protected function initUser(AbstractUser $user): void
123
    {
124 5
        if (null === $user->getUsername()) {
125
            throw new InvalidArgumentException('The user must have a username set to send them any email');
126
        }
127
128 5
        if (null === $user->getEmailAddress()) {
129
            throw new InvalidArgumentException('The user must have a username set to send them any email');
130
        }
131
132 5
        $this->user = $user;
133 5
    }
134
135 1
    protected function getTokenUrl(string $token, string $username): string
136
    {
137 1
        $path = $this->populatePathVariables($this->getTokenPath(), [
138 1
            'token' => $token,
139 1
            'username' => $username,
140
        ]);
141
142 1
        $refererUrlHelper = $this->container->get(RefererUrlHelper::class);
143
144 1
        return $refererUrlHelper->getAbsoluteUrl($path);
145
    }
146
147 1
    private function getTokenPath(): string
148
    {
149 1
        if (null === $this->defaultRedirectPath && null === $this->redirectPathQueryKey) {
150
            throw new InvalidArgumentException('The `defaultRedirectPath` or `redirectPathQueryKey` must be set');
151
        }
152
153 1
        $requestStack = $this->container->get(RequestStack::class);
154 1
        $request = $requestStack->getMasterRequest();
155
156 1
        $path = ($request && $this->redirectPathQueryKey) ?
157
            $request->query->get($this->redirectPathQueryKey, $this->defaultRedirectPath) :
158 1
            $this->defaultRedirectPath;
159
160 1
        if (null === $path) {
161
            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 1
        return $path;
165
    }
166
167 1
    private function populatePathVariables(string $path, array $variables): string
168
    {
169 1
        preg_match_all('/{{[\s]*(\w+)[\s]*}}/', $path, $matches);
170 1
        foreach ($matches[0] as $matchIndex => $fullMatch) {
171 1
            if (\array_key_exists($varKey = $matches[1][$matchIndex], $variables)) {
172 1
                $path = str_replace($fullMatch, $variables[$varKey], $path);
173
            }
174
        }
175
176 1
        return $path;
177
    }
178
179 5
    private function validateContext(array $context): void
180
    {
181 5
        $requiredKeys = self::getRequiredContextKeys();
182 5
        foreach ($requiredKeys as $requiredKey) {
183 5
            if (!\array_key_exists($requiredKey, $context)) {
184
                throw new InvalidArgumentException(sprintf('The context key `%s` is required to create the email message', $requiredKey));
185
            }
186
        }
187 5
    }
188
}
189