From 9084df7ee17d1316b3341852210d7367f104c344 Mon Sep 17 00:00:00 2001 From: albinhallen Date: Mon, 7 Oct 2024 10:25:18 +0200 Subject: [PATCH] feat: add Laravel 10 support BREAKING CHANGE: Requires Laravel v10 or above fix: phpstan ignore mocks fix: add missing package test: add test with attachments included ci: only run supported versions of php chore: clean up fix: change method to set attachment headers ci: change phpunit schema in runtime fix: fix typing fix: include phpunit and phpstan in composer.json ci: update workflow build: basic docker compose for local development feat: Laravel 10 compatability --- .github/workflows/ci.yml | 45 +++++---------- composer.json | 13 +++-- docker-compose.yml | 25 ++++++++ src/Transport/NewsletterTransport.php | 80 +++++++++++++++----------- tests/Unit/NewsletterTransportTest.php | 65 +++++++++++++++++++++ 5 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 docker-compose.yml create mode 100644 tests/Unit/NewsletterTransportTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92fa2a1..a5f63c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,45 +2,19 @@ name: CI(PhpStan -> PhpUnit) on: pull_request: - branches: - - "*" - schedule: - - cron: '0 0 * * *' jobs: - phpstan: - runs-on: ubuntu-latest - if: (!contains(github.event.head_commit.message, '[skip ci]')) - steps: - - uses: actions/checkout@v2 - - uses: php-actions/composer@v6 - - uses: php-actions/phpstan@v3 - with: - memory_limit: 512M - configuration: ./phpstan.neon - php-tests: if: (!contains(github.event.head_commit.message, '[skip ci]')) - needs: [phpstan] strategy: matrix: os: [ubuntu-latest] - php: [8.1, 8.0, 7.4] - laravel: [7.*, 8.*, 9.*] + php: [8.1, 8.2, 8.3] + laravel: [10.*] dependency-version: [prefer-lowest, prefer-stable] include: - - laravel: 9.* - testbench: 7.* - - laravel: 8.* - testbench: 6.23 - - laravel: 7.* - testbench: 5.* - exclude: - - laravel: 9.* - php: 7.4 - # https://bytexd.com/fix-laravel-return-type-of-illuminatesupportcollectionoffsetexistskey/ - - laravel: 7.* - php: 8.1 + - laravel: 10.* + testbench: 8.* runs-on: ${{ matrix.os }} @@ -55,4 +29,13 @@ jobs: coverage: none - run: composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update --dev - run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - - run: vendor/bin/phpunit + - run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + + - name: Static analysis + run: vendor/bin/phpstan --memory-limit=512M analyze + + - name: Migrate phpunit schema + run: vendor/bin/phpunit --migrate-configuration + + - name: Unit testing + run: vendor/bin/phpunit diff --git a/composer.json b/composer.json index f53269d..164a4a8 100644 --- a/composer.json +++ b/composer.json @@ -27,10 +27,15 @@ } }, "require-dev": { - "orchestra/testbench": "^6.0" + "orchestra/testbench": "^8.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^10.0", + "spatie/temporary-directory": "^2.2" }, "require": { - "php": "^7.3|^8.0", - "guzzlehttp/guzzle": "^7.0" + "php": "^8.1|^8.2|^8.3", + "guzzlehttp/guzzle": "^7.0", + "illuminate/contracts": "^10.0", + "symfony/mailer": "^6.2" } -} \ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..383c81e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + php: + image: php:8.3-fpm # Or any other version you want + volumes: + - ./:/var/www/html # Mount the current directory to the container + working_dir: /var/www/html + ports: + - "8088:80" + depends_on: + - composer + networks: + - newsletter-driver-network + + composer: + image: composer:latest + volumes: + - ./:/var/www/html # Same mount so composer can install dependencies + working_dir: /var/www/html + networks: + - newsletter-driver-network + entrypoint: ['composer'] + +networks: + newsletter-driver-network: + driver: bridge \ No newline at end of file diff --git a/src/Transport/NewsletterTransport.php b/src/Transport/NewsletterTransport.php index 45d2eac..bd5ba2e 100644 --- a/src/Transport/NewsletterTransport.php +++ b/src/Transport/NewsletterTransport.php @@ -4,24 +4,25 @@ use Exception; use GuzzleHttp\Exception\GuzzleException; -use Illuminate\Mail\Transport\Transport; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Transport\AbstractTransport; use Lundalogik\NewsletterDriver\Newsletter\AttachmentModel; -use Lundalogik\NewsletterDriver\Newsletter\SendingDomain; use Lundalogik\NewsletterDriver\Newsletter\SendTransactionMailArgs; use Lundalogik\NewsletterDriver\Newsletter\SendTransactionMailBatchArgs; use Lundalogik\NewsletterDriver\Newsletter\TransactionMail; -use Swift_Mime_Attachment; -use Swift_Mime_SimpleMessage; -use Swift_TransportException; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mailer\SentMessage; -class NewsletterTransport extends Transport +class NewsletterTransport extends AbstractTransport { /** * TransactionMail instance. * * @var TransactionMail */ - protected $api; + protected TransactionMail $api; /** * Create a new NewsletterTransport instance. @@ -32,53 +33,56 @@ class NewsletterTransport extends Transport public function __construct(TransactionMail $api) { $this->api = $api; + + parent::__construct(); } /** - * {@inheritdoc} + * Send the given message. + * Triggered by parent::send() + * + * @param SentMessage $message + * @return void */ - public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + protected function doSend(SentMessage $message): void { - $this->beforeSendPerformed($message); - try { - $this->api->sendBatch( - $this->getSendTransactionMailBatchArgs($message) - ); + $originalMessage = $message->getOriginalMessage(); + if ($originalMessage instanceof Email) { + $this->api->sendBatch( + $this->getSendTransactionMailBatchArgs($message->getEnvelope(), $originalMessage) + ); + } } catch (GuzzleException $e) { - throw new Swift_TransportException( + throw new TransportException( 'Request to Newsletter API failed.', $e->getCode(), new Exception($e) ); } - - $this->sendPerformed($message); - - return $this->numberOfRecipients($message); } + /** * Get the SendTransactionMailBatchArgs from the message * - * @param Swift_Mime_SimpleMessage $message + * @param Envelope $envelope + * @param Email $message * @return SendTransactionMailBatchArgs */ - protected function getSendTransactionMailBatchArgs(Swift_Mime_SimpleMessage $message) + protected function getSendTransactionMailBatchArgs(Envelope $envelope, Email $message): SendTransactionMailBatchArgs { $sendTransactionMailArgs = []; - $from = $message->getFrom(); - - [$fromEmail] = array_keys($from); - [$fromName] = array_values($from); + $fromEmail = $envelope->getSender()->getAddress(); + $fromName = $envelope->getSender()->getName(); - foreach ($message->getTo() as $toEmail => $toName) { + foreach ($envelope->getRecipients() as $index => $to) { $sendTransactionMailArgs[] = (new SendTransactionMailArgs()) - ->to($toEmail, $toName) + ->to($to->getAddress(), $to->getName()) ->from($fromEmail, $fromName) ->subject($message->getSubject()) - ->htmlContent($message->getBody()); + ->htmlContent($message->getHtmlBody()); } return new SendTransactionMailBatchArgs( @@ -90,16 +94,16 @@ protected function getSendTransactionMailBatchArgs(Swift_Mime_SimpleMessage $mes /** * Get an array of AttachmentModel from the message * - * @param Swift_Mime_SimpleMessage $message + * @param Email $message * @return AttachmentModel[] */ - protected function buildAttachmentModels(Swift_Mime_SimpleMessage $message) + protected function buildAttachmentModels(Email $message): array { - return collect($message->getChildren()) - ->filter(function ($child) { - return $child->getHeaders()->get('content-disposition') !== null; + return collect($message->getAttachments()) + ->filter(function (DataPart $child) { + return $child->getPreparedHeaders()->get('Content-Disposition') !== null; }) - ->map(function (Swift_Mime_Attachment $attachment) { + ->map(function (DataPart $attachment) { return (new AttachmentModel()) ->fileData($attachment->getBody()) ->fileNameWithExtension($attachment->getFilename()) @@ -107,4 +111,12 @@ protected function buildAttachmentModels(Swift_Mime_SimpleMessage $message) }) ->toArray(); } + + /** + * @return string + */ + public function __toString(): string + { + return "newsletter"; + } } diff --git a/tests/Unit/NewsletterTransportTest.php b/tests/Unit/NewsletterTransportTest.php new file mode 100644 index 0000000..9cca79a --- /dev/null +++ b/tests/Unit/NewsletterTransportTest.php @@ -0,0 +1,65 @@ +deleteWhenDestroyed() + ->create(); + + $tmpPath = $tempDir->path('test.txt'); + file_put_contents($tmpPath, 'this is a test'); + + $message = new Email(); + $message->from('noreply@lime-forms.com') + ->to('albin.hallen@lime.tech') + ->subject('Test from laravel-newsletter-driver') + ->html("

This is a test email from laravel-newsletter-driver

") + ->attachFromPath($tmpPath, 'test.txt'); + + $api = Mockery::mock(TransactionMail::class); + + /** @phpstan-ignore-next-line */ + $api->shouldReceive('sendBatch') + ->once() + ->with(Mockery::on(function ($args) use ($message) { + $this->assertInstanceOf(SendTransactionMailBatchArgs::class, $args); + + $batchArgs = $args->toArray(); + $firstBatch = (object) $batchArgs['SendTransactionMailArgs'][0]; + + $this->assertEquals($firstBatch->RecipientEmail, $message->getTo()[0]->getAddress()); + $this->assertEquals($firstBatch->RecipientName, $message->getTo()[0]->getName()); + $this->assertEquals($firstBatch->FromEmail, $message->getFrom()[0]->getAddress()); + $this->assertEquals($firstBatch->FromName, $message->getFrom()[0]->getName()); + $this->assertEquals($firstBatch->Subject, $message->getSubject()); + $this->assertNotEmpty($firstBatch->HtmlContent); + + $firstBatchAttachments = $batchArgs['BatchAttachments']; + $this->assertEquals('test.txt', $firstBatchAttachments[0]['FileNameWithExtension']); + + return 1 === count($batchArgs['SendTransactionMailArgs']); + }))->andReturn(); + + /** @phpstan-ignore-next-line */ + (new NewsletterTransport($api))->send($message); + } +}