Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gsteel committed Mar 18, 2020
0 parents commit 1526f70
Show file tree
Hide file tree
Showing 17 changed files with 640 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitattributes
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
44 changes: 44 additions & 0 deletions .github/workflows/phpunit.yml
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/[email protected]
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor/
phpunit.xml
/build/
composer.lock
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# PSR-11 Container Factories for Postmark API Clients

### Introduction

This very small library provides [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible factories for creating
Postmark HTTP clients for either servers for sending mail, or the admin client for managing your account.

[Postmark](https://postmarkapp.com) is a reliable transactional email delivery service. These factories return clients
from their [official PHP library](https://github.com/wildbit/postmark-php).

### Install

```bash
composer require netglue/psr-container-postmark
```

### Usage

By default, the container will look for application configuration using the id `config` which is a generally accepted standard. If your PSR-11 container doesn't return an associative array when calling `$container->get('config')` then this lib will likely be useless to you.

Postmark-specific configuration is, by default, expected underneath the `'postmark'` key, though this _can_ be modified.

Assuming defaults, your configuration for the clients should look like this:

```php
return [
'postmark' => [
'server_token' => 'Your Server Token',
'server_timeout' => 30, // The default is 30, as per Postmark's libs so this option can be omitted
'admin_token' => 'Your Account Token', // Only required if you are using the Admin client to manage an account
],
];
```

You would then wire up your container so that a key of your choosing is mapped to the factory classes, perhaps:

```php
return [
'dependencies' => [
'factories' => [
Postmark\PostmarkClient::class => Netglue\PsrContainer\Postmark\ClientFactory::class,
Postmark\PostmarkAdminClient::class => Netglue\PsrContainer\Postmark\AdminClientFactory::class,
],
],
];
```

If you run multiple servers for some reason, you can wire up the factory in the following way to use different configuration for different servers:

```php
return [
'dependencies' => [
'factories' => [
'EmailServer1' => [Netglue\PsrContainer\Postmark\ClientFactory::class, 'postmark_server_1'],
'EmailServer2' => [Netglue\PsrContainer\Postmark\ClientFactory::class, 'postmark_server_2'],
],
],
];
```

Given the above container setup, you'd need to specify two top-level configuration arrays with a server key in each for
`'postmark_server_1'` and `'postmark_server_2'`

### Laminas Integration

Because I use Laminas _(Formerly Zend)_ components a lot, this lib will auto-wire dependencies _(if you choose to allow it)_ during composer installation thanks to [laminas-component-installer](https://docs.laminas.dev/laminas-component-installer/).

_fin._

52 changes: 52 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "netglue/psr-container-postmark",
"description": "Postmark client factories for PSR Containers",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "George Steel",
"email": "[email protected]"
}
],
"config": {
"sort-packages": true
},
"require": {
"php": ">=7.3",
"wildbit/postmark-php": "^2.9",
"psr/container": "^1.0"
},
"autoload": {
"psr-4": {
"Netglue\\PsrContainer\\Postmark\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Netglue\\PsrContainer\\PostmarkTest\\": "tests"
}
},
"require-dev": {
"doctrine/coding-standard": "^7.0",
"laminas/laminas-component-installer": "^2.1",
"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\\PsrContainer\\Postmark\\ConfigProvider"
}
}
}
30 changes: 30 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<ruleset name="Prismic.io Coding Standard">
<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>
17 changes: 17 additions & 0 deletions phpunit.xml.dist
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="Prismic PHP SDK Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
25 changes: 25 additions & 0 deletions src/AdminClientFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark;

use Netglue\PsrContainer\Postmark\Exception\MissingAccountKey;
use Postmark\PostmarkAdminClient;
use Psr\Container\ContainerInterface;
use function sprintf;

class AdminClientFactory extends BaseFactory
{
public function __invoke(ContainerInterface $container) : PostmarkAdminClient
{
$config = $this->retrieveConfig($container);
$token = $config['account_token'] ?? null;
if (empty($token)) {
throw MissingAccountKey::withConfigPath(sprintf('[%s][account_token]', $this->section));
}

$timeout = $config['server_timeout'] ?? self::DEFAULT_API_TIMEOUT;

return new PostmarkAdminClient($token, (int) $timeout);
}
}
42 changes: 42 additions & 0 deletions src/BaseFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark;

use Netglue\PsrContainer\Postmark\Exception\BadMethodCall;
use Psr\Container\ContainerInterface;
use function array_key_exists;

abstract class BaseFactory
{
public const DEFAULT_CONFIG_SECTION = 'postmark';
public const DEFAULT_API_TIMEOUT = 30;

/** @var string */
protected $section;

public function __construct(string $section = self::DEFAULT_CONFIG_SECTION)
{
$this->section = $section;
}

/** @inheritDoc */
public static function __callStatic(string $name, array $arguments)
{
if (! array_key_exists(0, $arguments) || ! $arguments[0] instanceof ContainerInterface) {
throw new BadMethodCall(
'The first argument to __callStatic must be an instance of ContainerInterface'
);
}

return (new static($name))($arguments[0]);
}

/** @return mixed[] */
protected function retrieveConfig(ContainerInterface $container) : array
{
$config = $container->has('config') ? $container->get('config') : [];

return $config[$this->section] ?? [];
}
}
25 changes: 25 additions & 0 deletions src/ClientFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark;

use Netglue\PsrContainer\Postmark\Exception\MissingServerKey;
use Postmark\PostmarkClient;
use Psr\Container\ContainerInterface;
use function sprintf;

class ClientFactory extends BaseFactory
{
public function __invoke(ContainerInterface $container) : PostmarkClient
{
$config = $this->retrieveConfig($container);
$token = $config['server_token'] ?? null;
if (empty($token)) {
throw MissingServerKey::withConfigPath(sprintf('[%s][server_token]', $this->section));
}

$timeout = $config['server_timeout'] ?? self::DEFAULT_API_TIMEOUT;

return new PostmarkClient($token, (int) $timeout);
}
}
23 changes: 23 additions & 0 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark;

use Postmark\PostmarkAdminClient;
use Postmark\PostmarkClient;

class ConfigProvider
{
/** @return mixed[] */
public function __invoke() : array
{
return [
'dependencies' => [
'factories' => [
PostmarkClient::class => ClientFactory::class,
PostmarkAdminClient::class => AdminClientFactory::class,
],
],
];
}
}
10 changes: 10 additions & 0 deletions src/Exception/BadMethodCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark\Exception;

use BadMethodCallException;

class BadMethodCall extends BadMethodCallException
{
}
20 changes: 20 additions & 0 deletions src/Exception/MissingAccountKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);

namespace Netglue\PsrContainer\Postmark\Exception;

use RuntimeException;
use function sprintf;

class MissingAccountKey extends RuntimeException
{
public static function withConfigPath(string $path) : self
{
$message = sprintf(
'Expected a non-empty string to use as the account api key at %s',
$path
);

return new static($message, 500);
}
}
Loading

0 comments on commit 1526f70

Please sign in to comment.