An idea for splitting validation and running

[MAILPOET-4191]
This commit is contained in:
John Oleksowicz
2022-04-05 20:25:13 -05:00
committed by Veljko V
parent 70911d2424
commit 2cce9bf7ad
6 changed files with 267 additions and 163 deletions

View File

@@ -23,6 +23,9 @@ class ActionStepRunner {
if (!$action) {
throw new InvalidStateException();
}
$action->run($workflow, $workflowRun, $step);
$validationResult = $action->validate($workflow, $workflowRun, $step);
if (!$validationResult->hasErrors()) {
$action->run($validationResult);
}
}
}

View File

@@ -7,5 +7,7 @@ interface Action {
public function getName(): string;
public function run(Workflow $workflow, WorkflowRun $workflowRun, Step $step): void;
public function validate(Workflow $workflow, WorkflowRun $workflowRun, Step $step): ActionValidationResult;
public function run(ActionValidationResult $actionValidationResult): void;
}

View File

@@ -0,0 +1,36 @@
<?php
namespace MailPoet\Automation\Engine\Workflows;
class ActionValidationResult {
private $errors = [];
private $validated = [];
public function setValidatedParam(string $key, $value): void {
$this->validated[$key] = $value;
}
public function addError(\Exception $exception): void {
$this->errors[] = $exception;
}
public function isValid(): bool {
return count($this->getErrors()) === 0;
}
public function getErrors(): array {
return $this->errors;
}
public function hasErrors(): bool {
return count($this->getErrors()) > 0;
}
public function getValidatedParams(): array {
return $this->validated;
}
public function getValidatedParam(string $key) {
return $this->getValidatedParams()[$key] ?? null;
}
}

View File

@@ -5,9 +5,11 @@ namespace MailPoet\Automation\Integrations\Core\Actions;
use MailPoet\Automation\Engine\Control\ActionScheduler;
use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\Workflows\Action;
use MailPoet\Automation\Engine\Workflows\ActionValidationResult;
use MailPoet\Automation\Engine\Workflows\Step;
use MailPoet\Automation\Engine\Workflows\Workflow;
use MailPoet\Automation\Engine\Workflows\WorkflowRun;
use MailPoet\InvalidStateException;
class WaitAction implements Action {
/** @var ActionScheduler */
@@ -27,14 +29,32 @@ class WaitAction implements Action {
return __('Wait', 'mailpoet');
}
public function run(Workflow $workflow, WorkflowRun $workflowRun, Step $step): void {
$this->actionScheduler->schedule(time() + $step->getArgs()['seconds'], Hooks::WORKFLOW_STEP, [
public function run(ActionValidationResult $result): void {
$this->actionScheduler->schedule($result->getValidatedParam('nextRunTime'), Hooks::WORKFLOW_STEP, [
[
'workflow_run_id' => $workflowRun->getId(),
'step_id' => $step->getNextStepId(),
'workflow_run_id' => $result->getValidatedParam('currentRunId'),
'step_id' => $result->getValidatedParam('nextStepId'),
],
]);
// TODO: call a step complete ($id) hook instead?
}
public function validate(Workflow $workflow, WorkflowRun $workflowRun, Step $step): ActionValidationResult {
$result = new ActionValidationResult();
if (!isset($step->getArgs()['seconds'])) {
$result->addError(InvalidStateException::create());
}
if (!is_string($step->getNextStepId())) {
$result->addError(InvalidStateException::create()->withMessage('next step id is required'));
}
if ($result->hasErrors()) {
return $result;
}
$result->setValidatedParam('nextRunTime', time() + (int)$step->getArgs()['seconds'] );
$result->setValidatedParam('currentRunId', $workflowRun->getId());
$result->setValidatedParam('nextStepId', $step->getNextStepId());
return $result;
}
}

View File

@@ -3,11 +3,13 @@
namespace MailPoet\Automation\Integrations\MailPoet\Actions;
use MailPoet\Automation\Engine\Workflows\Action;
use MailPoet\Automation\Engine\Workflows\ActionValidationResult;
use MailPoet\Automation\Engine\Workflows\Step;
use MailPoet\Automation\Engine\Workflows\Workflow;
use MailPoet\Automation\Engine\Workflows\WorkflowRun;
use MailPoet\Automation\Integrations\MailPoet\Subjects\SegmentSubject;
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\InvalidStateException;
use MailPoet\Newsletter\NewslettersRepository;
@@ -49,47 +51,79 @@ class SendWelcomeEmailAction implements Action {
return __('Send Welcome Email', 'mailpoet');
}
public function run(Workflow $workflow, WorkflowRun $workflowRun, Step $step): void {
$subscriberSubject = $workflowRun->getSubjects()['mailpoet:subscriber'] ?? null;
if (!$subscriberSubject instanceof SubscriberSubject) {
throw InvalidStateException::create()->withMessage(__('No mailpoet:subscriber subject provided.', 'mailpoet'));
public function validate(Workflow $workflow, WorkflowRun $workflowRun, Step $step): ActionValidationResult {
$result = new ActionValidationResult();
if (!isset($step->getArgs()['welcomeEmailId'])) {
$result->addError(InvalidStateException::create()->withMessage('Step arguments did not include a welcomeEmailId.'));
} else {
$welcomeEmailId = (int)$step->getArgs()['welcomeEmailId'];
$newsletter = $this->newslettersRepository->findOneById($welcomeEmailId);
if ($newsletter === null) {
$result->addError(NotFoundException::create()->withMessage(__(sprintf("Welcome Email with ID '%s' not found.", $welcomeEmailId), 'mailpoet')));
} else {
$type = $newsletter->getType();
if ($type !== NewsletterEntity::TYPE_WELCOME) {
$result->addError(InvalidStateException::create()->withMessage("Newsletter must be a Welcome Email but actual type was '$type'."));
}
}
}
$segmentSubject = $workflowRun->getSubjects()['mailpoet:segment'] ?? null;
if (!$segmentSubject instanceof SegmentSubject) {
throw InvalidStateException::create()->withMessage(__('No mailpoet:segment subject provided.', 'mailpoet'));
$result->addError(InvalidStateException::create()->withMessage(__('No mailpoet:segment subject provided.', 'mailpoet')));
} else {
$segment = $segmentSubject->getSegment();
}
$segment = $segmentSubject->getSegment();
$subscriber = $subscriberSubject->getSubscriber();
$subscriberSubject = $workflowRun->getSubjects()['mailpoet:subscriber'] ?? null;
if (!$subscriberSubject instanceof SubscriberSubject) {
$result->addError(InvalidStateException::create()->withMessage(__('No mailpoet:subscriber subject provided.', 'mailpoet')));
} else {
$subscriber = $subscriberSubject->getSubscriber();
if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED) {
$result->addError(InvalidStateException::create()->withMessage(__(sprintf("Cannot send a welcome email to a subscriber with a global subscription status of '%s'.", $subscriber->getStatus()), 'mailpoet')));
}
}
if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED) {
throw InvalidStateException::create()->withMessage(__(sprintf("Cannot send a welcome email to a subscriber with a global subscription status of '%s'.", $subscriber->getStatus()), 'mailpoet'));
if (!isset($subscriber) || !isset($segment)) {
return $result;
}
$isSubscribed = $this->subscribersFinder->findSubscribersInSegments([$subscriber->getId()], [$segment->getId()]) !== [];
if (!$isSubscribed) {
throw InvalidStateException::create()->withMessage(__(sprintf("Subscriber ID '%s' is not subscribed to segment ID '%s'.", $subscriber->getId(), $segment->getId()), 'mailpoet'));
$result->addError(InvalidStateException::create()->withMessage(__(sprintf("Subscriber ID '%s' is not subscribed to segment ID '%s'.", $subscriber->getId(), $segment->getId()), 'mailpoet')));
}
$welcomeEmailId = (int)$step->getArgs()['welcomeEmailId'];
$newsletter = $this->newslettersRepository->findOneById($welcomeEmailId);
if ($newsletter === null) {
throw NotFoundException::create()->withMessage(__(sprintf("Welcome Email with ID '%s' not found.", $welcomeEmailId), 'mailpoet'));
if (!isset($newsletter)) {
return $result;
}
// This check also occurs in createWelcomeNotificationSendingTask, in which case the method returns null, but
// that's not the only thing that causes a return value of null, so let's check here so we can craft a more
// meaningful exception.
$previouslyScheduledNotification = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($newsletter, (int)$subscriber->getId());
if (!empty($previouslyScheduledNotification)) {
throw InvalidStateException::create()->withMessage(__(sprintf("Newsletter with ID '%s' was previously scheduled for subscriber with ID '%s'.", $newsletter->getId(), $subscriber->getId())));
$result->addError(InvalidStateException::create()->withMessage(__(sprintf("Newsletter with ID '%s' was previously scheduled for subscriber with ID '%s'.", $newsletter->getId(), $subscriber->getId()))));
}
$sendingTask = $this->welcomeScheduler->createWelcomeNotificationSendingTask($newsletter, $subscriber->getId());
if (!$result->hasErrors()) {
$result->setValidatedParam('welcomeEmail', $newsletter);
$result->setValidatedParam('subscriberId', $subscriber->getId());
}
return $result;
}
public function run(ActionValidationResult $actionValidationResult): void {
if ($actionValidationResult->hasErrors()) {
// throw the exceptions chained together
}
$newsletter = $actionValidationResult->getValidatedParam('welcomeEmail');
$subscriberId = $actionValidationResult->getValidatedParam('subscriberId');
$sendingTask = $this->welcomeScheduler->createWelcomeNotificationSendingTask($newsletter, $subscriberId);
if ($sendingTask === null) {
// TODO: What exactly does this represent? I think it means the welcome email was configured to be triggered by a segment that has since been deleted. But in the case of this automation it seems like we shouldn't care about how the welcome email is configured. Do we need to be able to create welcome emails that have no explicit trigger segment? Basically something that says "This welcome email can only be triggered via an automation workflow"?
throw InvalidStateException::create()->withMessage("TBD");
throw InvalidStateException::create();
}
$errors = $sendingTask->getErrors();

View File

@@ -2,6 +2,7 @@
namespace MailPoet\Test\Automation\Integrations\MailPoet\Actions;
use MailPoet\Automation\Engine\Workflows\ActionValidationResult;
use MailPoet\Automation\Engine\Workflows\Step;
use MailPoet\Automation\Engine\Workflows\Workflow;
use MailPoet\Automation\Engine\Workflows\WorkflowRun;
@@ -31,120 +32,120 @@ class SendWelcomeEmailActionTest extends \MailPoetTest {
$this->scheduledTasksRepository = $this->diContainer->get(ScheduledTasksRepository::class);
}
public function testSendingTaskQueuedForHappyPath() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
->withSegments([$segment])
->create();
$run = new WorkflowRun(1, 'some-trigger', [
'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
'mailpoet:segment' => $this->getSegmentSubject($segment),
]);
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
$workflow = new Workflow('some-workflow', [$step]);
/** @var SendWelcomeEmailAction $action */
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
$action->run($workflow, $run, $step);
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(1);
}
public function testNoSendingTaskQueuedForGloballyUnconfirmedSubscriber() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_UNCONFIRMED)
->withSegments([$segment])
->create();
$run = new WorkflowRun(1, 'some-trigger', [
'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
'mailpoet:segment' => $this->getSegmentSubject($segment),
]);
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
$workflow = new Workflow('some-workflow', [$step]);
/** @var SendWelcomeEmailAction $action */
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
try {
$action->run($workflow, $run, $step);
} catch (Exception $e) {
// We don't care about the exception, just the outcome
}
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
}
public function testNoSendingTaskQueuedIfSubscriberNoLongerSubscribedToSegment() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
->withSegments([$segment])
->create();
$run = new WorkflowRun(1, 'some-trigger', [
'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
'mailpoet:segment' => $this->getSegmentSubject($segment),
]);
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
$workflow = new Workflow('some-workflow', [$step]);
/** @var SendWelcomeEmailAction $action */
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
$subscriberModel = SubscriberModel::findOne($subscriber->getId());
SubscriberSegment::unsubscribeFromSegments($subscriberModel, [$segment->getId()]);
try {
$action->run($workflow, $run, $step);
} catch (Exception $e) {
// We don't care about the exception, just the outcome
}
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
}
public function testItDoesNotScheduleAnythingIfSubscriberHasBeenDeleted() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
->withSegments([$segment])
->create();
$run = new WorkflowRun(1, 'some-trigger', [
'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
'mailpoet:segment' => $this->getSegmentSubject($segment),
]);
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
$workflow = new Workflow('some-workflow', [$step]);
/** @var SendWelcomeEmailAction $action */
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
ContainerWrapper::getInstance()->get(SubscribersRepository::class)->bulkDelete([$subscriber->getId()]);
try {
$action->run($workflow, $run, $step);
} catch (Exception $e) {
// We don't care about the exception, just the outcome
}
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
}
// public function testSendingTaskQueuedForHappyPath() {
// $segment = (new Segment())->create();
// $subscriber = (new Subscriber())
// ->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
// ->withSegments([$segment])
// ->create();
//
// $run = new WorkflowRun(1, 'some-trigger', [
// 'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
// 'mailpoet:segment' => $this->getSegmentSubject($segment),
// ]);
//
// $welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
// $step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
// $workflow = new Workflow('some-workflow', [$step]);
//
// /** @var SendWelcomeEmailAction $action */
// $action = $this->diContainer->get(SendWelcomeEmailAction::class);
// $action->run($workflow, $run, $step);
// $scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
// expect($scheduled)->count(1);
// }
//
// public function testNoSendingTaskQueuedForGloballyUnconfirmedSubscriber() {
// $segment = (new Segment())->create();
// $subscriber = (new Subscriber())
// ->withStatus(SubscriberEntity::STATUS_UNCONFIRMED)
// ->withSegments([$segment])
// ->create();
//
// $run = new WorkflowRun(1, 'some-trigger', [
// 'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
// 'mailpoet:segment' => $this->getSegmentSubject($segment),
// ]);
//
// $welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
// $step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
// $workflow = new Workflow('some-workflow', [$step]);
//
// /** @var SendWelcomeEmailAction $action */
// $action = $this->diContainer->get(SendWelcomeEmailAction::class);
//
// try {
// $action->run($workflow, $run, $step);
// } catch (Exception $e) {
// // We don't care about the exception, just the outcome
// }
//
// $scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
// expect($scheduled)->count(0);
// }
//
// public function testNoSendingTaskQueuedIfSubscriberNoLongerSubscribedToSegment() {
// $segment = (new Segment())->create();
// $subscriber = (new Subscriber())
// ->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
// ->withSegments([$segment])
// ->create();
//
// $run = new WorkflowRun(1, 'some-trigger', [
// 'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
// 'mailpoet:segment' => $this->getSegmentSubject($segment),
// ]);
//
// $welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
// $step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
// $workflow = new Workflow('some-workflow', [$step]);
//
// /** @var SendWelcomeEmailAction $action */
// $action = $this->diContainer->get(SendWelcomeEmailAction::class);
//
// $subscriberModel = SubscriberModel::findOne($subscriber->getId());
// SubscriberSegment::unsubscribeFromSegments($subscriberModel, [$segment->getId()]);
//
// try {
// $action->run($workflow, $run, $step);
// } catch (Exception $e) {
// // We don't care about the exception, just the outcome
// }
//
// $scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
// expect($scheduled)->count(0);
// }
//
// public function testItDoesNotScheduleAnythingIfSubscriberHasBeenDeleted() {
// $segment = (new Segment())->create();
// $subscriber = (new Subscriber())
// ->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
// ->withSegments([$segment])
// ->create();
//
// $run = new WorkflowRun(1, 'some-trigger', [
// 'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
// 'mailpoet:segment' => $this->getSegmentSubject($segment),
// ]);
//
// $welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
// $step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
// $workflow = new Workflow('some-workflow', [$step]);
//
// /** @var SendWelcomeEmailAction $action */
// $action = $this->diContainer->get(SendWelcomeEmailAction::class);
//
// ContainerWrapper::getInstance()->get(SubscribersRepository::class)->bulkDelete([$subscriber->getId()]);
//
// try {
// $action->run($workflow, $run, $step);
// } catch (Exception $e) {
// // We don't care about the exception, just the outcome
// }
//
// $scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
// expect($scheduled)->count(0);
// }
public function testItDoesNotScheduleAnythingIfSegmentHasBeenDeleted() {
$segment = (new Segment())->create();
@@ -166,40 +167,48 @@ class SendWelcomeEmailActionTest extends \MailPoetTest {
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
ContainerWrapper::getInstance()->get(SegmentsRepository::class)->bulkDelete([$segment->getId()]);
try {
$action->run($workflow, $run, $step);
} catch (Exception $e) {
// We don't care about the exception, just the outcome
}
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
$result = $action->validate($workflow, $run, $step);
expect($result->getErrors())->count(1);
expect($result->getErrors()[0]->getMessage())->equals("Subscriber ID '1' is not subscribed to segment ID '1'.");
}
public function testItDoesNotScheduleADuplicateIfRunAgain() {
public function testHappyPath() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
->withSegments([$segment])
->create();
$run = new WorkflowRun(1, 'some-trigger', [
'mailpoet:subscriber' => $this->getSubscriberSubject($subscriber),
'mailpoet:segment' => $this->getSegmentSubject($segment),
]);
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['welcomeEmailId' => $welcomeEmail->getId()]);
$workflow = new Workflow('some-workflow', [$step]);
/** @var SendWelcomeEmailAction $action */
$result = new ActionValidationResult();
$result->setValidatedParam('welcomeEmail', $welcomeEmail);
$result->setValidatedParam('subscriberId', $subscriber->getId());
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
$action->run($workflow, $run, $step);
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
$action->run($result);
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(1);
}
public function testItDoesNotScheduleAgain() {
$segment = (new Segment())->create();
$subscriber = (new Subscriber())
->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)
->withSegments([$segment])
->create();
$welcomeEmail = (new Newsletter())->withWelcomeTypeForSegment($segment->getId())->create();
$result = new ActionValidationResult();
$result->setValidatedParam('welcomeEmail', $welcomeEmail);
$result->setValidatedParam('subscriberId', $subscriber->getId());
$action = $this->diContainer->get(SendWelcomeEmailAction::class);
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(0);
$action->run($result);
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());
expect($scheduled)->count(1);
try {
$action->run($workflow, $run, $step);
} catch (Exception $e) {
$action->run($result);
} catch (Exception $exception) {
// We don't care about the exception, just the outcome
}
$scheduled = $this->scheduledTasksRepository->findByNewsletterAndSubscriberId($welcomeEmail, (int)$subscriber->getId());