diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 0000000..dc4b6fa
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,49 @@
+on:
+ - push
+
+name: Run PHPStan checks
+
+jobs:
+ mutation:
+ name: PHPStan ${{ matrix.php }}-${{ matrix.os }}
+
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+
+ php:
+ - "8.2"
+ - "8.3"
+ - "8.4"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php }}"
+ coverage: pcov
+ ini-values: assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On
+ tools: composer:v2, cs2pr
+
+ - name: Determine composer cache directory
+ run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+ - name: Cache dependencies installed with composer
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.COMPOSER_CACHE_DIR }}
+ key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ php${{ matrix.php }}-composer-
+
+ - name: Install dependencies with composer
+ run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+ - name: Run static analysis with PHPStan
+ run: vendor/bin/phpstan analyse
diff --git a/README.md b/README.md
index 696be56..33e7754 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,26 @@
# dot-mail
-> [!IMPORTANT]
+Dotkernel's mail service.
+
> dot-mail is a wrapper on top of [symfony mailer](https://github.com/symfony/mailer)
-## dot-mail badges
+## Documentation
+
+Documentation is available at: https://docs.dotkernel.org/dot-mail/.
+
+## Badges

-
+
[](https://github.com/dotkernel/dot-mail/issues)
[](https://github.com/dotkernel/dot-mail/network)
[](https://github.com/dotkernel/dot-mail/stargazers)
-[](https://github.com/dotkernel/dot-mail/blob/5.0/LICENSE.md)
+[](https://github.com/dotkernel/dot-mail/blob/5.2/LICENSE.md)
-[](https://github.com/dotkernel/dot-mail/actions/workflows/continuous-integration.yml)
-[](https://codecov.io/gh/dotkernel/dot-mail)
+[](https://github.com/dotkernel/dot-mail/actions/workflows/continuous-integration.yml)
+[](https://codecov.io/gh/dotkernel/dot-mail)
+[](https://github.com/dotkernel/dot-mail/actions/workflows/static-analysis.yml)
## Installation
diff --git a/composer.json b/composer.json
index 4d7416a..46398b8 100644
--- a/composer.json
+++ b/composer.json
@@ -37,8 +37,9 @@
"require-dev": {
"laminas/laminas-coding-standard": "^3.0",
"mikey179/vfsstream": "^v1.6.11",
- "phpunit/phpunit": "^10.5",
- "vimeo/psalm": "^6.0"
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.2"
},
"autoload": {
"psr-4": {
@@ -59,7 +60,6 @@
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
- "test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
- "static-analysis": "psalm --shepherd --stats"
+ "static-analysis": "phpstan analyse --memory-limit 1G"
}
}
diff --git a/docs/book/v5/overview.md b/docs/book/v5/overview.md
index 706ac44..a1551cf 100644
--- a/docs/book/v5/overview.md
+++ b/docs/book/v5/overview.md
@@ -1,7 +1,23 @@
# Overview
+Dotkernel's mail service.
+
> dot-mail is a wrapper on top of [symfony mailer](https://github.com/symfony/mailer)
+## Badges
+
+
+
+
+[](https://github.com/dotkernel/dot-mail/issues)
+[](https://github.com/dotkernel/dot-mail/network)
+[](https://github.com/dotkernel/dot-mail/stargazers)
+[](https://github.com/dotkernel/dot-mail/blob/5.2/LICENSE.md)
+
+[](https://github.com/dotkernel/dot-mail/actions/workflows/continuous-integration.yml)
+[](https://codecov.io/gh/dotkernel/dot-mail)
+[](https://github.com/dotkernel/dot-mail/actions/workflows/static-analysis.yml)
+
## Extra features
- the option to log the results of the mailing process it provides the developer with more information and greater control.
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..349be25
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,8 @@
+includes:
+ - vendor/phpstan/phpstan-phpunit/extension.neon
+parameters:
+ level: 5
+ paths:
+ - src
+ - test
+ treatPhpDocTypesAsCertain: false
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
deleted file mode 100644
index f2f9ee2..0000000
--- a/psalm-baseline.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100644
index ed4c186..0000000
--- a/psalm.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Email.php b/src/Email.php
index f9bb236..547ff35 100644
--- a/src/Email.php
+++ b/src/Email.php
@@ -8,6 +8,7 @@
use DateTimeInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\Header\MailboxListHeader;
use Symfony\Component\Mime\Message;
use Symfony\Component\Mime\Part\AbstractPart;
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
@@ -46,13 +47,10 @@ class Email extends Message
private ?string $textCharset = null;
private ?string $html = null;
- private ?string $htmlCharset = null;
- private array $attachments = [];
- private ?AbstractPart $cachedBody = null;
+ private ?string $htmlCharset = null;
+ private array $attachments = [];
+ private mixed $cachedBody;
- /**
- * @return $this
- */
public function subject(string $subject): static
{
return $this->setHeaderBody('Text', 'Subject', $subject);
@@ -63,9 +61,6 @@ public function getSubject(): ?string
return $this->getHeaders()->getHeaderBody('Subject');
}
- /**
- * @return $this
- */
public function date(DateTimeInterface $dateTime): static
{
return $this->setHeaderBody('Date', 'Date', $dateTime);
@@ -76,9 +71,6 @@ public function getDate(): ?DateTimeImmutable
return $this->getHeaders()->getHeaderBody('Date');
}
- /**
- * @return $this
- */
public function returnPath(Address|string $address): static
{
return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
@@ -201,7 +193,7 @@ public function getPriority(): int
{
[$priority] = sscanf($this->getHeaders()->getHeaderBody('X-Priority') ?? '', '%[1-5]');
- return (int) $priority ?? 3;
+ return $priority !== null ? (int) $priority : 3;
}
public function text(string $body, string $charset = 'utf-8'): static
@@ -315,7 +307,7 @@ private function generateBody(): AbstractPart
return $part ?? new TextPart($this->text, $this->textCharset);
}
- private function prepareParts(): ?array
+ private function prepareParts(): array
{
$names = [];
$htmlPart = null;
@@ -373,7 +365,8 @@ private function setHeaderBody(string $type, string $name, mixed $body): static
private function addListAddressHeaderBody(string $name, array $addresses): static
{
- if (! $header = $this->getHeaders()->get($name)) {
+ $header = $this->getHeaders()->get($name);
+ if (! $header instanceof MailboxListHeader) {
return $this->setListAddressHeaderBody($name, $addresses);
}
$header->addAddresses(Address::createArray($addresses));
@@ -385,7 +378,7 @@ private function setListAddressHeaderBody(string $name, array $addresses): stati
{
$addresses = Address::createArray($addresses);
$headers = $this->getHeaders();
- if ($header = $headers->get($name)) {
+ if (($header = $headers->get($name)) instanceof MailboxListHeader) {
$header->setAddresses($addresses);
} else {
$headers->addMailboxListHeader($name, $addresses);
diff --git a/test/Event/MailEventTest.php b/test/Event/MailEventTest.php
index d10ee4a..d6323be 100644
--- a/test/Event/MailEventTest.php
+++ b/test/Event/MailEventTest.php
@@ -29,16 +29,16 @@ public function testMailEventCanBeCreated(): void
{
$defaultMailEvent = new MailEvent($this->mailService);
- $this->assertInstanceOf(MailEvent::class, $defaultMailEvent);
- $this->assertInstanceOf(MailServiceInterface::class, $defaultMailEvent->getMailService());
+ $this->assertContainsOnlyInstancesOf(MailEvent::class, [$defaultMailEvent]);
+ $this->assertContainsOnlyInstancesOf(MailServiceInterface::class, [$defaultMailEvent->getMailService()]);
$customMailEvent = new MailEvent($this->mailService, MailEvent::EVENT_MAIL_SEND_ERROR);
$mailServiceInterface = $this->createMock(MailServiceInterface::class);
$customMailEvent->setMailService($mailServiceInterface);
- $this->assertInstanceOf(MailEvent::class, $customMailEvent);
+ $this->assertContainsOnlyInstancesOf(MailEvent::class, [$customMailEvent]);
$this->assertSame(MailEvent::EVENT_MAIL_SEND_ERROR, $customMailEvent->getName());
- $this->assertInstanceOf(MailServiceInterface::class, $customMailEvent->getMailService());
+ $this->assertContainsOnlyInstancesOf(MailServiceInterface::class, [$customMailEvent->getMailService()]);
}
public function testEventPropagation(): void
diff --git a/test/Factory/MailOptionsAbstractFactoryTest.php b/test/Factory/MailOptionsAbstractFactoryTest.php
index 650a48f..cdf2840 100644
--- a/test/Factory/MailOptionsAbstractFactoryTest.php
+++ b/test/Factory/MailOptionsAbstractFactoryTest.php
@@ -53,7 +53,7 @@ public function testGeneratesMailOptions(): void
$subject = (new MailOptionsAbstractFactory())($container, $defaultName);
- $this->assertInstanceOf(MailOptions::class, $subject);
+ $this->assertContainsOnlyInstancesOf(MailOptions::class, [$subject]);
}
/**
@@ -73,6 +73,6 @@ public function testIsSpecificConfigArray(): void
$subject = (new MailOptionsAbstractFactory())($container, $defaultName);
- $this->assertInstanceOf(MailOptions::class, $subject);
+ $this->assertContainsOnlyInstancesOf(MailOptions::class, [$subject]);
}
}
diff --git a/test/Factory/MailServiceAbstractFactoryTest.php b/test/Factory/MailServiceAbstractFactoryTest.php
index a926bba..34177f2 100644
--- a/test/Factory/MailServiceAbstractFactoryTest.php
+++ b/test/Factory/MailServiceAbstractFactoryTest.php
@@ -127,7 +127,7 @@ public function testGenerateServiceWithProvidedAdapter(): void
->method('getSmtpOptions')
->willReturn($this->smtpOptions);
- $mailService = (new Subject())($this->container, $requestedName);
+ $mailService = ($this->subject)($this->container, $requestedName);
$this->assertInstanceOf(MailService::class, $mailService);
$this->assertInstanceOf(EsmtpTransport::class, $mailService->getTransport());
}
@@ -197,7 +197,7 @@ public function testGenerateServiceWithoutProvidedAdapter(): void
$this->expectException(RuntimeException::class);
- $mailService = (new Subject())($this->container, $requestedName);
+ $mailService = ($this->subject)($this->container, $requestedName);
$this->assertInstanceOf(MailService::class, $mailService);
$this->assertInstanceOf(EsmtpTransport::class, $mailService->getTransport());
@@ -226,7 +226,7 @@ public function testGenerateServiceInvalidAdapter(): void
]);
$this->expectException(InvalidArgumentException::class);
- (new Subject())($this->container, $requestedName);
+ ($this->subject)($this->container, $requestedName);
}
/**
@@ -258,6 +258,6 @@ public function testGenerateServiceThrowException(): void
]);
$this->expectException(InvalidArgumentException::class);
- (new Subject())($this->container, $requestedName);
+ ($this->subject)($this->container, $requestedName);
}
}
diff --git a/test/Options/MailOptionsTest.php b/test/Options/MailOptionsTest.php
index 5e78703..4382364 100644
--- a/test/Options/MailOptionsTest.php
+++ b/test/Options/MailOptionsTest.php
@@ -31,8 +31,8 @@ public function testGettersAndSetters(): void
$this->assertSame(EsmtpTransport::class, $subject->getTransport());
$this->assertSame($transportMap, $subject->getTransportMap());
- $this->assertInstanceOf(MessageOptions::class, $subject->getMessageOptions());
- $this->assertInstanceOf(SmtpOptions::class, $subject->getSmtpOptions());
+ $this->assertContainsOnlyInstancesOf(MessageOptions::class, [$subject->getMessageOptions()]);
+ $this->assertContainsOnlyInstancesOf(SmtpOptions::class, [$subject->getSmtpOptions()]);
$this->assertSame($eventListeners, $subject->getEventListeners());
}
}
diff --git a/test/Options/MessageOptionsTest.php b/test/Options/MessageOptionsTest.php
index 1179384..7a68400 100644
--- a/test/Options/MessageOptionsTest.php
+++ b/test/Options/MessageOptionsTest.php
@@ -45,7 +45,7 @@ public function testGettersAndSetters(): void
$this->assertSame($cc, $subject->getCc());
$this->assertSame($bcc, $subject->getBcc());
$this->assertSame($messageSubject, $subject->getSubject());
- $this->assertInstanceOf(BodyOptions::class, $subject->getBody());
- $this->assertInstanceOf(AttachmentsOptions::class, $subject->getAttachments());
+ $this->assertContainsOnlyInstancesOf(BodyOptions::class, [$subject->getBody()]);
+ $this->assertContainsOnlyInstancesOf(AttachmentsOptions::class, [$subject->getAttachments()]);
}
}
diff --git a/test/Service/MailServiceTest.php b/test/Service/MailServiceTest.php
index b87c78a..db8c13d 100644
--- a/test/Service/MailServiceTest.php
+++ b/test/Service/MailServiceTest.php
@@ -81,12 +81,12 @@ public function testGettersAndSetters(): void
public function testCreateMailEvent(): void
{
$defaultMailEvent = $this->mailService->createMailEvent();
- $this->assertInstanceOf(MailEvent::class, $defaultMailEvent);
+ $this->assertContainsOnlyInstancesOf(MailEvent::class, [$defaultMailEvent]);
$this->assertSame(MailEvent::EVENT_MAIL_PRE_SEND, $defaultMailEvent->getName());
$result = new MailResult();
$mailEvent = $this->mailService->createMailEvent('testName', $result);
- $this->assertInstanceOf(MailEvent::class, $mailEvent);
+ $this->assertContainsOnlyInstancesOf(MailEvent::class, [$mailEvent]);
$this->assertSame('testName', $mailEvent->getName());
$this->assertSame(MailResult::DEFAULT_MESSAGE, $mailEvent->getResult()->getMessage());
}