From 7ec09310b27ad77ca6f844e4c81bb4e36c6bdf32 Mon Sep 17 00:00:00 2001 From: Jeremy Dormitzer Date: Wed, 23 Jan 2019 18:34:25 -0500 Subject: [PATCH] Use a type-safe config class instead of passing in an array --- README.md | 50 +++--- src/ActivityPub.php | 8 +- src/Config/ActivityPubConfig.php | 98 ++++++++++++ src/Config/ActivityPubConfigBuilder.php | 199 ++++++++++++++++++++++++ src/Config/ActivityPubModule.php | 40 +---- test/ActivityPubTest.php | 14 +- test/Config/ActivityPubModuleTest.php | 15 +- test/TestConfig/SQLiteTestCase.php | 14 +- test/bootstrap.php | 10 +- 9 files changed, 364 insertions(+), 84 deletions(-) create mode 100644 src/Config/ActivityPubConfig.php create mode 100644 src/Config/ActivityPubConfigBuilder.php diff --git a/README.md b/README.md index 3dd8c98..a258d55 100644 --- a/README.md +++ b/README.md @@ -30,32 +30,34 @@ Basic usage example: ``` php function() { - if ( current_user_is_logged_in() ) { - return get_actor_id_for_current_user(); - } else { - return false; - } - }, - // Database connection options, passed directly to Doctrine: - 'dbOptions' => array( - 'driver' => 'pdo_mysql', - 'user' => 'mysql' - 'password' => 'thePa$$word', - 'dbname' => 'my-database', - ), - // Database table name prefix for compatibility with $wpdb->prefix, etc.: - // Default: '' - 'dbPrefix' => 'activitypub_', -) ); +$config = ActivityPubConfig::createBuilder() + // Function to determine if the current request should be associated + // with an ActivityPub actor. It should return the actor id associated + // with the current request, or false if the current request is not associated + // with the actor. This is where you can plug in your application's user + // management system: + ->setAuthFunction( function() { + if ( current_user_is_logged_in() ) { + return get_actor_id_for_current_user(); + } else { + return false; + } + } ) + // Database connection options, passed directly to Doctrine: + ->setDbConnectionParams( array( + 'driver' => 'pdo_mysql', + 'user' => 'mysql' + 'password' => 'thePa$$word', + 'dbname' => 'my-database', + ) ) + // Database table name prefix for compatibility with $wpdb->prefix, etc.: + // Default: '' + ->setDbPrefix( 'activitypub_' ) + ->build(); +$activitypub = new ActivityPub( $config ); // Routing incoming ActivityPub requests to ActivityPub-PHP if ( in_array( $_SERVER['HTTP_ACCEPT'], diff --git a/src/ActivityPub.php b/src/ActivityPub.php index c3cbbec..667febb 100644 --- a/src/ActivityPub.php +++ b/src/ActivityPub.php @@ -5,6 +5,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; use ActivityPub\Auth\AuthListener; use ActivityPub\Auth\SignatureListener; +use ActivityPub\Config\ActivityPubConfig; use ActivityPub\Config\ActivityPubModule; use ActivityPub\Http\Router; use Doctrine\ORM\EntityManager; @@ -27,12 +28,11 @@ class ActivityPub /** * Constructs a new ActivityPub instance * - * @param array $opts Array of options. Valid keys are - * 'dbOptions', 'dbprefix', and 'isDevMode'. + * @param ActivityPubConfig $config Configuration options */ - public function __construct( array $opts ) + public function __construct( ActivityPubConfig $config ) { - $this->module = new ActivityPubModule( $opts ); + $this->module = new ActivityPubModule( $config); } /** diff --git a/src/Config/ActivityPubConfig.php b/src/Config/ActivityPubConfig.php new file mode 100644 index 0000000..9fa1f56 --- /dev/null +++ b/src/Config/ActivityPubConfig.php @@ -0,0 +1,98 @@ +createBuilder()->build() + * + * @param ActivityPubConfigBuilder $builder + */ + public function __construct( ActivityPubConfigBuilder $builder ) + { + $this->dbConnectionParams = $builder->getDbConnectionParams(); + $this->isDevMode = $builder->getIsDevMode(); + $this->dbPrefix = $builder->getDbPrefix(); + $this->authFunction = $builder->getAuthFunction(); + $this->jsonLdContext = $builder->getJsonLdContext(); + } + + public function createBuilder() + { + return new ActivityPubConfigBuilder(); + } + + /** + * @var array + */ + public function getDbConnectionParams() + { + return $this->dbConnectionParams; + } + + /** + * @var bool + */ + public function getIsDevMode() + { + return $this->isDevMode; + } + + /** + * @var string + */ + public function getDbPrefix() + { + return $this->dbPrefix; + + } + + /** + * @var Callable + */ + public function getAuthFunction() + { + return $this->authFunction; + } + + /** + * @var array + */ + public function getJsonLdContext() + { + return $this->jsonLdContext; + } +} +?> diff --git a/src/Config/ActivityPubConfigBuilder.php b/src/Config/ActivityPubConfigBuilder.php new file mode 100644 index 0000000..35e8ac0 --- /dev/null +++ b/src/Config/ActivityPubConfigBuilder.php @@ -0,0 +1,199 @@ +setDbConnectionParams( array( + * 'driver' => 'pdo_sqlite', + * 'path' => __DIR__ . '/db.sqlite' + * ) ) + * ->build(); + * + * See the `set*` methods below for descriptions of available options. + */ +class ActivityPubConfigBuilder +{ + /** + * @var array + */ + private $dbConnectionParams; + + /** + * @var bool + */ + private $isDevMode; + + /** + * @var string + */ + private $dbPrefix; + + /** + * @var Callable + */ + private $authFunction; + + /** + * @var array + */ + private $jsonLdContext; + + /** + * Creates a new ActivityPubConfig instance with default values + * + * See the `set*` methods below for individual option defaults. + */ + public function __construct() + { + $this->isDevMode = false; + $this->dbPrefix = ''; + $this->authFunction = function() { + return false; + }; + $this->jsonLDContext = ContextProvider::DEFAULT_CONTEXT; + } + + /** + * Validates and builds the config instance + * + * @return ActivityPubConfig + */ + public function build() + { + $this->validate(); + return new ActivityPubConfig( $this ); + } + + private function validate() + { + if ( ! $this->dbConnectionParams ) { + throw new Exception( "Missing required option 'dbConnectionParams'" ); + } + } + + /** + * The `dbConnectionParams` are the Doctrine connection parameters, + * passed directly through to EntityManager::create(). See + * https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/getting-started.html#obtaining-the-entitymanager + * + * This option is required and has no default. + * @param array $dbConnectionParams The connection parameters + * @return ActivityPubConfigBuilder The builder instance + */ + public function setDbConnectionParams( array $dbConnectionParams ) + { + $this->dbConnectionParams = $dbConnectionParams; + return $this; + } + + /** + * @return array + * + * + */ + public function getDbConnectionParams() + { + return $this->dbConnectionParams; + } + + /** + * If `isDevMode` is true, the Doctrine EntityManager configuration will + * be set to development mode. + * + * Default: false + * @param bool $isDevMode + * @return ActivityPubConfigBuilder The builder instance + */ + public function setIsDevMode( bool $isDevMode ) + { + $this->isDevMode = $isDevMode; + return $this; + } + + /** + * @return bool + * + * + */ + public function getIsDevMode() + { + return $this->isDevMode; + } + + /** + * The `dbPrefix` is a string that is prepended to all SQL tables created + * by the ActivityPub library. This is useful for environments like multi-site + * WordPress installations where table prefixes are used to distinguish tables + * for different sites. + * + * Default: '' + * @param string $dbPrefix + * @return ActivityPubConfigBuilder The builder instance + */ + public function setDbPrefix( string $dbPrefix ) + { + $this->dbPrefix = $dbPrefix; + return $this; + } + + /** + * @return string + */ + public function getDbPrefix() + { + return $this->dbPrefix; + } + + /** + * The `authFunction` is used to bridge your application's user management + * system with ActivityPub. It should be a Callable that takes no arguments + * and returns the ID of the ActivityPub actor associated with the user + * authenticated to the current request, if any. If no such actor exists, + * it should return `false`. + * + * Default: function() { return false; }, i.e. HTTP signatures are the only valid + * authentication mechanism. + * @param Callable $authFunction + * @return ActivityPubConfigBuilder The builder instance + */ + public function setAuthFunction( Callable $authFunction ) + { + $this->authFunction = $authFunction; + return $this; + } + + /** + * @return Callable + */ + public function getAuthFunction() + { + return $this->authFunction; + } + + /** + * The `jsonLdContext` option sets a custom JSON-LD context on all + * objects created by the ActivityPub library. See https://json-ld.org/. + * + * Default: array( 'https://www.w3.org/ns/activitystreams', + * 'https://w3id.org/security/v1' ) + * @param array $jsonLdContext + * @return ActivityPubConfigBuilder The builder instance + */ + public function setJsonLdContext( array $jsonLdContext ) + { + $this->jsonLdContext = $jsonLdContext; + return $this; + } + + public function getJsonLdContext() + { + return $this->jsonLdContext; + } +} +?> diff --git a/src/Config/ActivityPubModule.php b/src/Config/ActivityPubModule.php index 72714eb..96f622e 100644 --- a/src/Config/ActivityPubModule.php +++ b/src/Config/ActivityPubModule.php @@ -6,6 +6,7 @@ use ActivityPub\Auth\AuthService; use ActivityPub\Auth\SignatureListener; use ActivityPub\Controllers\GetController; use ActivityPub\Controllers\PostController; +use ActivityPub\Config\ActivityPubConfig; use ActivityPub\Crypto\HttpSignatureService; use ActivityPub\Database\PrefixNamingStrategy; use ActivityPub\Http\Router; @@ -34,30 +35,16 @@ class ActivityPubModule */ private $injector; - public function __construct( $options ) + public function __construct( ActivityPubConfig $config ) { - $defaults = array( - 'isDevMode' => false, - 'dbPrefix' => '', - 'authFunction' => function() { - return false; - }, - 'context' => array( - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - ), - ); - $options = array_merge( $defaults, $options ); - $this->validateOptions( $options ); - $this->injector = new ContainerBuilder; $dbConfig = Setup::createAnnotationMetadataConfiguration( - array( __DIR__ . '/../Entities' ), $options['isDevMode'] + array( __DIR__ . '/../Entities' ), $config->getIsDevMode() ); - $namingStrategy = new PrefixNamingStrategy( $options['dbPrefix'] ); + $namingStrategy = new PrefixNamingStrategy( $config->getDbPrefix() ); $dbConfig->setNamingStrategy( $namingStrategy ); - $dbParams = $options['dbOptions']; + $dbParams = $config->getDbConnectionParams(); $this->injector->register( EntityManager::class, EntityManager::class ) ->setArguments( array( $dbParams, $dbConfig ) ) ->setFactory( array( EntityManager::class, 'create' ) ); @@ -85,12 +72,12 @@ class ActivityPubModule ->addArgument( new Reference( ObjectsService::class ) ); $this->injector->register( AuthListener::class, AuthListener::class ) - ->addArgument( $options['authFunction'] ); + ->addArgument( $config->getAuthFunction() ); $this->injector->register( AuthService::class, AuthService::class ); $this->injector->register( ContextProvider::class, ContextProvider::class ) - ->addArgument( $options['context'] ); + ->addArgument( $config->getJsonLdContext() ); $this->injector->register( CollectionsService::class, CollectionsService::class ) ->addArgument( self::COLLECTION_PAGE_SIZE ) @@ -127,18 +114,5 @@ class ActivityPubModule { return $this->injector->get( $id ); } - - private function validateOptions( $options ) - { - $required = array( 'dbOptions' ); - $actual = array_keys( $options ); - $missing = array_diff( $required, $actual ); - if ( count( $missing ) > 0 ) { - throw new InvalidArgumentException( - 'Missing required options: ' . print_r( $missing, t ) - ); - } - } - } ?> diff --git a/test/ActivityPubTest.php b/test/ActivityPubTest.php index 4f4503c..001fb15 100644 --- a/test/ActivityPubTest.php +++ b/test/ActivityPubTest.php @@ -2,6 +2,7 @@ namespace ActivityPub\Test; use ActivityPub\ActivityPub; +use ActivityPub\Config\ActivityPubConfig; use ActivityPub\Test\TestConfig\SQLiteTestCase; use ActivityPub\Test\TestConfig\ArrayDataSet; @@ -19,12 +20,13 @@ class ActivityPubTest extends SQLiteTestCase * @depends testItCreatesSchema */ public function testItUpdatesSchema() { - $activityPub = new ActivityPub(array( - 'dbOptions' => array( - 'driver' => 'pdo_sqlite', - 'path' => $this->getDbPath(), - ), - ) ); + $config = ActivityPubConfig::createBuilder() + ->setDbConnectionParams( array( + 'driver' => 'pdo_sqlite', + 'path' => $this->getDbPath(), + ) ) + ->build(); + $activityPub = new ActivityPub( $config ); $activityPub->updateSchema(); $this->assertTrue( file_exists( $this->getDbPath() ) ); } diff --git a/test/Config/ActivityPubModuleTest.php b/test/Config/ActivityPubModuleTest.php index 1f27c77..b5a10e0 100644 --- a/test/Config/ActivityPubModuleTest.php +++ b/test/Config/ActivityPubModuleTest.php @@ -1,6 +1,7 @@ array( - 'driver' => 'pdo_sqlite', - 'path' => ':memory:', - ), - ); - $this->module = new ActivityPubModule( $opts ); + $config = ActivityPubConfig::createBuilder() + ->setDbConnectionParams( array( + 'driver' => 'pdo_sqlite', + 'path' => ':memory:', + ) ) + ->build(); + $this->module = new ActivityPubModule( $config ); } public function testItInjects() diff --git a/test/TestConfig/SQLiteTestCase.php b/test/TestConfig/SQLiteTestCase.php index a786f80..3859548 100644 --- a/test/TestConfig/SQLiteTestCase.php +++ b/test/TestConfig/SQLiteTestCase.php @@ -2,6 +2,7 @@ namespace ActivityPub\Test\TestConfig; use ActivityPub\ActivityPub; +use ActivityPub\Config\ActivityPubConfig; use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; use PHPUnit\DbUnit\Operation\Composite; @@ -22,12 +23,13 @@ abstract class SQLiteTestCase extends TestCase if ( file_exists( $dbPath ) ) { unlink( $dbPath ); } - $activityPub = new ActivityPub( array( - 'dbOptions' => array( - 'driver' => 'pdo_sqlite', - 'path' => $dbPath, - ), - ) ); + $config = ActivityPubConfig::createBuilder() + ->setDbConnectionParams( array( + 'driver' => 'pdo_sqlite', + 'path' => $dbPath, + ) ) + ->build(); + $activityPub = new ActivityPub( $config ); $activityPub->updateSchema(); } diff --git a/test/bootstrap.php b/test/bootstrap.php index 379e1fb..380772c 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -2,16 +2,18 @@ namespace ActivityPub\Test; use ActivityPub\ActivityPub; +use ActivityPub\Config\ActivityPubConfig; $dbPath = dirname( __FILE__ ) . '/../db.sqlite'; if ( file_exists( $dbPath ) ) { unlink( $dbPath ); } -$activityPub = new ActivityPub( array( - 'dbOptions' => array( +$config = ActivityPubConfig::createBuilder() + ->setDbConnectionParams( array( 'driver' => 'pdo_sqlite', 'path' => $dbPath, - ), -) ); + ) ) + ->build(); +$activityPub = new ActivityPub( $config ); $activityPub->updateSchema(); ?>