From 1cd221aa3c8a5605bf52a11d654ad933a5f8a88b Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Sat, 22 Nov 2025 01:18:51 -0500 Subject: [PATCH] Refactor mail wrappers for improved null-safety checks and update dependencies --- .github/workflows/phpunit.yml | 35 ++++++++++++++++++++++++++++--- README.md | 1 + composer.json | 10 ++++----- psalm.xml | 3 +-- src/Envelope.php | 2 +- src/MailerFactory.php | 18 ++++++++-------- src/Wrapper/AmazonSesWrapper.php | 6 +++--- src/Wrapper/MailgunApiWrapper.php | 8 +++---- src/Wrapper/PHPMailerWrapper.php | 16 +++++++------- 9 files changed, 63 insertions(+), 36 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 2b4481e..fc5f29a 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,15 +18,44 @@ jobs: strategy: matrix: php-version: + - "8.5" - "8.4" - "8.3" - - "8.2" - - "8.1" steps: - uses: actions/checkout@v5 - run: composer install - - run: ./vendor/bin/phpunit + - run: composer test + + Psalm: + name: Psalm Static Analyzer + runs-on: ubuntu-latest + permissions: + # for github/codeql-action/upload-sarif to upload SARIF results + security-events: write + container: + image: byjg/php:8.4-cli + options: --user root --privileged + + steps: + - name: Git checkout + uses: actions/checkout@v4 + + - name: Composer + run: composer install + + - 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 + --show-info=true + --report=psalm-results.sarif || [ $? = 2 ] + + - name: Upload Analysis results to GitHub + uses: github/codeql-action/upload-sarif@v4 + if: github.ref == 'refs/heads/master' + with: + sarif_file: psalm-results.sarif Documentation: if: github.ref == 'refs/heads/master' diff --git a/README.md b/README.md index d3f10da..1cb22b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Mail Wrapper +[![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) [![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-mailwrapper/) diff --git a/composer.json b/composer.json index e1d5a08..c5ed6b5 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "byjg/mailwrapper", - "description": "A lightweight wrapper for send mail. The interface is tottaly decoupled from the sender. The sender availables are: PHP Mailer, AWS SES Api, Mandril Api.", + "description": "A lightweight wrapper for sending email. The interface is totally decoupled from the sender, providing a single interface for sending mail regardless of the underlying mail service.", "autoload": { "psr-4": { "ByJG\\Mail\\": "src/" @@ -14,7 +14,7 @@ "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": ">=8.1 <8.5", + "php": ">=8.3 <8.6", "ext-curl": "*", "byjg/convert": "^6.0", "byjg/webrequest": "^6.0", @@ -22,12 +22,12 @@ "phpmailer/phpmailer": ">=6.4.1" }, "require-dev": { - "phpunit/phpunit": "^10|^11", - "vimeo/psalm": "^5.9|^6.12" + "phpunit/phpunit": "^10.5|^11.5", + "vimeo/psalm": "^5.9|^6.13" }, "scripts": { "test": "vendor/bin/phpunit", - "psalm": "vendor/bin/psalm" + "psalm": "vendor/bin/psalm --threads=1" }, "license": "MIT" } diff --git a/psalm.xml b/psalm.xml index b208114..037de8b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ - diff --git a/src/Envelope.php b/src/Envelope.php index 5927354..b5d11c4 100644 --- a/src/Envelope.php +++ b/src/Envelope.php @@ -163,7 +163,7 @@ public function getBodyText(): string "\n" ], $this->body - ); + ) ?? $this->body; return strip_tags($body); } diff --git a/src/MailerFactory.php b/src/MailerFactory.php index ab7cb93..5963f1a 100644 --- a/src/MailerFactory.php +++ b/src/MailerFactory.php @@ -15,10 +15,11 @@ class MailerFactory { + /** @var array> */ private static array $config = []; /** - * @param string $class + * @param class-string $class * * @throws InvalidMailHandlerException * @@ -26,11 +27,12 @@ class MailerFactory */ public static function registerMailer(string $class): void { - if (!in_array(MailWrapperInterface::class, class_implements($class))) { + $implements = class_implements($class); + if ($implements === false || !in_array(MailWrapperInterface::class, $implements)) { throw new InvalidMailHandlerException('Class not implements ConnectorInterface!'); } - /** @var MailWrapperInterface $class */ + /** @var class-string $class */ $protocolList = $class::schema(); foreach ($protocolList as $item) { self::$config[$item] = $class; @@ -45,16 +47,14 @@ public static function registerMailer(string $class): void */ public static function create(UriInterface|string $connection): MailWrapperInterface { - $uri = $connection; - if (is_string($connection)) { - $uri = new Uri($connection); - } + $uri = is_string($connection) ? new Uri($connection) : $connection; - if (!isset(self::$config[$uri->getScheme()])) { + $scheme = $uri->getScheme(); + if (!isset(self::$config[$scheme])) { throw new ProtocolNotRegisteredException('Protocol not found/registered!'); } - $class = self::$config[$uri->getScheme()]; + $class = self::$config[$scheme]; return new $class($uri); } diff --git a/src/Wrapper/AmazonSesWrapper.php b/src/Wrapper/AmazonSesWrapper.php index 28d8650..640edbf 100644 --- a/src/Wrapper/AmazonSesWrapper.php +++ b/src/Wrapper/AmazonSesWrapper.php @@ -27,10 +27,10 @@ public function getSesClient(): SesClient //Send the message (which must be base 64 encoded): return new SesClient([ 'credentials' => new Credentials( - $this->uri->getUsername(), - $this->uri->getPassword() + $this->uri?->getUsername() ?? '', + $this->uri?->getPassword() ?? '' ), - 'region' => $this->uri->getHost(), + 'region' => $this->uri?->getHost() ?? '', 'version' => '2010-12-01' ]); } diff --git a/src/Wrapper/MailgunApiWrapper.php b/src/Wrapper/MailgunApiWrapper.php index 2dc4c47..6e5cab8 100644 --- a/src/Wrapper/MailgunApiWrapper.php +++ b/src/Wrapper/MailgunApiWrapper.php @@ -51,11 +51,11 @@ public function __construct(Uri $uri, ?ClientInterface $client = null) */ public function getRequestObject(): RequestInterface { - $domainName = $this->uri->getHost(); + $domainName = $this->uri?->getHost() ?? ''; $apiUri = $this->getApiUri(); $uri = Uri::getInstance("https://$apiUri/v3/$domainName/messages") - ->withUserInfo('api', $this->uri->getUsername()); + ->withUserInfo('api', $this->uri?->getUsername() ?? ''); return Request::getInstance($uri)->withMethod("POST"); } @@ -104,7 +104,7 @@ public function send(Envelope $envelope): SendResult foreach ($envelope->getAttachments() as $name => $attachment) { $message[] = new MultiPartItem( $attachment['disposition'], - file_get_contents($attachment['content']), + file_get_contents($attachment['content']) ?: '', $name, $attachment['content-type'] ); @@ -128,7 +128,7 @@ public function send(Envelope $envelope): SendResult private function getApiUri() { - $query = $this->uri->getQueryPart('region'); + $query = $this->uri?->getQueryPart('region'); if (isset($this->regions[$query])) { return $this->regions[$query]; } diff --git a/src/Wrapper/PHPMailerWrapper.php b/src/Wrapper/PHPMailerWrapper.php index 55f7ed3..d6a8596 100644 --- a/src/Wrapper/PHPMailerWrapper.php +++ b/src/Wrapper/PHPMailerWrapper.php @@ -46,8 +46,8 @@ protected function prepareMailer(Envelope $envelope): PHPMailerOverride $mail->isSMTP(); // telling the class to use SMTP - if ($this->uri->getScheme() != "smtp") { - $mail->SMTPSecure = $this->uri->getScheme(); // ssl ou tls! + if ($this->uri?->getScheme() != "smtp") { + $mail->SMTPSecure = $this->uri?->getScheme() ?? ''; // ssl ou tls! } $replyTo = Util::decomposeEmail($envelope->getReplyTo()); @@ -114,14 +114,12 @@ public function send(Envelope $envelope): SendResult $mail = $this->prepareMailer($envelope); - $mail->Host = $this->uri->getHost(); - $mail->Port = $this->uri->getPort(); + $mail->Host = $this->uri?->getHost() ?? ''; + $mail->Port = $this->uri?->getPort() ?? 25; - if (!empty($this->uri->getUsername())) { - $mail->SMTPAuth = true; - $mail->Username = $this->uri->getUsername(); // SMTP account username - $mail->Password = $this->uri->getPassword(); // SMTP account password - } + $mail->SMTPAuth = !empty($this->uri?->getUsername()); + $mail->Username = $this->uri?->getUsername() ?? ''; // SMTP account username + $mail->Password = $this->uri?->getPassword() ?? ''; // SMTP account password if (!$mail->send()) { throw new MailApiException($mail->ErrorInfo);