From c4be8dbdc6cb7c80b96d17b38cb2bf38784cbb31 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Mon, 10 Aug 2026 23:10:16 -0400 Subject: [PATCH 1/6] Update `byjg/webrequest` dependency to version ^7.0 in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c5ed6b5..d76d03c 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.3 <8.6", "ext-curl": "*", "byjg/convert": "^6.0", - "byjg/webrequest": "^6.0", + "byjg/webrequest": "^7.0", "aws/aws-sdk-php": "~3.20", "phpmailer/phpmailer": ">=6.4.1" }, From c141e426b3c101dd740e1e5fa21589b2e892beac Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Sun, 23 Aug 2026 21:12:57 -0400 Subject: [PATCH 2/6] Prepare 7.0: PHP 8.3-8.6, PHPUnit 12.5, Psalm as a tool Widen the PHP constraint to ">=8.3 <8.7"; the previous "<8.6" is exclusive and excluded PHP 8.6. Bump byjg/* dependencies to ^7.0. While 7.0 is unreleased these resolve to 7.0.x-dev from each component's 7.0 branch, via minimum-stability dev with prefer-stable. Pin PHPUnit to ^12.5 and move Psalm to tools/psalm/composer.json. Psalm enumerates supported PHP versions and no release lists 8.6, so as a require-dev it made "composer install" fail on the 8.6 build before any test ran. "composer psalm" bootstraps the tool and runs it. CI: add PHP 8.6 to the matrix; the Psalm job runs on 8.5. Housekeeping: rename phpunit.xml.dist to phpunit.xml, carry the 6.0 changelog onto this branch, and update CHANGELOG-7.0.md. --- .github/workflows/phpunit.yml | 10 ++- CHANGELOG-6.0.md | 154 ++++++++++++++++++++++++++++++++ CHANGELOG-7.0.md | 45 ++++++++++ composer.json | 12 +-- phpunit.xml.dist => phpunit.xml | 0 psalm.xml | 2 +- tools/psalm/composer.json | 5 ++ 7 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG-6.0.md create mode 100644 CHANGELOG-7.0.md rename phpunit.xml.dist => phpunit.xml (100%) create mode 100644 tools/psalm/composer.json diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index fc5f29a..4dca2b0 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,6 +18,7 @@ jobs: strategy: matrix: php-version: + - "8.6" - "8.5" - "8.4" - "8.3" @@ -34,7 +35,7 @@ jobs: # for github/codeql-action/upload-sarif to upload SARIF results security-events: write container: - image: byjg/php:8.4-cli + image: byjg/php:8.5-cli options: --user root --privileged steps: @@ -44,10 +45,15 @@ jobs: - name: Composer run: composer install + - name: Composer (psalm) + + run: composer --working-dir=tools/psalm update --no-interaction + + - name: Psalm # Note: Ignoring error code 2, which just signals that some # flaws were found, not that Psalm itself failed to run. - run: ./vendor/bin/psalm + run: ./tools/psalm/vendor/bin/psalm --show-info=true --report=psalm-results.sarif || [ $? = 2 ] diff --git a/CHANGELOG-6.0.md b/CHANGELOG-6.0.md new file mode 100644 index 0000000..4b1b949 --- /dev/null +++ b/CHANGELOG-6.0.md @@ -0,0 +1,154 @@ +# Changelog for Version 6.0 + +Version 6.0 represents a major update to the MailWrapper library, focusing on improved type safety, modern PHP standards, and enhanced null-safety checks. This release includes several breaking changes that require attention when upgrading from version 5.x. + +## New Features + +### Enhanced Type Safety +- Added comprehensive type declarations throughout the codebase, including parameter types, return types, and property types +- Introduced PHPDoc annotations with generic types (e.g., `@var array>`) +- Added `#[\Override]` attributes to ensure proper method inheritance + +### Improved Null Safety +- Refactored mail wrappers with null-coalescing operators for safer null handling +- Enhanced null-safety checks in `PHPMailerWrapper`, `AmazonSesWrapper`, and `MailgunApiWrapper` +- Improved handling of optional URI components (username, password, host, port, scheme) + +### Documentation Improvements +- Comprehensive Docusaurus-compatible documentation structure +- New detailed documentation pages: + - Getting Started guide + - Envelope configuration guide + - Connection Strings reference + - Mailer Factory usage + - Attachments handling + - Custom Wrappers implementation + - Exception handling guide + +### Testing & CI Enhancements +- Updated PHPUnit configuration with improved structure and container options +- Enhanced GitHub Actions workflow configuration +- Added `composer test` and `composer psalm` scripts for easier development workflow + +## Breaking Changes + +| Aspect | Before (5.x) | After (6.0) | Description | +|--------|--------------|-------------|-------------| +| **PHP Version** | `>=8.1 <8.4` | `>=8.3 <8.6` | Minimum PHP version increased to 8.3, added support for PHP 8.4 and 8.5 | +| **byjg/convert** | `^5.0` | `^6.0` | Dependency upgraded to major version 6 | +| **byjg/webrequest** | `^5.0` | `^6.0` | Dependency upgraded to major version 6 | +| **PHPUnit** | `^9.6` | `^10.5\|^11.5` | Dev dependency upgraded to PHPUnit 10 or 11 | +| **Psalm** | `^5.9` | `^5.9\|^6.13` | Dev dependency supports Psalm 6 | +| **Test Files** | `*WrapperTest.php` | `*TestWrapper.php` | Test class files renamed from suffix pattern | +| **MailerFactory::create()** | `string $connection` | `UriInterface\|string $connection` | Now accepts PSR-7 UriInterface in addition to string | +| **MailerFactory::registerMailer()** | No return type | `void` return type | Explicit void return type added | +| **Type Strictness** | Relaxed null handling | Strict null-safety | All wrapper methods now use null-safe operators (`?->`) and null coalescing (`??`) | + +### Additional API Changes + +- **MailerFactory::registerMailer()**: Parameter now properly typed as `class-string` +- **PHPMailerWrapper**: URI property access now uses null-safe operators throughout +- **Envelope**: Minor adjustments for stricter type checking +- **Test namespace**: Test classes use proper PSR-4 autoloading with `Tests\` namespace + +## Path to Upgrade from 5.x to 6.0 + +### Step 1: Check PHP Version +Ensure your environment is running PHP 8.3 or higher: +```bash +php --version +``` + +If you're running PHP 8.1 or 8.2, you must upgrade to PHP 8.3+ before upgrading to MailWrapper 6.0. + +### Step 2: Update Dependencies +Update your `composer.json`: +```json +{ + "require": { + "byjg/mailwrapper": "^6.0" + } +} +``` + +Then run: +```bash +composer update byjg/mailwrapper +``` + +This will automatically update the required dependencies (`byjg/convert` and `byjg/webrequest` to version 6). + +### Step 3: Update Dev Dependencies (if applicable) +If you have PHPUnit or Psalm in your project: +```bash +composer update phpunit/phpunit vimeo/psalm --with-dependencies +``` + +### Step 4: Review Custom Implementations +If you have custom mail wrappers that implement `MailWrapperInterface`: + +1. Add proper type declarations to all methods +2. Add `#[\Override]` attribute to interface methods +3. Ensure null-safety in URI handling: + ```php + // Before + $host = $this->uri->getHost(); + + // After + $host = $this->uri?->getHost() ?? 'default-host'; + ``` + +### Step 5: Update MailerFactory Usage (Optional) +If you pass URI objects to `MailerFactory::create()`, ensure they implement `Psr\Http\Message\UriInterface`: +```php +use ByJG\Util\Uri; +use ByJG\Mail\MailerFactory; + +// Both styles now work: +$mailer1 = MailerFactory::create('smtp://user:pass@smtp.example.com'); +$mailer2 = MailerFactory::create(new Uri('smtp://user:pass@smtp.example.com')); +``` + +### Step 6: Run Tests +After upgrading, run your test suite to ensure compatibility: +```bash +vendor/bin/phpunit +``` + +If you use Psalm: +```bash +vendor/bin/psalm +``` + +### Step 7: Update Test Files (if extending MailWrapper tests) +If your project extends any MailWrapper test classes, note the renamed files: +- `BaseWrapperTest.php` → `BaseTestWrapper.php` +- `PHPMailerWrapperTest.php` → `PHPMailerTestWrapper.php` +- `AmazonSesWrapperTest.php` → `AmazonSesTestWrapper.php` +- `MailgunWrapperTest.php` → `MailgunTestWrapper.php` +- `FakeSenderWrapperTest.php` → `FakeSenderTestWrapper.php` + +## Bug Fixes + +- Fixed Psalm-related type issues for improved static analysis +- Improved null-safety checks preventing potential null pointer exceptions +- Enhanced SMTP authentication handling with proper null checks +- Fixed potential issues with missing URI components in wrapper configurations + +## Notes + +- All existing connection string formats remain compatible +- No changes to the public API for `Envelope` class usage +- Wrapper registration and usage patterns remain the same +- Documentation now available in comprehensive Docusaurus format + +## Migration Support + +If you encounter issues during migration, please: +1. Review the new documentation in the `docs/` directory +2. Check the updated examples in the README +3. File an issue on GitHub: https://github.com/byjg/php-mailwrapper/issues + +--- + +**Full Changelog**: https://github.com/byjg/php-mailwrapper/compare/5.0.2...6.0 diff --git a/CHANGELOG-7.0.md b/CHANGELOG-7.0.md new file mode 100644 index 0000000..df38e47 --- /dev/null +++ b/CHANGELOG-7.0.md @@ -0,0 +1,45 @@ +# Changelog - Version 7.0 + +> **Status: in development.** This document tracks changes landing on the `7.0` branch. +> Nothing here is released yet, and the contents may still change. + +## Breaking Changes + +- None. + +## Requirements + +- PHP 8.3, 8.4, 8.5 and 8.6 are now supported: `"php": ">=8.3 <8.7"`. + The previous `<8.6` upper bound excluded PHP 8.6, since `<8.6` is exclusive. + +### ByJG dependencies + +- `byjg/convert` is now `^7.0`. +- `byjg/webrequest` is now `^7.0`. + +While 7.0 is unreleased these resolve to `7.0.x-dev` from each component's +`7.0` branch, via `minimum-stability: dev` with `prefer-stable: true`. + +## Toolchain + +- PHPUnit updated to `^12.5`. +- Psalm moved out of `require-dev` into its own manifest, `tools/psalm/composer.json`. + + Psalm enumerates the PHP versions it supports and no published release lists + 8.6. As a dev dependency it made `composer install` fail on the 8.6 build job + before any test ran. It now installs separately, only for the Psalm job. + + `composer psalm` still works — it bootstraps the tool and runs it. + +- PHPUnit 13 is deliberately **not** used. It requires PHP `>=8.4.1`, breaking the + 8.3 floor, and needs `sebastian/diff ^9.0`, which stable Psalm 6.16.1 rejects — + a combination that silently resolves Psalm to an unreleased `6.x-dev` branch. + +## Continuous Integration + +- The build matrix now includes PHP 8.6. +- The Psalm job runs on PHP 8.5 and installs Psalm from `tools/psalm`. + +## Housekeeping + +- `phpunit.xml.dist` renamed to `phpunit.xml`. diff --git a/composer.json b/composer.json index d76d03c..e92a114 100644 --- a/composer.json +++ b/composer.json @@ -14,20 +14,22 @@ "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": ">=8.3 <8.6", + "php": ">=8.3 <8.7", "ext-curl": "*", - "byjg/convert": "^6.0", + "byjg/convert": "^7.0", "byjg/webrequest": "^7.0", "aws/aws-sdk-php": "~3.20", "phpmailer/phpmailer": ">=6.4.1" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.5", - "vimeo/psalm": "^5.9|^6.13" + "phpunit/phpunit": "^12.5" }, "scripts": { "test": "vendor/bin/phpunit", - "psalm": "vendor/bin/psalm --threads=1" + "psalm": [ + "@composer --working-dir=tools/psalm update --no-interaction", + "tools/psalm/vendor/bin/psalm --threads=1" + ] }, "license": "MIT" } diff --git a/phpunit.xml.dist b/phpunit.xml similarity index 100% rename from phpunit.xml.dist rename to phpunit.xml diff --git a/psalm.xml b/psalm.xml index 037de8b..c1976fd 100644 --- a/psalm.xml +++ b/psalm.xml @@ -7,7 +7,7 @@ cacheDirectory="/tmp/psalm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" - xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + xsi:schemaLocation="https://getpsalm.org/schema/config tools/psalm/vendor/vimeo/psalm/config.xsd" > diff --git a/tools/psalm/composer.json b/tools/psalm/composer.json new file mode 100644 index 0000000..197dd5f --- /dev/null +++ b/tools/psalm/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "vimeo/psalm": "^6.16" + } +} From 22111e6dbd7b758c971efb8288e20142c30d8ec4 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Mon, 14 Sep 2026 12:08:23 -0400 Subject: [PATCH 3/6] Fix the license badge link and drop the hand-written dependency graph licensing.html returns 404; the page is /license/. The Dependencies mermaid drifted from composer.json, which is now what the site's graph is built from (collect-deps.py in add-doc). --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index df9f7c5..c7c0a3a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A lightweight wrapper for sending email. The interface is totally decoupled from [![Build Status](https://github.com/byjg/php-mailwrapper/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/php-mailwrapper/actions/workflows/phpunit.yml) [![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) [![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-mailwrapper/) -[![GitHub license](https://img.shields.io/github/license/byjg/php-mailwrapper.svg)](https://opensource.byjg.com/opensource/licensing.html) +[![GitHub license](https://img.shields.io/github/license/byjg/php-mailwrapper.svg)](https://opensource.byjg.com/license/) [![GitHub release](https://img.shields.io/github/release/byjg/php-mailwrapper.svg)](https://github.com/byjg/php-mailwrapper/releases/) ## Available Wrappers @@ -90,13 +90,5 @@ See [Connection Strings](docs/connection-strings.md) for detailed configuration ./vendor/bin/phpunit ``` -## Dependencies - -```mermaid -flowchart TD - byjg/mailwrapper --> byjg/convert - byjg/mailwrapper --> byjg/webrequest -``` - ---- [Open source ByJG](http://opensource.byjg.com) From fb7d12ad273481943ffa3b113574c54629459020 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 23 Sep 2026 21:06:28 -0400 Subject: [PATCH 4/6] FakeSenderWrapper keeps sent envelopes; run the wrapper tests FakeSenderWrapper now keeps every envelope it is given: getSent() returns them, oldest first, and clear() forgets them, so a test can check what the application would have sent. Sending still does nothing and always succeeds. The wrapper tests were named *TestWrapper.php, which PHPUnit's default Test.php suffix skips, so the Amazon SES, FakeSender, Mailgun and PHPMailer tests never ran. Rename them to *WrapperTest.php. The Amazon SES test had broken unnoticed once getSesClient() declared a SesClient return type; it now sends through a real SesClient with the SDK's Aws\MockHandler, and the MockSender stand-in is removed. None of these tests reach a real service. Suite: 18 -> 38 tests (3 functional tests still skip without credentials). --- CHANGELOG-7.0.md | 12 +++++++ README.md | 2 +- docs/connection-strings.md | 17 ++++++++-- docs/getting-started.md | 2 +- docs/mailer-factory.md | 3 ++ src/Wrapper/FakeSenderWrapper.php | 26 ++++++++++++++ ...stWrapper.php => AmazonSesWrapperTest.php} | 34 +++++++++++++------ ...tWrapper.php => FakeSenderWrapperTest.php} | 24 ++++++++++++- ...TestWrapper.php => MailgunWrapperTest.php} | 2 +- tests/MockSender.php | 27 --------------- ...stWrapper.php => PHPMailerWrapperTest.php} | 2 +- 11 files changed, 107 insertions(+), 44 deletions(-) rename tests/{AmazonSesTestWrapper.php => AmazonSesWrapperTest.php} (76%) rename tests/{FakeSenderTestWrapper.php => FakeSenderWrapperTest.php} (68%) rename tests/{MailgunTestWrapper.php => MailgunWrapperTest.php} (99%) delete mode 100644 tests/MockSender.php rename tests/{PHPMailerTestWrapper.php => PHPMailerWrapperTest.php} (97%) diff --git a/CHANGELOG-7.0.md b/CHANGELOG-7.0.md index df38e47..d6db957 100644 --- a/CHANGELOG-7.0.md +++ b/CHANGELOG-7.0.md @@ -7,6 +7,13 @@ - None. +## New Features + +- `FakeSenderWrapper` keeps every envelope it is given. `FakeSenderWrapper::getSent()` + returns them, oldest first, and `FakeSenderWrapper::clear()` forgets them, so a test can + check what the application would have sent. Sending still does nothing and always + succeeds. + ## Requirements - PHP 8.3, 8.4, 8.5 and 8.6 are now supported: `"php": ">=8.3 <8.7"`. @@ -43,3 +50,8 @@ While 7.0 is unreleased these resolve to `7.0.x-dev` from each component's ## Housekeeping - `phpunit.xml.dist` renamed to `phpunit.xml`. +- The wrapper tests now run. They were named `*TestWrapper.php`, which PHPUnit's default + `Test.php` suffix skips, so the Amazon SES, FakeSender, Mailgun and PHPMailer tests had + not been running. They are renamed to `*WrapperTest.php`. The Amazon SES test, broken + unnoticed since `getSesClient()` gained its `SesClient` return type, now uses the SDK's + `Aws\MockHandler` instead of the `MockSender` stand-in, which is removed. diff --git a/README.md b/README.md index c7c0a3a..30da94a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A lightweight wrapper for sending email. The interface is totally decoupled from - **AWS SES** - Amazon Simple Email Service (using API directly) - **Mailgun** - Mailgun API (using API directly) - **SendMail** - PHP's built-in mail() function -- **FakeSender** - For testing (does nothing) +- **FakeSender** - For testing (sends nothing, keeps the envelopes for assertions) ## Install diff --git a/docs/connection-strings.md b/docs/connection-strings.md index e44502e..c835ea7 100644 --- a/docs/connection-strings.md +++ b/docs/connection-strings.md @@ -30,7 +30,7 @@ scheme://username:password@host:port | sendmail | PHP's built-in mail() function | `sendmail://localhost` | SendMailWrapper | | mailgun | Mailgun API | `mailgun://YOUR_API_KEY@YOUR_DOMAIN` | MailgunApiWrapper | | ses | Amazon SES API | `ses://ACCESS_KEY_ID:SECRET_KEY@REGION` | AmazonSesWrapper | -| fakesender | Testing (does nothing) | `fakesender://localhost` | FakeSenderWrapper | +| fakesender | Testing (sends nothing) | `fakesender://localhost` | FakeSenderWrapper | ## Examples @@ -106,4 +106,17 @@ $mailer = \ByJG\Mail\MailerFactory::create( ); ``` -The FakeSender wrapper does nothing and always returns success. It's useful for testing without actually sending emails. +The FakeSender wrapper sends nothing and always returns success. It's useful for testing without actually sending emails. + +It keeps every envelope it is given, so a test can check what would have been sent: + +```php +use ByJG\Mail\Wrapper\FakeSenderWrapper; + +FakeSenderWrapper::clear(); // forget earlier sends +$service->requestPasswordReset($email); // code under test sends through the mailer + +$sent = FakeSenderWrapper::getSent(); // list, oldest first +$this->assertCount(1, $sent); +$this->assertEquals('Password Reset', $sent[0]->getSubject()); +``` diff --git a/docs/getting-started.md b/docs/getting-started.md index df93620..ce844a2 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -56,7 +56,7 @@ MailWrapper is organized into three main components: - **AWS SES** - Amazon Simple Email Service (using API directly) - **Mailgun** - Mailgun API - **SendMail** - PHP's built-in mail() function -- **FakeSender** - For testing (does nothing) +- **FakeSender** - For testing (sends nothing, keeps the envelopes for assertions) ## Running Tests diff --git a/docs/mailer-factory.md b/docs/mailer-factory.md index 0042354..358b7ab 100644 --- a/docs/mailer-factory.md +++ b/docs/mailer-factory.md @@ -160,3 +160,6 @@ if ($isTestEnvironment) { $mailer = \ByJG\Mail\MailerFactory::create($productionConnection); } ``` + +`FakeSenderWrapper::getSent()` returns the envelopes it was given, and `clear()` forgets them. +See [FakeSender](connection-strings.md#fakesender-testing). diff --git a/src/Wrapper/FakeSenderWrapper.php b/src/Wrapper/FakeSenderWrapper.php index a87bc0d..6f2221b 100644 --- a/src/Wrapper/FakeSenderWrapper.php +++ b/src/Wrapper/FakeSenderWrapper.php @@ -5,8 +5,15 @@ use ByJG\Mail\Envelope; use ByJG\Mail\SendResult; +/** + * Sends nothing and always succeeds. Every envelope it is given is kept, so a test can + * check what the application would have sent with getSent(). + */ class FakeSenderWrapper extends BaseWrapper { + /** @var list */ + protected static array $sent = []; + #[\Override] public static function schema(): array { @@ -16,6 +23,25 @@ public static function schema(): array #[\Override] public function send(Envelope $envelope): SendResult { + self::$sent[] = $envelope; return new SendResult(true, 'fake-id-123'); } + + /** + * Every envelope sent through any FakeSenderWrapper since the last clear(), oldest first. + * + * @return list + */ + public static function getSent(): array + { + return self::$sent; + } + + /** + * Forget the envelopes sent so far. Call it before the code under test sends. + */ + public static function clear(): void + { + self::$sent = []; + } } diff --git a/tests/AmazonSesTestWrapper.php b/tests/AmazonSesWrapperTest.php similarity index 76% rename from tests/AmazonSesTestWrapper.php rename to tests/AmazonSesWrapperTest.php index 2217462..c7b6502 100644 --- a/tests/AmazonSesTestWrapper.php +++ b/tests/AmazonSesWrapperTest.php @@ -3,6 +3,9 @@ namespace Tests; use Aws\Credentials\Credentials; +use Aws\MockHandler; +use Aws\Result; +use Aws\Ses\SesClient; use ByJG\Mail\Envelope; use ByJG\Mail\Exception\InvalidEMailException; use ByJG\Mail\Exception\InvalidMessageFormatException; @@ -11,30 +14,41 @@ use ByJG\Util\Uri; use PHPMailer\PHPMailer\Exception; -class AmazonSesTestWrapper extends BaseTestWrapper +class AmazonSesWrapperTest extends BaseTestWrapper { /** - * @param $envelope - * @return array + * Send through a real SesClient whose HTTP layer is the SDK's MockHandler: it answers + * with a queued result and keeps the command, so the test can check what was sent. + * + * @return array{0: array, 1: SendResult} The SendRawEmail arguments and the send result * @throws InvalidEMailException * @throws InvalidMessageFormatException * @throws Exception */ public function doMockedRequest(Envelope $envelope): array { + $handler = new MockHandler(); + $handler->append(new Result([ + 'MessageId' => 'EXAMPLEf3f73d99b-c63fb06f-d263-41f8-a0fb-d0dc67d56c07-000000', + ])); + $sesClient = new SesClient([ + 'credentials' => new Credentials('ACCESS_KEY_ID', 'SECRET_KEY'), + 'region' => 'us-east-1', + 'version' => '2010-12-01', + 'handler' => $handler, + ]); + $object = $this->getMockBuilder(AmazonSesWrapper::class) ->onlyMethods(['getSesClient']) ->setConstructorArgs([new Uri('ses://ACCESS_KEY_ID:SECRET_KEY@REGION')]) ->getMock(); - - $mock = new MockSender(); $object->expects($this->once()) ->method('getSesClient') - ->willReturn($mock); + ->willReturn($sesClient); $result = $object->send($envelope); - return [$mock, $result]; + return [$handler->getLastCommand()->toArray(), $result]; } public function testGetSesClient(): void @@ -61,9 +75,9 @@ public function testGetSesClient(): void */ protected function send(Envelope $envelope, string $rawEmail): SendResult { - [$mock, $result] = $this->doMockedRequest($envelope); + [$sent, $result] = $this->doMockedRequest($envelope); $mimeMessage = $this->fixVariableFields(file_get_contents(__DIR__ . '/resources/' . $rawEmail . '.eml')); - $mock->result['RawMessage']['Data'] = $this->fixVariableFields($mock->result['RawMessage']['Data']); + $sent = ['RawMessage' => ['Data' => $this->fixVariableFields($sent['RawMessage']['Data'])]]; $expected = [ 'RawMessage' => [ @@ -71,7 +85,7 @@ protected function send(Envelope $envelope, string $rawEmail): SendResult ] ]; - $this->assertEquals($expected, $mock->result); + $this->assertEquals($expected, $sent); return $result; } diff --git a/tests/FakeSenderTestWrapper.php b/tests/FakeSenderWrapperTest.php similarity index 68% rename from tests/FakeSenderTestWrapper.php rename to tests/FakeSenderWrapperTest.php index 76ca88c..ca82c5a 100644 --- a/tests/FakeSenderTestWrapper.php +++ b/tests/FakeSenderWrapperTest.php @@ -10,7 +10,7 @@ use ByJG\Mail\Wrapper\FakeSenderWrapper; use ByJG\Util\Uri; -class FakeSenderTestWrapper extends BaseTestWrapper +class FakeSenderWrapperTest extends BaseTestWrapper { /** * @param Envelope $envelope @@ -54,6 +54,28 @@ public function testAttachmentEnvelope(): void $this->assertEquals('fake-id-123', $result->id); } + public function testSentEnvelopesAreKeptInOrder(): void + { + FakeSenderWrapper::clear(); + $first = $this->getBasicEnvelope(); + $second = $this->getFullEnvelope(); + + $this->doFakeSend($first); + $this->doFakeSend($second); + + $this->assertSame([$first, $second], FakeSenderWrapper::getSent()); + } + + public function testClearForgetsSentEnvelopes(): void + { + $this->doFakeSend($this->getBasicEnvelope()); + $this->assertNotEmpty(FakeSenderWrapper::getSent()); + + FakeSenderWrapper::clear(); + + $this->assertSame([], FakeSenderWrapper::getSent()); + } + public function testEmbedImageEnvelope(): void { $envelope = $this->getEmbedImageEnvelope(); diff --git a/tests/MailgunTestWrapper.php b/tests/MailgunWrapperTest.php similarity index 99% rename from tests/MailgunTestWrapper.php rename to tests/MailgunWrapperTest.php index e4bcdc1..4e58592 100644 --- a/tests/MailgunTestWrapper.php +++ b/tests/MailgunWrapperTest.php @@ -16,7 +16,7 @@ use ByJG\WebRequest\Psr7\MemoryStream; use Psr\Http\Client\ClientExceptionInterface; -class MailgunTestWrapper extends BaseTestWrapper +class MailgunWrapperTest extends BaseTestWrapper { /** diff --git a/tests/MockSender.php b/tests/MockSender.php deleted file mode 100644 index 704bd9e..0000000 --- a/tests/MockSender.php +++ /dev/null @@ -1,27 +0,0 @@ -result = $raw; - - return new Result([ - 'MessageId' => 'EXAMPLEf3f73d99b-c63fb06f-d263-41f8-a0fb-d0dc67d56c07-000000', - ]); - } - - // Mailgun Wrapper - public function postMultiPartForm($message): string - { - $this->result = $message; - return '{"id": "123445"}'; - } -} \ No newline at end of file diff --git a/tests/PHPMailerTestWrapper.php b/tests/PHPMailerWrapperTest.php similarity index 97% rename from tests/PHPMailerTestWrapper.php rename to tests/PHPMailerWrapperTest.php index f23aa6a..7566f53 100644 --- a/tests/PHPMailerTestWrapper.php +++ b/tests/PHPMailerWrapperTest.php @@ -10,7 +10,7 @@ use ByJG\Util\Uri; use PHPMailer\PHPMailer\Exception; -class PHPMailerTestWrapper extends BaseTestWrapper +class PHPMailerWrapperTest extends BaseTestWrapper { /** * @param Envelope $envelope From fb2dcd9150fc6493e79753318efa7471be42562c Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 9 Oct 2026 15:32:30 -0400 Subject: [PATCH 5/6] Align with the 7.0 standard - Install Psalm as psalm/phar instead of the tools/psalm project - phpunit.xml: PHPUnit 12.5 schema - Workflow: actions/checkout@v7 - README: badges, Installation and Running Tests sections, https links - Remove FUNDING.yml (comes from byjg/.github) and example.php - CHANGELOG-7.0.md updated --- .github/FUNDING.yml | 3 --- .github/workflows/phpunit.yml | 11 +++-------- CHANGELOG-7.0.md | 18 +++++++++--------- README.md | 7 ++++--- composer.json | 8 +++----- example.php | 21 --------------------- phpunit.xml | 2 +- psalm.xml | 2 +- tools/psalm/composer.json | 5 ----- 9 files changed, 21 insertions(+), 56 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 example.php delete mode 100644 tools/psalm/composer.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 3a9251f..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: byjg diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 4dca2b0..08505e1 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -24,7 +24,7 @@ jobs: - "8.3" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - run: composer install - run: composer test @@ -40,20 +40,15 @@ jobs: steps: - name: Git checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Composer run: composer install - - name: Composer (psalm) - - run: composer --working-dir=tools/psalm update --no-interaction - - - name: Psalm # Note: Ignoring error code 2, which just signals that some # flaws were found, not that Psalm itself failed to run. - run: ./tools/psalm/vendor/bin/psalm + run: ./vendor/bin/psalm.phar --show-info=true --report=psalm-results.sarif || [ $? = 2 ] diff --git a/CHANGELOG-7.0.md b/CHANGELOG-7.0.md index d6db957..cfc1129 100644 --- a/CHANGELOG-7.0.md +++ b/CHANGELOG-7.0.md @@ -30,22 +30,22 @@ While 7.0 is unreleased these resolve to `7.0.x-dev` from each component's ## Toolchain - PHPUnit updated to `^12.5`. -- Psalm moved out of `require-dev` into its own manifest, `tools/psalm/composer.json`. +- Psalm is installed as `psalm/phar` instead of `vimeo/psalm`. - Psalm enumerates the PHP versions it supports and no published release lists - 8.6. As a dev dependency it made `composer install` fail on the 8.6 build job - before any test ran. It now installs separately, only for the Psalm job. - - `composer psalm` still works — it bootstraps the tool and runs it. + `vimeo/psalm` lists the PHP versions it supports and no published release includes + 8.6, so as a dev dependency it made `composer install` fail on the 8.6 build job + before any test ran. `psalm/phar` requires only `php ^8.2` and bundles its own + dependencies, so it installs on every PHP version in the matrix and cannot conflict + with the project's. Psalm itself still refuses to *run* on 8.6, which is why the + Psalm job uses 8.5. `composer psalm` runs it. - PHPUnit 13 is deliberately **not** used. It requires PHP `>=8.4.1`, breaking the - 8.3 floor, and needs `sebastian/diff ^9.0`, which stable Psalm 6.16.1 rejects — - a combination that silently resolves Psalm to an unreleased `6.x-dev` branch. + 8.3 floor. ## Continuous Integration - The build matrix now includes PHP 8.6. -- The Psalm job runs on PHP 8.5 and installs Psalm from `tools/psalm`. +- The Psalm job runs on PHP 8.5. ## Housekeeping diff --git a/README.md b/README.md index 30da94a..afde3cc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ A lightweight wrapper for sending email. The interface is totally decoupled from [![Sponsor](https://img.shields.io/badge/Sponsor-%23ea4aaa?logo=githubsponsors&logoColor=white&labelColor=0d1117)](https://github.com/sponsors/byjg) [![Build Status](https://github.com/byjg/php-mailwrapper/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/php-mailwrapper/actions/workflows/phpunit.yml) -[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) +[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](https://opensource.byjg.com) +[![Install MCP Server](https://img.shields.io/badge/Install-MCP_Server-8A2BE2?logo=modelcontextprotocol&logoColor=white)](https://opensource.byjg.com/docs/ai/mcpserver-byjg-docs/) [![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-mailwrapper/) [![GitHub license](https://img.shields.io/github/license/byjg/php-mailwrapper.svg)](https://opensource.byjg.com/license/) [![GitHub release](https://img.shields.io/github/release/byjg/php-mailwrapper.svg)](https://github.com/byjg/php-mailwrapper/releases/) @@ -22,7 +23,7 @@ A lightweight wrapper for sending email. The interface is totally decoupled from - **SendMail** - PHP's built-in mail() function - **FakeSender** - For testing (sends nothing, keeps the envelopes for assertions) -## Install +## Installation ```shell composer require "byjg/mailwrapper" @@ -91,4 +92,4 @@ See [Connection Strings](docs/connection-strings.md) for detailed configuration ``` ---- -[Open source ByJG](http://opensource.byjg.com) +[Open source ByJG](https://opensource.byjg.com) diff --git a/composer.json b/composer.json index e92a114..e00302d 100644 --- a/composer.json +++ b/composer.json @@ -22,14 +22,12 @@ "phpmailer/phpmailer": ">=6.4.1" }, "require-dev": { - "phpunit/phpunit": "^12.5" + "phpunit/phpunit": "^12.5", + "psalm/phar": "^6.16" }, "scripts": { "test": "vendor/bin/phpunit", - "psalm": [ - "@composer --working-dir=tools/psalm update --no-interaction", - "tools/psalm/vendor/bin/psalm --threads=1" - ] + "psalm": "vendor/bin/psalm.phar" }, "license": "MIT" } diff --git a/example.php b/example.php deleted file mode 100644 index a62e61a..0000000 --- a/example.php +++ /dev/null @@ -1,21 +0,0 @@ -setFrom('johndoe@example.com', 'John Doe'); -$envelope->addTo('jane@example.com'); -$envelope->setSubject('Email Subject'); -$envelope->setBody('html text body'); - -// The the email with the selected mailer. -$mailer->send($envelope); diff --git a/phpunit.xml b/phpunit.xml index c090a6d..b9df5cf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,7 +20,7 @@ and open the template in the editor. failOnDeprecation="true" failOnPhpunitDeprecation="true" stopOnFailure="false" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"> + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd"> diff --git a/psalm.xml b/psalm.xml index c1976fd..37a8931 100644 --- a/psalm.xml +++ b/psalm.xml @@ -7,7 +7,7 @@ cacheDirectory="/tmp/psalm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" - xsi:schemaLocation="https://getpsalm.org/schema/config tools/psalm/vendor/vimeo/psalm/config.xsd" + xsi:schemaLocation="https://getpsalm.org/schema/config https://raw.githubusercontent.com/vimeo/psalm/6.x/config.xsd" > diff --git a/tools/psalm/composer.json b/tools/psalm/composer.json deleted file mode 100644 index 197dd5f..0000000 --- a/tools/psalm/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": { - "vimeo/psalm": "^6.16" - } -} From c82e87b593ed9779718da798381af36c625c80e4 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 9 Oct 2026 15:34:44 -0400 Subject: [PATCH 6/6] Ignore indirect deprecations in the test suite PHP 8.6 deprecates "is" as a class name and guzzlehttp/promises, required by the AWS SDK, has a class called Is. With failOnDeprecation the 8.6 build failed on a deprecation raised in vendor code. Deprecations raised in src/ still fail the suite. --- CHANGELOG-7.0.md | 3 +++ phpunit.xml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-7.0.md b/CHANGELOG-7.0.md index cfc1129..184d6e1 100644 --- a/CHANGELOG-7.0.md +++ b/CHANGELOG-7.0.md @@ -55,3 +55,6 @@ While 7.0 is unreleased these resolve to `7.0.x-dev` from each component's not been running. They are renamed to `*WrapperTest.php`. The Amazon SES test, broken unnoticed since `getSesClient()` gained its `SesClient` return type, now uses the SDK's `Aws\MockHandler` instead of the `MockSender` stand-in, which is removed. +- `phpunit.xml` sets `ignoreIndirectDeprecations="true"`. PHP 8.6 deprecates `is` as a class + name, and `guzzlehttp/promises` (required by the AWS SDK) has a class called `Is`. + Deprecations raised inside this package's own `src/` still fail the suite. diff --git a/phpunit.xml b/phpunit.xml index b9df5cf..2fc7feb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -28,7 +28,9 @@ and open the template in the editor. - + ./src/