jenkins-bot has submitted this change and it was merged. Change subject: \SMW\ContextAware + SMW\BaseContext ......................................................................
\SMW\ContextAware + SMW\BaseContext Making an object ContextAware allows to inject objects that depend a context rather than directly linked to the DIC and it also enables support objects to be accessed on requests rather than during instantiation. Depending on a context (base, mock etc.) will give full control over the object life-cycle as the context internally represents the current invoked DIC and with it its available object definitions while when changing to an object definition is necessary it is done in only one place, the DependencyContainer. Instead of injection a builder directly through a constructor, we will use the context as a single representative to carry out the responsibility to enable access to a builder and its definitions. ## Notes http://accu.org/index.php/journals/246 Change-Id: Ib6fce6d34f0b9101d4781cde0dd5fe268d30605c --- M SemanticMediaWiki.classes.php M includes/Setup.php A includes/context/BaseContext.php A includes/context/ContextAware.php A includes/context/ContextInjector.php A includes/context/ContextResource.php A includes/context/EmptyContext.php A includes/context/README.md M includes/dic/SharedDependencyContainer.php M includes/hooks/FunctionHook.php M includes/hooks/FunctionHookRegistry.php M includes/hooks/README.md A tests/phpunit/includes/context/BaseContextTest.php A tests/phpunit/includes/context/EmptyContextTest.php M tests/phpunit/includes/dic/SharedDependencyContainerTest.php M tests/phpunit/includes/hooks/FunctionHookRegistryTest.php 16 files changed, 648 insertions(+), 39 deletions(-) Approvals: Mwjames: Looks good to me, approved jenkins-bot: Verified diff --git a/SemanticMediaWiki.classes.php b/SemanticMediaWiki.classes.php index db6248f..fb2a533 100644 --- a/SemanticMediaWiki.classes.php +++ b/SemanticMediaWiki.classes.php @@ -104,6 +104,13 @@ 'SMW\Deserializers\SemanticDataDeserializer' => 'includes/serializer/Deserializers/SemanticDataDeserializer.php', 'SMW\Serializers\QueryResultSerializer' => 'includes/serializer/Serializers/QueryResultSerializer.php', + // Context + 'SMW\EmptyContext' => 'includes/context/EmptyContext.php', + 'SMW\BaseContext' => 'includes/context/BaseContext.php', + 'SMW\ContextResource' => 'includes/context/ContextResource.php', + 'SMW\ContextAware' => 'includes/context/ContextAware.php', + 'SMW\ContextInjector' => 'includes/context/ContextInjector.php', + // Cache 'SMW\CacheHandler' => 'includes/cache/CacheHandler.php', 'SMW\CacheableResultMapper' => 'includes/cache/CacheableResultMapper.php', diff --git a/includes/Setup.php b/includes/Setup.php index fed4ac9..288feaa 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -21,7 +21,7 @@ * * @ingroup SMW */ -final class Setup { +final class Setup implements ContextAware { /** @var array */ protected $globals; @@ -29,16 +29,18 @@ /** @var Settings */ protected $settings; - /** @var DependencyBuilder */ - protected $dependencyBuilder = null; + /** @var ContextResource */ + protected $context = null; /** * @since 1.9 * * @param array &$globals + * @param ContextResource|null $context */ - public function __construct( &$globals ) { + public function __construct( &$globals, ContextResource $context = null ) { $this->globals =& $globals; + $this->context = $context; } /** @@ -96,30 +98,38 @@ } /** + * @see ContextAware::withContext + * + * @since 1.9 + * + * @return ContextResource + */ + public function withContext() { + + if ( $this->context === null ) { + $this->context = new BaseContext(); + } + + return $this->context; + } + + /** * Load Semantic MediaWiki specific settings * * @since 1.9 */ protected function loadSettings() { - - $this->settings = Settings::newFromGlobals( $this->globals ); - + $this->settings = $this->registerSettings( Settings::newFromGlobals( $this->globals ) ); } /** - * Returns a DependencyBuilder + * Register settings * * @since 1.9 */ - protected function getDependencyBuilder() { - - // Use the DefaultContext to determine the builder - if ( $this->dependencyBuilder === null ) { - $this->dependencyBuilder = new SimpleDependencyBuilder( new SharedDependencyContainer() ); - $this->dependencyBuilder->getContainer()->registerObject( 'Settings', $this->settings ); - } - - return $this->dependencyBuilder; + protected function registerSettings( Settings $settings ) { + $this->withContext()->getDependencyBuilder()->getContainer()->registerObject( 'Settings', $settings ); + return $settings; } /** @@ -308,7 +318,7 @@ */ protected function registerFunctionHooks() { - $hookRegistry = $this->getDependencyBuilder()->newObject( 'FunctionHookRegistry' ); + $hookRegistry = $this->withContext()->getDependencyBuilder()->newObject( 'FunctionHookRegistry' ); /** * Hook: Called by BaseTemplate when building the toolbox array and @@ -319,7 +329,7 @@ * @since 1.9 */ $this->globals['wgHooks']['BaseTemplateToolbox'][] = function ( $skinTemplate, &$toolbox ) use ( $hookRegistry ) { - return $hookRegistry->register( new BaseTemplateToolbox( $skinTemplate, $toolbox ) )->process(); + return $hookRegistry->load( new BaseTemplateToolbox( $skinTemplate, $toolbox ) )->process(); }; // Old-style registration @@ -359,7 +369,7 @@ protected function registerParserHooks() { $settings = $this->settings; - $objectBuilder = $this->getDependencyBuilder(); + $objectBuilder = $this->withContext()->getDependencyBuilder(); /** * Called when the parser initialises for the first time diff --git a/includes/context/BaseContext.php b/includes/context/BaseContext.php new file mode 100644 index 0000000..fde463c --- /dev/null +++ b/includes/context/BaseContext.php @@ -0,0 +1,79 @@ +<?php + +namespace SMW; + +/** + * Default implementation of the ContextResource interface + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @author mwjames + */ +class BaseContext implements ContextResource { + + /** @var DependencyBuilder */ + private $dependencyBuilder = null; + + /** + * @since 1.9 + * + * @param DependencyBuilder|null $builder + */ + public function __construct( DependencyBuilder $builder = null ) { + $this->dependencyBuilder = $this->register( $builder ); + } + + /** + * Returns a Store object + * + * @since 1.9 + * + * @return Store + */ + public function getStore() { + return $this->getDependencyBuilder()->newObject( 'Store' ); + } + + /** + * Returns a Settings object + * + * @since 1.9 + * + * @return Settings + */ + public function getSettings() { + return $this->getDependencyBuilder()->newObject( 'Settings' ); + } + + /** + * Returns a DependencyBuilder object + * + * @since 1.9 + * + * @return DependencyBuilder + */ + public function getDependencyBuilder() { + return $this->dependencyBuilder; + } + + /** + * Register a builder + * + * @note Always register a builder with a self-reference to the current + * context object to ensure all objects are accessing the context derive + * from the same "root" + * + * @since 1.9 + */ + protected function register( DependencyBuilder $builder = null ) { + + if ( $builder === null ) { + $builder = new SimpleDependencyBuilder( new SharedDependencyContainer() ); + } + + $builder->getContainer()->registerObject( 'BaseContext', $this ); + return $builder; + } + +} diff --git a/includes/context/ContextAware.php b/includes/context/ContextAware.php new file mode 100644 index 0000000..c11fe37 --- /dev/null +++ b/includes/context/ContextAware.php @@ -0,0 +1,27 @@ +<?php + +namespace SMW; + +/** + * Interface that describes access to a context object + * + * @note It is expected that a context object is either injected using a constructor + * or the implements a Contextinjector interface + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @author mwjames + */ +interface ContextAware { + + /** + * Returns a ContextResource object + * + * @since 1.9 + * + * @return ContextResource + */ + public function withContext(); + +} diff --git a/includes/context/ContextInjector.php b/includes/context/ContextInjector.php new file mode 100644 index 0000000..b2e7210 --- /dev/null +++ b/includes/context/ContextInjector.php @@ -0,0 +1,22 @@ +<?php + +namespace SMW; + +/** + * Interface that describes a method to inject a context object + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @author mwjames + */ +interface ContextInjector { + + /** + * Invokes a ContextResource object + * + * @since 1.9 + */ + public function invokeContext( ContextResource $context ); + +} diff --git a/includes/context/ContextResource.php b/includes/context/ContextResource.php new file mode 100644 index 0000000..69c71ba --- /dev/null +++ b/includes/context/ContextResource.php @@ -0,0 +1,42 @@ +<?php + +namespace SMW; + +/** + * Interface that describes a ContextResource object + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @author mwjames + */ +interface ContextResource { + + /** + * Returns a Store object + * + * @since 1.9 + * + * @return Store + */ + public function getStore(); + + /** + * Returns a Settings object + * + * @since 1.9 + * + * @return Settings + */ + public function getSettings(); + + /** + * Returns a DependencyBuilder object + * + * @since 1.9 + * + * @return DependencyBuilder + */ + public function getDependencyBuilder(); + +} diff --git a/includes/context/EmptyContext.php b/includes/context/EmptyContext.php new file mode 100644 index 0000000..a4fbe8a --- /dev/null +++ b/includes/context/EmptyContext.php @@ -0,0 +1,76 @@ +<?php + +namespace SMW; + +/** + * Implementing a ContextResource interface and returning null + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @author mwjames + */ +class EmptyContext implements ContextResource { + + /** @var DependencyBuilder */ + private $dependencyBuilder = null; + + /** + * @since 1.9 + */ + public function __construct() { + $this->dependencyBuilder = $this->register( null ); + } + + /** + * Returns a Store object + * + * @since 1.9 + * + * @return Store + */ + public function getStore() { + return $this->getDependencyBuilder()->newObject( 'Store' ); + } + + /** + * Returns a Settings object + * + * @since 1.9 + * + * @return Settings + */ + public function getSettings() { + return $this->getDependencyBuilder()->newObject( 'Settings' ); + } + + /** + * Returns a DependencyBuilder object + * + * @since 1.9 + * + * @return DependencyBuilder + */ + public function getDependencyBuilder() { + return $this->dependencyBuilder; + } + + /** + * Register a builder + * + * @since 1.9 + */ + protected function register( DependencyBuilder $builder = null ) { + + if ( $builder === null ) { + $builder = new SimpleDependencyBuilder( new EmptyDependencyContainer() ); + } + + $builder->getContainer()->registerObject( 'Settings', null ); + $builder->getContainer()->registerObject( 'Store', null ); + $builder->getContainer()->registerObject( 'BaseContext', $this ); + + return $builder; + } + +} diff --git a/includes/context/README.md b/includes/context/README.md new file mode 100644 index 0000000..1fa7a2c --- /dev/null +++ b/includes/context/README.md @@ -0,0 +1,72 @@ +A context object (see [Encapsulate Context Pattern][ak]) collects commonly used service objects and encapsulate those objects within a single available instance. + +## Components +* ContextResource describes an interface to access Store, Settings, and a DependencyBuilder context +* ContextAware describes an interface to access a context object +* ContextInjector describes an interface to inject an context object +* BaseContext implements the ContextResource interface +* EmptyContext implements the ContextResource interface, returning null objects + +#### Example +```php +class Foo implements ContextAware { + + /** @var ContextResource */ + protected $context = null; + + /** + * @since 1.9 + * + * @param ContextResource $context + */ + public function __construct( ContextResource $context = null ) { + $this->context = $context; + } + + public function withContext() { + + if ( $this->context === null ) { + $this->context = new BaseContext(); + } + + return $this->context; + } + + public function getBaz() { + return $this->withContext()->getDependencyBuilder()->newObject( 'Baz' ); + } + +} +``` +```php +class Bar implements ContextAware, ContextInjector { + + /** @var ContextResource */ + protected $context = null; + + public function invokeContext( ContextResource $context ) { + $this->context = $context; + } + + public function withContext() { + return $this->context; + } + + public function getBaz() { + return $this->withContext()->getDependencyBuilder()->newObject( 'Baz' ); + } + +} +``` +```php +$foo = new Foo( new BaseContext() ); +$baz = $foo->getBaz(); + +$bar = new Bar(); +$bar->invokeContext( new BaseContext() ); +$baz = $bar->getBaz(); +``` + +For information about "how to use" the DependencyBuilder, please see /dic/README.md. + +[ak]: http://accu.org/index.php/journals/246 "The Encapsulate Context Pattern" \ No newline at end of file diff --git a/includes/dic/SharedDependencyContainer.php b/includes/dic/SharedDependencyContainer.php index 32c47fc..b0b1527 100644 --- a/includes/dic/SharedDependencyContainer.php +++ b/includes/dic/SharedDependencyContainer.php @@ -219,7 +219,18 @@ * @return FunctionHookRegistry */ 'FunctionHookRegistry' => function ( DependencyBuilder $builder ) { - return new FunctionHookRegistry(); + return new FunctionHookRegistry( $builder->newObject( 'BaseContext' ) ); + }, + + /** + * BaseContext object definition + * + * @since 1.9 + * + * @return BaseContext + */ + 'BaseContext' => function ( DependencyBuilder $builder ) { + return new BaseContext( $builder ); } ); diff --git a/includes/hooks/FunctionHook.php b/includes/hooks/FunctionHook.php index 26deb9b..905380e 100644 --- a/includes/hooks/FunctionHook.php +++ b/includes/hooks/FunctionHook.php @@ -18,7 +18,10 @@ * * @ingroup Hook */ -abstract class FunctionHook extends DependencyInjector { +abstract class FunctionHook extends DependencyInjector implements ContextInjector { + + /** @var ContextResource */ + protected $context; /** * Main method that initiates the processing of the registered @@ -30,4 +33,13 @@ */ public abstract function process(); + /** + * @see ContextInjector::invokeContext + * + * @since 1.9 + */ + public function invokeContext( ContextResource $context ) { + $this->context = $context; + } + } diff --git a/includes/hooks/FunctionHookRegistry.php b/includes/hooks/FunctionHookRegistry.php index 891e090..c1aa356 100644 --- a/includes/hooks/FunctionHookRegistry.php +++ b/includes/hooks/FunctionHookRegistry.php @@ -18,10 +18,56 @@ * * @ingroup Hook */ -class FunctionHookRegistry { +class FunctionHookRegistry implements ContextAware { + + /** @var ContextResource */ + protected $context; /** - * Method to register a hook and its DependencyBuilder + * @since 1.9 + * + * @param ContextResource $contextObject + */ + public function __construct( ContextResource $context = null ) { + $this->context = $context; + } + + /** + * @see ContextAware::withContext + * + * @since 1.9 + * + * @return ContextResource + */ + public function withContext() { + + if ( $this->context === null ) { + $this->context = new BaseContext(); + } + + return $this->context; + } + + /** + * Load the hook and inject it with an appropriate context + * + * @since 1.9 + * + * @param FunctionHook $hook + * + * @return FunctionHook + */ + public function load( FunctionHook $hook ) { + + // FIXME legacy use the context instead + $hook->setDependencyBuilder( $this->withContext()->getDependencyBuilder() ); + $hook->invokeContext( $this->withContext() ); + + return $hook; + } + + /** + * Method to register a hook * * @since 1.9 * @@ -30,11 +76,8 @@ * @return FunctionHook */ public static function register( FunctionHook $hook ) { - - // FIXME Expecting a context object to derive a builder - - $hook->setDependencyBuilder( new SimpleDependencyBuilder( new SharedDependencyContainer() ) ); - return $hook; + $instance = new self(); + return $instance->load( $hook ); } } diff --git a/includes/hooks/README.md b/includes/hooks/README.md index 3a58897..90d5330 100644 --- a/includes/hooks/README.md +++ b/includes/hooks/README.md @@ -18,7 +18,16 @@ #### ParserAfterTidy ParserAfterTidy is used to re-introduce content, update base annotations (e.g. special properties, categories etc.) and in case of a manual article purge initiates a store update (LinksUpdateConstructed wouldn't work because it acts only on link changes and therefore would not trigger a LinksUpdateConstructed event). -### SpecialStatsAddExtra +#### SpecialStatsAddExtra SpecialStatsAddExtra is used to add additional statistic being shown at Special:Statistics. +#### SkinAfterContent +Extend the display with content from the Factbox. + +#### OutputPageParserOutput +Rendering the Factbox and updating the FactboxCache. + +#### TitleMoveComplete +Update the Store after an article has been deleted. + [hooks]: https://www.mediawiki.org/wiki/Hooks "Manual:Hooks" \ No newline at end of file diff --git a/tests/phpunit/includes/context/BaseContextTest.php b/tests/phpunit/includes/context/BaseContextTest.php new file mode 100644 index 0000000..9f1e19e --- /dev/null +++ b/tests/phpunit/includes/context/BaseContextTest.php @@ -0,0 +1,124 @@ +<?php + +namespace SMW\Test; + +use SMW\BaseContext; + +/** + * @covers \SMW\BaseContext + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @group SMW + * @group SMWExtension + * + * @author mwjames + */ +class BaseContextTest extends SemanticMediaWikiTestCase { + + /** + * Returns the name of the class to be tested + * + * @return string|false + */ + public function getClass() { + return '\SMW\BaseContext'; + } + + /** + * Helper method that returns a BaseContext object + * + * @since 1.9 + */ + private function newInstance( $builder = null ) { + return new BaseContext( $builder ); + } + + /** + * @since 1.9 + */ + public function testConstructor() { + $this->assertInstanceOf( $this->getClass(), $this->newInstance() ); + } + + /** + * @since 1.9 + */ + public function testGetSettings() { + + $settings = $this->newSettings( array( 'Foo' => 'Bar' ) ); + $instance = $this->newInstance(); + $instance->getDependencyBuilder()->getContainer()->registerObject( 'Settings', $settings ); + + $this->assertInstanceOf( + '\SMW\Settings', + $instance->getSettings(), + 'Asserts that getSettings() yields a Settings object' + ); + + $this->assertEquals( + $settings, + $instance->getSettings(), + 'Asserts that getSettings() yields an expected result' + ); + + $this->assertTrue( + $instance->getSettings() === $instance->getDependencyBuilder()->newObject( 'Settings' ), + "Asserts that getSettings() returns the same instance (syncronized object instance)" + ); + + } + + /** + * @since 1.9 + */ + public function testGetStore() { + + $store = $this->newMockBuilder()->newObject( 'Store' ); + $instance = $this->newInstance(); + $instance->getDependencyBuilder()->getContainer()->registerObject( 'Store', $store ); + + $this->assertInstanceOf( + '\SMW\Store', + $instance->getStore(), + 'Asserts that getStore() yields a Store object' + ); + + $this->assertEquals( + $store, + $instance->getStore(), + 'Asserts that getSettings() yields an expected result' + ); + + $this->assertTrue( + $instance->getStore() === $instance->getDependencyBuilder()->newObject( 'Store' ), + "Asserts that getStore() returns the same instance (syncronized object instance)" + ); + + } + + /** + * @since 1.9 + */ + public function testSetGetDependencyBuilder() { + + $builder = $this->newDependencyBuilder(); + $instance = $this->newInstance(); + + $this->assertInstanceOf( + '\SMW\DependencyBuilder', + $instance->getDependencyBuilder(), + 'Asserts that getDependencyBuilder() yields a default DependencyBuilder object' + ); + + $instance = $this->newInstance( $builder ); + + $this->assertTrue( + $builder === $instance->getDependencyBuilder(), + 'Asserts that getDependencyBuilder() yields the same instance used for constructor injection' + ); + + } + +} diff --git a/tests/phpunit/includes/context/EmptyContextTest.php b/tests/phpunit/includes/context/EmptyContextTest.php new file mode 100644 index 0000000..deccc98 --- /dev/null +++ b/tests/phpunit/includes/context/EmptyContextTest.php @@ -0,0 +1,59 @@ +<?php + +namespace SMW\Test; + +use SMW\EmptyContext; + +/** + * @covers \SMW\EmptyContext + * + * @licence GNU GPL v2+ + * @since 1.9 + * + * @group SMW + * @group SMWExtension + * + * @author mwjames + */ +class EmptyContextTest extends SemanticMediaWikiTestCase { + + /** + * Returns the name of the class to be tested + * + * @return string|false + */ + public function getClass() { + return '\SMW\EmptyContext'; + } + + /** + * Helper method that returns a EmptyContext object + * + * @since 1.9 + */ + private function newInstance() { + return new EmptyContext(); + } + + /** + * @since 1.9 + */ + public function testConstructor() { + $this->assertInstanceOf( $this->getClass(), $this->newInstance() ); + } + + /** + * @since 1.9 + */ + public function testGetStore() { + $this->assertNull( $this->newInstance()->getStore() ); + } + + /** + * @since 1.9 + */ + public function testGetSettings() { + $this->assertNull( $this->newInstance()->getSettings() ); + } + +} diff --git a/tests/phpunit/includes/dic/SharedDependencyContainerTest.php b/tests/phpunit/includes/dic/SharedDependencyContainerTest.php index cbdcf35..8c1d445 100644 --- a/tests/phpunit/includes/dic/SharedDependencyContainerTest.php +++ b/tests/phpunit/includes/dic/SharedDependencyContainerTest.php @@ -123,6 +123,7 @@ $provider[] = array( 'NamespaceExaminer', array( '\SMW\NamespaceExaminer' => array() ) ); $provider[] = array( 'UpdateObserver', array( '\SMW\UpdateObserver' => array() ) ); $provider[] = array( 'ObservableUpdateDispatcher', array( '\SMW\ObservableSubjectDispatcher' => array() ) ); + $provider[] = array( 'BaseContext', array( '\SMW\BaseContext' => array() ) ); $provider[] = array( 'RequestContext', array( '\IContextSource' => array() ) ); diff --git a/tests/phpunit/includes/hooks/FunctionHookRegistryTest.php b/tests/phpunit/includes/hooks/FunctionHookRegistryTest.php index 0f6d533..22deb3c 100644 --- a/tests/phpunit/includes/hooks/FunctionHookRegistryTest.php +++ b/tests/phpunit/includes/hooks/FunctionHookRegistryTest.php @@ -38,8 +38,6 @@ * Helper method that returns a FunctionHook object * * @since 1.9 - * - * @return FunctionHook */ private function newHook() { return $this->getMockForAbstractClass( '\SMW\FunctionHook' ); @@ -49,24 +47,41 @@ * Helper method that returns a FunctionHookRegistry object * * @since 1.9 - * - * @return FunctionHookRegistry */ - private function newInstance() { - return new FunctionHookRegistry(); + private function newInstance( $context = null ) { + return new FunctionHookRegistry( $context ); } /** - * @test FunctionHookRegistry::__construct - * * @since 1.9 */ public function testConstructor() { + $this->assertInstanceOf( '\SMW\FunctionHook', FunctionHookRegistry::register( $this->newHook() ), - 'Failed asserting FunctionHook instance' + 'Asserts that register() returns a FunctionHook instance' ); + + } + + /** + * @since 1.9 + */ + public function testWithContext() { + + $this->assertInstanceOf( + '\SMW\ContextResource', + $this->newInstance()->withContext(), + 'Asserts that getContext() returns a default context' + ); + + $this->assertInstanceOf( + '\SMW\EmptyContext', + $this->newInstance( new \SMW\EmptyContext() )->withContext(), + 'Asserts that getContext() returns a empty context' + ); + } } -- To view, visit https://gerrit.wikimedia.org/r/85428 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ib6fce6d34f0b9101d4781cde0dd5fe268d30605c Gerrit-PatchSet: 7 Gerrit-Project: mediawiki/extensions/SemanticMediaWiki Gerrit-Branch: master Gerrit-Owner: Mwjames <[email protected]> Gerrit-Reviewer: Mwjames <[email protected]> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
