-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 80c46ec
Showing
35 changed files
with
1,423 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/.gitattributes export-ignore | ||
/.gitignore export-ignore | ||
/.github export-ignore | ||
/composer.lock export-ignore | ||
/phpcs.xml export-ignore | ||
/phpunit.xml.dist export-ignore | ||
/tests/ export-ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
name: PHPUnit Test Suite | ||
on: | ||
push: | ||
branches: | ||
- master | ||
pull_request: | ||
branches: | ||
- master | ||
jobs: | ||
run-tests: | ||
name: PHP ${{ matrix.php-versions }} | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
php-versions: ['7.3', '7.4'] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Setup PHP Action | ||
uses: shivammathur/setup-php@1.6.1 | ||
with: | ||
php-version: ${{ matrix.php-versions }} | ||
coverage: pcov | ||
- name: Get composer cache directory | ||
id: composer-cache | ||
run: echo "::set-output name=dir::$(composer config cache-files-dir)" | ||
- name: Cache composer dependencies | ||
uses: actions/cache@v1 | ||
with: | ||
path: ${{ steps.composer-cache.outputs.dir }} | ||
key: ${{ runner.os }}-${{ matrix.php-versions }}-composer-${{ hashFiles('**/composer.json') }} | ||
restore-keys: ${{ runner.os }}-composer- | ||
- name: Install dependencies | ||
run: composer install --prefer-dist --no-progress --no-suggest --ignore-platform-reqs | ||
- name: Check CS | ||
run: composer cs-check | ||
- name: PHPUnit | ||
run: php -dpcov.directory=. -dpcov.enabled=1 -dpcov.exclude="~vendor~" ./vendor/bin/phpunit --stop-on-error --coverage-clover=coverage.xml | ||
- name: Upload Coverage to CodeCov | ||
uses: codecov/codecov-action@v1 | ||
with: | ||
token: ${{ secrets.CODECOV_TOKEN }} | ||
file: ./coverage.xml | ||
fail_ci_if_error: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/vendor/ | ||
/build/ | ||
composer.lock | ||
phpunit.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Laminas Mail Utilities | ||
|
||
### Introduction | ||
|
||
This is a small package to scratch two primary itches | ||
|
||
- Validate `Laminas\Mail\Message` instances according to configurable constraints such as a maximum number of recipients, or a non-empty subject line for example. | ||
- Provide simple behaviours and interfaces for email messages that should be sent via popular transactional or marketing email service providers; for example, key/value metadata or tagging/categorisation. | ||
|
||
### Installation | ||
|
||
```bash | ||
composer require netglue/laminas-mail-utils | ||
``` | ||
|
||
### Shipped Validators | ||
|
||
Validators can be used as part of your input filter setup, or by creating vendor specific validator chains. Here's an example of a concrete validator chain that would be helpful validating messages to be sent via the Postmark API, which imposes certain restrictions on recipients etc: | ||
|
||
```php | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace App; | ||
|
||
use Netglue\Mail\Validator\HasFromAddress; | ||
use Netglue\Mail\Validator\HasSubject; | ||
use Netglue\Mail\Validator\HasToRecipient; | ||
use Netglue\Mail\Validator\TotalFromCount; | ||
use Netglue\Mail\Validator\TotalRecipientCount; | ||
use Netglue\Mail\Validator\TotalReplyToCount; | ||
use Laminas\Validator\ValidatorChain; | ||
use Laminas\Validator\ValidatorPluginManager; | ||
|
||
final class PostmarkMessageValidator extends ValidatorChain | ||
{ | ||
private const MAX_RECIPIENTS = 50; | ||
|
||
public function __construct(?ValidatorPluginManager $pluginManager = null) | ||
{ | ||
parent::__construct(); | ||
if ($pluginManager) { | ||
$this->setPluginManager($pluginManager); | ||
} | ||
|
||
$this->configureDefaults(); | ||
} | ||
|
||
private function configureDefaults() : void | ||
{ | ||
$this->attachByName(HasFromAddress::class); | ||
$this->attachByName(TotalFromCount::class, ['max' => 1]); | ||
$this->attachByName(HasSubject::class); | ||
$this->attachByName(HasToRecipient::class); | ||
$this->attachByName(TotalRecipientCount::class, ['max' => self::MAX_RECIPIENTS]); | ||
$this->attachByName(TotalReplyToCount::class, ['max' => 1]); | ||
} | ||
} | ||
|
||
``` | ||
|
||
### Shipped Traits & Interfaces | ||
|
||
You'll find a collection of interfaces that are pretty minimal but hopefully encapsulate what a lot of transactional email service providers offer when it comes to additional features WRT sending mail - for example, assigning tags or categories to individual messages or turning click tracking or open tracking on and off. | ||
|
||
Typically, you make a concrete descendant of `Laminas\Mail\Message` and implement whichever interfaces suit the provider best so that you have type safety when working with the capabilities of any given message instance. Again, using Postmark as an example, perhaps something like this: | ||
|
||
```php | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace App\Postmark\Message; | ||
|
||
use Netglue\Mail\Message\KeyValueMetadata; | ||
use Netglue\Mail\Message\KeyValueMetadataBehaviour; | ||
use Netglue\Mail\Message\OpenTracking; | ||
use Netglue\Mail\Message\OpenTrackingBehaviour; | ||
use Netglue\Mail\Message\TaggableMessage; | ||
use Netglue\Mail\Message\TaggableMessageBehaviour; | ||
use Laminas\Mail\Message; | ||
|
||
class MyPostmarkMessage extends Message implements TaggableMessage, KeyValueMetadata, OpenTracking | ||
{ | ||
use OpenTrackingBehaviour; | ||
use TaggableMessageBehaviour; | ||
use KeyValueMetadataBehaviour { | ||
addMetaData as parentAddMetaData; | ||
} | ||
} | ||
``` | ||
|
||
```php | ||
// $message is given to us from some factory or service somewhere | ||
if ($message instanceof TaggableMessage) { | ||
$someVendorApi->setMessageTag($message->getTag()); | ||
} | ||
``` | ||
|
||
### The plan… | ||
|
||
… is to implement vendor specific packages that lever these utilities, with, as you may have guessed, Postmark at the top of the list currently… | ||
|
||
The package is currently immature and subject to probable BC breaks and contributions are welcomed if this scratches any itches for you 👍 | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"name": "netglue/laminas-mail-utils", | ||
"description": "A collection of straight-forward validators for Laminas mail messages and some common behaviours for transactional email messages", | ||
"type": "library", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "George Steel", | ||
"email": "george@net-glue.co.uk" | ||
} | ||
], | ||
"config": { | ||
"sort-packages": true | ||
}, | ||
"require": { | ||
"php": ">=7.3", | ||
"laminas/laminas-mail": "^2.10", | ||
"laminas/laminas-mime": "^2.7", | ||
"laminas/laminas-validator": "^2.13" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"Netglue\\Mail\\": "src" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"Netglue\\MailTest\\": "tests" | ||
} | ||
}, | ||
"require-dev": { | ||
"doctrine/coding-standard": "^7.0", | ||
"laminas/laminas-component-installer": "^2.1", | ||
"laminas/laminas-config-aggregator": "^1.2", | ||
"laminas/laminas-servicemanager": "^3.4", | ||
"phpunit/phpunit": "^9.0", | ||
"roave/security-advisories": "dev-master" | ||
}, | ||
"scripts": { | ||
"check": [ | ||
"@cs-check", | ||
"@test" | ||
], | ||
"cs-check": "phpcs", | ||
"cs-fix": "phpcbf", | ||
"test": "phpunit --colors=always", | ||
"test-coverage": "phpunit --colors=always --coverage-html build/report" | ||
}, | ||
"extra": { | ||
"laminas": { | ||
"component": "Netglue\\Mail\\ConfigProvider" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?xml version="1.0"?> | ||
<ruleset name="Custom Coding Standard based on Doctrine"> | ||
<arg name="basepath" value="."/> | ||
<arg name="extensions" value="php"/> | ||
<arg name="parallel" value="80"/> | ||
<arg name="cache" value=".php_cs.cache"/> | ||
<arg name="colors"/> | ||
|
||
<!-- Ignore warnings, show progress of the run and show sniff names --> | ||
<arg value="nps"/> | ||
|
||
<!-- Paths to check --> | ||
<file>src</file> | ||
<file>tests</file> | ||
|
||
<!-- Inherit rules from Doctrine Coding Standard --> | ||
<rule ref="Doctrine"> | ||
<exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes.IncorrectWhitespaceBetweenOpenTagAndDeclare"/> | ||
<!-- I like return types to look like this: ") :? Type" or ") : Type" --> | ||
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing.WhitespaceAfterNullabilitySymbol" /> | ||
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing.NoSpaceBetweenColonAndNullabilitySymbol" /> | ||
|
||
<!-- Whilst this lib is compatible with 7.3, exclude this sniff --> | ||
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" /> | ||
</rule> | ||
|
||
<rule ref="Generic.Formatting.MultipleStatementAlignment.NotSame"> | ||
<severity>0</severity> | ||
</rule> | ||
</ruleset> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" | ||
bootstrap="vendor/autoload.php" | ||
colors="true"> | ||
<testsuites> | ||
<testsuite name="Unit Tests"> | ||
<directory>./tests</directory> | ||
</testsuite> | ||
</testsuites> | ||
|
||
<filter> | ||
<whitelist> | ||
<directory suffix=".php">./src</directory> | ||
</whitelist> | ||
</filter> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Netglue\Mail; | ||
|
||
use Laminas\ServiceManager\Factory\InvokableFactory; | ||
|
||
class ConfigProvider | ||
{ | ||
/** @return mixed[] */ | ||
public function __invoke() : array | ||
{ | ||
return [ | ||
'validators' => $this->validators(), | ||
]; | ||
} | ||
|
||
/** @return mixed[] */ | ||
private function validators() : array | ||
{ | ||
return [ | ||
'factories' => [ | ||
Validator\HasFromAddress::class => InvokableFactory::class, | ||
Validator\HasSubject::class => InvokableFactory::class, | ||
Validator\HasToRecipient::class => InvokableFactory::class, | ||
Validator\TotalFromCount::class => InvokableFactory::class, | ||
Validator\TotalRecipientCount::class => InvokableFactory::class, | ||
Validator\TotalReplyToCount::class => InvokableFactory::class, | ||
], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Netglue\Mail\Exception; | ||
|
||
use InvalidArgumentException; | ||
|
||
class InvalidArgument extends InvalidArgumentException | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Netglue\Mail\Message; | ||
|
||
interface KeyValueMetadata | ||
{ | ||
/** | ||
* Return key:value metadata for the email message | ||
* | ||
* @return mixed[] | ||
*/ | ||
public function getMetaData() : iterable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Netglue\Mail\Message; | ||
|
||
use Netglue\Mail\Exception\InvalidArgument; | ||
use function gettype; | ||
use function is_scalar; | ||
use function sprintf; | ||
|
||
trait KeyValueMetadataBehaviour | ||
{ | ||
/** @var mixed[] */ | ||
private $metaData = []; | ||
|
||
/** @param mixed $value */ | ||
public function addMetaData(string $key, $value) : void | ||
{ | ||
if (empty($key)) { | ||
throw new InvalidArgument( | ||
'Metadata keys cannot be empty' | ||
); | ||
} | ||
|
||
if (! is_scalar($value) && $value !== null) { | ||
throw new InvalidArgument(sprintf( | ||
'Expected the metadata value to be scalar. Received %s.', | ||
gettype($value) | ||
)); | ||
} | ||
|
||
$this->metaData[$key] = $value; | ||
} | ||
|
||
/** @param mixed[] $metadata */ | ||
public function setMetaData(iterable $metadata) : void | ||
{ | ||
$this->metaData = []; | ||
foreach ($metadata as $key => $value) { | ||
$this->addMetaData($key, $value); | ||
} | ||
} | ||
|
||
/** @return mixed[] $value */ | ||
public function getMetaData() : iterable | ||
{ | ||
return $this->metaData; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Netglue\Mail\Message; | ||
|
||
interface MultipleTags | ||
{ | ||
/** | ||
* Return the tags for email messages that can be tagged with multiple different 'tags' | ||
* | ||
* @return iterable|string[] | ||
*/ | ||
public function getTags() : iterable; | ||
} |
Oops, something went wrong.