From 0aaf57b7329ce57a970be21eac49a4067fcff590 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 13:53:54 +0100 Subject: [PATCH 01/13] Remove support for older Laravel versions --- src/Sender.php | 13 +++-------- src/SentMessage.php | 25 ++------------------- tests/MailableReaderTest.php | 36 +----------------------------- tests/SenderTest.php | 17 +++++--------- tests/TestingMailEventListener.php | 22 ------------------ 5 files changed, 12 insertions(+), 101 deletions(-) delete mode 100644 tests/TestingMailEventListener.php diff --git a/src/Sender.php b/src/Sender.php index 60a80fe..72ce8a1 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -26,9 +26,8 @@ public function send(Email $email): void $this->buildMessage($message, $email); }); - // This is used so we can assert things on the sent message in Laravel 9+ since we cannot use - // the Swift Mailer plugin anymore. So this is purely used for in the PHPUnit tests. - if (version_compare(app()->version(), '9.0.0', '>=') && !is_null($sentMessage)) { + // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) + if (!is_null($sentMessage)) { event(new MessageSent($sentMessage)); } @@ -50,13 +49,7 @@ private function buildMessage(Message $message, Email $email): void ->subject($email->getSubject()) ->from($email->getFromAddress(), $email->getFromName()); - if (version_compare(app()->version(), '9.0.0', '>=')) { - // Symfony Mailer - $message->html($email->getBody()); - } else { - // SwiftMail - $message->setBody($email->getBody(), 'text/html'); - } + $message->html($email->getBody()); $attachmentMap = [ 'attachment' => 'attach', diff --git a/src/SentMessage.php b/src/SentMessage.php index 570d524..c58bbdb 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -4,7 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; -use Swift_Mime_SimpleMimeEntity; +use Symfony\Component\Mime\Email; use Symfony\Component\Mime\Part\DataPart; class SentMessage @@ -19,7 +19,7 @@ class SentMessage public $attachments = []; public $headers = []; - public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $email): SentMessage + public static function createFromSymfonyMailer(Email $email): SentMessage { $sentMessage = new self(); @@ -54,25 +54,4 @@ public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $em return $sentMessage; } - - public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message): SentMessage - { - $sentMessage = new self(); - - $sentMessage->from = $message->getFrom(); - $sentMessage->to = $message->getTo(); - $sentMessage->cc = $message->getCc(); - $sentMessage->bcc = $message->getBcc(); - $sentMessage->replyTo = $message->getReplyTo(); - $sentMessage->subject = $message->getSubject(); - $sentMessage->body = $message->getBody(); - $sentMessage->attachments = array_map(function(Swift_Mime_SimpleMimeEntity $entity) { - return [ - 'body' => $entity->getBody(), - 'disposition' => $entity->getContentType() . ' ' . $entity->getHeaders()->get('content-disposition'), - ]; - }, $message->getChildren()); - - return $sentMessage; - } } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index c8fbd11..1fc6acb 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -13,10 +13,6 @@ class MailableReaderTest extends TestCase { private function mailable(): Mailable { - if (version_compare(app()->version(), '10.0.0', '>=')) { - return new Laravel10TestMailable(); - } - return new TestMailable(); } @@ -121,41 +117,11 @@ public function it_extracts_the_from_address_and_or_name() ->from(null, 'Marick') )->send(); - // 8.x no longer accepts an empty address. - // https://github.com/laravel/framework/pull/39035 - if (version_compare(app()->version(), '8.0.0', '>=')) { - $this->assertFalse($email->hasFrom()); - } else { - $this->assertTrue($email->hasFrom()); - $this->assertEquals(config('mail.from.address'), $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); - } + $this->assertFalse($email->hasFrom()); } } class TestMailable extends Mailable -{ - /** - * Build the message. - * - * @return $this - */ - public function build() - { - return $this->to('john@doe.com') - ->cc(['john+cc@doe.com', 'john+cc2@doe.com']) - ->bcc(['john+bcc@doe.com', 'john+bcc2@doe.com']) - ->replyTo(['replyto@example.com', 'replyto2@example.com']) - ->subject('Your order has shipped!') - ->attach(__DIR__ . '/files/pdf-sample.pdf', [ - 'mime' => 'application/pdf', - ]) - ->attachData('

Thanks for your oder

', 'order.html') - ->view('tests::dummy', ['name' => 'John Doe']); - } -} - -class Laravel10TestMailable extends Mailable { public function content(): Content { diff --git a/tests/SenderTest.php b/tests/SenderTest.php index eb86b66..1745d45 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -6,28 +6,23 @@ use Illuminate\Support\Facades\Event; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; -use Swift_Events_SendEvent; use Illuminate\Support\Facades\Mail; use Stackkit\LaravelDatabaseEmails\Email; class SenderTest extends TestCase { - /** @var Swift_Events_SendEvent[] */ + /** @var array */ public $sent = []; public function setUp(): void { parent::setUp(); - if (version_compare(app()->version(), '9.0.0', '>=')) { - Event::listen(MessageSent::class, function (MessageSent $event) { - $this->sent[] = SentMessage::createFromSymfonyMailer( - $event->message->getSymfonySentMessage()->getOriginalMessage() - ); - }); - } else { - Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this)); - } + Event::listen(MessageSent::class, function (MessageSent $event) { + $this->sent[] = SentMessage::createFromSymfonyMailer( + $event->message->getSymfonySentMessage()->getOriginalMessage() + ); + }); } /** @test */ diff --git a/tests/TestingMailEventListener.php b/tests/TestingMailEventListener.php deleted file mode 100644 index 4fa32f1..0000000 --- a/tests/TestingMailEventListener.php +++ /dev/null @@ -1,22 +0,0 @@ -test = $test; - } - - public function beforeSendPerformed(\Swift_Events_Event $event) - { - $this->test->sent[] = SentMessage::createFromSwiftMailer($event->getMessage()); - } -} From a01f0c74f76d19941677574d8f68da1c72817e62 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:00:58 +0100 Subject: [PATCH 02/13] Update to new PHPUnit attributes --- phpunit.xml | 12 +++++----- tests/ConfigCacheTest.php | 5 +++-- tests/ConfigTest.php | 11 ++++----- tests/DatabaseInteractionTest.php | 37 ++++++++++++++++--------------- tests/EncryptionTest.php | 17 +++++++------- tests/MailableReaderTest.php | 17 +++++++------- tests/PruneTest.php | 6 ++--- tests/QueuedEmailsTest.php | 17 +++++++------- tests/SendEmailsCommandTest.php | 19 ++++++++-------- tests/SenderTest.php | 25 +++++++++++---------- tests/ValidatorTest.php | 25 +++++++++++---------- 11 files changed, 100 insertions(+), 91 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index eb3301d..a1ec749 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,5 @@ - - - - src/ - - + ./tests/ @@ -19,4 +14,9 @@ + + + src/ + + diff --git a/tests/ConfigCacheTest.php b/tests/ConfigCacheTest.php index 2851e6b..5b1fccc 100644 --- a/tests/ConfigCacheTest.php +++ b/tests/ConfigCacheTest.php @@ -2,18 +2,19 @@ namespace Tests; +use PHPUnit\Framework\Attributes\Test; use Throwable; class ConfigCacheTest extends TestCase { - /** @test */ + #[Test] public function the_configuration_file_can_be_cached() { $failed = false; try { serialize(require __DIR__ . '/../config/laravel-database-emails.php'); - } catch (Throwable $e) { + } catch (Throwable) { $failed = true; } diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index e6c376d..72c5b31 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -2,11 +2,12 @@ namespace Tests; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Config; class ConfigTest extends TestCase { - /** @test */ + #[Test] public function test_max_attempt_count() { $this->assertEquals(3, Config::maxAttemptCount()); @@ -16,7 +17,7 @@ public function test_max_attempt_count() $this->assertEquals(5, Config::maxAttemptCount()); } - /** @test */ + #[Test] public function test_encrypt_emails() { $this->assertFalse(Config::encryptEmails()); @@ -26,7 +27,7 @@ public function test_encrypt_emails() $this->assertTrue(Config::encryptEmails()); } - /** @test */ + #[Test] public function test_testing() { $this->assertFalse(Config::testing()); @@ -36,7 +37,7 @@ public function test_testing() $this->assertTrue(Config::testing()); } - /** @test */ + #[Test] public function test_test_email_address() { $this->assertEquals('test@email.com', Config::testEmailAddress()); @@ -46,7 +47,7 @@ public function test_test_email_address() $this->assertEquals('test+update@email.com', Config::testEmailAddress()); } - /** @test */ + #[Test] public function test_cronjob_email_limit() { $this->assertEquals(20, Config::cronjobEmailLimit()); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 1bc8d71..2eb67fb 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -4,10 +4,11 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; class DatabaseInteractionTest extends TestCase { - /** @test */ + #[Test] public function label_should_be_saved_correctly() { $email = $this->sendEmail(['label' => 'welcome-email']); @@ -16,7 +17,7 @@ public function label_should_be_saved_correctly() $this->assertEquals('welcome-email', $email->getLabel()); } - /** @test */ + #[Test] public function recipient_should_be_saved_correctly() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); @@ -24,7 +25,7 @@ public function recipient_should_be_saved_correctly() $this->assertEquals('john@doe.com', $email->getRecipient()); } - /** @test */ + #[Test] public function cc_and_bcc_should_be_saved_correctly() { $email = $this->sendEmail([ @@ -44,7 +45,7 @@ public function cc_and_bcc_should_be_saved_correctly() $this->assertEquals(['jane@doe.com'], $email->getBcc()); } - /** @test */ + #[Test] public function reply_to_should_be_saved_correctly() { $email = $this->sendEmail([ @@ -58,7 +59,7 @@ public function reply_to_should_be_saved_correctly() $this->assertEquals(['john@doe.com'], $email->getReplyTo()); } - /** @test */ + #[Test] public function subject_should_be_saved_correclty() { $email = $this->sendEmail(['subject' => 'test subject']); @@ -67,7 +68,7 @@ public function subject_should_be_saved_correclty() $this->assertEquals('test subject', $email->getSubject()); } - /** @test */ + #[Test] public function view_should_be_saved_correctly() { $email = $this->sendEmail(['view' => 'tests::dummy']); @@ -76,7 +77,7 @@ public function view_should_be_saved_correctly() $this->assertEquals('tests::dummy', $email->getView()); } - /** @test */ + #[Test] public function encrypted_should_be_saved_correctly() { $email = $this->sendEmail(); @@ -92,7 +93,7 @@ public function encrypted_should_be_saved_correctly() $this->assertTrue($email->isEncrypted()); } - /** @test */ + #[Test] public function scheduled_date_should_be_saved_correctly() { $email = $this->sendEmail(); @@ -105,7 +106,7 @@ public function scheduled_date_should_be_saved_correctly() $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate()); } - /** @test */ + #[Test] public function the_body_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -116,7 +117,7 @@ public function the_body_should_be_saved_correctly() $this->assertSame($expectedBody, $email->getBody()); } - /** @test */ + #[Test] public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); @@ -132,7 +133,7 @@ public function from_should_be_saved_correctly() $this->assertEquals('Marick', $email->getFromName()); } - /** @test */ + #[Test] public function variables_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); @@ -141,7 +142,7 @@ public function variables_should_be_saved_correctly() $this->assertEquals(['name' => 'John Doe'], $email->getVariables()); } - /** @test */ + #[Test] public function the_sent_date_should_be_null() { $email = $this->sendEmail(); @@ -150,7 +151,7 @@ public function the_sent_date_should_be_null() $this->assertNull($email->getSendDate()); } - /** @test */ + #[Test] public function failed_should_be_zero() { $email = $this->sendEmail(); @@ -159,7 +160,7 @@ public function failed_should_be_zero() $this->assertFalse($email->hasFailed()); } - /** @test */ + #[Test] public function attempts_should_be_zero() { $email = $this->sendEmail(); @@ -168,7 +169,7 @@ public function attempts_should_be_zero() $this->assertEquals(0, $email->getAttempts()); } - /** @test */ + #[Test] public function the_scheduled_date_should_be_saved_correctly() { Carbon::setTestNow(Carbon::now()); @@ -181,7 +182,7 @@ public function the_scheduled_date_should_be_saved_correctly() $this->assertEquals($scheduledFor, $email->getScheduledDate()); } - /** @test */ + #[Test] public function recipient_should_be_swapped_for_test_address_when_in_testing_mode() { $this->app['config']->set('laravel-database-emails.testing.enabled', function () { @@ -194,7 +195,7 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod $this->assertEquals('test@address.com', $email->getRecipient()); } - /** @test */ + #[Test] public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() @@ -219,7 +220,7 @@ public function attachments_should_be_saved_correctly() $this->assertEquals(__DIR__ . '/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); } - /** @test */ + #[Test] public function in_memory_attachments_should_be_saved_correctly() { $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index 36a9328..e835512 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -3,6 +3,7 @@ namespace Tests; use Illuminate\Mail\Mailables\Address; +use PHPUnit\Framework\Attributes\Test; class EncryptionTest extends TestCase { @@ -15,7 +16,7 @@ public function setUp(): void $this->sendEmail(); } - /** @test */ + #[Test] public function an_email_should_be_marked_as_encrypted() { $email = $this->sendEmail(); @@ -23,7 +24,7 @@ public function an_email_should_be_marked_as_encrypted() $this->assertTrue($email->isEncrypted()); } - /** @test */ + #[Test] public function the_recipient_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); @@ -33,7 +34,7 @@ public function the_recipient_should_be_encrypted_and_decrypted() $this->assertEquals('john@doe.com', $email->getRecipient()); } - /** @test */ + #[Test] public function cc_and_bb_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ @@ -48,7 +49,7 @@ public function cc_and_bb_should_be_encrypted_and_decrypted() $this->assertEquals($bcc, $email->getBcc()); } - /** @test */ + #[Test] public function reply_to_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ @@ -79,7 +80,7 @@ public function reply_to_should_be_encrypted_and_decrypted() $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo()); } - /** @test */ + #[Test] public function the_subject_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['subject' => 'test subject']); @@ -89,7 +90,7 @@ public function the_subject_should_be_encrypted_and_decrypted() $this->assertEquals('test subject', $email->getSubject()); } - /** @test */ + #[Test] public function the_variables_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -105,7 +106,7 @@ public function the_variables_should_be_encrypted_and_decrypted() ); } - /** @test */ + #[Test] public function the_body_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -117,7 +118,7 @@ public function the_body_should_be_encrypted_and_decrypted() $this->assertEquals($expectedBody, $email->getBody()); } - /** @test */ + #[Test] public function from_should_be_encrypted_and_decrypted() { $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 1fc6acb..6691d8e 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -7,6 +7,7 @@ use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class MailableReaderTest extends TestCase @@ -16,7 +17,7 @@ private function mailable(): Mailable return new TestMailable(); } - /** @test */ + #[Test] public function it_extracts_the_recipient() { $composer = Email::compose() @@ -34,7 +35,7 @@ public function it_extracts_the_recipient() $this->assertContains('jane@doe.com', $composer->getData('recipient')); } - /** @test */ + #[Test] public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -42,7 +43,7 @@ public function it_extracts_cc_addresses() $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc')); } - /** @test */ + #[Test] public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -50,7 +51,7 @@ public function it_extracts_bcc_addresses() $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); } - /** @test */ + #[Test] public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -58,7 +59,7 @@ public function it_extracts_reply_to_addresses() $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to')); } - /** @test */ + #[Test] public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); @@ -66,7 +67,7 @@ public function it_extracts_the_subject() $this->assertEquals('Your order has shipped!', $composer->getData('subject')); } - /** @test */ + #[Test] public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); @@ -74,7 +75,7 @@ public function it_extracts_the_body() $this->assertEquals("Name: John Doe\n", $composer->getData('body')); } - /** @test */ + #[Test] public function it_extracts_attachments() { $email = Email::compose()->mailable($this->mailable())->send(); @@ -91,7 +92,7 @@ public function it_extracts_attachments() $this->assertEquals('

Thanks for your oder

', $attachments[1]['attachment']['data']); } - /** @test */ + #[Test] public function it_extracts_the_from_address_and_or_name() { $email = Email::compose()->mailable( diff --git a/tests/PruneTest.php b/tests/PruneTest.php index 03a51cc..ddcf8f1 100644 --- a/tests/PruneTest.php +++ b/tests/PruneTest.php @@ -2,13 +2,13 @@ namespace Tests; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class PruneTest extends TestCase { - /** @test */ + #[Test] public function by_default_mails_are_pruned_after_6_months() { $email = $this->sendEmail(); @@ -29,7 +29,7 @@ public function by_default_mails_are_pruned_after_6_months() $this->assertNull($email->fresh()); } - /** @test */ + #[Test] public function can_change_when_emails_are_pruned() { Email::pruneWhen(function (Email $email) { diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 0bb7b96..a0b277d 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -3,6 +3,7 @@ namespace Tests; use Illuminate\Support\Facades\Queue; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; use Illuminate\Support\Facades\Mail; @@ -13,7 +14,7 @@ public function setUp(): void parent::setUp(); } - /** @test */ + #[Test] public function queueing_an_email_will_leave_sending_on_false() { $email = $this->queueEmail(); @@ -21,7 +22,7 @@ public function queueing_an_email_will_leave_sending_on_false() $this->assertEquals(0, $email->sending); } - /** @test */ + #[Test] public function queueing_an_email_will_set_the_queued_at_column() { $email = $this->queueEmail(); @@ -29,7 +30,7 @@ public function queueing_an_email_will_set_the_queued_at_column() $this->assertNotNull($email->queued_at); } - /** @test */ + #[Test] public function queueing_an_email_will_dispatch_a_job() { Queue::fake(); @@ -41,7 +42,7 @@ public function queueing_an_email_will_dispatch_a_job() }); } - /** @test */ + #[Test] public function emails_can_be_queued_on_a_specific_connection() { Queue::fake(); @@ -53,7 +54,7 @@ public function emails_can_be_queued_on_a_specific_connection() }); } - /** @test */ + #[Test] public function emails_can_be_queued_on_a_specific_queue() { Queue::fake(); @@ -65,7 +66,7 @@ public function emails_can_be_queued_on_a_specific_queue() }); } - /** @test */ + #[Test] public function emails_can_be_queued_with_a_delay() { Queue::fake(); @@ -79,7 +80,7 @@ public function emails_can_be_queued_with_a_delay() }); } - /** @test */ + #[Test] public function the_send_email_job_will_call_send_on_the_email_instance() { Queue::fake(); @@ -93,7 +94,7 @@ public function the_send_email_job_will_call_send_on_the_email_instance() $job->handle(); } - /** @test */ + #[Test] public function the_mail_will_be_marked_as_sent_when_job_is_finished() { Queue::fake(); diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index ccd38d7..00b52f1 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -5,11 +5,12 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Queue; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Store; class SendEmailsCommandTest extends TestCase { - /** @test */ + #[Test] public function an_email_should_be_marked_as_sent() { $email = $this->sendEmail(); @@ -19,7 +20,7 @@ public function an_email_should_be_marked_as_sent() $this->assertNotNull($email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function the_number_of_attempts_should_be_incremented() { $email = $this->sendEmail(); @@ -31,7 +32,7 @@ public function the_number_of_attempts_should_be_incremented() $this->assertEquals(1, $email->fresh()->getAttempts()); } - /** @test */ + #[Test] public function an_email_should_not_be_sent_once_it_is_marked_as_sent() { $email = $this->sendEmail(); @@ -46,7 +47,7 @@ public function an_email_should_not_be_sent_once_it_is_marked_as_sent() $this->assertEquals($firstSend, $email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function an_email_should_not_be_sent_if_it_is_queued() { Queue::fake(); @@ -58,7 +59,7 @@ public function an_email_should_not_be_sent_if_it_is_queued() $this->assertNull($email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database() { $email = $this->sendEmail(); @@ -71,7 +72,7 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database $this->assertStringContainsString('RfcComplianceException', $email->fresh()->getError()); } - /** @test */ + #[Test] public function the_number_of_emails_sent_per_minute_should_be_limited() { for ($i = 1; $i <= 30; $i++) { @@ -85,7 +86,7 @@ public function the_number_of_emails_sent_per_minute_should_be_limited() $this->assertEquals(5, DB::table('emails')->whereNull('sent_at')->count()); } - /** @test */ + #[Test] public function an_email_should_never_be_sent_before_its_scheduled_date() { $email = $this->scheduleEmail(Carbon::now()->addHour(1)); @@ -101,7 +102,7 @@ public function an_email_should_never_be_sent_before_its_scheduled_date() $this->assertNotNull($email->getSendDate()); } - /** @test */ + #[Test] public function emails_will_be_sent_until_max_try_count_has_been_reached() { $this->app['config']['mail.driver'] = 'does-not-exist'; @@ -116,7 +117,7 @@ public function emails_will_be_sent_until_max_try_count_has_been_reached() $this->assertCount(0, (new Store)->getQueue()); } - /** @test */ + #[Test] public function the_failed_status_and_error_is_cleared_if_a_previously_failed_email_is_sent_succesfully() { $email = $this->sendEmail(); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 1745d45..b6a6907 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -4,6 +4,7 @@ use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Event; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; use Illuminate\Support\Facades\Mail; @@ -25,7 +26,7 @@ public function setUp(): void }); } - /** @test */ + #[Test] public function it_sends_an_email() { $this->sendEmail(); @@ -35,7 +36,7 @@ public function it_sends_an_email() $this->artisan('email:send'); } - /** @test */ + #[Test] public function the_email_has_a_correct_from_email_and_from_name() { $this->app['config']->set('mail.from.address', 'testfromaddress@gmail.com'); @@ -76,7 +77,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->assertEquals('Marick', $from[key($from)]); } - /** @test */ + #[Test] public function it_sends_emails_to_the_correct_recipients() { $this->sendEmail(['recipient' => 'john@doe.com']); @@ -94,7 +95,7 @@ public function it_sends_emails_to_the_correct_recipients() $this->assertArrayHasKey('john+2@doe.com', $to); } - /** @test */ + #[Test] public function it_adds_the_cc_addresses() { $this->sendEmail(['cc' => 'cc@test.com']); @@ -112,7 +113,7 @@ public function it_adds_the_cc_addresses() $this->assertArrayHasKey('cc+2@test.com', $cc); } - /** @test */ + #[Test] public function it_adds_the_bcc_addresses() { $this->sendEmail(['bcc' => 'bcc@test.com']); @@ -130,7 +131,7 @@ public function it_adds_the_bcc_addresses() $this->assertArrayHasKey('bcc+2@test.com', $bcc); } - /** @test */ + #[Test] public function the_email_has_the_correct_subject() { $this->sendEmail(['subject' => 'Hello World']); @@ -142,7 +143,7 @@ public function the_email_has_the_correct_subject() $this->assertEquals('Hello World', $subject); } - /** @test */ + #[Test] public function the_email_has_the_correct_body() { $this->sendEmail(['variables' => ['name' => 'John Doe']]); @@ -157,7 +158,7 @@ public function the_email_has_the_correct_body() $this->assertEquals(view('tests::dummy'), $body); } - /** @test */ + #[Test] public function attachments_are_added_to_the_email() { $this->composeEmail() @@ -170,7 +171,7 @@ public function attachments_are_added_to_the_email() $this->assertCount(1, $attachments); } - /** @test */ + #[Test] public function raw_attachments_are_added_to_the_email() { $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); @@ -191,7 +192,7 @@ public function raw_attachments_are_added_to_the_email() $this->assertTrue(md5($attachment['body']) == md5($rawData)); } - /** @test */ + #[Test] public function old_json_encoded_attachments_can_still_be_read() { $email = $this->sendEmail(); @@ -206,7 +207,7 @@ public function old_json_encoded_attachments_can_still_be_read() $this->assertEquals([4, 5, 6], $email->fresh()->getAttachments()); } - /** @test */ + #[Test] public function emails_can_be_sent_immediately() { $this->app['config']->set('laravel-database-emails.immediately', false); @@ -222,7 +223,7 @@ public function emails_can_be_sent_immediately() $this->assertCount(1, $this->sent); } - /** @test */ + #[Test] public function it_adds_the_reply_to_addresses() { $this->sendEmail(['reply_to' => 'replyto@test.com']); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index b8b02ce..45ff991 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -4,11 +4,12 @@ use Carbon; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class ValidatorTest extends TestCase { - /** @test */ + #[Test] public function a_label_cannot_contain_more_than_255_characters() { $this->expectException(InvalidArgumentException::class); @@ -18,7 +19,7 @@ public function a_label_cannot_contain_more_than_255_characters() ->send(); } - /** @test */ + #[Test] public function a_recipient_is_required() { $this->expectException(InvalidArgumentException::class); @@ -28,7 +29,7 @@ public function a_recipient_is_required() ->send(); } - /** @test */ + #[Test] public function a_recipient_cannot_be_empty() { $this->expectException(InvalidArgumentException::class); @@ -39,7 +40,7 @@ public function a_recipient_cannot_be_empty() ->send(); } - /** @test */ + #[Test] public function the_recipient_email_must_be_valid() { $this->expectException(InvalidArgumentException::class); @@ -50,7 +51,7 @@ public function the_recipient_email_must_be_valid() ->send(); } - /** @test */ + #[Test] public function cc_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -65,7 +66,7 @@ public function cc_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function bcc_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -80,7 +81,7 @@ public function bcc_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function reply_to_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -95,7 +96,7 @@ public function reply_to_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function a_subject_is_required() { $this->expectException(InvalidArgumentException::class); @@ -106,7 +107,7 @@ public function a_subject_is_required() ->send(); } - /** @test */ + #[Test] public function a_view_is_required() { $this->expectException(InvalidArgumentException::class); @@ -118,7 +119,7 @@ public function a_view_is_required() ->send(); } - /** @test */ + #[Test] public function the_view_must_exist() { // this view exists, if error thrown -> fail test @@ -142,7 +143,7 @@ public function the_view_must_exist() ->send(); } - /** @test */ + #[Test] public function variables_must_be_defined_as_an_array() { $email = Email::compose() @@ -168,7 +169,7 @@ public function variables_must_be_defined_as_an_array() } } - /** @test */ + #[Test] public function the_scheduled_date_must_be_a_carbon_instance_or_a_valid_date() { // invalid From bc9c2c257e9ed140b9f5129aec8300ec1d4d9ae5 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:49:16 +0100 Subject: [PATCH 03/13] wip --- .github/workflows/run-tests.yml | 35 ++++++++++----- composer.json | 30 ++++++++++--- docker-compose.yml | 18 ++++++++ testbench.yaml | 23 ++++++++++ tests/QueuedEmailsTest.php | 9 ++-- tests/TestCase.php | 44 +++++++++++-------- workbench/app/Models/.gitkeep | 0 .../Providers/WorkbenchServiceProvider.php | 24 ++++++++++ workbench/bootstrap/.gitkeep | 0 workbench/bootstrap/app.php | 19 ++++++++ workbench/bootstrap/providers.php | 5 +++ workbench/database/factories/.gitkeep | 0 workbench/database/migrations/.gitkeep | 0 workbench/database/seeders/.gitkeep | 0 workbench/database/seeders/DatabaseSeeder.php | 17 +++++++ workbench/resources/views/.gitkeep | 0 workbench/routes/.gitkeep | 0 workbench/routes/console.php | 8 ++++ workbench/routes/web.php | 7 +++ 19 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 docker-compose.yml create mode 100644 testbench.yaml create mode 100644 workbench/app/Models/.gitkeep create mode 100644 workbench/app/Providers/WorkbenchServiceProvider.php create mode 100644 workbench/bootstrap/.gitkeep create mode 100644 workbench/bootstrap/app.php create mode 100644 workbench/bootstrap/providers.php create mode 100644 workbench/database/factories/.gitkeep create mode 100644 workbench/database/migrations/.gitkeep create mode 100644 workbench/database/seeders/.gitkeep create mode 100644 workbench/database/seeders/DatabaseSeeder.php create mode 100644 workbench/resources/views/.gitkeep create mode 100644 workbench/routes/.gitkeep create mode 100644 workbench/routes/console.php create mode 100644 workbench/routes/web.php diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 22a5b6b..39e170d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,23 +62,22 @@ jobs: needs: access_check strategy: matrix: + db: [ 'mysql', 'sqlite' ] payload: - - { laravel: '11.*', php: '8.3', 'testbench': '9.*' } - - { laravel: '11.*', php: '8.2', 'testbench': '9.*' } - - { laravel: '10.*', php: '8.3', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.2', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.1', 'testbench': '8.*' } + - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } + - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } + - { laravel: '10.*', php: '8.3', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.2', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.1', 'testbench': '8.*', collision: '7.*' } - name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} + name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} services: mysql: image: mysql:8 env: - MYSQL_USER: root - MYSQL_ROOT_PASSWORD: root - MYSQL_PASSWORD: - MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_USER: test + MYSQL_PASSWORD: test MYSQL_DATABASE: test ports: - 3307:3306 @@ -97,9 +96,23 @@ jobs: extensions: mbstring, dom, fileinfo, mysql coverage: none + - name: Set up MySQL and PostgreSQL + run: | + MYSQL_PORT=3307 POSTGRES_PORT=5432 docker compose up ${{ matrix.db }} -d + - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" "nunomaduro/collision:${{ matrix.payload.collision }}" --no-interaction --no-update composer update --prefer-stable --prefer-dist --no-interaction + if [ "${{ matrix.db }}" = "mysql" ]; then + while ! mysqladmin ping --host=127.0.0.1 --user=test --port=3307 --password=test --silent; do + echo "Waiting for MySQL..." + sleep 1 + done + else + echo "Not waiting for MySQL." + fi - name: Execute tests + env: + DB_DRIVER: ${{ matrix.db }} run: composer test diff --git a/composer.json b/composer.json index ed7846f..c3df63c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,10 @@ }, "autoload-dev": { "psr-4": { - "Tests\\": "tests/" + "Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" } }, "extra": { @@ -32,19 +35,36 @@ "require-dev": { "mockery/mockery": "^1.2", "orchestra/testbench": "^8.0|^9.0", + "nunomaduro/collision": "^7.0|^8.0", "laravel/pint": "^1.14" }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { "l11": [ - "composer update laravel/framework:11.* orchestra/testbench:9.* --with-all-dependencies" + "composer update laravel/framework:11.* orchestra/testbench:9.* nunomaduro/collision:8.* --with-all-dependencies" ], "l10": [ - "composer update laravel/framework:10.* orchestra/testbench:8.* --with-all-dependencies" + "composer update laravel/framework:10.* orchestra/testbench:8.* nunomaduro/collision:7.* --with-all-dependencies" ], "test": [ - "CI_DB_DRIVER=sqlite CI_DB_DATABASE=:memory: phpunit" + "testbench package:test" + ], + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve" + ], + "lint": [ + "@php vendor/bin/pint", + "@php vendor/bin/phpstan analyse" ] } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..972092b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + mysql: + image: mysql:8 + ports: + - '${MYSQL_PORT:-3307}:3306' + environment: + MYSQL_USER: 'test' + MYSQL_PASSWORD: 'test' + MYSQL_DATABASE: 'test' + MYSQL_RANDOM_ROOT_PASSWORD: true + pgsql: + image: postgres:14 + ports: + - '${POSTGRES_PORT:-5432}:5432' + environment: + POSTGRES_USER: 'test' + POSTGRES_PASSWORD: 'test' + POSTGRES_DB: 'test' diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 0000000..707fae0 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,23 @@ +providers: + - Workbench\App\Providers\WorkbenchServiceProvider + - Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider + +migrations: + - workbench/database/migrations + +seeders: + - Workbench\Database\Seeders\DatabaseSeeder + +workbench: + start: '/' + install: true + health: false + discovers: + web: true + api: false + commands: false + components: false + views: false + build: [] + assets: [] + sync: [] diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index a0b277d..560fee1 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -9,14 +9,11 @@ class QueuedEmailsTest extends TestCase { - public function setUp(): void - { - parent::setUp(); - } - #[Test] public function queueing_an_email_will_leave_sending_on_false() { + Queue::fake(); + $email = $this->queueEmail(); $this->assertEquals(0, $email->sending); @@ -25,6 +22,8 @@ public function queueing_an_email_will_leave_sending_on_false() #[Test] public function queueing_an_email_will_set_the_queued_at_column() { + Queue::fake(); + $email = $this->queueEmail(); $this->assertNotNull($email->queued_at); diff --git a/tests/TestCase.php b/tests/TestCase.php index 482aded..7e3a78d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,12 +2,18 @@ namespace Tests; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Foundation\Testing\RefreshDatabase; use Stackkit\LaravelDatabaseEmails\Email; +use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; class TestCase extends \Orchestra\Testbench\TestCase { protected $invalid; + use LazilyRefreshDatabase; + public function setUp(): void { parent::setUp(); @@ -31,20 +37,10 @@ function () { Email::truncate(); } - /** - * Get package providers. At a minimum this is the package being tested, but also - * would include packages upon which our package depends, e.g. Cartalyst/Sentry - * In a normal app environment these would be added to the 'providers' array in - * the config/app.php file. - * - * @param \Illuminate\Foundation\Application $app - * - * @return array - */ protected function getPackageProviders($app) { return [ - \Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider::class, + LaravelDatabaseEmailsServiceProvider::class, ]; } @@ -61,15 +57,25 @@ protected function getEnvironmentSetUp($app) $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); $app['config']->set('database.default', 'testbench'); + $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ - 'driver' => getenv('CI_DB_DRIVER'), - 'host' => getenv('CI_DB_HOST'), - 'port' => getenv('CI_DB_PORT'), - 'database' => getenv('CI_DB_DATABASE'), - 'username' => getenv('CI_DB_USERNAME'), - 'password' => getenv('CI_DB_PASSWORD'), - 'prefix' => '', - 'strict' => true, + 'driver' => $driver, + ...match($driver) { + 'sqlite' => [ + 'database' => ':memory:', + ], + 'mysql' => [ + 'host' => '127.0.0.1', + 'port' => 3307, + ], + 'pgsql' => [ + 'host' => '127.0.0.1', + 'port' => 5432, + ], + }, + 'database' => 'test', + 'username' => 'test', + 'password' => 'test', ]); $app['config']->set('mail.driver', 'log'); diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 0000000..e8cec9c --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php new file mode 100644 index 0000000..3ac44ad --- /dev/null +++ b/workbench/bootstrap/providers.php @@ -0,0 +1,5 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 0000000..86a06c5 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,7 @@ + Date: Sat, 16 Mar 2024 14:49:59 +0100 Subject: [PATCH 04/13] Pint --- .../2017_12_14_151403_create_emails_table.php | 4 +- ...151421_add_attachments_to_emails_table.php | 4 +- ..._12_22_114011_add_from_to_emails_table.php | 4 +- ...02_21000_add_queued_at_to_emails_table.php | 4 +- ...28_140000_add_reply_to_to_emails_table.php | 4 +- src/Config.php | 12 -- src/Email.php | 103 +++--------------- src/EmailComposer.php | 67 ++---------- src/Encrypter.php | 20 +--- src/HasEncryptedAttributes.php | 2 +- src/LaravelDatabaseEmailsServiceProvider.php | 18 +-- src/MailableReader.php | 22 +--- src/Preparer.php | 35 +----- src/SendEmailsCommand.php | 7 -- src/Sender.php | 9 +- src/SentMessage.php | 8 ++ src/Validator.php | 29 ++--- tests/ConfigCacheTest.php | 2 +- tests/DatabaseInteractionTest.php | 16 +-- tests/EncryptionTest.php | 10 +- tests/MailableReaderTest.php | 8 +- tests/PruneTest.php | 8 +- tests/QueuedEmailsTest.php | 2 +- tests/SendEmailsCommandTest.php | 4 +- tests/SenderTest.php | 8 +- tests/TestCase.php | 22 ++-- workbench/database/seeders/DatabaseSeeder.php | 1 - 27 files changed, 104 insertions(+), 329 deletions(-) diff --git a/database/migrations/2017_12_14_151403_create_emails_table.php b/database/migrations/2017_12_14_151403_create_emails_table.php index dc49d1f..23489da 100644 --- a/database/migrations/2017_12_14_151403_create_emails_table.php +++ b/database/migrations/2017_12_14_151403_create_emails_table.php @@ -1,8 +1,8 @@ update([ 'attempts' => $this->attempts + 1, - 'sending' => 1, + 'sending' => 1, ]); } /** * Mark the e-mail as sent. - * - * @return void */ public function markAsSent(): void { @@ -490,30 +424,25 @@ public function markAsSent(): void $this->update([ 'sending' => 0, 'sent_at' => $now, - 'failed' => 0, - 'error' => '', + 'failed' => 0, + 'error' => '', ]); } /** * Mark the e-mail as failed. - * - * @param Exception $exception - * @return void */ public function markAsFailed(Exception $exception): void { $this->update([ 'sending' => 0, - 'failed' => 1, - 'error' => (string) $exception, + 'failed' => 1, + 'error' => (string) $exception, ]); } /** * Send the e-mail. - * - * @return void */ public function send(): void { @@ -522,8 +451,6 @@ public function send(): void /** * Retry sending the e-mail. - * - * @return void */ public function retry(): void { @@ -531,12 +458,12 @@ public function retry(): void $retry->fill( [ - 'id' => null, - 'attempts' => 0, - 'sending' => 0, - 'failed' => 0, - 'error' => null, - 'sent_at' => null, + 'id' => null, + 'attempts' => 0, + 'sending' => 0, + 'failed' => 0, + 'error' => null, + 'sent_at' => null, 'delivered_at' => null, ] ); @@ -545,11 +472,10 @@ public function retry(): void } /** - * @param string $key - * @param mixed $default + * @param mixed $default * @return mixed */ - public function getRawDatabaseValue(string $key = null, $default = null) + public function getRawDatabaseValue(?string $key = null, $default = null) { if (method_exists($this, 'getRawOriginal')) { return $this->getRawOriginal($key, $default); @@ -559,7 +485,6 @@ public function getRawDatabaseValue(string $key = null, $default = null) } /** - * @param Closure $closure * @return void */ public static function pruneWhen(Closure $closure) diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 352e63e..905d0a1 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -24,8 +24,6 @@ class EmailComposer /** * Create a new EmailComposer instance. - * - * @param Email $email */ public function __construct(Email $email) { @@ -34,8 +32,6 @@ public function __construct(Email $email) /** * Get the e-mail that is being composed. - * - * @return Email */ public function getEmail(): Email { @@ -45,9 +41,7 @@ public function getEmail(): Email /** * Set a data value. * - * @param string $key - * @param mixed $value - * @return self + * @param mixed $value */ public function setData(string $key, $value): self { @@ -59,8 +53,7 @@ public function setData(string $key, $value): self /** * Get a data value. * - * @param string $key - * @param mixed $default + * @param mixed $default * @return mixed */ public function getData(string $key, $default = null) @@ -74,9 +67,6 @@ public function getData(string $key, $default = null) /** * Determine if the given data value was set. - * - * @param string $key - * @return bool */ public function hasData(string $key): bool { @@ -85,9 +75,6 @@ public function hasData(string $key): bool /** * Set the e-mail label. - * - * @param string $label - * @return self */ public function label(string $label): self { @@ -96,10 +83,6 @@ public function label(string $label): self /** * Set the e-mail from address and aname. - * - * @param string|null $address - * @param string|null $name - * @return self */ public function from(?string $address = null, ?string $name = null): self { @@ -109,8 +92,7 @@ public function from(?string $address = null, ?string $name = null): self /** * Set the e-mail recipient(s). * - * @param string|array $recipient - * @return self + * @param string|array $recipient */ public function recipient($recipient): self { @@ -120,8 +102,7 @@ public function recipient($recipient): self /** * Define the carbon-copy address(es). * - * @param string|array $cc - * @return self + * @param string|array $cc */ public function cc($cc): self { @@ -131,8 +112,7 @@ public function cc($cc): self /** * Define the blind carbon-copy address(es). * - * @param string|array $bcc - * @return self + * @param string|array $bcc */ public function bcc($bcc): self { @@ -142,8 +122,7 @@ public function bcc($bcc): self /** * Define the reply-to address(es). * - * @param string|array $replyTo - * @return self + * @param string|array $replyTo */ public function replyTo($replyTo): self { @@ -152,9 +131,6 @@ public function replyTo($replyTo): self /** * Set the e-mail subject. - * - * @param string $subject - * @return self */ public function subject(string $subject): self { @@ -163,9 +139,6 @@ public function subject(string $subject): self /** * Set the e-mail view. - * - * @param string $view - * @return self */ public function view(string $view): self { @@ -174,9 +147,6 @@ public function view(string $view): self /** * Set the e-mail variables. - * - * @param array $variables - * @return self */ public function variables(array $variables): self { @@ -186,8 +156,7 @@ public function variables(array $variables): self /** * Schedule the e-mail. * - * @param mixed $scheduledAt - * @return Email + * @param mixed $scheduledAt */ public function schedule($scheduledAt): Email { @@ -197,8 +166,7 @@ public function schedule($scheduledAt): Email /** * Schedule the e-mail. * - * @param mixed $scheduledAt - * @return Email + * @param mixed $scheduledAt */ public function later($scheduledAt): Email { @@ -210,10 +178,7 @@ public function later($scheduledAt): Email /** * Queue the e-mail. * - * @param string|null $connection - * @param string|null $queue - * @param \DateTimeInterface|\DateInterval|int|null $delay - * @return Email + * @param \DateTimeInterface|\DateInterval|int|null $delay */ public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email { @@ -230,9 +195,6 @@ public function queue(?string $connection = null, ?string $queue = null, $delay /** * Set the Mailable. - * - * @param Mailable $mailable - * @return self */ public function mailable(Mailable $mailable): self { @@ -245,10 +207,6 @@ public function mailable(Mailable $mailable): self /** * Attach a file to the e-mail. - * - * @param string $file - * @param array $options - * @return self */ public function attach(string $file, array $options = []): self { @@ -261,11 +219,6 @@ public function attach(string $file, array $options = []): self /** * Attach in-memory data as an attachment. - * - * @param string $data - * @param string $name - * @param array $options - * @return self */ public function attachData(string $data, string $name, array $options = []): self { @@ -278,8 +231,6 @@ public function attachData(string $data, string $name, array $options = []): sel /** * Send the e-mail. - * - * @return Email */ public function send(): Email { diff --git a/src/Encrypter.php b/src/Encrypter.php index 14052a7..7045c2c 100644 --- a/src/Encrypter.php +++ b/src/Encrypter.php @@ -8,8 +8,6 @@ class Encrypter { /** * Encrypt the given e-mail. - * - * @param EmailComposer $composer */ public function encrypt(EmailComposer $composer): void { @@ -30,8 +28,6 @@ public function encrypt(EmailComposer $composer): void /** * Mark the e-mail as encrypted. - * - * @param EmailComposer $composer */ private function setEncrypted(EmailComposer $composer): void { @@ -40,8 +36,6 @@ private function setEncrypted(EmailComposer $composer): void /** * Encrypt the e-mail reply-to. - * - * @param EmailComposer $composer */ private function encryptReplyTo(EmailComposer $composer): void { @@ -54,8 +48,6 @@ private function encryptReplyTo(EmailComposer $composer): void /** * Encrypt the e-mail addresses of the recipients. - * - * @param EmailComposer $composer */ private function encryptRecipients(EmailComposer $composer): void { @@ -63,15 +55,13 @@ private function encryptRecipients(EmailComposer $composer): void $email->fill([ 'recipient' => encrypt($email->recipient), - 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', - 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', + 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', + 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', ]); } /** * Encrypt the e-mail addresses for the from field. - * - * @param EmailComposer $composer */ private function encryptFrom(EmailComposer $composer): void { @@ -84,8 +74,6 @@ private function encryptFrom(EmailComposer $composer): void /** * Encrypt the e-mail subject. - * - * @param EmailComposer $composer */ private function encryptSubject(EmailComposer $composer): void { @@ -98,8 +86,6 @@ private function encryptSubject(EmailComposer $composer): void /** * Encrypt the e-mail variables. - * - * @param EmailComposer $composer */ private function encryptVariables(EmailComposer $composer): void { @@ -116,8 +102,6 @@ private function encryptVariables(EmailComposer $composer): void /** * Encrypt the e-mail body. - * - * @param EmailComposer $composer */ private function encryptBody(EmailComposer $composer): void { diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index 01dfbb4..7d02012 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -41,7 +41,7 @@ trait HasEncryptedAttributes /** * Get an attribute from the model. * - * @param string $key + * @param string $key * @return mixed */ public function getAttribute($key) diff --git a/src/LaravelDatabaseEmailsServiceProvider.php b/src/LaravelDatabaseEmailsServiceProvider.php index 57e249b..a7b898c 100644 --- a/src/LaravelDatabaseEmailsServiceProvider.php +++ b/src/LaravelDatabaseEmailsServiceProvider.php @@ -10,8 +10,6 @@ class LaravelDatabaseEmailsServiceProvider extends ServiceProvider { /** * Perform post-registration booting of services. - * - * @return void */ public function boot(): void { @@ -21,28 +19,24 @@ public function boot(): void /** * Boot the config for the package. - * - * @return void */ private function bootConfig(): void { - $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; - $configDir = $baseDir . 'config' . DIRECTORY_SEPARATOR; + $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; + $configDir = $baseDir.'config'.DIRECTORY_SEPARATOR; $this->publishes([ - $configDir . 'laravel-database-emails.php' => config_path('laravel-database-emails.php'), + $configDir.'laravel-database-emails.php' => config_path('laravel-database-emails.php'), ], 'laravel-database-emails-config'); } /** * Boot the database for the package. - * - * @return void */ private function bootDatabase(): void { - $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; - $migrationsDir = $baseDir . 'database' . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR; + $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; + $migrationsDir = $baseDir.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR; if ($this->app['config']->get('laravel-database-emails.manual_migrations')) { $this->publishes([ @@ -55,8 +49,6 @@ private function bootDatabase(): void /** * Register the service provider. - * - * @return void */ public function register(): void { diff --git a/src/MailableReader.php b/src/MailableReader.php index 86be654..22a2238 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -12,8 +12,6 @@ class MailableReader { /** * Read the mailable and pass the data to the email composer. - * - * @param EmailComposer $composer */ public function read(EmailComposer $composer): void { @@ -46,8 +44,7 @@ public function read(EmailComposer $composer): void /** * Convert the mailable addresses array into a array with only e-mails. * - * @param string $from - * @return array + * @param string $from */ private function convertMailableAddresses($from): array { @@ -58,8 +55,6 @@ private function convertMailableAddresses($from): array /** * Read the mailable recipient to the email composer. - * - * @param EmailComposer $composer */ private function readRecipient(EmailComposer $composer): void { @@ -72,14 +67,12 @@ private function readRecipient(EmailComposer $composer): void /** * Read the mailable from field to the email composer. - * - * @param EmailComposer $composer */ private function readFrom(EmailComposer $composer): void { $from = reset($composer->getData('mailable')->from); - if (!$from) { + if (! $from) { return; } @@ -91,8 +84,6 @@ private function readFrom(EmailComposer $composer): void /** * Read the mailable cc to the email composer. - * - * @param EmailComposer $composer */ private function readCc(EmailComposer $composer): void { @@ -105,8 +96,6 @@ private function readCc(EmailComposer $composer): void /** * Read the mailable bcc to the email composer. - * - * @param EmailComposer $composer */ private function readBcc(EmailComposer $composer): void { @@ -119,8 +108,6 @@ private function readBcc(EmailComposer $composer): void /** * Read the mailable reply-to to the email composer. - * - * @param EmailComposer $composer */ private function readReplyTo(EmailComposer $composer): void { @@ -133,8 +120,6 @@ private function readReplyTo(EmailComposer $composer): void /** * Read the mailable subject to the email composer. - * - * @param EmailComposer $composer */ private function readSubject(EmailComposer $composer): void { @@ -144,7 +129,6 @@ private function readSubject(EmailComposer $composer): void /** * Read the mailable body to the email composer. * - * @param EmailComposer $composer * @throws Exception */ private function readBody(EmailComposer $composer): void @@ -158,8 +142,6 @@ private function readBody(EmailComposer $composer): void /** * Read the mailable attachments to the email composer. - * - * @param EmailComposer $composer */ private function readAttachments(EmailComposer $composer): void { diff --git a/src/Preparer.php b/src/Preparer.php index ea405d0..884cf89 100644 --- a/src/Preparer.php +++ b/src/Preparer.php @@ -11,8 +11,6 @@ class Preparer { /** * Prepare the given e-mail for database storage. - * - * @param EmailComposer $composer */ public function prepare(EmailComposer $composer): void { @@ -47,8 +45,6 @@ public function prepare(EmailComposer $composer): void /** * Prepare the label for database storage. - * - * @param EmailComposer $composer */ private function prepareLabel(EmailComposer $composer): void { @@ -63,8 +59,6 @@ private function prepareLabel(EmailComposer $composer): void /** * Prepare the recipient for database storage. - * - * @param EmailComposer $composer */ private function prepareRecipient(EmailComposer $composer): void { @@ -79,8 +73,6 @@ private function prepareRecipient(EmailComposer $composer): void /** * Prepare the from values for database storage. - * - * @param EmailComposer $composer */ private function prepareFrom(EmailComposer $composer): void { @@ -91,8 +83,6 @@ private function prepareFrom(EmailComposer $composer): void /** * Prepare the carbon copies for database storage. - * - * @param EmailComposer $composer */ private function prepareCc(EmailComposer $composer): void { @@ -107,8 +97,6 @@ private function prepareCc(EmailComposer $composer): void /** * Prepare the carbon copies for database storage. - * - * @param EmailComposer $composer */ private function prepareBcc(EmailComposer $composer): void { @@ -123,8 +111,6 @@ private function prepareBcc(EmailComposer $composer): void /** * Prepare the reply-to for database storage. - * - * @param EmailComposer $composer */ private function prepareReplyTo(EmailComposer $composer): void { @@ -150,8 +136,6 @@ private function prepareReplyTo(EmailComposer $composer): void /** * Prepare the subject for database storage. - * - * @param EmailComposer $composer */ private function prepareSubject(EmailComposer $composer): void { @@ -162,8 +146,6 @@ private function prepareSubject(EmailComposer $composer): void /** * Prepare the view for database storage. - * - * @param EmailComposer $composer */ private function prepareView(EmailComposer $composer): void { @@ -174,8 +156,6 @@ private function prepareView(EmailComposer $composer): void /** * Prepare the variables for database storage. - * - * @param EmailComposer $composer */ private function prepareVariables(EmailComposer $composer): void { @@ -190,8 +170,6 @@ private function prepareVariables(EmailComposer $composer): void /** * Prepare the e-mail body for database storage. - * - * @param EmailComposer $composer */ private function prepareBody(EmailComposer $composer): void { @@ -210,8 +188,6 @@ private function prepareBody(EmailComposer $composer): void /** * Prepare the e-mail attachments. - * - * @param EmailComposer $composer */ private function prepareAttachments(EmailComposer $composer): void { @@ -219,14 +195,14 @@ private function prepareAttachments(EmailComposer $composer): void foreach ((array) $composer->getData('attachments', []) as $attachment) { $attachments[] = [ - 'type' => 'attachment', + 'type' => 'attachment', 'attachment' => $attachment, ]; } foreach ((array) $composer->getData('rawAttachments', []) as $rawAttachment) { $attachments[] = [ - 'type' => 'rawAttachment', + 'type' => 'rawAttachment', 'attachment' => $rawAttachment, ]; } @@ -238,8 +214,6 @@ private function prepareAttachments(EmailComposer $composer): void /** * Prepare the scheduled date for database storage. - * - * @param EmailComposer $composer */ private function prepareScheduled(EmailComposer $composer): void { @@ -260,8 +234,6 @@ private function prepareScheduled(EmailComposer $composer): void /** * Prepare the e-mail so it can be sent immediately. - * - * @param EmailComposer $composer */ private function prepareImmediately(EmailComposer $composer): void { @@ -272,8 +244,6 @@ private function prepareImmediately(EmailComposer $composer): void /** * Prepare the queued date. - * - * @param EmailComposer $composer */ private function prepareQueued(EmailComposer $composer): void { @@ -283,5 +253,4 @@ private function prepareQueued(EmailComposer $composer): void ]); } } - } diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index 039ca37..481a0d6 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -33,8 +33,6 @@ class SendEmailsCommand extends Command /** * Create a new SendEmailsCommand instance. - * - * @param Store $store */ public function __construct(Store $store) { @@ -45,8 +43,6 @@ public function __construct(Store $store) /** * Execute the console command. - * - * @return void */ public function handle(): void { @@ -77,9 +73,6 @@ public function handle(): void /** * Output a table with the cronjob result. - * - * @param Collection $emails - * @return void */ protected function result(Collection $emails): void { diff --git a/src/Sender.php b/src/Sender.php index 72ce8a1..55424eb 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -11,8 +11,6 @@ class Sender { /** * Send the given e-mail. - * - * @param Email $email */ public function send(Email $email): void { @@ -27,7 +25,7 @@ public function send(Email $email): void }); // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) - if (!is_null($sentMessage)) { + if (! is_null($sentMessage)) { event(new MessageSent($sentMessage)); } @@ -36,9 +34,6 @@ public function send(Email $email): void /** * Build the e-mail message. - * - * @param Message $message - * @param Email $email */ private function buildMessage(Message $message, Email $email): void { @@ -52,7 +47,7 @@ private function buildMessage(Message $message, Email $email): void $message->html($email->getBody()); $attachmentMap = [ - 'attachment' => 'attach', + 'attachment' => 'attach', 'rawAttachment' => 'attachData', ]; diff --git a/src/SentMessage.php b/src/SentMessage.php index c58bbdb..f3dff66 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -10,13 +10,21 @@ class SentMessage { public $from = []; + public $to = []; + public $cc = []; + public $bcc = []; + public $replyTo = []; + public $subject = ''; + public $body = ''; + public $attachments = []; + public $headers = []; public static function createFromSymfonyMailer(Email $email): SentMessage diff --git a/src/Validator.php b/src/Validator.php index 69d130f..ef5979b 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -4,12 +4,13 @@ namespace Stackkit\LaravelDatabaseEmails; -use Exception; +use const FILTER_VALIDATE_EMAIL; + use Carbon\Carbon; +use Exception; use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Arr; use InvalidArgumentException; -use const FILTER_VALIDATE_EMAIL; class Validator { @@ -23,7 +24,6 @@ class Validator /** * Validate the data that was given to the e-mail composer. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ public function validate(EmailComposer $composer): void @@ -50,20 +50,18 @@ public function validate(EmailComposer $composer): void /** * Validate the defined label. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateLabel(EmailComposer $composer): void { if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) { - throw new InvalidArgumentException('The given label [' . $composer->getData('label') . '] is too large for database storage'); + throw new InvalidArgumentException('The given label ['.$composer->getData('label').'] is too large for database storage'); } } /** * Validate the given recipient(s). * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateRecipient(EmailComposer $composer): void @@ -80,7 +78,7 @@ private function validateRecipient(EmailComposer $composer): void foreach ($recipients as $recipient) { if (! filter_var($recipient, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address [' . $recipient . '] is invalid'); + throw new InvalidArgumentException('E-mail address ['.$recipient.'] is invalid'); } } } @@ -88,7 +86,6 @@ private function validateRecipient(EmailComposer $composer): void /** * Validate the carbon copy e-mail addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateCc(EmailComposer $composer): void @@ -99,7 +96,7 @@ private function validateCc(EmailComposer $composer): void foreach ((array) $composer->getData('cc') as $cc) { if (! filter_var($cc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address [' . $cc . '] is invalid'); + throw new InvalidArgumentException('E-mail address ['.$cc.'] is invalid'); } } } @@ -107,7 +104,6 @@ private function validateCc(EmailComposer $composer): void /** * Validate the blind carbon copy e-mail addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateBcc(EmailComposer $composer): void @@ -118,7 +114,7 @@ private function validateBcc(EmailComposer $composer): void foreach ((array) $composer->getData('bcc') as $bcc) { if (! filter_var($bcc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address [' . $bcc . '] is invalid'); + throw new InvalidargumentException('E-mail address ['.$bcc.'] is invalid'); } } } @@ -126,7 +122,6 @@ private function validateBcc(EmailComposer $composer): void /** * Validate the reply-to addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateReplyTo(EmailComposer $composer): void @@ -141,7 +136,7 @@ private function validateReplyTo(EmailComposer $composer): void } if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address [' . $replyTo . '] is invalid'); + throw new InvalidargumentException('E-mail address ['.$replyTo.'] is invalid'); } } } @@ -149,7 +144,6 @@ private function validateReplyTo(EmailComposer $composer): void /** * Validate the e-mail subject. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateSubject(EmailComposer $composer): void @@ -162,7 +156,6 @@ private function validateSubject(EmailComposer $composer): void /** * Validate the e-mail view. * - * @param EmailComposer $composer * @throws InvalidARgumentException */ private function validateView(EmailComposer $composer): void @@ -178,14 +171,13 @@ private function validateView(EmailComposer $composer): void $view = $composer->getData('view'); if (! view()->exists($view)) { - throw new InvalidArgumentException('View [' . $view . '] does not exist'); + throw new InvalidArgumentException('View ['.$view.'] does not exist'); } } /** * Validate the e-mail variables. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateVariables(EmailComposer $composer): void @@ -198,7 +190,6 @@ private function validateVariables(EmailComposer $composer): void /** * Validate the scheduled date. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateScheduled(EmailComposer $composer): void @@ -217,7 +208,7 @@ private function validateScheduled(EmailComposer $composer): void try { Carbon::parse($scheduled); } catch (Exception $e) { - throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: ' . $e->getMessage()); + throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: '.$e->getMessage()); } } } diff --git a/tests/ConfigCacheTest.php b/tests/ConfigCacheTest.php index 5b1fccc..6af6a1f 100644 --- a/tests/ConfigCacheTest.php +++ b/tests/ConfigCacheTest.php @@ -13,7 +13,7 @@ public function the_configuration_file_can_be_cached() $failed = false; try { - serialize(require __DIR__ . '/../config/laravel-database-emails.php'); + serialize(require __DIR__.'/../config/laravel-database-emails.php'); } catch (Throwable) { $failed = true; } diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 2eb67fb..dcb2197 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -29,7 +29,7 @@ public function recipient_should_be_saved_correctly() public function cc_and_bcc_should_be_saved_correctly() { $email = $this->sendEmail([ - 'cc' => $cc = [ + 'cc' => $cc = [ 'john@doe.com', ], 'bcc' => $bcc = [ @@ -199,7 +199,7 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') ->send(); $this->assertCount(1, $email->getAttachments()); @@ -207,23 +207,23 @@ public function attachments_should_be_saved_correctly() $attachment = $email->getAttachments()[0]; $this->assertEquals('attachment', $attachment['type']); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachment['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachment['attachment']['file']); $email = $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') - ->attach(__DIR__ . '/files/pdf-sample-2.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample-2.pdf') ->send(); $this->assertCount(2, $email->getAttachments()); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); - $this->assertEquals(__DIR__ . '/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); } #[Test] public function in_memory_attachments_should_be_saved_correctly() { - $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); + $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); $email = $this->composeEmail() ->attachData($rawData, 'generated.pdf', [ diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index e835512..89f4b92 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -38,7 +38,7 @@ public function the_recipient_should_be_encrypted_and_decrypted() public function cc_and_bb_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ - 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], + 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], 'bcc' => $bcc = ['jane+1@doe.com', 'jane+2@doe.com'], ]); @@ -53,7 +53,7 @@ public function cc_and_bb_should_be_encrypted_and_decrypted() public function reply_to_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ - 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], + 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], ]); $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); $this->assertEquals($replyTo, $email->getReplyTo()); @@ -64,14 +64,14 @@ public function reply_to_should_be_encrypted_and_decrypted() // Test with a single Address object... $email = $this->sendEmail([ - 'reply_to' => new Address('john+1@doe.com', 'John Doe'), + 'reply_to' => new Address('john+1@doe.com', 'John Doe'), ]); $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo()); // Address with an array of Address objects... $email = $this->sendEmail([ - 'reply_to' => [ + 'reply_to' => [ new Address('john+1@doe.com', 'John Doe'), new Address('jane+1@doe.com', 'Jane Doe'), ], @@ -125,7 +125,7 @@ public function from_should_be_encrypted_and_decrypted() $expect = [ 'address' => 'marick@dolphiq.nl', - 'name' => 'Marick', + 'name' => 'Marick', ]; $this->assertEquals($expect, decrypt($email->getRawDatabaseValue('from'))); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 6691d8e..43933c7 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -85,7 +85,7 @@ public function it_extracts_attachments() $this->assertCount(2, $attachments); $this->assertEquals('attachment', $attachments[0]['type']); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); $this->assertEquals('rawAttachment', $attachments[1]['type']); $this->assertEquals('order.html', $attachments[1]['attachment']['name']); @@ -140,7 +140,7 @@ public function envelope(): Envelope return new Envelope( null, [ - new Address('john@doe.com', 'John Doe') + new Address('john@doe.com', 'John Doe'), ], ['john+cc@doe.com', 'john+cc2@doe.com'], ['john+bcc@doe.com', 'john+bcc2@doe.com'], @@ -152,10 +152,10 @@ public function envelope(): Envelope public function attachments(): array { return [ - Attachment::fromPath(__DIR__ . '/files/pdf-sample.pdf')->withMime('application/pdf'), + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf')->withMime('application/pdf'), Attachment::fromData(function () { return '

Thanks for your oder

'; - }, 'order.html') + }, 'order.html'), ]; } } diff --git a/tests/PruneTest.php b/tests/PruneTest.php index ddcf8f1..b562f35 100644 --- a/tests/PruneTest.php +++ b/tests/PruneTest.php @@ -13,11 +13,11 @@ public function by_default_mails_are_pruned_after_6_months() { $email = $this->sendEmail(); - Carbon::setTestNow($email->created_at . ' + 6 months'); + Carbon::setTestNow($email->created_at.' + 6 months'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertInstanceOf(Email::class, $email->fresh()); - Carbon::setTestNow($email->created_at . ' + 6 months + 1 day'); + Carbon::setTestNow($email->created_at.' + 6 months + 1 day'); // Ensure the email object has to be passed manually, otherwise we are acidentally // deleting everyone's e-mails... @@ -38,11 +38,11 @@ public function can_change_when_emails_are_pruned() $email = $this->sendEmail(); - Carbon::setTestNow($email->created_at . ' + 3 months'); + Carbon::setTestNow($email->created_at.' + 3 months'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertInstanceOf(Email::class, $email->fresh()); - Carbon::setTestNow($email->created_at . ' + 3 months + 1 day'); + Carbon::setTestNow($email->created_at.' + 3 months + 1 day'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertNull($email->fresh()); } diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 560fee1..5658f3c 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -2,10 +2,10 @@ namespace Tests; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; -use Illuminate\Support\Facades\Mail; class QueuedEmailsTest extends TestCase { diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 00b52f1..dbd58b4 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -123,8 +123,8 @@ public function the_failed_status_and_error_is_cleared_if_a_previously_failed_em $email = $this->sendEmail(); $email->update([ - 'failed' => true, - 'error' => 'Simulating some random error', + 'failed' => true, + 'error' => 'Simulating some random error', 'attempts' => 1, ]); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index b6a6907..5b638b8 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -4,11 +4,11 @@ use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; -use Illuminate\Support\Facades\Mail; -use Stackkit\LaravelDatabaseEmails\Email; class SenderTest extends TestCase { @@ -162,7 +162,7 @@ public function the_email_has_the_correct_body() public function attachments_are_added_to_the_email() { $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') ->send(); $this->artisan('email:send'); @@ -174,7 +174,7 @@ public function attachments_are_added_to_the_email() #[Test] public function raw_attachments_are_added_to_the_email() { - $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); + $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); $this->composeEmail() ->attachData($rawData, 'hello-ci.pdf', [ diff --git a/tests/TestCase.php b/tests/TestCase.php index 7e3a78d..2cada5b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,7 @@ namespace Tests; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; -use Illuminate\Foundation\Testing\RefreshDatabase; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -30,9 +28,9 @@ function () { }, ]; - view()->addNamespace('tests', __DIR__ . '/views'); + view()->addNamespace('tests', __DIR__.'/views'); - $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); Email::truncate(); } @@ -47,7 +45,7 @@ protected function getPackageProviders($app) /** * Define environment setup. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app * @return void */ protected function getEnvironmentSetUp($app) @@ -60,7 +58,7 @@ protected function getEnvironmentSetUp($app) $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ 'driver' => $driver, - ...match($driver) { + ...match ($driver) { 'sqlite' => [ 'database' => ':memory:', ], @@ -84,13 +82,13 @@ protected function getEnvironmentSetUp($app) public function createEmail($overwrite = []) { $params = array_merge([ - 'label' => 'welcome', + 'label' => 'welcome', 'recipient' => 'john@doe.com', - 'cc' => null, - 'bcc' => null, - 'reply_to' => null, - 'subject' => 'test', - 'view' => 'tests::dummy', + 'cc' => null, + 'bcc' => null, + 'reply_to' => null, + 'subject' => 'test', + 'view' => 'tests::dummy', 'variables' => ['name' => 'John Doe'], ], $overwrite); diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index d2afce7..9079f2d 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Workbench\Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder From 5971218cfc2820cd893b8e82ec149236eb4c20fc Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 16:27:30 +0100 Subject: [PATCH 05/13] wip --- composer.json | 5 ++- ...024_03_16_151608_change_binary_to_text.php | 38 +++++++++++++++++++ src/EmailComposer.php | 28 ++++++++++++++ src/HasEncryptedAttributes.php | 4 ++ src/MailableReader.php | 23 +++++++++++ tests/DatabaseInteractionTest.php | 4 +- tests/EnvelopeTest.php | 32 ++++++++++++++++ 7 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2024_03_16_151608_change_binary_to_text.php create mode 100644 tests/EnvelopeTest.php diff --git a/composer.json b/composer.json index c3df63c..0d16c4d 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ }, "require": { "ext-json": "*", - "laravel/framework": "^10.0|^11.0" + "laravel/framework": "^10.0|^11.0", + "doctrine/dbal": "^3.8" }, "require-dev": { "mockery/mockery": "^1.2", @@ -67,4 +68,4 @@ "@php vendor/bin/phpstan analyse" ] } -} \ No newline at end of file +} diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_change_binary_to_text.php new file mode 100644 index 0000000..6a196ca --- /dev/null +++ b/database/migrations/2024_03_16_151608_change_binary_to_text.php @@ -0,0 +1,38 @@ +text('recipient')->change(); + $table->text('cc')->nullable()->change(); + $table->text('bcc')->nullable()->change(); + $table->text('subject')->change(); + $table->text('variables')->nullable()->change(); + $table->text('body')->change(); + $table->text('attachments')->nullable()->change(); + $table->text('from')->nullable()->change(); + $table->text('reply_to')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 905d0a1..452eb25 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -5,6 +5,9 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; class EmailComposer { @@ -22,6 +25,9 @@ class EmailComposer */ protected $data = []; + public ?Envelope $envelope = null; + public ?Content $content = null; + /** * Create a new EmailComposer instance. */ @@ -30,6 +36,28 @@ public function __construct(Email $email) $this->email = $email; } + public function envelope(Envelope $envelope): self + { + $this->envelope = $envelope; + + if ($this->content) { + (new MailableReader())->read($this); + } + + return $this; + } + + public function content(Content $content): self + { + $this->content = $content; + + if ($this->envelope) { + (new MailableReader())->read($this); + } + + return $this; + } + /** * Get the e-mail that is being composed. */ diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index 7d02012..0c23f2d 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -48,6 +48,10 @@ public function getAttribute($key) { $value = $this->attributes[$key]; + if (is_resource($value)) { + $value = stream_get_contents($value); + } + if ($this->isEncrypted() && in_array($key, $this->encrypted)) { try { $value = decrypt($value); diff --git a/src/MailableReader.php b/src/MailableReader.php index 22a2238..2be0c0a 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -6,6 +6,9 @@ use Exception; use Illuminate\Container\Container; +use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; use ReflectionObject; class MailableReader @@ -15,6 +18,26 @@ class MailableReader */ public function read(EmailComposer $composer): void { + if ($composer->envelope && $composer->content) { + $composer->setData('mailable', new class($composer) extends Mailable { + public function __construct(private EmailComposer $composer) + { + // + } + + public function content(): Content + { + return $this->composer->content; + } + + public function envelope(): Envelope + { + return $this->composer->envelope; + } + }); + } + + if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { $reflected = (new ReflectionObject($composer->getData('mailable'))); $method = $reflected->getMethod('prepareMailableForDelivery'); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index dcb2197..f1f4b0e 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -233,7 +233,7 @@ public function in_memory_attachments_should_be_saved_correctly() $this->assertCount(1, $email->getAttachments()); - $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); - $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); +// $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); +// $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); } } diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php new file mode 100644 index 0000000..37dea5c --- /dev/null +++ b/tests/EnvelopeTest.php @@ -0,0 +1,32 @@ +envelope( + (new Envelope()) + ->subject('Hey') + ->from('asdf@gmail.com') + ->to('johndoe@example.com', 'janedoe@example.com') + ) + ->content( + (new Content()) + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->send(); + + $this->assertEquals(['johndoe@example.com'], $email->recipient); + } + +} From 6458dbed7a615c2ef59796f14aaeffdcee5c1d4f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 17 Mar 2024 22:22:56 +0100 Subject: [PATCH 06/13] wip --- src/Attachment.php | 63 ++++++ src/Config.php | 8 - src/Email.php | 342 ++---------------------------- src/EmailComposer.php | 178 ++++++---------- src/Encrypter.php | 114 ---------- src/MailableReader.php | 87 ++++---- src/Preparer.php | 256 ---------------------- src/SendEmailsCommand.php | 13 +- src/Sender.php | 54 +++-- src/Validator.php | 215 ------------------- test | Bin 0 -> 16384 bytes tests/ConfigTest.php | 10 - tests/DatabaseInteractionTest.php | 115 ++++------ tests/EncryptionTest.php | 134 ------------ tests/EnvelopeTest.php | 8 +- tests/MailableReaderTest.php | 44 ++-- tests/SendEmailsCommandTest.php | 34 +-- tests/SenderTest.php | 62 ++---- tests/TestCase.php | 31 ++- tests/ValidatorTest.php | 206 ------------------ tests/files/my-file.txt | 1 + tests/test | Bin 0 -> 16384 bytes 22 files changed, 356 insertions(+), 1619 deletions(-) create mode 100644 src/Attachment.php delete mode 100644 src/Encrypter.php delete mode 100644 src/Preparer.php delete mode 100644 src/Validator.php create mode 100644 test delete mode 100644 tests/EncryptionTest.php delete mode 100644 tests/ValidatorTest.php create mode 100644 tests/files/my-file.txt create mode 100644 tests/test diff --git a/src/Attachment.php b/src/Attachment.php new file mode 100644 index 0000000..48b2220 --- /dev/null +++ b/src/Attachment.php @@ -0,0 +1,63 @@ +as = $name; + + return $this; + } + + public function withMime(string $mime) + { + $this->mime = $mime; + + return $this; + } + + public function toArray(): array + { + return [ + 'path' => $this->path, + 'disk' => $this->disk, + 'as' => $this->as, + 'mime' => $this->mime, + ]; + } +} diff --git a/src/Config.php b/src/Config.php index 8523a7b..9c6ae83 100644 --- a/src/Config.php +++ b/src/Config.php @@ -14,14 +14,6 @@ public static function maxAttemptCount(): int return max(config('laravel-database-emails.attempts', 1), 3); } - /** - * Determine if newly created e-mails should be encrypted. - */ - public static function encryptEmails(): bool - { - return config('laravel-database-emails.encrypt', false); - } - /** * Determine if newly created e-mails should be sent to the test e-mail address. */ diff --git a/src/Email.php b/src/Email.php index 18a18a3..b8cfb46 100644 --- a/src/Email.php +++ b/src/Email.php @@ -6,10 +6,10 @@ use Carbon\Carbon; use Closure; -use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; +use Throwable; /** * @property $id @@ -28,7 +28,6 @@ * @property $sending * @property $failed * @property $error - * @property $encrypted * @property $queued_at * @property $scheduled_at * @property $sent_at @@ -36,9 +35,19 @@ */ class Email extends Model { - use HasEncryptedAttributes; use Prunable; + protected $casts = [ + 'failed' => 'boolean', + 'recipient' => 'json', + 'from' => 'json', + 'cc' => 'json', + 'bcc' => 'json', + 'reply_to' => 'json', + 'variables' => 'json', + 'attachments' => 'json', + ]; + /** * The table in which the e-mails are stored. * @@ -63,64 +72,12 @@ public static function compose(): EmailComposer return new EmailComposer(new static()); } - /** - * Get the e-mail id. - */ - public function getId(): int - { - return $this->id; - } - - /** - * Get the e-mail label. - */ - public function getLabel(): ?string - { - return $this->label; - } - - /** - * Get the e-mail recipient. - * - * @return string|array - */ - public function getRecipient() - { - return $this->recipient; - } - - /** - * Get the e-mail recipient. - * - * @return string|array - */ - public function getRecipientAttribute() - { - return $this->recipient; - } - - /** - * Get the e-mail from. - */ - public function getFrom(): ?array - { - return $this->from; - } - - /** - * Get the e-mail from. - */ - public function getFromAttribute(): ?array - { - return $this->from; - } - /** * Get the e-mail from address. */ public function getFromAddress(): ?string { - return $this->from['address'] ?? config('mail.from.address'); + return $this->from['address']; } /** @@ -128,263 +85,7 @@ public function getFromAddress(): ?string */ public function getFromName(): ?string { - return $this->from['name'] ?? config('mail.from.name'); - } - - /** - * Get the e-mail recipient(s) as string. - */ - public function getRecipientsAsString(): string - { - $glue = ', '; - - return implode($glue, (array) $this->recipient); - } - - /** - * Get the e-mail CC addresses. - * - * @return array|string - */ - public function getCc() - { - return $this->cc; - } - - /** - * Get the e-mail CC addresses. - * - * @return array|string - */ - public function getCcAttribute() - { - return $this->cc; - } - - /** - * Get the e-mail BCC addresses. - * - * @return array|string - */ - public function getBcc() - { - return $this->bcc; - } - - /** - * Get the e-mail BCC addresses. - * - * @return array|string - */ - public function getBccAttribute() - { - return $this->bcc; - } - - /** - * Get the e-mail reply-to addresses. - * - * @return array|string - */ - public function getReplyTo() - { - return $this->reply_to; - } - - /** - * Get the e-mail reply-to addresses. - * - * @return array|string - */ - public function getReplyToAttribute() - { - return $this->reply_to; - } - - /** - * Get the e-mail subject. - */ - public function getSubject(): string - { - return $this->subject; - } - - /** - * Get the e-mail subject. - */ - public function getSubjectAttribute(): string - { - return $this->subject; - } - - /** - * Get the e-mail view. - */ - public function getView(): string - { - return $this->view; - } - - /** - * Get the e-mail variables. - */ - public function getVariables(): ?array - { - return $this->variables; - } - - /** - * Get the e-mail variables. - */ - public function getVariablesAttribute(): ?array - { - return $this->variables; - } - - /** - * Get the e-mail body. - */ - public function getBody(): string - { - return $this->body; - } - - /** - * Get the e-mail body. - */ - public function getBodyAttribute(): string - { - return $this->body; - } - - /** - * Get the e-mail attachments. - */ - public function getAttachments(): array - { - return $this->attachments; - } - - /** - * Get the number of times this e-mail was attempted to send. - */ - public function getAttempts(): int - { - return $this->attempts; - } - - /** - * Get the queued date. - */ - public function getQueuedDate(): ?string - { - return $this->queued_at; - } - - /** - * Get the queued date as a Carbon instance. - */ - public function getQueuedDateAsCarbon(): Carbon - { - if ($this->queued_at instanceof Carbon) { - return $this->queued_at; - } - - return Carbon::parse($this->queued_at); - } - - /** - * Get the scheduled date. - */ - public function getScheduledDate(): ?string - { - return $this->scheduled_at; - } - - /** - * Determine if the e-mail has variables defined. - */ - public function hasVariables(): bool - { - return ! is_null($this->variables); - } - - /** - * Get the scheduled date as a Carbon instance. - */ - public function getScheduledDateAsCarbon(): Carbon - { - if ($this->scheduled_at instanceof Carbon) { - return $this->scheduled_at; - } - - return Carbon::parse($this->scheduled_at); - } - - /** - * Get the send date for this e-mail. - */ - public function getSendDate(): ?string - { - return $this->sent_at; - } - - /** - * Get the send error. - * - * @return string|string - */ - public function getError(): ?string - { - return $this->error; - } - - /** - * Determine if the e-mail should be sent with custom from values. - */ - public function hasFrom(): bool - { - return is_array($this->from) && count($this->from) > 0; - } - - /** - * Determine if the e-mail should be sent as a carbon copy. - */ - public function hasCc(): bool - { - return strlen($this->getRawDatabaseValue('cc')) > 0; - } - - /** - * Determine if the e-mail should be sent as a blind carbon copy. - */ - public function hasBcc(): bool - { - return strlen($this->getRawDatabaseValue('bcc')) > 0; - } - - /** - * Determine if the e-mail should sent with reply-to. - */ - public function hasReplyTo(): bool - { - return strlen($this->getRawDatabaseValue('reply_to') ?: '') > 0; - } - - /** - * Determine if the e-mail is scheduled to be sent later. - */ - public function isScheduled(): bool - { - return ! is_null($this->getScheduledDate()); - } - - /** - * Determine if the e-mail is encrypted. - */ - public function isEncrypted(): bool - { - return (bool) $this->getRawDatabaseValue('encrypted'); + return $this->from['name']; } /** @@ -432,7 +133,7 @@ public function markAsSent(): void /** * Mark the e-mail as failed. */ - public function markAsFailed(Exception $exception): void + public function markAsFailed(Throwable $exception): void { $this->update([ 'sending' => 0, @@ -471,19 +172,6 @@ public function retry(): void $retry->save(); } - /** - * @param mixed $default - * @return mixed - */ - public function getRawDatabaseValue(?string $key = null, $default = null) - { - if (method_exists($this, 'getRawOriginal')) { - return $this->getRawOriginal($key, $default); - } - - return $this->getOriginal($key, $default); - } - /** * @return void */ diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 452eb25..8c63a92 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -4,10 +4,11 @@ namespace Stackkit\LaravelDatabaseEmails; +use Closure; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Address; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Support\Carbon; class EmailComposer { @@ -26,8 +27,11 @@ class EmailComposer protected $data = []; public ?Envelope $envelope = null; + public ?Content $content = null; + public ?array $attachments = null; + /** * Create a new EmailComposer instance. */ @@ -36,25 +40,42 @@ public function __construct(Email $email) $this->email = $email; } - public function envelope(Envelope $envelope): self + public function envelope(null|Envelope|Closure $envelope = null): self { - $this->envelope = $envelope; + if ($envelope instanceof Closure) { + $this->envelope = $envelope($this->envelope ?: new Envelope()); - if ($this->content) { - (new MailableReader())->read($this); + return $this; } + $this->envelope = $envelope; + return $this; } - public function content(Content $content): self + public function content(null|Content|Closure $content = null): self { + if ($content instanceof Closure) { + $this->content = $content($this->content ?: new Content()); + + return $this; + } + $this->content = $content; - if ($this->envelope) { - (new MailableReader())->read($this); + return $this; + } + + public function attachments(null|array|Closure $attachments = null): self + { + if ($attachments instanceof Closure) { + $this->attachments = $attachments($this->attachments ?: []); + + return $this; } + $this->attachments = $attachments; + return $this; } @@ -106,89 +127,9 @@ public function hasData(string $key): bool */ public function label(string $label): self { - return $this->setData('label', $label); - } - - /** - * Set the e-mail from address and aname. - */ - public function from(?string $address = null, ?string $name = null): self - { - return $this->setData('from', compact('address', 'name')); - } + $this->email->label = $label; - /** - * Set the e-mail recipient(s). - * - * @param string|array $recipient - */ - public function recipient($recipient): self - { - return $this->setData('recipient', $recipient); - } - - /** - * Define the carbon-copy address(es). - * - * @param string|array $cc - */ - public function cc($cc): self - { - return $this->setData('cc', $cc); - } - - /** - * Define the blind carbon-copy address(es). - * - * @param string|array $bcc - */ - public function bcc($bcc): self - { - return $this->setData('bcc', $bcc); - } - - /** - * Define the reply-to address(es). - * - * @param string|array $replyTo - */ - public function replyTo($replyTo): self - { - return $this->setData('reply_to', $replyTo); - } - - /** - * Set the e-mail subject. - */ - public function subject(string $subject): self - { - return $this->setData('subject', $subject); - } - - /** - * Set the e-mail view. - */ - public function view(string $view): self - { - return $this->setData('view', $view); - } - - /** - * Set the e-mail variables. - */ - public function variables(array $variables): self - { - return $this->setData('variables', $variables); - } - - /** - * Schedule the e-mail. - * - * @param mixed $scheduledAt - */ - public function schedule($scheduledAt): Email - { - return $this->later($scheduledAt); + return $this; } /** @@ -198,7 +139,7 @@ public function schedule($scheduledAt): Email */ public function later($scheduledAt): Email { - $this->setData('scheduled_at', $scheduledAt); + $this->email->scheduled_at = Carbon::parse($scheduledAt); return $this->send(); } @@ -213,6 +154,8 @@ public function queue(?string $connection = null, ?string $queue = null, $delay $connection = $connection ?: config('queue.default'); $queue = $queue ?: 'default'; + $this->email->queued_at = now(); + $this->setData('queued', true); $this->setData('connection', $connection); $this->setData('queue', $queue); @@ -236,38 +179,41 @@ public function mailable(Mailable $mailable): self /** * Attach a file to the e-mail. */ - public function attach(string $file, array $options = []): self - { - $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; - - $attachments[] = compact('file', 'options'); - - return $this->setData('attachments', $attachments); - } - - /** - * Attach in-memory data as an attachment. - */ - public function attachData(string $data, string $name, array $options = []): self - { - $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; - - $attachments[] = compact('data', 'name', 'options'); - - return $this->setData('rawAttachments', $attachments); - } + // public function attach(string $file, array $options = []): self + // { + // $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; + // + // $attachments[] = compact('file', 'options'); + // + // return $this->setData('attachments', $attachments); + // } + // + // /** + // * Attach in-memory data as an attachment. + // */ + // public function attachData(string $data, string $name, array $options = []): self + // { + // $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; + // + // $attachments[] = compact('data', 'name', 'options'); + // + // return $this->setData('rawAttachments', $attachments); + // } /** * Send the e-mail. */ public function send(): Email { - (new Validator())->validate($this); - - (new Preparer())->prepare($this); + if ($this->envelope && $this->content) { + (new MailableReader())->read($this); + } - if (Config::encryptEmails()) { - (new Encrypter())->encrypt($this); + if (! $this->email->from) { + $this->email->from = [ + 'address' => config('mail.from.address'), + 'name' => config('mail.from.name'), + ]; } $this->email->save(); diff --git a/src/Encrypter.php b/src/Encrypter.php deleted file mode 100644 index 7045c2c..0000000 --- a/src/Encrypter.php +++ /dev/null @@ -1,114 +0,0 @@ -setEncrypted($composer); - - $this->encryptRecipients($composer); - - $this->encryptReplyTo($composer); - - $this->encryptFrom($composer); - - $this->encryptSubject($composer); - - $this->encryptVariables($composer); - - $this->encryptBody($composer); - } - - /** - * Mark the e-mail as encrypted. - */ - private function setEncrypted(EmailComposer $composer): void - { - $composer->getEmail()->setAttribute('encrypted', 1); - } - - /** - * Encrypt the e-mail reply-to. - */ - private function encryptReplyTo(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'reply_to' => $composer->hasData('reply_to') ? encrypt($email->reply_to) : '', - ]); - } - - /** - * Encrypt the e-mail addresses of the recipients. - */ - private function encryptRecipients(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'recipient' => encrypt($email->recipient), - 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', - 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', - ]); - } - - /** - * Encrypt the e-mail addresses for the from field. - */ - private function encryptFrom(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'from' => encrypt($email->from), - ]); - } - - /** - * Encrypt the e-mail subject. - */ - private function encryptSubject(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'subject' => encrypt($email->subject), - ]); - } - - /** - * Encrypt the e-mail variables. - */ - private function encryptVariables(EmailComposer $composer): void - { - if (! $composer->hasData('variables')) { - return; - } - - $email = $composer->getEmail(); - - $email->fill([ - 'variables' => encrypt($email->variables), - ]); - } - - /** - * Encrypt the e-mail body. - */ - private function encryptBody(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'body' => encrypt($email->body), - ]); - } -} diff --git a/src/MailableReader.php b/src/MailableReader.php index 2be0c0a..7f92c0d 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -4,6 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; +use Error; use Exception; use Illuminate\Container\Container; use Illuminate\Mail\Mailable; @@ -19,7 +20,8 @@ class MailableReader public function read(EmailComposer $composer): void { if ($composer->envelope && $composer->content) { - $composer->setData('mailable', new class($composer) extends Mailable { + $composer->setData('mailable', new class($composer) extends Mailable + { public function __construct(private EmailComposer $composer) { // @@ -34,10 +36,14 @@ public function envelope(): Envelope { return $this->composer->envelope; } + + public function attachments(): array + { + return $this->composer->attachments ?? []; + } }); } - if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { $reflected = (new ReflectionObject($composer->getData('mailable'))); $method = $reflected->getMethod('prepareMailableForDelivery'); @@ -71,8 +77,8 @@ public function envelope(): Envelope */ private function convertMailableAddresses($from): array { - return collect($from)->map(function ($recipient) { - return $recipient['address']; + return collect($from)->mapWithKeys(function ($recipient) { + return [$recipient['address'] => $recipient['name']]; })->toArray(); } @@ -81,11 +87,16 @@ private function convertMailableAddresses($from): array */ private function readRecipient(EmailComposer $composer): void { - $to = $this->convertMailableAddresses( - $composer->getData('mailable')->to - ); + if (config('laravel-database-emails.testing.enabled')) { + $composer->getEmail()->recipient = [ + config('laravel-database-emails.testing.email') => null, + ]; - $composer->recipient($to); + return; + } + + $composer->getEmail()->recipient = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->to); } /** @@ -93,16 +104,7 @@ private function readRecipient(EmailComposer $composer): void */ private function readFrom(EmailComposer $composer): void { - $from = reset($composer->getData('mailable')->from); - - if (! $from) { - return; - } - - $composer->from( - $from['address'], - $from['name'] - ); + $composer->getEmail()->from = head($composer->getData('mailable')->from); } /** @@ -110,11 +112,8 @@ private function readFrom(EmailComposer $composer): void */ private function readCc(EmailComposer $composer): void { - $cc = $this->convertMailableAddresses( - $composer->getData('mailable')->cc - ); - - $composer->cc($cc); + $composer->getEmail()->cc = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->cc); } /** @@ -122,11 +121,8 @@ private function readCc(EmailComposer $composer): void */ private function readBcc(EmailComposer $composer): void { - $bcc = $this->convertMailableAddresses( - $composer->getData('mailable')->bcc - ); - - $composer->bcc($bcc); + $composer->getEmail()->bcc = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->bcc); } /** @@ -134,11 +130,8 @@ private function readBcc(EmailComposer $composer): void */ private function readReplyTo(EmailComposer $composer): void { - $replyTo = $this->convertMailableAddresses( - $composer->getData('mailable')->replyTo - ); - - $composer->replyTo($replyTo); + $composer->getEmail()->reply_to = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->replyTo); } /** @@ -146,7 +139,7 @@ private function readReplyTo(EmailComposer $composer): void */ private function readSubject(EmailComposer $composer): void { - $composer->subject($composer->getData('mailable')->subject); + $composer->getEmail()->subject = $composer->getData('mailable')->subject; } /** @@ -156,11 +149,12 @@ private function readSubject(EmailComposer $composer): void */ private function readBody(EmailComposer $composer): void { - $composer->setData('view', ''); - + /** @var Mailable $mailable */ $mailable = $composer->getData('mailable'); - $composer->setData('body', view($mailable->view, $mailable->buildViewData())->render()); + $composer->getEmail()->view = $mailable->view; + $composer->getEmail()->variables = $mailable->buildViewData(); + $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(); } /** @@ -170,12 +164,19 @@ private function readAttachments(EmailComposer $composer): void { $mailable = $composer->getData('mailable'); - foreach ((array) $mailable->attachments as $attachment) { - call_user_func_array([$composer, 'attach'], $attachment); - } + $composer->getEmail()->attachments = array_map(function (array $attachment) { + if (! $attachment['file'] instanceof Attachment) { + throw new Error('The attachment is not an instance of '.Attachment::class.'.'); + } - foreach ((array) $mailable->rawAttachments as $rawAttachment) { - call_user_func_array([$composer, 'attachData'], $rawAttachment); - } + return $attachment['file']->toArray(); + }, $mailable->attachments); + } + + private function prepareAddressForDatabaseStorage(array $addresses): array + { + return collect($addresses)->mapWithKeys(function ($recipient) { + return [$recipient['address'] => $recipient['name']]; + })->toArray(); } } diff --git a/src/Preparer.php b/src/Preparer.php deleted file mode 100644 index 884cf89..0000000 --- a/src/Preparer.php +++ /dev/null @@ -1,256 +0,0 @@ -prepareLabel($composer); - - $this->prepareRecipient($composer); - - $this->prepareFrom($composer); - - $this->prepareCc($composer); - - $this->prepareBcc($composer); - - $this->prepareReplyTo($composer); - - $this->prepareSubject($composer); - - $this->prepareView($composer); - - $this->prepareVariables($composer); - - $this->prepareBody($composer); - - $this->prepareAttachments($composer); - - $this->prepareScheduled($composer); - - $this->prepareImmediately($composer); - - $this->prepareQueued($composer); - } - - /** - * Prepare the label for database storage. - */ - private function prepareLabel(EmailComposer $composer): void - { - if (! $composer->hasData('label')) { - return; - } - - $composer->getEmail()->fill([ - 'label' => $composer->getData('label'), - ]); - } - - /** - * Prepare the recipient for database storage. - */ - private function prepareRecipient(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->recipient(Config::testEmailAddress()); - } - - $composer->getEmail()->fill([ - 'recipient' => json_encode($composer->getData('recipient')), - ]); - } - - /** - * Prepare the from values for database storage. - */ - private function prepareFrom(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'from' => json_encode($composer->getData('from', '')), - ]); - } - - /** - * Prepare the carbon copies for database storage. - */ - private function prepareCc(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->setData('cc', []); - } - - $composer->getEmail()->fill([ - 'cc' => json_encode($composer->getData('cc', [])), - ]); - } - - /** - * Prepare the carbon copies for database storage. - */ - private function prepareBcc(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->setData('bcc', []); - } - - $composer->getEmail()->fill([ - 'bcc' => json_encode($composer->getData('bcc', [])), - ]); - } - - /** - * Prepare the reply-to for database storage. - */ - private function prepareReplyTo(EmailComposer $composer): void - { - $value = $composer->getData('reply_to', []); - - if (! is_array($value)) { - $value = [$value]; - } - - foreach ($value as $i => $v) { - if ($v instanceof Address) { - $value[$i] = [ - 'address' => $v->address, - 'name' => $v->name, - ]; - } - } - - $composer->getEmail()->fill([ - 'reply_to' => json_encode($value), - ]); - } - - /** - * Prepare the subject for database storage. - */ - private function prepareSubject(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'subject' => $composer->getData('subject'), - ]); - } - - /** - * Prepare the view for database storage. - */ - private function prepareView(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'view' => $composer->getData('view'), - ]); - } - - /** - * Prepare the variables for database storage. - */ - private function prepareVariables(EmailComposer $composer): void - { - if (! $composer->hasData('variables')) { - return; - } - - $composer->getEmail()->fill([ - 'variables' => json_encode($composer->getData('variables')), - ]); - } - - /** - * Prepare the e-mail body for database storage. - */ - private function prepareBody(EmailComposer $composer): void - { - // If the body was predefined (by for example a mailable), use that. - if ($composer->hasData('body')) { - $body = $composer->getData('body'); - } else { - $body = view( - $composer->getData('view'), - $composer->hasData('variables') ? $composer->getData('variables') : [] - )->render(); - } - - $composer->getEmail()->fill(compact('body')); - } - - /** - * Prepare the e-mail attachments. - */ - private function prepareAttachments(EmailComposer $composer): void - { - $attachments = []; - - foreach ((array) $composer->getData('attachments', []) as $attachment) { - $attachments[] = [ - 'type' => 'attachment', - 'attachment' => $attachment, - ]; - } - - foreach ((array) $composer->getData('rawAttachments', []) as $rawAttachment) { - $attachments[] = [ - 'type' => 'rawAttachment', - 'attachment' => $rawAttachment, - ]; - } - - $composer->getEmail()->fill([ - 'attachments' => serialize($attachments), - ]); - } - - /** - * Prepare the scheduled date for database storage. - */ - private function prepareScheduled(EmailComposer $composer): void - { - if (! $composer->hasData('scheduled_at')) { - return; - } - - $scheduled = $composer->getData('scheduled_at'); - - if (is_string($scheduled)) { - $scheduled = Carbon::parse($scheduled); - } - - $composer->getEmail()->fill([ - 'scheduled_at' => $scheduled->toDateTimeString(), - ]); - } - - /** - * Prepare the e-mail so it can be sent immediately. - */ - private function prepareImmediately(EmailComposer $composer): void - { - if (Config::sendImmediately()) { - $composer->getEmail()->fill(['sending' => 1]); - } - } - - /** - * Prepare the queued date. - */ - private function prepareQueued(EmailComposer $composer): void - { - if ($composer->getData('queued', false) === true) { - $composer->getEmail()->fill([ - 'queued_at' => Carbon::now()->toDateTimeString(), - ]); - } - } -} diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index 481a0d6..e8ed7fc 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -4,9 +4,10 @@ namespace Stackkit\LaravelDatabaseEmails; -use Exception; use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Arr; +use Throwable; class SendEmailsCommand extends Command { @@ -61,7 +62,7 @@ public function handle(): void try { $email->send(); - } catch (Exception $e) { + } catch (Throwable $e) { $email->markAsFailed($e); } } @@ -82,10 +83,10 @@ protected function result(Collection $emails): void $this->table($headers, $emails->map(function (Email $email) { return [ - $email->getId(), - $email->getRecipientsAsString(), - $email->getSubject(), - $email->hasFailed() ? 'Failed' : 'OK', + $email->id, + implode(',', array_column(Arr::wrap($email->recipient), 'recipient')), + $email->subject, + $email->failed ? 'Failed' : 'OK', ]; })); } diff --git a/src/Sender.php b/src/Sender.php index 55424eb..7f755c2 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -4,6 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; +use Illuminate\Mail\Attachment; use Illuminate\Mail\Message; use Illuminate\Support\Facades\Mail; @@ -37,22 +38,43 @@ public function send(Email $email): void */ private function buildMessage(Message $message, Email $email): void { - $message->to($email->getRecipient()) - ->cc($email->hasCc() ? $email->getCc() : []) - ->bcc($email->hasBcc() ? $email->getBcc() : []) - ->replyTo($email->hasReplyTo() ? $email->getReplyTo() : []) - ->subject($email->getSubject()) - ->from($email->getFromAddress(), $email->getFromName()); - - $message->html($email->getBody()); - - $attachmentMap = [ - 'attachment' => 'attach', - 'rawAttachment' => 'attachData', - ]; - - foreach ($email->getAttachments() as $attachment) { - call_user_func_array([$message, $attachmentMap[$attachment['type']]], $attachment['attachment']); + $message->to($email->recipient) + ->cc($email->cc ?: []) + ->bcc($email->bcc ?: []) + ->replyTo($email->reply_to ?: []) + ->subject($email->subject) + ->from($email->getFromAddress(), $email->getFromName()) + ->html($email->body); + + foreach ($email->attachments as $dbAttachment) { + $attachment = match (true) { + isset($dbAttachment['disk']) => Attachment::fromStorageDisk( + $dbAttachment['disk'], + $dbAttachment['path'] + ), + default => Attachment::fromPath($dbAttachment['path']), + }; + + if (! empty($dbAttachment['mime'])) { + $attachment->withMime($dbAttachment['mime']); + } + + if (! empty($dbAttachment['as'])) { + $attachment->as($dbAttachment['as']); + } + + $message->attach($attachment); } } + + public function prepAddresses(array $addresses) + { + return $addresses; + $new = []; + foreach ($addresses as $value) { + $new[$value['address']] = $value['name']; + } + + return $new; + } } diff --git a/src/Validator.php b/src/Validator.php deleted file mode 100644 index ef5979b..0000000 --- a/src/Validator.php +++ /dev/null @@ -1,215 +0,0 @@ -validateLabel($composer); - - $this->validateRecipient($composer); - - $this->validateCc($composer); - - $this->validateBcc($composer); - - $this->validateReplyTo($composer); - - $this->validateSubject($composer); - - $this->validateView($composer); - - $this->validateVariables($composer); - - $this->validateScheduled($composer); - } - - /** - * Validate the defined label. - * - * @throws InvalidArgumentException - */ - private function validateLabel(EmailComposer $composer): void - { - if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) { - throw new InvalidArgumentException('The given label ['.$composer->getData('label').'] is too large for database storage'); - } - } - - /** - * Validate the given recipient(s). - * - * @throws InvalidArgumentException - */ - private function validateRecipient(EmailComposer $composer): void - { - if (! $composer->hasData('recipient')) { - throw new InvalidArgumentException('No recipient specified'); - } - - $recipients = (array) $composer->getData('recipient'); - - if (count($recipients) == 0) { - throw new InvalidArgumentException('No recipient specified'); - } - - foreach ($recipients as $recipient) { - if (! filter_var($recipient, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address ['.$recipient.'] is invalid'); - } - } - } - - /** - * Validate the carbon copy e-mail addresses. - * - * @throws InvalidArgumentException - */ - private function validateCc(EmailComposer $composer): void - { - if (! $composer->hasData('cc')) { - return; - } - - foreach ((array) $composer->getData('cc') as $cc) { - if (! filter_var($cc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address ['.$cc.'] is invalid'); - } - } - } - - /** - * Validate the blind carbon copy e-mail addresses. - * - * @throws InvalidArgumentException - */ - private function validateBcc(EmailComposer $composer): void - { - if (! $composer->hasData('bcc')) { - return; - } - - foreach ((array) $composer->getData('bcc') as $bcc) { - if (! filter_var($bcc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address ['.$bcc.'] is invalid'); - } - } - } - - /** - * Validate the reply-to addresses. - * - * @throws InvalidArgumentException - */ - private function validateReplyTo(EmailComposer $composer): void - { - if (! $composer->hasData('reply_to')) { - return; - } - - foreach (Arr::wrap($composer->getData('reply_to')) as $replyTo) { - if ($replyTo instanceof Address) { - $replyTo = $replyTo->address; - } - - if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address ['.$replyTo.'] is invalid'); - } - } - } - - /** - * Validate the e-mail subject. - * - * @throws InvalidArgumentException - */ - private function validateSubject(EmailComposer $composer): void - { - if (! $composer->hasData('subject')) { - throw new InvalidArgumentException('No subject specified'); - } - } - - /** - * Validate the e-mail view. - * - * @throws InvalidARgumentException - */ - private function validateView(EmailComposer $composer): void - { - if ($composer->hasData('mailable')) { - return; - } - - if (! $composer->hasData('view')) { - throw new InvalidArgumentException('No view specified'); - } - - $view = $composer->getData('view'); - - if (! view()->exists($view)) { - throw new InvalidArgumentException('View ['.$view.'] does not exist'); - } - } - - /** - * Validate the e-mail variables. - * - * @throws InvalidArgumentException - */ - private function validateVariables(EmailComposer $composer): void - { - if ($composer->hasData('variables') && ! is_array($composer->getData('variables'))) { - throw new InvalidArgumentException('Variables must be an array'); - } - } - - /** - * Validate the scheduled date. - * - * @throws InvalidArgumentException - */ - private function validateScheduled(EmailComposer $composer): void - { - if (! $composer->hasData('scheduled_at')) { - return; - } - - $scheduled = $composer->getData('scheduled_at'); - - if (! $scheduled instanceof Carbon && ! is_string($scheduled)) { - throw new InvalidArgumentException('Scheduled date must be a Carbon\Carbon instance or a strtotime-valid string'); - } - - if (is_string($scheduled)) { - try { - Carbon::parse($scheduled); - } catch (Exception $e) { - throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: '.$e->getMessage()); - } - } - } -} diff --git a/test b/test new file mode 100644 index 0000000000000000000000000000000000000000..d79a5857e4f08dd52b2c70d86e8b68487b55dbca GIT binary patch literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5assertEquals(5, Config::maxAttemptCount()); } - #[Test] - public function test_encrypt_emails() - { - $this->assertFalse(Config::encryptEmails()); - - $this->app['config']->set('laravel-database-emails.encrypt', true); - - $this->assertTrue(Config::encryptEmails()); - } - #[Test] public function test_testing() { diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index f1f4b0e..4ce0e36 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -3,8 +3,10 @@ namespace Tests; use Carbon\Carbon; +use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\DB; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Attachment; class DatabaseInteractionTest extends TestCase { @@ -14,7 +16,7 @@ public function label_should_be_saved_correctly() $email = $this->sendEmail(['label' => 'welcome-email']); $this->assertEquals('welcome-email', DB::table('emails')->find(1)->label); - $this->assertEquals('welcome-email', $email->getLabel()); + $this->assertEquals('welcome-email', $email->label); } #[Test] @@ -22,7 +24,7 @@ public function recipient_should_be_saved_correctly() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); - $this->assertEquals('john@doe.com', $email->getRecipient()); + $this->assertEquals(['john@doe.com' => null], $email->recipient); } #[Test] @@ -37,26 +39,20 @@ public function cc_and_bcc_should_be_saved_correctly() ], ]); - $this->assertEquals(json_encode($cc), DB::table('emails')->find(1)->cc); - $this->assertTrue($email->hasCc()); - $this->assertEquals(['john@doe.com'], $email->getCc()); - $this->assertEquals(json_encode($bcc), DB::table('emails')->find(1)->bcc); - $this->assertTrue($email->hasBcc()); - $this->assertEquals(['jane@doe.com'], $email->getBcc()); + $this->assertEquals(['john@doe.com' => null], $email->cc); + $this->assertEquals(['jane@doe.com' => null], $email->bcc); } #[Test] public function reply_to_should_be_saved_correctly() { $email = $this->sendEmail([ - 'reply_to' => $replyTo = [ + 'reply_to' => [ 'john@doe.com', ], ]); - $this->assertEquals(json_encode($replyTo), DB::table('emails')->find(1)->reply_to); - $this->assertTrue($email->hasReplyTo()); - $this->assertEquals(['john@doe.com'], $email->getReplyTo()); + $this->assertEquals(['john@doe.com' => null], $email->reply_to); } #[Test] @@ -65,7 +61,7 @@ public function subject_should_be_saved_correclty() $email = $this->sendEmail(['subject' => 'test subject']); $this->assertEquals('test subject', DB::table('emails')->find(1)->subject); - $this->assertEquals('test subject', $email->getSubject()); + $this->assertEquals('test subject', $email->subject); } #[Test] @@ -74,23 +70,7 @@ public function view_should_be_saved_correctly() $email = $this->sendEmail(['view' => 'tests::dummy']); $this->assertEquals('tests::dummy', DB::table('emails')->find(1)->view); - $this->assertEquals('tests::dummy', $email->getView()); - } - - #[Test] - public function encrypted_should_be_saved_correctly() - { - $email = $this->sendEmail(); - - $this->assertEquals(0, DB::table('emails')->find(1)->encrypted); - $this->assertFalse($email->isEncrypted()); - - $this->app['config']['laravel-database-emails.encrypt'] = true; - - $email = $this->sendEmail(); - - $this->assertEquals(1, DB::table('emails')->find(2)->encrypted); - $this->assertTrue($email->isEncrypted()); + $this->assertEquals('tests::dummy', $email->view); } #[Test] @@ -98,12 +78,12 @@ public function scheduled_date_should_be_saved_correctly() { $email = $this->sendEmail(); $this->assertNull(DB::table('emails')->find(1)->scheduled_at); - $this->assertNull($email->getScheduledDate()); + $this->assertNull($email->scheduled_at); Carbon::setTestNow(Carbon::create(2019, 1, 1, 1, 2, 3)); $email = $this->scheduleEmail('+2 weeks'); $this->assertNotNull(DB::table('emails')->find(2)->scheduled_at); - $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate()); + $this->assertEquals('2019-01-15 01:02:03', $email->scheduled_at); } #[Test] @@ -114,7 +94,7 @@ public function the_body_should_be_saved_correctly() $expectedBody = "Name: Jane Doe\n"; $this->assertSame($expectedBody, DB::table('emails')->find(1)->body); - $this->assertSame($expectedBody, $email->getBody()); + $this->assertSame($expectedBody, $email->body); } #[Test] @@ -122,13 +102,14 @@ public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); - $this->assertFalse($email->hasFrom()); - $this->assertEquals(config('mail.from.address'), $email->getFromAddress()); - $this->assertEquals(config('mail.from.name'), $email->getFromName()); + $this->assertEquals($email->from['address'], $email->getFromAddress()); + $this->assertEquals($email->from['name'], $email->getFromName()); - $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); + $email = $this->composeEmail([ + 'from' => new Address('marick@dolphiq.nl', 'Marick'), + ])->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); $this->assertEquals('Marick', $email->getFromName()); } @@ -139,7 +120,7 @@ public function variables_should_be_saved_correctly() $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); $this->assertEquals(json_encode(['name' => 'John Doe'], 1), DB::table('emails')->find(1)->variables); - $this->assertEquals(['name' => 'John Doe'], $email->getVariables()); + $this->assertEquals(['name' => 'John Doe'], $email->variables); } #[Test] @@ -148,7 +129,7 @@ public function the_sent_date_should_be_null() $email = $this->sendEmail(); $this->assertNull(DB::table('emails')->find(1)->sent_at); - $this->assertNull($email->getSendDate()); + $this->assertNull($email->sent_at); } #[Test] @@ -166,7 +147,7 @@ public function attempts_should_be_zero() $email = $this->sendEmail(); $this->assertEquals(0, DB::table('emails')->find(1)->attempts); - $this->assertEquals(0, $email->getAttempts()); + $this->assertEquals(0, $email->attempts); } #[Test] @@ -178,8 +159,7 @@ public function the_scheduled_date_should_be_saved_correctly() $email = $this->scheduleEmail('+2 weeks'); - $this->assertTrue($email->isScheduled()); - $this->assertEquals($scheduledFor, $email->getScheduledDate()); + $this->assertEquals($scheduledFor, $email->scheduled_at); } #[Test] @@ -192,48 +172,43 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod $email = $this->sendEmail(['recipient' => 'jane@doe.com']); - $this->assertEquals('test@address.com', $email->getRecipient()); + $this->assertEquals(['test@address.com' => null], $email->recipient); } #[Test] public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') - ->send(); - - $this->assertCount(1, $email->getAttachments()); - - $attachment = $email->getAttachments()[0]; - - $this->assertEquals('attachment', $attachment['type']); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachment['attachment']['file']); - - $email = $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') - ->attach(__DIR__.'/files/pdf-sample-2.pdf') + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/pdf-sample2.pdf'), + // Attachment::fromStorage('pdf-sample.pdf'), + Attachment::fromStorageDisk('my-custom-disk', 'pdf-sample-2.pdf'), + ]) ->send(); - $this->assertCount(2, $email->getAttachments()); + $this->assertCount(3, $email->attachments); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); - $this->assertEquals(__DIR__.'/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); + $this->assertEquals( + [ + 'path' => __DIR__.'/files/pdf-sample.pdf', + 'disk' => null, + 'as' => null, + 'mime' => null, + ], + $email->attachments[0] + ); } #[Test] - public function in_memory_attachments_should_be_saved_correctly() + public function in_memory_attachments_are_not_supported() { - $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); + $this->expectExceptionMessage('Raw attachments are not supported in the database email driver.'); - $email = $this->composeEmail() - ->attachData($rawData, 'generated.pdf', [ - 'mime' => 'application/pdf', + $this->composeEmail() + ->attachments([ + Attachment::fromData(fn () => file_get_contents(__DIR__.'/files/pdf-sample.pdf'), 'pdf-sample'), ]) ->send(); - - $this->assertCount(1, $email->getAttachments()); - -// $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); -// $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); } } diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php deleted file mode 100644 index 89f4b92..0000000 --- a/tests/EncryptionTest.php +++ /dev/null @@ -1,134 +0,0 @@ -app['config']['laravel-database-emails.encrypt'] = true; - - $this->sendEmail(); - } - - #[Test] - public function an_email_should_be_marked_as_encrypted() - { - $email = $this->sendEmail(); - - $this->assertTrue($email->isEncrypted()); - } - - #[Test] - public function the_recipient_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['recipient' => 'john@doe.com']); - - $this->assertEquals('john@doe.com', decrypt($email->getRawDatabaseValue('recipient'))); - - $this->assertEquals('john@doe.com', $email->getRecipient()); - } - - #[Test] - public function cc_and_bb_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail([ - 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], - 'bcc' => $bcc = ['jane+1@doe.com', 'jane+2@doe.com'], - ]); - - $this->assertEquals($cc, decrypt($email->getRawDatabaseValue('cc'))); - $this->assertEquals($bcc, decrypt($email->getRawDatabaseValue('bcc'))); - - $this->assertEquals($cc, $email->getCc()); - $this->assertEquals($bcc, $email->getBcc()); - } - - #[Test] - public function reply_to_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail([ - 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], - ]); - $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertEquals($replyTo, $email->getReplyTo()); - - if (! class_exists(Address::class)) { - return; - } - - // Test with a single Address object... - $email = $this->sendEmail([ - 'reply_to' => new Address('john+1@doe.com', 'John Doe'), - ]); - $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo()); - - // Address with an array of Address objects... - $email = $this->sendEmail([ - 'reply_to' => [ - new Address('john+1@doe.com', 'John Doe'), - new Address('jane+1@doe.com', 'Jane Doe'), - ], - ]); - $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo()); - } - - #[Test] - public function the_subject_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['subject' => 'test subject']); - - $this->assertEquals('test subject', decrypt($email->getRawDatabaseValue('subject'))); - - $this->assertEquals('test subject', $email->getSubject()); - } - - #[Test] - public function the_variables_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); - - $this->assertEquals( - ['name' => 'Jane Doe'], - decrypt($email->getRawDatabaseValue('variables')) - ); - - $this->assertEquals( - ['name' => 'Jane Doe'], - $email->getVariables() - ); - } - - #[Test] - public function the_body_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); - - $expectedBody = "Name: Jane Doe\n"; - - $this->assertEquals($expectedBody, decrypt($email->getRawDatabaseValue('body'))); - - $this->assertEquals($expectedBody, $email->getBody()); - } - - #[Test] - public function from_should_be_encrypted_and_decrypted() - { - $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); - - $expect = [ - 'address' => 'marick@dolphiq.nl', - 'name' => 'Marick', - ]; - - $this->assertEquals($expect, decrypt($email->getRawDatabaseValue('from'))); - $this->assertEquals($expect, $email->getFrom()); - } -} diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index 37dea5c..d09d0e3 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -17,7 +17,7 @@ public function test_it_can_set_the_envelope() (new Envelope()) ->subject('Hey') ->from('asdf@gmail.com') - ->to('johndoe@example.com', 'janedoe@example.com') + ->to(['johndoe@example.com', 'janedoe@example.com']) ) ->content( (new Content()) @@ -26,7 +26,9 @@ public function test_it_can_set_the_envelope() ) ->send(); - $this->assertEquals(['johndoe@example.com'], $email->recipient); + $this->assertEquals([ + 'johndoe@example.com' => null, + 'janedoe@example.com' => null, + ], $email->recipient); } - } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 43933c7..3129df8 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -4,10 +4,10 @@ use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Address; -use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Attachment; use Stackkit\LaravelDatabaseEmails\Email; class MailableReaderTest extends TestCase @@ -23,16 +23,16 @@ public function it_extracts_the_recipient() $composer = Email::compose() ->mailable($this->mailable()); - $this->assertEquals(['john@doe.com'], $composer->getData('recipient')); + $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->getEmail()->recipient); $composer = Email::compose() ->mailable( $this->mailable()->to(['jane@doe.com']) ); - $this->assertCount(2, $composer->getData('recipient')); - $this->assertContains('john@doe.com', $composer->getData('recipient')); - $this->assertContains('jane@doe.com', $composer->getData('recipient')); + $this->assertCount(2, $composer->getEmail()->recipient); + $this->assertArrayHasKey('john@doe.com', $composer->getEmail()->recipient); + $this->assertArrayHasKey('jane@doe.com', $composer->getEmail()->recipient); } #[Test] @@ -40,7 +40,7 @@ public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc')); + $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->getEmail()->cc); } #[Test] @@ -48,7 +48,7 @@ public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); + $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->getEmail()->bcc); } #[Test] @@ -56,7 +56,7 @@ public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to')); + $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->getEmail()->reply_to); } #[Test] @@ -64,7 +64,7 @@ public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals('Your order has shipped!', $composer->getData('subject')); + $this->assertEquals('Your order has shipped!', $composer->getEmail()->subject); } #[Test] @@ -72,7 +72,7 @@ public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals("Name: John Doe\n", $composer->getData('body')); + $this->assertEquals("Name: John Doe\n", $composer->getEmail()->body); } #[Test] @@ -80,16 +80,11 @@ public function it_extracts_attachments() { $email = Email::compose()->mailable($this->mailable())->send(); - $attachments = $email->getAttachments(); + $attachments = $email->attachments; $this->assertCount(2, $attachments); - $this->assertEquals('attachment', $attachments[0]['type']); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); - - $this->assertEquals('rawAttachment', $attachments[1]['type']); - $this->assertEquals('order.html', $attachments[1]['attachment']['name']); - $this->assertEquals('

Thanks for your oder

', $attachments[1]['attachment']['data']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['path']); } #[Test] @@ -100,7 +95,7 @@ public function it_extracts_the_from_address_and_or_name() ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); $this->assertEquals('Marick', $email->getFromName()); @@ -109,16 +104,17 @@ public function it_extracts_the_from_address_and_or_name() ->from('marick@dolphiq.nl') )->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals(config('mail.from.name'), $email->getFromName()); + $this->assertEquals(null, $email->getFromName()); $email = Email::compose()->mailable( ($this->mailable()) - ->from(null, 'Marick') + ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertFalse($email->hasFrom()); + $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); + $this->assertEquals('Marick', $email->getFromName()); } } @@ -153,9 +149,7 @@ public function attachments(): array { return [ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf')->withMime('application/pdf'), - Attachment::fromData(function () { - return '

Thanks for your oder

'; - }, 'order.html'), + Attachment::fromStorageDisk(__DIR__.'/files/pdf-sample.pdf', 'my-local-disk')->withMime('application/pdf'), ]; } } diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index dbd58b4..43b75f1 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -17,7 +17,7 @@ public function an_email_should_be_marked_as_sent() $this->artisan('email:send'); - $this->assertNotNull($email->fresh()->getSendDate()); + $this->assertNotNull($email->fresh()->sent_at); } #[Test] @@ -25,11 +25,11 @@ public function the_number_of_attempts_should_be_incremented() { $email = $this->sendEmail(); - $this->assertEquals(0, $email->fresh()->getAttempts()); + $this->assertEquals(0, $email->fresh()->attempts); $this->artisan('email:send'); - $this->assertEquals(1, $email->fresh()->getAttempts()); + $this->assertEquals(1, $email->fresh()->attempts); } #[Test] @@ -39,12 +39,12 @@ public function an_email_should_not_be_sent_once_it_is_marked_as_sent() $this->artisan('email:send'); - $this->assertNotNull($firstSend = $email->fresh()->getSendDate()); + $this->assertNotNull($firstSend = $email->fresh()->sent_at); $this->artisan('email:send'); - $this->assertEquals(1, $email->fresh()->getAttempts()); - $this->assertEquals($firstSend, $email->fresh()->getSendDate()); + $this->assertEquals(1, $email->fresh()->attempts); + $this->assertEquals($firstSend, $email->fresh()->sent_at); } #[Test] @@ -56,7 +56,7 @@ public function an_email_should_not_be_sent_if_it_is_queued() $this->artisan('email:send'); - $this->assertNull($email->fresh()->getSendDate()); + $this->assertNull($email->fresh()->sent_at); } #[Test] @@ -64,12 +64,12 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database { $email = $this->sendEmail(); - $email->update(['recipient' => 'asdf']); + $email->update(['recipient' => ['asdf' => null]]); $this->artisan('email:send'); $this->assertTrue($email->fresh()->hasFailed()); - $this->assertStringContainsString('RfcComplianceException', $email->fresh()->getError()); + $this->assertStringContainsString('RfcComplianceException', $email->fresh()->error); } #[Test] @@ -92,14 +92,14 @@ public function an_email_should_never_be_sent_before_its_scheduled_date() $email = $this->scheduleEmail(Carbon::now()->addHour(1)); $this->artisan('email:send'); $email = $email->fresh(); - $this->assertEquals(0, $email->getAttempts()); - $this->assertNull($email->getSendDate()); + $this->assertEquals(0, $email->attempts); + $this->assertNull($email->sent_at); $email->update(['scheduled_at' => Carbon::now()->toDateTimeString()]); $this->artisan('email:send'); $email = $email->fresh(); - $this->assertEquals(1, $email->getAttempts()); - $this->assertNotNull($email->getSendDate()); + $this->assertEquals(1, $email->attempts); + $this->assertNotNull($email->sent_at); } #[Test] @@ -128,12 +128,12 @@ public function the_failed_status_and_error_is_cleared_if_a_previously_failed_em 'attempts' => 1, ]); - $this->assertTrue($email->fresh()->hasFailed()); - $this->assertEquals('Simulating some random error', $email->fresh()->getError()); + $this->assertTrue($email->fresh()->failed); + $this->assertEquals('Simulating some random error', $email->fresh()->error); $this->artisan('email:send'); - $this->assertFalse($email->fresh()->hasFailed()); - $this->assertEmpty($email->fresh()->getError()); + $this->assertFalse($email->fresh()->failed); + $this->assertEmpty($email->fresh()->error); } } diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 5b638b8..159295d 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\Test; +use RuntimeException; +use Stackkit\LaravelDatabaseEmails\Attachment; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; @@ -54,7 +56,7 @@ public function the_email_has_a_correct_from_email_and_from_name() // custom from... $this->sent = []; - $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); + $this->composeEmail(['from' => new Address('marick@dolphiq.nl', 'Marick')])->send(); $this->artisan('email:send'); $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); @@ -62,19 +64,11 @@ public function the_email_has_a_correct_from_email_and_from_name() // only address $this->sent = []; - $this->composeEmail()->from('marick@dolphiq.nl')->send(); + $this->composeEmail(['from' => 'marick@dolphiq.nl'])->send(); $this->artisan('email:send'); $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); - $this->assertEquals(config('mail.from.name'), $from[key($from)]); - - // only name - $this->sent = []; - $this->composeEmail()->from(null, 'Marick')->send(); - $this->artisan('email:send'); - $from = reset($this->sent)->from; - $this->assertEquals(config('mail.from.address'), key($from)); - $this->assertEquals('Marick', $from[key($from)]); + $this->assertEquals(null, $from[key($from)]); } #[Test] @@ -162,49 +156,31 @@ public function the_email_has_the_correct_body() public function attachments_are_added_to_the_email() { $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + ]) ->send(); $this->artisan('email:send'); $attachments = reset($this->sent)->attachments; - $this->assertCount(1, $attachments); + $this->assertCount(2, $attachments); + $this->assertEquals('Test123'."\n", $attachments[1]['body']); + $this->assertEquals('text/plain disposition: attachment filename: Test123 file', $attachments[1]['disposition']); } #[Test] - public function raw_attachments_are_added_to_the_email() + public function raw_attachments_are_not_added_to_the_email() { - $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Raw attachments are not supported in the database email driver.'); $this->composeEmail() - ->attachData($rawData, 'hello-ci.pdf', [ - 'mime' => 'application/pdf', + ->attachments([ + Attachment::fromData(fn () => 'test', 'test.txt'), ]) ->send(); - $this->artisan('email:send'); - - $attachments = reset($this->sent)->attachments; - $attachment = reset($attachments); - - $this->assertCount(1, $attachments); - $this->assertStringContainsString('hello-ci.pdf', $attachment['disposition']); - $this->assertStringContainsString('application/pdf', $attachment['disposition']); - $this->assertTrue(md5($attachment['body']) == md5($rawData)); - } - - #[Test] - public function old_json_encoded_attachments_can_still_be_read() - { - $email = $this->sendEmail(); - $email->attachments = json_encode([1, 2, 3]); - $email->save(); - - $this->assertEquals([1, 2, 3], $email->fresh()->getAttachments()); - - $email->attachments = serialize([4, 5, 6]); - $email->save(); - - $this->assertEquals([4, 5, 6], $email->fresh()->getAttachments()); } #[Test] @@ -240,10 +216,6 @@ public function it_adds_the_reply_to_addresses() $this->assertArrayHasKey('replyto1@test.com', $replyTo); $this->assertArrayHasKey('replyto2@test.com', $replyTo); - if (! class_exists(Address::class)) { - return; - } - $this->sent = []; $this->sendEmail([ 'reply_to' => new Address('replyto@test.com', 'NoReplyTest'), diff --git a/tests/TestCase.php b/tests/TestCase.php index 2cada5b..c0fc03d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,6 +3,8 @@ namespace Tests; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -54,6 +56,11 @@ protected function getEnvironmentSetUp($app) $app['config']->set('laravel-database-emails.testing.enabled', false); $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); + $app['config']->set('filesystems.disks.my-custom-disk', [ + 'driver' => 'local', + 'root' => storage_path('app'), + ]); + $app['config']->set('database.default', 'testbench'); $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ @@ -90,17 +97,25 @@ public function createEmail($overwrite = []) 'subject' => 'test', 'view' => 'tests::dummy', 'variables' => ['name' => 'John Doe'], + 'from' => null, ], $overwrite); return Email::compose() ->label($params['label']) - ->recipient($params['recipient']) - ->cc($params['cc']) - ->bcc($params['bcc']) - ->replyTo($params['reply_to']) - ->subject($params['subject']) - ->view($params['view']) - ->variables($params['variables']); + ->envelope( + (new Envelope()) + ->to($params['recipient']) + ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) + ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) + ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) + ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) + ->subject($params['subject']) + ) + ->content( + (new Content()) + ->view($params['view']) + ->with($params['variables']) + ); } public function composeEmail($overwrite = []) @@ -115,7 +130,7 @@ public function sendEmail($overwrite = []) public function scheduleEmail($scheduledFor, $overwrite = []) { - return $this->createEmail($overwrite)->schedule($scheduledFor); + return $this->createEmail($overwrite)->later($scheduledFor); } public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = []) diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php deleted file mode 100644 index 45ff991..0000000 --- a/tests/ValidatorTest.php +++ /dev/null @@ -1,206 +0,0 @@ -expectException(InvalidArgumentException::class); - - Email::compose() - ->label(str_repeat('a', 256)) - ->send(); - } - - #[Test] - public function a_recipient_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No recipient specified'); - - Email::compose() - ->send(); - } - - #[Test] - public function a_recipient_cannot_be_empty() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No recipient specified'); - - Email::compose() - ->recipient([]) - ->send(); - } - - #[Test] - public function the_recipient_email_must_be_valid() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('not-a-valid-email-address') - ->send(); - } - - #[Test] - public function cc_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->cc([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function bcc_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->bcc([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function reply_to_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->replyTo([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function a_subject_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No subject specified'); - - Email::compose() - ->recipient('john@doe.com') - ->send(); - } - - #[Test] - public function a_view_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No view specified'); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->send(); - } - - #[Test] - public function the_view_must_exist() - { - // this view exists, if error thrown -> fail test - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->send(); - } catch (InvalidArgumentException $e) { - $this->fail('Expected view [tests::dummy] to exist but it does not'); - } - - // this view does not exist -> expect exception - $this->expectException(InvalidArgumentException::class); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::does-not-exist') - ->send(); - } - - #[Test] - public function variables_must_be_defined_as_an_array() - { - $email = Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy'); - - foreach ($this->invalid as $type) { - try { - $email->variables($type)->send(); - $this->fail('Expected exception to be thrown'); - } catch (\TypeError $e) { - $this->assertEquals($e->getCode(), 0); - } - } - - $valid = []; - - try { - $email->variables($valid)->send(); - } catch (InvalidArgumentException $e) { - $this->fail('Did not expect exception to be thrown'); - } - } - - #[Test] - public function the_scheduled_date_must_be_a_carbon_instance_or_a_valid_date() - { - // invalid - foreach ($this->invalid as $value) { - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule($value); - $this->fail('Expected exception to be thrown'); - } catch (InvalidArgumentException $e) { - $this->assertEquals(0, $e->getCode()); - } - } - - // valid - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule('+2 week'); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule(Carbon\Carbon::now()); - } catch (InvalidArgumentException $e) { - $this->fail('Dit not expect exception to be thrown'); - } - } -} diff --git a/tests/files/my-file.txt b/tests/files/my-file.txt new file mode 100644 index 0000000..8f214dd --- /dev/null +++ b/tests/files/my-file.txt @@ -0,0 +1 @@ +Test123 diff --git a/tests/test b/tests/test new file mode 100644 index 0000000000000000000000000000000000000000..d79a5857e4f08dd52b2c70d86e8b68487b55dbca GIT binary patch literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5 Date: Sun, 17 Mar 2024 22:27:37 +0100 Subject: [PATCH 07/13] wip --- src/Email.php | 4 ++-- tests/TestCase.php | 23 ++++++++++------------- tests/test | Bin 16384 -> 0 bytes 3 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 tests/test diff --git a/src/Email.php b/src/Email.php index b8cfb46..a07c81c 100644 --- a/src/Email.php +++ b/src/Email.php @@ -7,8 +7,8 @@ use Carbon\Carbon; use Closure; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Prunable; use Throwable; /** @@ -35,7 +35,7 @@ */ class Email extends Model { - use Prunable; + use MassPrunable; protected $casts = [ 'failed' => 'boolean', diff --git a/tests/TestCase.php b/tests/TestCase.php index c0fc03d..c5f46cc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -102,19 +102,16 @@ public function createEmail($overwrite = []) return Email::compose() ->label($params['label']) - ->envelope( - (new Envelope()) - ->to($params['recipient']) - ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) - ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) - ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) - ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) - ->subject($params['subject']) - ) - ->content( - (new Content()) - ->view($params['view']) - ->with($params['variables']) + ->envelope(fn (Envelope $envelope) => $envelope + ->to($params['recipient']) + ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) + ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) + ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) + ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) + ->subject($params['subject'])) + ->content(fn (Content $content) => $content + ->view($params['view']) + ->with($params['variables']) ); } diff --git a/tests/test b/tests/test deleted file mode 100644 index d79a5857e4f08dd52b2c70d86e8b68487b55dbca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5 Date: Sat, 23 Mar 2024 00:27:54 +0100 Subject: [PATCH 08/13] wip --- .gitignore | 2 + README.md | 172 +++++++++--------- UPGRADING.md | 37 ++++ ...atabase-emails.php => database-emails.php} | 33 +--- ...024_03_16_151608_change_binary_to_text.php | 35 +++- src/Attachment.php | 12 +- src/Config.php | 10 +- src/Email.php | 119 +++--------- src/EmailComposer.php | 23 +++ src/LaravelDatabaseEmailsServiceProvider.php | 12 +- src/MailableReader.php | 29 +-- src/SendEmailsCommand.php | 2 + src/Sender.php | 13 +- test | Bin 16384 -> 49152 bytes testbench.yaml | 2 +- tests/ConfigCacheTest.php | 2 +- tests/ConfigTest.php | 8 +- tests/DatabaseInteractionTest.php | 13 +- tests/EnvelopeTest.php | 100 ++++++++++ tests/MailableReaderTest.php | 12 +- tests/SendEmailsCommandTest.php | 2 +- tests/SenderTest.php | 8 +- tests/TestCase.php | 10 +- workbench/app/Models/User.php | 11 ++ .../app/Models/UserWithPreferredEmail.php | 13 ++ .../app/Models/UserWithPreferredLocale.php | 15 ++ .../app/Models/UserWithPreferredName.php | 13 ++ .../Providers/WorkbenchServiceProvider.php | 5 +- workbench/lang/en/messages.php | 5 + workbench/lang/fil-PH/messages.php | 5 + .../resources/views/locale-email.blade.php | 2 + workbench/storage/app/public/test.txt | 1 + 32 files changed, 437 insertions(+), 289 deletions(-) create mode 100644 UPGRADING.md rename config/{laravel-database-emails.php => database-emails.php} (61%) create mode 100644 workbench/app/Models/User.php create mode 100644 workbench/app/Models/UserWithPreferredEmail.php create mode 100644 workbench/app/Models/UserWithPreferredLocale.php create mode 100644 workbench/app/Models/UserWithPreferredName.php create mode 100644 workbench/lang/en/messages.php create mode 100644 workbench/lang/fil-PH/messages.php create mode 100644 workbench/resources/views/locale-email.blade.php create mode 100644 workbench/storage/app/public/test.txt diff --git a/.gitignore b/.gitignore index f11b2a6..b730e63 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ vendor/ composer.lock .phpunit.result.cache .phpunit.cache +test + diff --git a/README.md b/README.md index 0dc8cdf..9471348 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,7 @@ We feel the package is currently feature complete, but feel free to send a pull # Requirements -This package requires Laravel 6.0 or higher. - -Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions. +This package requires Laravel 10 or 11. # Installation @@ -34,7 +32,8 @@ composer require stackkit/laravel-database-emails Publish the configuration files. ```bash -php artisan vendor:publish --tag=laravel-database-emails-config +php artisan vendor:publish --tag=database-emails-config +php artisan vendor:publish --tag=database-emails-migrations ``` Create the database table required for this package. @@ -65,68 +64,89 @@ protected function schedule(Schedule $schedule) ### Send an email +E-mails are composed the same way mailables are created. + ```php label('welcome') - ->recipient('john@doe.com') - ->subject('This is a test') - ->view('emails.welcome') - ->variables([ - 'name' => 'John Doe', + ->content(fn (Content $content) => $content + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->envelope(fn (Envelope $envelope) => $envelope + ->subject('Hello') + ->from('johndoe@example.com', 'John Doe') + ->to('janedoe@example.com', 'Jane Doe') + ) + ->attachments([ + Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'), ]) ->send(); +]) ``` -### Specify multiple recipients +### Sending emails to users in your application ```php recipient([ - 'john@doe.com', - 'jane@doe.com' - ]); + ->user($user) + ->send(); ``` -### CC and BCC +By default, the `name` column will be used to set the recipient's name. If you wish to use another column, you should implement the `preferredEmailName` method in your model. ```php cc('john@doe.com') - ->cc(['john@doe.com', 'jane@doe.com']) - ->bcc('john@doe.com') - ->bcc(['john@doe.com', 'jane@doe.com']); +class User extends Model +{ + public function preferredEmailName(): string + { + return $this->first_name; + } +} ``` -### Reply-To +By default, the `email` column will be used to set the recipient's e-mail address. If you wish to use another column, you should implement the `preferredEmailAddress` method in your model. ```php replyTo(['john@doe.com', 'jane@doe.com']); +class User extends Model +{ + public function preferredEmailAddress(): string + { + return $this->work_email; + } +} +``` -Email::compose() - ->replyTo(new Address('john@doe.com', 'John Doe')); +By default, the app locale will be used to set the recipient's locale. If you wish to use another column, you should implement the `preferredEmailLocale` method in your model. -Email::compose() - ->replyTo([ - new Address('john@doe.com', 'John Doe'), - new Address('jane@doe.com', 'Jane Doe'), - ]); +```php +locale; + } +} ``` ### Using mailables @@ -145,36 +165,28 @@ Email::compose() ### Attachments -```php -attach('/path/to/file'); -``` +To start attaching files to your e-mails, you may use the `attach` method like you normally would in Laravel. +However, you will have to use this package's `Attachment` class. -Or for in-memory attachments: ```php attachData('

Your order has shipped!

', 'order.html'); + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + Attachment::fromStorageDisk('my-custom-disk', 'test.txt'), + ]) + ->send(); ``` -### Custom Sender - -```php -from('john@doe.com', 'John Doe'); -``` + +Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. + ### Scheduling @@ -189,27 +201,6 @@ Email::compose() ->later('+2 hours'); ``` -### Encryption (Optional) - -If you wish to encrypt your e-mails, please enable the `encrypt` option in the configuration file. This is disabled by default. Encryption and decryption will be handled by Laravel's built-in encryption mechanism. Please note that by encrypting the e-mail it takes more disk space. - -```text -Without encryption - -7 bytes (label) -16 bytes (recipient) -20 bytes (subject) -48 bytes (view name) -116 bytes (variables) -1874 bytes (e-mail content) -4 bytes (attempts, sending, failed, encrypted) -57 bytes (created_at, updated_at, deleted_at) -... x 10.000 rows = ± 21.55 MB - -With encryption the table size is ± 50.58 MB. -``` - - ### Queueing e-mails **Important**: When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. Please make sure it is removed from `app/Console/Kernel.php`. @@ -235,22 +226,41 @@ Email::compose() ->queue(null, null, now()->addMinutes(10)); ``` -### Test mode (Optional) +If you need more flexibility over how to queued mails are retried, please implement your own email job. + +Within the job you can send the mail like this: + +```php +use Stackkit\LaravelDatabaseEmails\Sender; + +(new Sender)->send($email); +``` + +### Test mode When enabled, all newly created e-mails will be sent to the specified test e-mail address. This is turned off by default. +``` +DATABASE_EMAILS_TESTING_ENABLED=true +DATABASE_EMAILS_TESTING_EMAIL=your-test-recipient@example.com +``` + ### E-mails to send per minute -To configure how many e-mails should be sent each command, please check the `limit` option. The default is `20` e-mails every command. +To configure how many e-mails should be sent each command. + +``` +DATABASE_EMAILS_LIMIT=20 +``` -### Send e-mails immediately (Optional) +### Send e-mails immediately Useful during development when Laravel Scheduler is not running To enable, set the following environment variable: ``` -LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY=true +DATABASE_EMAILS_IMMEDIATELY=true ``` ### Pruning models diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..0fb32bb --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,37 @@ +# From 6.x to 7.x + +## Database changes (Impact: high) + +The way addresses are stored in the database has changed. Therefore, emails created in 6.x and below are incompatible. + +When you upgrade, the existing database table will be renamed to "emails_old" and a new table will be created. + +## Creating emails (Impact: high) + +The way emails are composed has changed and now borrows a lot from Laravel's mailable. + +```php +use Illuminate\Mail\Mailables\Content; +use Stackkit\LaravelDatabaseEmails\Attachment;use Stackkit\LaravelDatabaseEmails\Email; +use Illuminate\Mail\Mailables\Envelope; + +Email::compose() + ->content(fn (Content $content) => $content + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->envelope(fn (Envelope $envelope) => $envelope + ->subject('Hello') + ->from('johndoe@example.com', 'John Doe') + ->to('janedoe@example.com', 'Jane Doe') + ) + ->attachments([ + Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'), + ]) + ->send(); +]) +``` + +## Encryption (Impact: moderate) + +E-mail encryption has been removed from the package. diff --git a/config/laravel-database-emails.php b/config/database-emails.php similarity index 61% rename from config/laravel-database-emails.php rename to config/database-emails.php index 6c179ef..6f9d433 100644 --- a/config/laravel-database-emails.php +++ b/config/database-emails.php @@ -15,18 +15,6 @@ 'attempts' => 3, - /* - |-------------------------------------------------------------------------- - | Encryption - |-------------------------------------------------------------------------- - | - | Here you may enable encryption for all e-mails. The e-mail will be encrypted according - | your application's configuration (OpenSSL AES-256-CBC by default). - | - */ - - 'encrypt' => false, - /* |-------------------------------------------------------------------------- | Test E-mail @@ -40,9 +28,9 @@ 'testing' => [ - 'email' => 'test@email.com', + 'email' => env('DATABASE_EMAILS_TESTING_EMAIL'), - 'enabled' => env('LARAVEL_DATABASE_EMAILS_TESTING_ENABLED', true), + 'enabled' => env('DATABASE_EMAILS_TESTING_ENABLED', false), ], @@ -57,7 +45,7 @@ | */ - 'limit' => 20, + 'limit' => env('DATABASE_EMAILS_LIMIT', 20), /* |-------------------------------------------------------------------------- @@ -70,18 +58,5 @@ | */ - 'immediately' => env('LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY', false), - - /* - |-------------------------------------------------------------------------- - | Manual migrations - |-------------------------------------------------------------------------- - | - | This option allows you to use: - | `php artisan vendor:publish --tag=laravel-database-emails-migrations` to push migrations - | to your app's folder, so you're free to modify before migrating. - | - */ - - 'manual_migrations' => (bool) env('LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS', false), + 'immediately' => env('DATABASE_EMAILS_IMMEDIATELY', false), ]; diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_change_binary_to_text.php index 6a196ca..9b18e51 100644 --- a/database/migrations/2024_03_16_151608_change_binary_to_text.php +++ b/database/migrations/2024_03_16_151608_change_binary_to_text.php @@ -13,16 +13,31 @@ class ChangeBinaryToText extends Migration */ public function up() { - Schema::table('emails', function (Blueprint $table) { - $table->text('recipient')->change(); - $table->text('cc')->nullable()->change(); - $table->text('bcc')->nullable()->change(); - $table->text('subject')->change(); - $table->text('variables')->nullable()->change(); - $table->text('body')->change(); - $table->text('attachments')->nullable()->change(); - $table->text('from')->nullable()->change(); - $table->text('reply_to')->nullable()->change(); + Schema::rename('emails', 'emails_old'); + + Schema::create('emails', function (Blueprint $table) { + $table->increments('id'); + $table->string('label')->nullable(); + $table->json('recipient'); + $table->json('cc')->nullable(); + $table->json('bcc')->nullable(); + $table->string('subject'); + $table->string('view'); + $table->json('variables')->nullable(); + $table->text('body'); + $table->integer('attempts')->default(0); + $table->boolean('sending')->default(0); + $table->boolean('failed')->default(0); + $table->text('error')->nullable(); + $table->json('attachments')->nullable(); + $table->json('from')->nullable(); + $table->json('reply_to')->nullable(); + $table->timestamp('queued_at')->nullable(); + $table->timestamp('scheduled_at')->nullable(); + $table->timestamp('sent_at')->nullable()->index(); + $table->timestamp('delivered_at')->nullable(); + $table->timestamps(); + $table->softDeletes(); }); } diff --git a/src/Attachment.php b/src/Attachment.php index 48b2220..dc4d016 100644 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -17,34 +17,34 @@ public function __construct(public string $path, public ?string $disk = null) // } - public static function fromPath($path) + public static function fromPath(string $path): self { return new static($path); } - public static function fromData() + public static function fromData(): void { throw new RuntimeException('Raw attachments are not supported in the database email driver.'); } - public static function fromStorage() + public static function fromStorage(): void { throw new RuntimeException('Raw attachments are not supported in the database email driver.'); } - public static function fromStorageDisk($disk, $path) + public static function fromStorageDisk($disk, $path): self { return new static($path, $disk); } - public function as(string $name) + public function as(string $name): self { $this->as = $name; return $this; } - public function withMime(string $mime) + public function withMime(string $mime): self { $this->mime = $mime; diff --git a/src/Config.php b/src/Config.php index 9c6ae83..689eff4 100644 --- a/src/Config.php +++ b/src/Config.php @@ -11,7 +11,7 @@ class Config */ public static function maxAttemptCount(): int { - return max(config('laravel-database-emails.attempts', 1), 3); + return max(config('database-emails.attempts', 1), 3); } /** @@ -19,7 +19,7 @@ public static function maxAttemptCount(): int */ public static function testing(): bool { - return (bool) config('laravel-database-emails.testing.enabled', false); + return (bool) config('database-emails.testing.enabled', false); } /** @@ -27,7 +27,7 @@ public static function testing(): bool */ public static function testEmailAddress(): string { - return config('laravel-database-emails.testing.email'); + return config('database-emails.testing.email'); } /** @@ -35,7 +35,7 @@ public static function testEmailAddress(): string */ public static function cronjobEmailLimit(): int { - return config('laravel-database-emails.limit', 20); + return config('database-emails.limit', 20); } /** @@ -43,6 +43,6 @@ public static function cronjobEmailLimit(): int */ public static function sendImmediately(): bool { - return (bool) config('laravel-database-emails.immediately', false); + return (bool) config('database-emails.immediately', false); } } diff --git a/src/Email.php b/src/Email.php index a07c81c..9bea33d 100644 --- a/src/Email.php +++ b/src/Email.php @@ -12,26 +12,26 @@ use Throwable; /** - * @property $id - * @property $label - * @property $recipient - * @property $from - * @property $cc - * @property $bcc - * @property $reply_to - * @property $subject - * @property $view - * @property $variables - * @property $body - * @property $attachments - * @property $attempts - * @property $sending - * @property $failed - * @property $error - * @property $queued_at - * @property $scheduled_at - * @property $sent_at - * @property $delivered_at + * @property int $id + * @property string $label + * @property array $recipient + * @property array $from + * @property array $cc + * @property array $bcc + * @property array $reply_to + * @property string $subject + * @property string $view + * @property array $variables + * @property string $body + * @property array $attachments + * @property int $attempts + * @property int $sending + * @property int $failed + * @property int $error + * @property ?Carbon $queued_at + * @property ?Carbon $scheduled_at + * @property ?Carbon $sent_at + * @property ?Carbon $delivered_at */ class Email extends Model { @@ -48,65 +48,27 @@ class Email extends Model 'attachments' => 'json', ]; - /** - * The table in which the e-mails are stored. - * - * @var string - */ protected $table = 'emails'; - /** - * The guarded fields. - * - * @var array - */ protected $guarded = []; public static ?Closure $pruneQuery = null; - /** - * Compose a new e-mail. - */ public static function compose(): EmailComposer { return new EmailComposer(new static()); } - /** - * Get the e-mail from address. - */ - public function getFromAddress(): ?string - { - return $this->from['address']; - } - - /** - * Get the e-mail from address. - */ - public function getFromName(): ?string - { - return $this->from['name']; - } - - /** - * Determine if the e-mail is sent. - */ public function isSent(): bool { return ! is_null($this->sent_at); } - /** - * Determine if the e-mail failed to be sent. - */ public function hasFailed(): bool { return $this->failed == 1; } - /** - * Mark the e-mail as sending. - */ public function markAsSending(): void { $this->update([ @@ -115,9 +77,6 @@ public function markAsSending(): void ]); } - /** - * Mark the e-mail as sent. - */ public function markAsSent(): void { $now = Carbon::now()->toDateTimeString(); @@ -130,9 +89,6 @@ public function markAsSent(): void ]); } - /** - * Mark the e-mail as failed. - */ public function markAsFailed(Throwable $exception): void { $this->update([ @@ -142,48 +98,17 @@ public function markAsFailed(Throwable $exception): void ]); } - /** - * Send the e-mail. - */ public function send(): void { (new Sender())->send($this); } - /** - * Retry sending the e-mail. - */ - public function retry(): void - { - $retry = $this->replicate(); - - $retry->fill( - [ - 'id' => null, - 'attempts' => 0, - 'sending' => 0, - 'failed' => 0, - 'error' => null, - 'sent_at' => null, - 'delivered_at' => null, - ] - ); - - $retry->save(); - } - - /** - * @return void - */ - public static function pruneWhen(Closure $closure) + public static function pruneWhen(Closure $closure): void { static::$pruneQuery = $closure; } - /** - * @return Builder - */ - public function prunable() + public function prunable(): Builder { if (static::$pruneQuery) { return (static::$pruneQuery)($this); diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 8c63a92..aa7da9e 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -5,6 +5,8 @@ namespace Stackkit\LaravelDatabaseEmails; use Closure; +use Illuminate\Contracts\Translation\HasLocalePreference; +use Illuminate\Foundation\Auth\User; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; @@ -32,6 +34,8 @@ class EmailComposer public ?array $attachments = null; + public ?string $locale = null; + /** * Create a new EmailComposer instance. */ @@ -79,6 +83,25 @@ public function attachments(null|array|Closure $attachments = null): self return $this; } + public function user(User $user) + { + return $this->envelope(function (Envelope $envelope) use ($user) { + $name = method_exists($user, 'preferredEmailName') + ? $user->preferredEmailName() + : ($user->name ?? null); + + $email = method_exists($user, 'preferredEmailAddress') + ? $user->preferredEmailAddress() + : $user->email; + + if ($user instanceof HasLocalePreference) { + $this->locale = $user->preferredLocale(); + } + + return $envelope->to($email, $name); + }); + } + /** * Get the e-mail that is being composed. */ diff --git a/src/LaravelDatabaseEmailsServiceProvider.php b/src/LaravelDatabaseEmailsServiceProvider.php index a7b898c..30a9ea4 100644 --- a/src/LaravelDatabaseEmailsServiceProvider.php +++ b/src/LaravelDatabaseEmailsServiceProvider.php @@ -27,7 +27,7 @@ private function bootConfig(): void $this->publishes([ $configDir.'laravel-database-emails.php' => config_path('laravel-database-emails.php'), - ], 'laravel-database-emails-config'); + ], 'database-emails-config'); } /** @@ -38,13 +38,9 @@ private function bootDatabase(): void $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; $migrationsDir = $baseDir.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR; - if ($this->app['config']->get('laravel-database-emails.manual_migrations')) { - $this->publishes([ - $migrationsDir => "{$this->app->databasePath()}/migrations", - ], 'laravel-database-emails-migrations'); - } else { - $this->loadMigrationsFrom([$migrationsDir]); - } + $this->publishes([ + $migrationsDir => "{$this->app->databasePath()}/migrations", + ], 'database-emails-migrations'); } /** diff --git a/src/MailableReader.php b/src/MailableReader.php index 7f92c0d..22ded41 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -6,14 +6,15 @@ use Error; use Exception; -use Illuminate\Container\Container; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; -use ReflectionObject; +use Illuminate\Support\Traits\Localizable; class MailableReader { + use Localizable; + /** * Read the mailable and pass the data to the email composer. */ @@ -44,14 +45,10 @@ public function attachments(): array }); } - if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { - $reflected = (new ReflectionObject($composer->getData('mailable'))); - $method = $reflected->getMethod('prepareMailableForDelivery'); - $method->setAccessible(true); - $method->invoke($composer->getData('mailable')); - } else { - Container::getInstance()->call([$composer->getData('mailable'), 'build']); - } + (fn (Mailable $mailable) => $mailable->prepareMailableForDelivery())->call( + $composer->getData('mailable'), + $composer->getData('mailable'), + ); $this->readRecipient($composer); @@ -87,9 +84,9 @@ private function convertMailableAddresses($from): array */ private function readRecipient(EmailComposer $composer): void { - if (config('laravel-database-emails.testing.enabled')) { + if (config('database-emails.testing.enabled')) { $composer->getEmail()->recipient = [ - config('laravel-database-emails.testing.email') => null, + config('database-emails.testing.email') => null, ]; return; @@ -154,7 +151,13 @@ private function readBody(EmailComposer $composer): void $composer->getEmail()->view = $mailable->view; $composer->getEmail()->variables = $mailable->buildViewData(); - $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(); + + $localeToUse = $composer->locale ?? app()->currentLocale(); + + $this->withLocale( + $localeToUse, + fn () => $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(), + ); } /** diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index e8ed7fc..cf833c7 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -63,6 +63,8 @@ public function handle(): void try { $email->send(); } catch (Throwable $e) { + report($e); + $email->markAsFailed($e); } } diff --git a/src/Sender.php b/src/Sender.php index 7f755c2..94490c7 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -43,7 +43,7 @@ private function buildMessage(Message $message, Email $email): void ->bcc($email->bcc ?: []) ->replyTo($email->reply_to ?: []) ->subject($email->subject) - ->from($email->getFromAddress(), $email->getFromName()) + ->from($email->from['address'], $email->from['name']) ->html($email->body); foreach ($email->attachments as $dbAttachment) { @@ -66,15 +66,4 @@ private function buildMessage(Message $message, Email $email): void $message->attach($attachment); } } - - public function prepAddresses(array $addresses) - { - return $addresses; - $new = []; - foreach ($addresses as $value) { - $new[$value['address']] = $value['name']; - } - - return $new; - } } diff --git a/test b/test index d79a5857e4f08dd52b2c70d86e8b68487b55dbca..4be2b3b604b1eb95ab129e3dc85bf08f6d63bd2d 100644 GIT binary patch literal 49152 zcmeI&-)_@Z90zclBz040G6^B8;>M)gs8xjG&{8I(X{_r-C8(e+VrZL=oaD4MICk)# zLUz}R4RN!F*lX++_5^!`U2PY;-|rkdj+3}s*DI8LEgGET-}(8RkB^UICwosGIuSPx z{Gew?#vSD&MOBqA3`0>Ao&K)S-*BnXN_qH({#SGBXRYeW?cnFd#UGW+mE%(Vd0kul zbMdDu-!I;;{5Jple6Vn>a(q@*ltTal5ST@Q_GL|9Us6v>j@RXHc+Ym+kcHffm>scP z*_F-Rtq1#C#`exvTVETEqQHjXd&4aaW2KR1X_!A;(=`3Y4fTB-*=?7nb$z!xTANSP z??3qR;npw-qY#nEp;yO?_#qFBe&F=%U|_uD1H+CZ-|;#D?@@aV&!@lGb#ECB*KTvS zVI12*=g1DIAmAOR?@(03XuEzpAKU39WfaxUt%q^@1@9EZA3OYYmO7t^_U4H0g`<3a zcQCy}JBoO(A5qw_Ps5fQU4CH4Ze-kS-lXP`U)S*t8%E@K1BzX-%xr%Xp$Bx>I4S*m zBo6{Vpg{g6qFr#(8}y^|RSi2wyc^SgXlOKyt{w5n>G7nYVz`Wzb-C*t^I$rd4j0v& z$`SWP!l`0P!wZVZ(C!?G!#>hJ2>f0~|0?G3bhd!^-2sdIB+NXnYWn?U_2jx7=ZPVE z;kQE;$4-~UoH%xn7cvieq+=R+~} z^JM<=H_U6A+jDdJcN?lA=dtjWOZQPMxj51_ofS5VS%t+KamEL88l;tB1^Ym&-({){aTaoN?vwSI!vu5@F^XCB1?(st0vkyqp zN4j!32hQ{bWN22HRD2HUwRw8q>ib7_(u%aGGa>Q>FfE{ayXr`X{x&YQNOp)%K_e3j`nl0SG_<0uX=z1Rwwb2>kB^R;y*@n`*1s zT4T*S%(~00b?a{P9-{~S-XUjg$D?lt=nkBTr%$cF(Jlsj1 z1&dgD(;qTnE-QW(Z>@>$ueFl++@rb4YG$ddJe#aW zerdDv{olM)QBJBNmXbF`t3>|^ZK^ZFhaqFn$x=i$LCkW<6a_itvz?@lxc^t{e<<`1 z3j`nl0SG_<0uX=z1Rwwb2tWV=Ga^t^OE=W)rvhPB-2c;${;@y+0uX=z1Rwwb2tWV= z5P$##ATYxM3!-nh|DWL_jIKcd0uX=z1Rwwb2tWV=5P$##6b0Y^V-!FD0uX=z1Rwwb z2tWV=5P$##W?ul`|Ihw0Mh_tX0SG_<0uX=z1Rwwb2tWV=`2HVb00Izz00bZa0SG_< z0uX=z1RyZ`0=WO5{bP(CLI45~fB*y_009U<00Izz00i*+e~bYLKmY;|fB*y_009U< z00Izz!0Zd)`~TTL#^@mgAOHafKmY;|fB*y_009U<0N?*(3_t(^5P$##AOHafKmY;| KfB*z$U*K;NdPztC literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5assertEquals(3, Config::maxAttemptCount()); - $this->app['config']->set('laravel-database-emails.attempts', 5); + $this->app['config']->set('database-emails.attempts', 5); $this->assertEquals(5, Config::maxAttemptCount()); } @@ -22,7 +22,7 @@ public function test_testing() { $this->assertFalse(Config::testing()); - $this->app['config']->set('laravel-database-emails.testing.enabled', true); + $this->app['config']->set('database-emails.testing.enabled', true); $this->assertTrue(Config::testing()); } @@ -32,7 +32,7 @@ public function test_test_email_address() { $this->assertEquals('test@email.com', Config::testEmailAddress()); - $this->app['config']->set('laravel-database-emails.testing.email', 'test+update@email.com'); + $this->app['config']->set('database-emails.testing.email', 'test+update@email.com'); $this->assertEquals('test+update@email.com', Config::testEmailAddress()); } @@ -42,7 +42,7 @@ public function test_cronjob_email_limit() { $this->assertEquals(20, Config::cronjobEmailLimit()); - $this->app['config']->set('laravel-database-emails.limit', 15); + $this->app['config']->set('database-emails.limit', 15); $this->assertEquals(15, Config::cronjobEmailLimit()); } diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 4ce0e36..b8ea143 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -102,16 +102,16 @@ public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); - $this->assertEquals($email->from['address'], $email->getFromAddress()); - $this->assertEquals($email->from['name'], $email->getFromName()); + $this->assertEquals($email->from['address'], $email->from['address']); + $this->assertEquals($email->from['name'], $email->from['name']); $email = $this->composeEmail([ 'from' => new Address('marick@dolphiq.nl', 'Marick'), ])->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); } #[Test] @@ -165,10 +165,10 @@ public function the_scheduled_date_should_be_saved_correctly() #[Test] public function recipient_should_be_swapped_for_test_address_when_in_testing_mode() { - $this->app['config']->set('laravel-database-emails.testing.enabled', function () { + $this->app['config']->set('database-emails.testing.enabled', function () { return true; }); - $this->app['config']->set('laravel-database-emails.testing.email', 'test@address.com'); + $this->app['config']->set('database-emails.testing.email', 'test@address.com'); $email = $this->sendEmail(['recipient' => 'jane@doe.com']); @@ -182,7 +182,6 @@ public function attachments_should_be_saved_correctly() ->attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), Attachment::fromPath(__DIR__.'/files/pdf-sample2.pdf'), - // Attachment::fromStorage('pdf-sample.pdf'), Attachment::fromStorageDisk('my-custom-disk', 'pdf-sample-2.pdf'), ]) ->send(); diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index d09d0e3..4e29bc1 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -6,7 +6,12 @@ use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; +use Workbench\App\Models\User; +use Workbench\App\Models\UserWithPreferredEmail; +use Workbench\App\Models\UserWithPreferredLocale; +use Workbench\App\Models\UserWithPreferredName; class EnvelopeTest extends TestCase { @@ -31,4 +36,99 @@ public function test_it_can_set_the_envelope() 'janedoe@example.com' => null, ], $email->recipient); } + + #[Test] + public function test_it_can_pass_user_models() + { + $user = (new User())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'johndoe@example.com' => 'J. Doe', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_email() + { + $user = (new UserWithPreferredEmail())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'noreply@abc.com' => 'J. Doe', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_name() + { + $user = (new UserWithPreferredName())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'johndoe@example.com' => 'J.D.', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_locale() + { + $nonLocaleUser = (new User())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $emailForNonLocaleUser = Email::compose() + ->user($nonLocaleUser) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('locale-email')) + ->send(); + + $localeUser = (new UserWithPreferredLocale())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $emailForLocaleUser = Email::compose() + ->user($localeUser) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('locale-email')) + ->send(); + + $this->assertStringContainsString('Hello!', $emailForNonLocaleUser->body); + $this->assertStringContainsString('Kumusta!', $emailForLocaleUser->body); + } } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 3129df8..8e89065 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -96,8 +96,8 @@ public function it_extracts_the_from_address_and_or_name() )->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) @@ -105,16 +105,16 @@ public function it_extracts_the_from_address_and_or_name() )->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals(null, $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals(null, $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); } } diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 43b75f1..ae447ed 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -79,7 +79,7 @@ public function the_number_of_emails_sent_per_minute_should_be_limited() $this->sendEmail(); } - $this->app['config']['laravel-database-emails.limit'] = 25; + $this->app['config']['database-emails.limit'] = 25; $this->artisan('email:send'); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 159295d..7083b01 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -159,15 +159,17 @@ public function attachments_are_added_to_the_email() ->attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + Attachment::fromStorageDisk('my-custom-disk', 'test.txt'), ]) ->send(); $this->artisan('email:send'); $attachments = reset($this->sent)->attachments; - $this->assertCount(2, $attachments); + $this->assertCount(3, $attachments); $this->assertEquals('Test123'."\n", $attachments[1]['body']); $this->assertEquals('text/plain disposition: attachment filename: Test123 file', $attachments[1]['disposition']); + $this->assertEquals("my file from public disk\n", $attachments[2]['body']); } #[Test] @@ -186,12 +188,12 @@ public function raw_attachments_are_not_added_to_the_email() #[Test] public function emails_can_be_sent_immediately() { - $this->app['config']->set('laravel-database-emails.immediately', false); + $this->app['config']->set('database-emails.immediately', false); $this->sendEmail(); $this->assertCount(0, $this->sent); Email::truncate(); - $this->app['config']->set('laravel-database-emails.immediately', true); + $this->app['config']->set('database-emails.immediately', true); $this->sendEmail(); $this->assertCount(1, $this->sent); diff --git a/tests/TestCase.php b/tests/TestCase.php index c5f46cc..82ceaa1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Orchestra\Testbench\Concerns\WithWorkbench; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -13,6 +14,7 @@ class TestCase extends \Orchestra\Testbench\TestCase protected $invalid; use LazilyRefreshDatabase; + use WithWorkbench; public function setUp(): void { @@ -52,13 +54,13 @@ protected function getPackageProviders($app) */ protected function getEnvironmentSetUp($app) { - $app['config']->set('laravel-database-emails.attempts', 3); - $app['config']->set('laravel-database-emails.testing.enabled', false); - $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); + $app['config']->set('database-emails.attempts', 3); + $app['config']->set('database-emails.testing.enabled', false); + $app['config']->set('database-emails.testing.email', 'test@email.com'); $app['config']->set('filesystems.disks.my-custom-disk', [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => __DIR__.'/../workbench/storage/app/public', ]); $app['config']->set('database.default', 'testbench'); diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 0000000..d4ad593 --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,11 @@ +loadTranslationsFrom( + __DIR__.'/../../lang', + 'package' + ); } /** diff --git a/workbench/lang/en/messages.php b/workbench/lang/en/messages.php new file mode 100644 index 0000000..98f8a0b --- /dev/null +++ b/workbench/lang/en/messages.php @@ -0,0 +1,5 @@ + 'Hello!', +]; diff --git a/workbench/lang/fil-PH/messages.php b/workbench/lang/fil-PH/messages.php new file mode 100644 index 0000000..f801763 --- /dev/null +++ b/workbench/lang/fil-PH/messages.php @@ -0,0 +1,5 @@ + 'Kumusta!', +]; diff --git a/workbench/resources/views/locale-email.blade.php b/workbench/resources/views/locale-email.blade.php new file mode 100644 index 0000000..6d1b8a4 --- /dev/null +++ b/workbench/resources/views/locale-email.blade.php @@ -0,0 +1,2 @@ +{{ trans('workbench::messages.greeting') }} + diff --git a/workbench/storage/app/public/test.txt b/workbench/storage/app/public/test.txt new file mode 100644 index 0000000..77c2f3e --- /dev/null +++ b/workbench/storage/app/public/test.txt @@ -0,0 +1 @@ +my file from public disk From 75073f3a971b174d401b42e875146cce5ffeda35 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 00:57:55 +0100 Subject: [PATCH 09/13] wip --- README.md | 22 +++-- ...=> 2024_03_16_151608_create_new_table.php} | 3 +- logo.png | Bin 16182 -> 0 bytes src/Email.php | 10 +- src/EmailComposer.php | 65 ++----------- src/HasEncryptedAttributes.php | 92 ------------------ src/MailableReader.php | 9 ++ src/Sender.php | 11 +-- test | Bin 49152 -> 53248 bytes tests/ComposeTest.php | 53 ++++++++++ 10 files changed, 92 insertions(+), 173 deletions(-) rename database/migrations/{2024_03_16_151608_change_binary_to_text.php => 2024_03_16_151608_create_new_table.php} (94%) delete mode 100644 logo.png delete mode 100644 src/HasEncryptedAttributes.php create mode 100644 tests/ComposeTest.php diff --git a/README.md b/README.md index 9471348..3bf00f6 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,13 @@ -

- -

Build Status Latest Stable Version License

-# Package Documentation +# Introduction This package allows you to store and send e-mails using a database. -## Contribution - -The package is MIT licenced, meaning it's open source and you are free to copy or fork it and modify it any way you wish. - -We feel the package is currently feature complete, but feel free to send a pull request or help improve existing code. - # Requirements This package requires Laravel 10 or 11. @@ -188,6 +179,17 @@ Email::compose() Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. +### Attaching models to e-mails + +You may attach models to your e-mails. + +```php + +Email::compose() + ->model(User::find(1)); + +``` + ### Scheduling You may schedule an e-mail by calling `later` instead of `send`. You must provide a Carbon instance or a strtotime valid date. diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_create_new_table.php similarity index 94% rename from database/migrations/2024_03_16_151608_change_binary_to_text.php rename to database/migrations/2024_03_16_151608_create_new_table.php index 9b18e51..21bafee 100644 --- a/database/migrations/2024_03_16_151608_change_binary_to_text.php +++ b/database/migrations/2024_03_16_151608_create_new_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class ChangeBinaryToText extends Migration +class CreateNewTable extends Migration { /** * Run the migrations. @@ -31,6 +31,7 @@ public function up() $table->text('error')->nullable(); $table->json('attachments')->nullable(); $table->json('from')->nullable(); + $table->nullableMorphs('model'); $table->json('reply_to')->nullable(); $table->timestamp('queued_at')->nullable(); $table->timestamp('scheduled_at')->nullable(); diff --git a/logo.png b/logo.png deleted file mode 100644 index 69d1e49188ea12e0d97dac9e62c326c969c57636..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16182 zcmeI3c~}#78pnrK#O3g66|`1jtS5w-9E4;x36&!TYXD&5AfH0C>DYzFmNi z@_Yb5d{?iIwZ$rDz$k7EKs0zF7LaZ<6SDz85}a;E&}7WUO2o8!la$@|%~3W>uaUCn z1S%n=ITX|B6`2-nR%WCc%}hqc8g{VEU6KwH0gRXpVWk@lCM%pSW!vMz#BXvKWV7s3 zY{^pgR5BqeRvFC-#Vr^sFhIybxqJ~TNF2Zo6pMHQe-;no3PDH=as?cS3qxWU;<7ql zY?(XpOJdO^!KyHMXF9@?vUN6_83w_$w6uUUegJOKf?Tm!3_?7R$Kwz)IMxi44N2#i ztm7O(y5fXkR@9<5+w{1JMaD%E@f4es%_b9dzdEnWXzosAvUai~D1zyT8RP~)U~eJ~ z+O09CSPb@4)1V+`z>Jv5W+imoUUg<2Zo{oQypNRK?miO}Yob(k=kE1n*>tqQ?74@umPdqFPv0*Y&%YlR( zo=DB(!hu|vC-j4OFa$XTam=K|P~sYWQbunvc(8!)9K&fQH3m^Z8pMXYR21jAPH{9S zoP=A9h)t$9B3cYIo3s+JXV7V8_r8KdaRY84P79OqC17vEPAdCegCk5<8)8DSh%gy3 z5TMsi%hh>VIkTy7A;LAaQJg9(Emo)98hjF8ur$SK$hiJ~leqVpjJ zr#a-cXvk|3Vj?jQ)o=uS0m>0zL<$jC!{dlVNth7gBSDZ@)Rm%_FfXK1=&i(l&2ZYs z#BR3twprN1=SRC72EDyUnGuT>BP&qK?kd6Vrq)DEq9wgIMt-TN+})rfP{D@^ZEBi2J;dnm_`Q z{|5&B|6nPsNa`P4h?lCXKe!OiJ?j6t5Is8?)gdM=rjdbNo49M><<`!zqR&@XSH-?q zjS})N3MML<*j0$Vf{0f2!dOpc$NX5bA`L0_`9VC0c!6B#iq5z>c8C>*yC|(8^1qi*#;G*Y4@iMq5KUIrHh2))VRqUS^LGPo!}=uHL}Js*mf!9@W=Z!);(`B1zJE(#EOlfgyLhvH>$ zQGn2!w7A@R9^}MK#M7H;#ABLSt3K>cJmSeh6)GhFWR3@bWh(&S;WOgSpa)X&VW6Pr^9EW z7X15yfg4U(MN#~yFGeqYe9t$C>D>y>MK*PZ^- z<#z0q1YlTv_Px>8aXIl2ux00mIiKz7 z@(4B?8-QIie5Y+J!Bj$dO?*K6&x*@?RKi08M!L0>s%vDMN^W|T-8gX3JMngTjc(K3 zxf`C2nR7kdzw&_3%AkVnWy6DipB585;2tWS{b-Q&%Yq93^_G+|U%d6ONcB@(qjJ;T z?{}9isPXfvF77B)JiK38zs2?G@z}aGnb^fCRX!QVeSGE7!NLyF>w{lSZOFP4r&$}W%=Ocha#ig^k3AlD{#xjzI^QOv zYTi=L!HJufi0`(JJn+Mwg~OAYYX@LEmunN_XYM|myL#~UrkE!k%h<-_;!=+XP0@M2 zDJNFsdWUYCt5Z~e?YpXf+k^IhoW8k!R+gKmJ}IxY>cq@t{>L(0PM$cTU02_q^PULm^RB zO|Dl*uex$V;ThhE#gxMcIvSK4wm-Muh#!ysIpBlkPDBgXrV%=??<(s##4W?i4q zv}4q@49+R}P+KU!?2~vP^Av2zzJK`h?-sS4RC+Bt>KF3!7R9#H&7xwH2Cr_>N%tr& zkC?nYu9368WTEPC>r&my`stTrC(oPTcy?W2yn0_=v3ws`wRd?fUiHS+`JdMMPTHG! z;$PK5_n&@c;Q5kg+v58{_I4+fK{p zz9)~_6BqYM@^+tl<;NnMQI)H&+7MNDz~AR+V^MCI=i#Fp)sXx_oA-sk6qP3~+8fq( z?VX%&Inx%6*lNmI_+#sc=*8PwSA}RtoDateTimeString(); - $this->update([ 'sending' => 0, - 'sent_at' => $now, + 'sent_at' => now(), 'failed' => 0, 'error' => '', ]); @@ -116,4 +115,9 @@ public function prunable(): Builder return $this->where('created_at', '<', now()->subMonths(6)); } + + public function model(): MorphTo + { + return $this->morphTo(); + } } diff --git a/src/EmailComposer.php b/src/EmailComposer.php index aa7da9e..c62b7fc 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Contracts\Translation\HasLocalePreference; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; @@ -102,6 +103,13 @@ public function user(User $user) }); } + public function model(Model $model) + { + $this->setData('model', $model); + + return $this; + } + /** * Get the e-mail that is being composed. */ @@ -110,11 +118,6 @@ public function getEmail(): Email return $this->email; } - /** - * Set a data value. - * - * @param mixed $value - */ public function setData(string $key, $value): self { $this->data[$key] = $value; @@ -122,12 +125,6 @@ public function setData(string $key, $value): self return $this; } - /** - * Get a data value. - * - * @param mixed $default - * @return mixed - */ public function getData(string $key, $default = null) { if (! is_null($default) && ! $this->hasData($key)) { @@ -137,17 +134,11 @@ public function getData(string $key, $default = null) return $this->data[$key]; } - /** - * Determine if the given data value was set. - */ public function hasData(string $key): bool { return isset($this->data[$key]); } - /** - * Set the e-mail label. - */ public function label(string $label): self { $this->email->label = $label; @@ -155,11 +146,6 @@ public function label(string $label): self return $this; } - /** - * Schedule the e-mail. - * - * @param mixed $scheduledAt - */ public function later($scheduledAt): Email { $this->email->scheduled_at = Carbon::parse($scheduledAt); @@ -167,11 +153,6 @@ public function later($scheduledAt): Email return $this->send(); } - /** - * Queue the e-mail. - * - * @param \DateTimeInterface|\DateInterval|int|null $delay - */ public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email { $connection = $connection ?: config('queue.default'); @@ -187,9 +168,6 @@ public function queue(?string $connection = null, ?string $queue = null, $delay return $this->send(); } - /** - * Set the Mailable. - */ public function mailable(Mailable $mailable): self { $this->setData('mailable', $mailable); @@ -199,33 +177,6 @@ public function mailable(Mailable $mailable): self return $this; } - /** - * Attach a file to the e-mail. - */ - // public function attach(string $file, array $options = []): self - // { - // $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; - // - // $attachments[] = compact('file', 'options'); - // - // return $this->setData('attachments', $attachments); - // } - // - // /** - // * Attach in-memory data as an attachment. - // */ - // public function attachData(string $data, string $name, array $options = []): self - // { - // $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; - // - // $attachments[] = compact('data', 'name', 'options'); - // - // return $this->setData('rawAttachments', $attachments); - // } - - /** - * Send the e-mail. - */ public function send(): Email { if ($this->envelope && $this->content) { diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php deleted file mode 100644 index 0c23f2d..0000000 --- a/src/HasEncryptedAttributes.php +++ /dev/null @@ -1,92 +0,0 @@ -attributes[$key]; - - if (is_resource($value)) { - $value = stream_get_contents($value); - } - - if ($this->isEncrypted() && in_array($key, $this->encrypted)) { - try { - $value = decrypt($value); - } catch (DecryptException $e) { - $value = ''; - } - } - - if (in_array($key, $this->encoded) && is_string($value)) { - $decoded = json_decode($value, true); - - if (! is_null($decoded)) { - $value = $decoded; - } - } - - // BC fix for attachments in 4.1.0 and lower. - // Attachments were stored json encoded. - // Because this doesn't work for raw attachments, value is now serialized. - // Check if value is json encoded or serialized, and decode or unserialize accordingly. - if ($key == 'attachments') { - if (substr($value, 0, 2) === 'a:') { - $unserialized = @unserialize($value); - if ($value !== false) { - $value = $unserialized; - } - } else { - $decoded = json_decode($value, true); - - if (! is_null($decoded)) { - $value = $decoded; - } - } - } - - return $value; - } -} diff --git a/src/MailableReader.php b/src/MailableReader.php index 22ded41..9dc176c 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -65,6 +65,8 @@ public function attachments(): array $this->readBody($composer); $this->readAttachments($composer); + + $this->readModel($composer); } /** @@ -176,6 +178,13 @@ private function readAttachments(EmailComposer $composer): void }, $mailable->attachments); } + public function readModel(EmailComposer $composer): void + { + if ($composer->hasData('model')) { + $composer->getEmail()->model()->associate($composer->getData('model')); + } + } + private function prepareAddressForDatabaseStorage(array $addresses): array { return collect($addresses)->mapWithKeys(function ($recipient) { diff --git a/src/Sender.php b/src/Sender.php index 94490c7..736c830 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -10,9 +10,6 @@ class Sender { - /** - * Send the given e-mail. - */ public function send(Email $email): void { if ($email->isSent()) { @@ -25,17 +22,11 @@ public function send(Email $email): void $this->buildMessage($message, $email); }); - // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) - if (! is_null($sentMessage)) { - event(new MessageSent($sentMessage)); - } + event(new MessageSent($sentMessage)); $email->markAsSent(); } - /** - * Build the e-mail message. - */ private function buildMessage(Message $message, Email $email): void { $message->to($email->recipient) diff --git a/test b/test index 4be2b3b604b1eb95ab129e3dc85bf08f6d63bd2d..c940b5b00a3c99efa1bd6dc9f77bda83f3cc97de 100644 GIT binary patch delta 378 zcmZo@U~X8zJV9Def`Ng77l>hif1-}Dssw{x(IQ?hQ3f7nKL-9tesDbxTGXwXYOWst`&^xx%nxnIq@Zx1*u93Wr;<}8Hq(Y z3Q7>s%oHVs%)FA+^wgrsN4Pg{zQtn}g-nE?ndw001eCa(w^* delta 181 zcmZozz}(QlJV9DeoPmLX2Z&*SZ=#N|vN(fY(IQ^nHU@6iGG_ires 'John Doe', + 'email' => 'johndoe@example.com', + 'password' => 'secret', + ]); + + $email = Email::compose() + ->user($user) + ->model($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals($email->model_type, $user->getMorphClass()); + $this->assertEquals($email->model_id, $user->getKey()); + } + + #[Test] + public function models_can_be_empty(): void + { + $user = User::forceCreate([ + 'name' => 'John Doe', + 'email' => 'johndoe@example.com', + 'password' => 'secret', + ]); + + $email = Email::compose() + ->model($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertNull($email->model_type); + $this->assertNull($email->model_id); + } +} From dd232d88ed05f0a02ba4777f0d1308bde01df809 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 01:03:34 +0100 Subject: [PATCH 10/13] wip --- .github/workflows/run-tests.yml | 2 +- tests/ComposeTest.php | 2 +- tests/DatabaseInteractionTest.php | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 184589d..6790273 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,7 +62,7 @@ jobs: needs: access_check strategy: matrix: - db: [ 'mysql', 'sqlite' ] + db: [ 'mysql', 'sqlite', 'pgsql' ] payload: - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } diff --git a/tests/ComposeTest.php b/tests/ComposeTest.php index 7ffeb93..49e88b9 100644 --- a/tests/ComposeTest.php +++ b/tests/ComposeTest.php @@ -42,7 +42,7 @@ public function models_can_be_empty(): void ]); $email = Email::compose() - ->model($user) + ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) ->content(fn (Content $content) => $content->view('welcome')) ->send(); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index b8ea143..11fdb6d 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -119,7 +119,6 @@ public function variables_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); - $this->assertEquals(json_encode(['name' => 'John Doe'], 1), DB::table('emails')->find(1)->variables); $this->assertEquals(['name' => 'John Doe'], $email->variables); } From 8e87ad09b60c541931fbba553f482eb7f4ab427f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 12:37:05 +0100 Subject: [PATCH 11/13] wip --- README.md | 121 +++++++----------- UPGRADING.md | 51 +++++++- config/database-emails.php | 10 +- .../2017_12_14_151403_create_emails_table.php | 52 -------- ...151421_add_attachments_to_emails_table.php | 34 ----- ..._12_22_114011_add_from_to_emails_table.php | 34 ----- ...02_21000_add_queued_at_to_emails_table.php | 34 ----- ...28_140000_add_reply_to_to_emails_table.php | 30 ----- .../2024_03_16_151608_create_new_table.php | 4 +- src/EmailComposer.php | 95 +++++--------- src/MailableReader.php | 52 ++++---- src/SendEmailJob.php | 2 +- src/SendEmailsCommand.php | 49 ++----- src/Store.php | 7 +- test | Bin 53248 -> 0 bytes tests/MailableReaderTest.php | 18 +-- tests/QueuedEmailsTest.php | 11 ++ tests/TestCase.php | 4 +- workbench/app/Jobs/CustomSendEmailJob.php | 11 ++ 19 files changed, 207 insertions(+), 412 deletions(-) delete mode 100644 database/migrations/2017_12_14_151403_create_emails_table.php delete mode 100644 database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php delete mode 100644 database/migrations/2017_12_22_114011_add_from_to_emails_table.php delete mode 100644 database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php delete mode 100644 database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php delete mode 100644 test create mode 100644 workbench/app/Jobs/CustomSendEmailJob.php diff --git a/README.md b/README.md index 3bf00f6..120cc93 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Introduction -This package allows you to store and send e-mails using a database. +This package allows you to store and send e-mails using the database. # Requirements @@ -16,34 +16,26 @@ This package requires Laravel 10 or 11. Require the package using composer. -```bash +```shell composer require stackkit/laravel-database-emails ``` Publish the configuration files. -```bash +```shell php artisan vendor:publish --tag=database-emails-config php artisan vendor:publish --tag=database-emails-migrations ``` Create the database table required for this package. -```bash +```shell php artisan migrate ``` Add the e-mail cronjob to your scheduler ```php -command('email:send')->everyMinute()->withoutOverlapping(5); @@ -58,8 +50,6 @@ protected function schedule(Schedule $schedule) E-mails are composed the same way mailables are created. ```php -user($user) ->send(); ``` -By default, the `name` column will be used to set the recipient's name. If you wish to use another column, you should implement the `preferredEmailName` method in your model. +By default, the `name` column will be used to set the recipient's name. If you wish to use something different, you should implement the `preferredEmailName` method in your model. ```php -mailable(new OrderShipped()) ->send(); @@ -156,16 +128,11 @@ Email::compose() ### Attachments -To start attaching files to your e-mails, you may use the `attach` method like you normally would in Laravel. +To start attaching files to your e-mails, you may use the `attachments` method like you normally would in Laravel. However, you will have to use this package's `Attachment` class. ```php -attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), @@ -175,19 +142,16 @@ Email::compose() ->send(); ``` - -Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. - +> [!NOTE] +> `Attachment::fromData()` and `Attachment::fromStorage()` are not supported as they work with raw data. ### Attaching models to e-mails -You may attach models to your e-mails. +You may attach a model to an e-mail. This can be useful to attach a user or another model that belongs to the e-mail. ```php - Email::compose() ->model(User::find(1)); - ``` ### Scheduling @@ -195,64 +159,65 @@ Email::compose() You may schedule an e-mail by calling `later` instead of `send`. You must provide a Carbon instance or a strtotime valid date. ```php -later('+2 hours'); ``` ### Queueing e-mails -**Important**: When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. Please make sure it is removed from `app/Console/Kernel.php`. +> [!IMPORTANT] +> When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. ```php -queue(); -use Stackkit\LaravelDatabaseEmails\Email; +// On a specific connection +Email::compose()->queue(connection: 'sqs'); -Email::compose() - ->queue(); +// On a specific queue +Email::compose()->queue(queue: 'email-queue'); -// on a specific connection -Email::compose() - ->queue('sqs'); +// Delay (send mail in 10 minutes) +Email::compose()->queue(delay: now()->addMinutes(10)); +``` -// on a specific queue -Email::compose() - ->queue(null, 'email-queue'); +If you need more flexibility, you may also pass your own job class: -// timeout (send mail in 10 minutes) -Email::compose() - ->queue(null, null, now()->addMinutes(10)); +```php +Email::compose()->queue(jobClass: CustomSendEmailJob::class); ``` -If you need more flexibility over how to queued mails are retried, please implement your own email job. - -Within the job you can send the mail like this: +It could look like this: ```php -use Stackkit\LaravelDatabaseEmails\Sender; +send($email); +class CustomSendEmailJob extends SendEmailJob implements ShouldQueue +{ + // Define custom retries, backoff, etc... +} ``` ### Test mode When enabled, all newly created e-mails will be sent to the specified test e-mail address. This is turned off by default. -``` -DATABASE_EMAILS_TESTING_ENABLED=true -DATABASE_EMAILS_TESTING_EMAIL=your-test-recipient@example.com +```dotenv +DB_EMAILS_TESTING_ENABLED=true +DB_EMAILS_TESTING_EMAIL=your-test-recipient@example.com ``` ### E-mails to send per minute To configure how many e-mails should be sent each command. -``` -DATABASE_EMAILS_LIMIT=20 +```dotenv +DB_EMAILS_LIMIT=20 ``` ### Send e-mails immediately @@ -261,8 +226,8 @@ Useful during development when Laravel Scheduler is not running To enable, set the following environment variable: -``` -DATABASE_EMAILS_IMMEDIATELY=true +```dotenv +DB_EMAILS_IMMEDIATELY=true ``` ### Pruning models @@ -272,7 +237,7 @@ use Stackkit\LaravelDatabaseEmails\Email; $schedule->command('model:prune', [ '--model' => [Email::class], -])->everyMinute(); +])->daily(); ``` By default, e-mails are pruned when they are older than 6 months. diff --git a/UPGRADING.md b/UPGRADING.md index 0fb32bb..2825a32 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,18 +1,61 @@ # From 6.x to 7.x -## Database changes (Impact: high) +7.x is a bigger change which cleans up parts of the code base and modernizes the package. That means there are a few high-impact changes. + +## Database changes (Impact: High) The way addresses are stored in the database has changed. Therefore, emails created in 6.x and below are incompatible. When you upgrade, the existing database table will be renamed to "emails_old" and a new table will be created. -## Creating emails (Impact: high) +The table migration now needs to be published first. Please run this command: + +```shell +php artisan vendor:publish --tag=database-emails-migrations +``` + +Then, run the migration: + +```shell +php artisan migrate +``` + +## Environment variables, configurations (Impact: High) + +Environment variable names, as well as the config file name, have been shortened. + +Please publish the new configuration file: + +```shell +php artisan vendor:publish --tag=database-emails-config +``` + +You can remove the old configuration file. + +Rename the following environments: + +`LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` `→` `DB_EMAILS_TESTING_ENABLED` +`LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` `->` `DB_EMAILS_SEND_IMMEDIATELY` + +The following environments are new: + +- `DB_EMAILS_ATTEMPTS` +- `DB_EMAILS_TESTING_EMAIL` +- `DB_EMAILS_LIMIT` +- `DB_EMAILS_IMMEDIATELY` + +The following environments have been removed: + +- `LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS` because migrations are always published. + +## Creating emails (Impact: High) The way emails are composed has changed and now borrows a lot from Laravel's mailable. ```php use Illuminate\Mail\Mailables\Content; -use Stackkit\LaravelDatabaseEmails\Attachment;use Stackkit\LaravelDatabaseEmails\Email; +use Stackkit\LaravelDatabaseEmails\Attachment; +use Stackkit\LaravelDatabaseEmails\Email; use Illuminate\Mail\Mailables\Envelope; Email::compose() @@ -32,6 +75,6 @@ Email::compose() ]) ``` -## Encryption (Impact: moderate) +## Encryption (Impact: moderate/low) E-mail encryption has been removed from the package. diff --git a/config/database-emails.php b/config/database-emails.php index 6f9d433..0d85003 100644 --- a/config/database-emails.php +++ b/config/database-emails.php @@ -13,7 +13,7 @@ | */ - 'attempts' => 3, + 'attempts' => env('DB_EMAILS_ATTEMPTS', 3), /* |-------------------------------------------------------------------------- @@ -28,9 +28,9 @@ 'testing' => [ - 'email' => env('DATABASE_EMAILS_TESTING_EMAIL'), + 'email' => env('DB_EMAILS_TESTING_EMAIL'), - 'enabled' => env('DATABASE_EMAILS_TESTING_ENABLED', false), + 'enabled' => env('DB_EMAILS_TESTING_ENABLED', false), ], @@ -45,7 +45,7 @@ | */ - 'limit' => env('DATABASE_EMAILS_LIMIT', 20), + 'limit' => env('DB_EMAILS_LIMIT', 20), /* |-------------------------------------------------------------------------- @@ -58,5 +58,5 @@ | */ - 'immediately' => env('DATABASE_EMAILS_IMMEDIATELY', false), + 'immediately' => env('DB_EMAILS_IMMEDIATELY', false), ]; diff --git a/database/migrations/2017_12_14_151403_create_emails_table.php b/database/migrations/2017_12_14_151403_create_emails_table.php deleted file mode 100644 index 23489da..0000000 --- a/database/migrations/2017_12_14_151403_create_emails_table.php +++ /dev/null @@ -1,52 +0,0 @@ -increments('id'); - $table->string('label')->nullable(); - $table->binary('recipient'); - $table->binary('cc')->nullable(); - $table->binary('bcc')->nullable(); - $table->binary('subject'); - $table->string('view', 255); - $table->binary('variables')->nullable(); - $table->binary('body'); - $table->integer('attempts')->default(0); - $table->boolean('sending')->default(0); - $table->boolean('failed')->default(0); - $table->text('error')->nullable(); - $table->boolean('encrypted')->default(0); - $table->timestamp('scheduled_at')->nullable(); - $table->timestamp('sent_at')->nullable(); - $table->timestamp('delivered_at')->nullable(); - $table->timestamps(); - $table->softDeletes(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php b/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php deleted file mode 100644 index d47724e..0000000 --- a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -binary('attachments')->after('body')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php b/database/migrations/2017_12_22_114011_add_from_to_emails_table.php deleted file mode 100644 index 7d8c2b4..0000000 --- a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -binary('from')->after('body')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php b/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php deleted file mode 100644 index 5414048..0000000 --- a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -timestamp('queued_at')->after('encrypted')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php b/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php deleted file mode 100644 index 6847ba2..0000000 --- a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php +++ /dev/null @@ -1,30 +0,0 @@ -binary('reply_to')->nullable()->after('bcc'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2024_03_16_151608_create_new_table.php b/database/migrations/2024_03_16_151608_create_new_table.php index 21bafee..5c0424a 100644 --- a/database/migrations/2024_03_16_151608_create_new_table.php +++ b/database/migrations/2024_03_16_151608_create_new_table.php @@ -13,7 +13,9 @@ class CreateNewTable extends Migration */ public function up() { - Schema::rename('emails', 'emails_old'); + if (Schema::hasTable('emails')) { + Schema::rename('emails', 'emails_old'); + } Schema::create('emails', function (Blueprint $table) { $table->increments('id'); diff --git a/src/EmailComposer.php b/src/EmailComposer.php index c62b7fc..fc4d67c 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -15,19 +15,7 @@ class EmailComposer { - /** - * The e-mail that is being composed. - * - * @var Email - */ - private $email; - - /** - * The e-email data. - * - * @var array - */ - protected $data = []; + public ?Mailable $mailable = null; public ?Envelope $envelope = null; @@ -37,12 +25,21 @@ class EmailComposer public ?string $locale = null; - /** - * Create a new EmailComposer instance. - */ - public function __construct(Email $email) + public ?Model $model = null; + + public ?bool $queued = null; + + public ?string $connection = null; + + public ?string $queue = null; + + public int|Carbon|null $delay = null; + + public ?string $jobClass = null; + + public function __construct(public Email $email) { - $this->email = $email; + // } public function envelope(null|Envelope|Closure $envelope = null): self @@ -105,40 +102,11 @@ public function user(User $user) public function model(Model $model) { - $this->setData('model', $model); - - return $this; - } - - /** - * Get the e-mail that is being composed. - */ - public function getEmail(): Email - { - return $this->email; - } - - public function setData(string $key, $value): self - { - $this->data[$key] = $value; + $this->model = $model; return $this; } - public function getData(string $key, $default = null) - { - if (! is_null($default) && ! $this->hasData($key)) { - return $default; - } - - return $this->data[$key]; - } - - public function hasData(string $key): bool - { - return isset($this->data[$key]); - } - public function label(string $label): self { $this->email->label = $label; @@ -153,24 +121,25 @@ public function later($scheduledAt): Email return $this->send(); } - public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email + public function queue(?string $connection = null, ?string $queue = null, $delay = null, ?string $jobClass = null): Email { $connection = $connection ?: config('queue.default'); $queue = $queue ?: 'default'; $this->email->queued_at = now(); - $this->setData('queued', true); - $this->setData('connection', $connection); - $this->setData('queue', $queue); - $this->setData('delay', $delay); + $this->queued = true; + $this->connection = $connection; + $this->queue = $queue; + $this->delay = $delay; + $this->jobClass = $jobClass; return $this->send(); } public function mailable(Mailable $mailable): self { - $this->setData('mailable', $mailable); + $this->mailable = $mailable; (new MailableReader())->read($this); @@ -194,17 +163,21 @@ public function send(): Email $this->email->refresh(); - if ($this->getData('queued', false) === true) { - dispatch(new SendEmailJob($this->email)) - ->onConnection($this->getData('connection')) - ->onQueue($this->getData('queue')) - ->delay($this->getData('delay')); + if (Config::sendImmediately()) { + $this->email->send(); return $this->email; } - if (Config::sendImmediately()) { - $this->email->send(); + if ($this->queued) { + $job = $this->jobClass ?: SendEmailJob::class; + + dispatch(new $job($this->email)) + ->onConnection($this->connection) + ->onQueue($this->queue) + ->delay($this->delay); + + return $this->email; } return $this->email; diff --git a/src/MailableReader.php b/src/MailableReader.php index 9dc176c..bdc3876 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -20,8 +20,14 @@ class MailableReader */ public function read(EmailComposer $composer): void { + $canBeSent = $composer->mailable || ($composer->envelope && $composer->content); + + if (! $canBeSent) { + throw new Error('E-mail cannot be sent: no mailable or envelope and content provided.'); + } + if ($composer->envelope && $composer->content) { - $composer->setData('mailable', new class($composer) extends Mailable + $composer->mailable = new class($composer) extends Mailable { public function __construct(private EmailComposer $composer) { @@ -42,12 +48,12 @@ public function attachments(): array { return $this->composer->attachments ?? []; } - }); + }; } (fn (Mailable $mailable) => $mailable->prepareMailableForDelivery())->call( - $composer->getData('mailable'), - $composer->getData('mailable'), + $composer->mailable, + $composer->mailable, ); $this->readRecipient($composer); @@ -87,15 +93,15 @@ private function convertMailableAddresses($from): array private function readRecipient(EmailComposer $composer): void { if (config('database-emails.testing.enabled')) { - $composer->getEmail()->recipient = [ + $composer->email->recipient = [ config('database-emails.testing.email') => null, ]; return; } - $composer->getEmail()->recipient = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->to); + $composer->email->recipient = $this->prepareAddressForDatabaseStorage( + $composer->mailable->to); } /** @@ -103,7 +109,7 @@ private function readRecipient(EmailComposer $composer): void */ private function readFrom(EmailComposer $composer): void { - $composer->getEmail()->from = head($composer->getData('mailable')->from); + $composer->email->from = head($composer->mailable->from); } /** @@ -111,8 +117,8 @@ private function readFrom(EmailComposer $composer): void */ private function readCc(EmailComposer $composer): void { - $composer->getEmail()->cc = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->cc); + $composer->email->cc = $this->prepareAddressForDatabaseStorage( + $composer->mailable->cc); } /** @@ -120,8 +126,8 @@ private function readCc(EmailComposer $composer): void */ private function readBcc(EmailComposer $composer): void { - $composer->getEmail()->bcc = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->bcc); + $composer->email->bcc = $this->prepareAddressForDatabaseStorage( + $composer->mailable->bcc); } /** @@ -129,8 +135,8 @@ private function readBcc(EmailComposer $composer): void */ private function readReplyTo(EmailComposer $composer): void { - $composer->getEmail()->reply_to = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->replyTo); + $composer->email->reply_to = $this->prepareAddressForDatabaseStorage( + $composer->mailable->replyTo); } /** @@ -138,7 +144,7 @@ private function readReplyTo(EmailComposer $composer): void */ private function readSubject(EmailComposer $composer): void { - $composer->getEmail()->subject = $composer->getData('mailable')->subject; + $composer->email->subject = $composer->mailable->subject; } /** @@ -149,16 +155,16 @@ private function readSubject(EmailComposer $composer): void private function readBody(EmailComposer $composer): void { /** @var Mailable $mailable */ - $mailable = $composer->getData('mailable'); + $mailable = $composer->mailable; - $composer->getEmail()->view = $mailable->view; - $composer->getEmail()->variables = $mailable->buildViewData(); + $composer->email->view = $mailable->view; + $composer->email->variables = $mailable->buildViewData(); $localeToUse = $composer->locale ?? app()->currentLocale(); $this->withLocale( $localeToUse, - fn () => $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(), + fn () => $composer->email->body = view($mailable->view, $mailable->buildViewData())->render(), ); } @@ -167,9 +173,9 @@ private function readBody(EmailComposer $composer): void */ private function readAttachments(EmailComposer $composer): void { - $mailable = $composer->getData('mailable'); + $mailable = $composer->mailable; - $composer->getEmail()->attachments = array_map(function (array $attachment) { + $composer->email->attachments = array_map(function (array $attachment) { if (! $attachment['file'] instanceof Attachment) { throw new Error('The attachment is not an instance of '.Attachment::class.'.'); } @@ -180,8 +186,8 @@ private function readAttachments(EmailComposer $composer): void public function readModel(EmailComposer $composer): void { - if ($composer->hasData('model')) { - $composer->getEmail()->model()->associate($composer->getData('model')); + if ($composer->model) { + $composer->email->model()->associate($composer->model); } } diff --git a/src/SendEmailJob.php b/src/SendEmailJob.php index a7ba528..a865048 100644 --- a/src/SendEmailJob.php +++ b/src/SendEmailJob.php @@ -26,6 +26,6 @@ public function __construct(Email $email) public function handle(): void { - (new Sender())->send($this->email); + $this->email->send(); } } diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index cf833c7..b1fcb60 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -5,49 +5,19 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Console\Command; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; +use Illuminate\Support\LazyCollection; use Throwable; class SendEmailsCommand extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'email:send'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Send all queued e-mails'; - /** - * The e-mail repository. - * - * @var Store - */ - protected $store; - - /** - * Create a new SendEmailsCommand instance. - */ - public function __construct(Store $store) - { - parent::__construct(); - - $this->store = $store; - } - - /** - * Execute the console command. - */ - public function handle(): void + public function handle(Store $store): void { - $emails = $this->store->getQueue(); + $emails = $store->getQueue(); if ($emails->isEmpty()) { $this->line('There is nothing to send.'); @@ -60,13 +30,10 @@ public function handle(): void foreach ($emails as $email) { $progress->advance(); - try { - $email->send(); - } catch (Throwable $e) { - report($e); - - $email->markAsFailed($e); - } + rescue( + callback: fn () => $email->send(), + rescue: fn (Throwable $e) => $email->markAsFailed($e) + ); } $progress->finish(); @@ -77,7 +44,7 @@ public function handle(): void /** * Output a table with the cronjob result. */ - protected function result(Collection $emails): void + protected function result(LazyCollection $emails): void { $headers = ['ID', 'Recipient', 'Subject', 'Status']; diff --git a/src/Store.php b/src/Store.php index 3a9f4f2..bd26544 100644 --- a/src/Store.php +++ b/src/Store.php @@ -6,6 +6,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\LazyCollection; class Store { @@ -14,11 +15,11 @@ class Store * * @return Collection|Email[] */ - public function getQueue(): Collection + public function getQueue(): LazyCollection { $query = new Email(); - return $query + return Email::query() ->whereNull('deleted_at') ->whereNull('sent_at') ->whereNull('queued_at') @@ -30,6 +31,6 @@ public function getQueue(): Collection ->where('attempts', '<', Config::maxAttemptCount()) ->orderBy('created_at', 'asc') ->limit(Config::cronjobEmailLimit()) - ->get(); + ->cursor(); } } diff --git a/test b/test deleted file mode 100644 index c940b5b00a3c99efa1bd6dc9f77bda83f3cc97de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI&-)_@Z90zclq;=Ca4H7~IaWP}IQ456PP$&p#8e_Sr1QmaZHl$6K6Q5E;Vh8^z zWOu1xh+XU<_8NPIJ;5Gf7t?mJ`~A+b<2a79A+!q?z7Y-1`R8*!=Z}w_Jh*q;jhMFY z2YoxzE=$KHS(ZN6G)a<9(O;eZ#;*n)RL39ae>rzN@9>m#Dfp?rdQw`i>`IMC4W<5P z{m1q1>Nl2tUHNS#sO+x&zV^es%m|0T|4ZOiZB3Yq7)-eW4k^@fyo(=@V*6q4=} zBqOMQPsxJ753(9kUF_}w7o603Kk)lmg1+yto)rxTENe5>ED6o^j8Go*hF0WfS)au$ zW{zbOqhm)ba{G)os(Zkkm?}FHL6s@{O9Ac)AKu7-;xp^i#&`D#kfq5vvFBW|2BhiT%H(| z#RA@^LCI)V4+qp?Z!@0Xm+?eBH8q)1?PM~gAIfJ$z{V4YCo>LfihARmJUZig4ttSI zZ0m{N39UGG9V_-+Dkc}anhw&3yF2$DwzZwzFWX;hdM-gepRW1dR3snCyjnFUD<@0+v~-5Ax@6!i|B zZ{06_aaY1VQ(s~L3lv`0uX=z1Rwwb2tWV=5P-mkDe!5nD&4EKnyoFXdD$|rTILn=YV(?v z+-F-Jdv1wa-PTG~a(OyR)UsOFEOV<#FUxlL!=2<=Fx{;u4>rOykCNswd7dU^Gsju7 zhswn~*OseNOy@DLTT~1FE?!o|!l*+x?g!(V<(}duXS=jim294KThy>+nN&s76gA`z zw2NoDQmsmUmfqarE#GP->2nXoi`k4yRr;!!jp$M^@z5h}|E*(7(x}GMQsGTrE8c!W zn`zAWVaU{TvJ_EH5VJBw5hZ1aXFEwAe*Z5w{*dS&UJ!r)1Rwwb2tWV=5P$##AOHaf zEQr8{TsbReKNSdT;s^f67o>4$1_U4g0SG_<0uX=z1Rwwb2tWV=r2?xw6!-t7HVA|O z1Rwwb2tWV=5P$##AOHafEQ$cW|6i1zL^~h=0SG_<0uX=z1Rwwb2tWV=^9Atz|9l(7 zKmY;|fB*y_009U<00Izz00b6A0N?*FN>8F45P$##AOHafKmY;|fB*y_0D<`exc{GT zgBS=v00Izz00bZa0SG_<0uX?}q6pyk|BKR-Xa@u!009U<00Izz00bZa0SG`~z5u@e zpKpU02tWV=5P$##AOHafKmY;|fWV>%;QRkY=}EK$0uX=z1Rwwb2tWV=5P$##ATVD5 f_y6;45CZ`SKmY;|fB*y_009U<00IzL6oJ11Q?>)l diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 8e89065..7e15865 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -23,16 +23,16 @@ public function it_extracts_the_recipient() $composer = Email::compose() ->mailable($this->mailable()); - $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->getEmail()->recipient); + $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->email->recipient); $composer = Email::compose() ->mailable( $this->mailable()->to(['jane@doe.com']) ); - $this->assertCount(2, $composer->getEmail()->recipient); - $this->assertArrayHasKey('john@doe.com', $composer->getEmail()->recipient); - $this->assertArrayHasKey('jane@doe.com', $composer->getEmail()->recipient); + $this->assertCount(2, $composer->email->recipient); + $this->assertArrayHasKey('john@doe.com', $composer->email->recipient); + $this->assertArrayHasKey('jane@doe.com', $composer->email->recipient); } #[Test] @@ -40,7 +40,7 @@ public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->getEmail()->cc); + $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->email->cc); } #[Test] @@ -48,7 +48,7 @@ public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->getEmail()->bcc); + $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->email->bcc); } #[Test] @@ -56,7 +56,7 @@ public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->getEmail()->reply_to); + $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->email->reply_to); } #[Test] @@ -64,7 +64,7 @@ public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals('Your order has shipped!', $composer->getEmail()->subject); + $this->assertEquals('Your order has shipped!', $composer->email->subject); } #[Test] @@ -72,7 +72,7 @@ public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals("Name: John Doe\n", $composer->getEmail()->body); + $this->assertEquals("Name: John Doe\n", $composer->email->body); } #[Test] diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 5658f3c..124f57f 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; +use Workbench\App\Jobs\CustomSendEmailJob; class QueuedEmailsTest extends TestCase { @@ -105,4 +106,14 @@ public function the_mail_will_be_marked_as_sent_when_job_is_finished() $this->assertTrue($email->isSent()); } + + #[Test] + public function developers_can_choose_their_own_job() + { + Queue::fake(); + + $email = $this->queueEmail(jobClass: CustomSendEmailJob::class); + + Queue::assertPushed(fn (CustomSendEmailJob $job) => $job->email->id === $email->id); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 82ceaa1..08dbe88 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -132,8 +132,8 @@ public function scheduleEmail($scheduledFor, $overwrite = []) return $this->createEmail($overwrite)->later($scheduledFor); } - public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = []) + public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = [], ?string $jobClass = null) { - return $this->createEmail($overwrite)->queue($connection, $queue, $delay); + return $this->createEmail($overwrite)->queue($connection, $queue, $delay, $jobClass); } } diff --git a/workbench/app/Jobs/CustomSendEmailJob.php b/workbench/app/Jobs/CustomSendEmailJob.php new file mode 100644 index 0000000..ddd3ddd --- /dev/null +++ b/workbench/app/Jobs/CustomSendEmailJob.php @@ -0,0 +1,11 @@ + Date: Sat, 23 Mar 2024 12:48:04 +0100 Subject: [PATCH 12/13] Update UPGRADING.md --- UPGRADING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 2825a32..b38359d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -34,8 +34,8 @@ You can remove the old configuration file. Rename the following environments: -`LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` `→` `DB_EMAILS_TESTING_ENABLED` -`LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` `->` `DB_EMAILS_SEND_IMMEDIATELY` +- `LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` → `DB_EMAILS_TESTING_ENABLED` +- `LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` → `DB_EMAILS_SEND_IMMEDIATELY` The following environments are new: From 95ca3aebd2d844c71620a8ae898b731f1c97f83a Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 13:12:30 +0100 Subject: [PATCH 13/13] Modernize send emails command --- src/SendEmailsCommand.php | 43 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index b1fcb60..71817ee 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -5,8 +5,6 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Console\Command; -use Illuminate\Support\Arr; -use Illuminate\Support\LazyCollection; use Throwable; class SendEmailsCommand extends Command @@ -20,43 +18,28 @@ public function handle(Store $store): void $emails = $store->getQueue(); if ($emails->isEmpty()) { - $this->line('There is nothing to send.'); + $this->components->info('There is nothing to send.'); return; } - $progress = $this->output->createProgressBar($emails->count()); + $this->components->info('Sending '.count($emails).' e-mail(s).'); foreach ($emails as $email) { - $progress->advance(); + $recipients = implode(', ', array_keys($email->recipient)); + $line = str($email->subject)->limit(40).' - '.str($recipients)->limit(40); - rescue( - callback: fn () => $email->send(), - rescue: fn (Throwable $e) => $email->markAsFailed($e) - ); - } + rescue(function () use ($email, $line) { + $email->send(); - $progress->finish(); + $this->components->twoColumnDetail($line, 'DONE'); + }, function (Throwable $e) use ($email, $line) { + $email->markAsFailed($e); - $this->result($emails); - } + $this->components->twoColumnDetail($line, 'FAIL'); + }); + } - /** - * Output a table with the cronjob result. - */ - protected function result(LazyCollection $emails): void - { - $headers = ['ID', 'Recipient', 'Subject', 'Status']; - - $this->line("\n"); - - $this->table($headers, $emails->map(function (Email $email) { - return [ - $email->id, - implode(',', array_column(Arr::wrap($email->recipient), 'recipient')), - $email->subject, - $email->failed ? 'Failed' : 'OK', - ]; - })); + $this->newLine(); } }