From 018275919ce1862e08c9aaf7f8098c610fe71916 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 14 Nov 2021 20:28:36 +0100 Subject: [PATCH] Create interface for static Module calls --- index.php | 1 + src/App.php | 7 ++- src/App/Module.php | 57 ++++++++---------- src/App/Page.php | 8 +-- src/BaseModule.php | 71 +++++++++++----------- src/Capabilities/ICanHandleRequests.php | 79 +++++++++++++++++++++++++ tests/src/App/ModeTest.php | 12 ++-- tests/src/App/ModuleTest.php | 28 +++++---- 8 files changed, 170 insertions(+), 93 deletions(-) create mode 100644 src/Capabilities/ICanHandleRequests.php diff --git a/index.php b/index.php index 4f690ed518..cfb5937368 100644 --- a/index.php +++ b/index.php @@ -46,5 +46,6 @@ $a->runFrontend( $dice->create(\Friendica\Core\PConfig\Capability\IManagePersonalConfigValues::class), $dice->create(\Friendica\Security\Authentication::class), $dice->create(\Friendica\App\Page::class), + $dice, $start_time ); diff --git a/src/App.php b/src/App.php index 75f5314af5..63d28fc80c 100644 --- a/src/App.php +++ b/src/App.php @@ -21,6 +21,7 @@ namespace Friendica; +use Dice\Dice; use Exception; use Friendica\App\Arguments; use Friendica\App\BaseURL; @@ -575,7 +576,7 @@ class App * @throws HTTPException\InternalServerErrorException * @throws \ImagickException */ - public function runFrontend(App\Module $module, App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, float $start_time) + public function runFrontend(App\Module $module, App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, Dice $dice, float $start_time) { $this->profiler->set($start_time, 'start'); $this->profiler->set(microtime(true), 'classinit'); @@ -702,11 +703,11 @@ class App $page['page_title'] = $moduleName; if (!$this->mode->isInstall() && !$this->mode->has(App\Mode::MAINTENANCEDISABLED)) { - $module = new Module('maintenance', Maintenance::class); + $module = new Module('maintenance', new Maintenance()); } else { // determine the module class and save it to the module instance // @todo there's an implicit dependency due SESSION::start(), so it has to be called here (yet) - $module = $module->determineClass($this->args, $router, $this->config); + $module = $module->determineClass($this->args, $router, $this->config, $dice); } // Let the module run it's internal process (init, get, post, ...) diff --git a/src/App/Module.php b/src/App/Module.php index 5b7c3d1500..98d2d620d2 100644 --- a/src/App/Module.php +++ b/src/App/Module.php @@ -21,8 +21,9 @@ namespace Friendica\App; +use Dice\Dice; use Friendica\App; -use Friendica\BaseModule; +use Friendica\Capabilities\ICanHandleRequests; use Friendica\Core; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\LegacyModule; @@ -80,15 +81,10 @@ class Module private $module; /** - * @var BaseModule The module class + * @var ICanHandleRequests The module class */ private $module_class; - /** - * @var array The module parameters - */ - private $module_parameters; - /** * @var bool true, if the module is a backend module */ @@ -108,21 +104,13 @@ class Module } /** - * @return string The base class name + * @return ICanHandleRequests The base class name */ - public function getClassName() + public function getClass(): ICanHandleRequests { return $this->module_class; } - /** - * @return array The module parameters extracted from the route - */ - public function getParameters() - { - return $this->module_parameters; - } - /** * @return bool True, if the current module is a backend module * @see Module::BACKEND_MODULES for a list @@ -132,11 +120,12 @@ class Module return $this->isBackend; } - public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, array $moduleParameters = [], bool $isBackend = false, bool $printNotAllowedAddon = false) + public function __construct(string $module = self::DEFAULT, ICanHandleRequests $module_class = null, bool $isBackend = false, bool $printNotAllowedAddon = false) { + $defaultClass = static::DEFAULT_CLASS; + $this->module = $module; - $this->module_class = $moduleClass; - $this->module_parameters = $moduleParameters; + $this->module_class = $module_class ?? new $defaultClass(); $this->isBackend = $isBackend; $this->printNotAllowedAddon = $printNotAllowedAddon; } @@ -164,21 +153,22 @@ class Module $isBackend = in_array($module, Module::BACKEND_MODULES);; - return new Module($module, $this->module_class, [], $isBackend, $this->printNotAllowedAddon); + return new Module($module,null, $isBackend, $this->printNotAllowedAddon); } /** * Determine the class of the current module * - * @param Arguments $args The Friendica execution arguments - * @param Router $router The Friendica routing instance + * @param Arguments $args The Friendica execution arguments + * @param Router $router The Friendica routing instance * @param IManageConfigValues $config The Friendica Configuration + * @param Dice $dice The Dependency Injection container * * @return Module The determined module of this call * * @throws \Exception */ - public function determineClass(Arguments $args, Router $router, IManageConfigValues $config) + public function determineClass(Arguments $args, Router $router, IManageConfigValues $config, Dice $dice) { $printNotAllowedAddon = false; @@ -222,7 +212,10 @@ class Module $module_class = $module_class ?: PageNotFound::class; } - return new Module($this->module, $module_class, $module_parameters, $this->isBackend, $printNotAllowedAddon); + /** @var ICanHandleRequests $module */ + $module = $dice->create($module_class, [$module_parameters]); + + return new Module($this->module, $module, $this->isBackend, $printNotAllowedAddon); } /** @@ -304,32 +297,32 @@ class Module Core\Hook::callAll($this->module . '_mod_init', $placeholder); - call_user_func([$this->module_class, 'init'], $this->module_parameters); + $this->module_class::init($this->module_class::getParameters()); $profiler->set(microtime(true) - $timestamp, 'init'); if ($server['REQUEST_METHOD'] === Router::DELETE) { - call_user_func([$this->module_class, 'delete'], $this->module_parameters); + $this->module_class::delete($this->module_class::getParameters()); } if ($server['REQUEST_METHOD'] === Router::PATCH) { - call_user_func([$this->module_class, 'patch'], $this->module_parameters); + $this->module_class::patch($this->module_class::getParameters()); } if ($server['REQUEST_METHOD'] === Router::POST) { Core\Hook::callAll($this->module . '_mod_post', $post); - call_user_func([$this->module_class, 'post'], $this->module_parameters); + $this->module_class::post($this->module_class::getParameters()); } if ($server['REQUEST_METHOD'] === Router::PUT) { - call_user_func([$this->module_class, 'put'], $this->module_parameters); + $this->module_class::put($this->module_class::getParameters()); } Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); - call_user_func([$this->module_class, 'afterpost'], $this->module_parameters); + $this->module_class::afterpost($this->module_class::getParameters()); // "rawContent" is especially meant for technical endpoints. // This endpoint doesn't need any theme initialization or other comparable stuff. - call_user_func([$this->module_class, 'rawContent'], $this->module_parameters); + $this->module_class::rawContent($this->module_class::getParameters()); } } diff --git a/src/App/Page.php b/src/App/Page.php index fcb2d18533..c20f26cefb 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -347,13 +347,13 @@ class Page implements ArrayAccess $content = ''; try { - $moduleClass = $module->getClassName(); + $moduleClass = $module->getClass(); $arr = ['content' => $content]; - Hook::callAll($moduleClass . '_mod_content', $arr); + Hook::callAll( $moduleClass::getClassName() . '_mod_content', $arr); $content = $arr['content']; - $arr = ['content' => call_user_func([$moduleClass, 'content'], $module->getParameters())]; - Hook::callAll($moduleClass . '_mod_aftercontent', $arr); + $arr = ['content' => $moduleClass::content($moduleClass::getParameters())]; + Hook::callAll($moduleClass::getClassName() . '_mod_aftercontent', $arr); $content .= $arr['content']; } catch (HTTPException $e) { $content = ModuleHTTPException::content($e); diff --git a/src/BaseModule.php b/src/BaseModule.php index ced1e55c39..90ba9e1329 100644 --- a/src/BaseModule.php +++ b/src/BaseModule.php @@ -21,6 +21,7 @@ namespace Friendica; +use Friendica\Capabilities\ICanHandleRequests; use Friendica\Core\Logger; use Friendica\Model\User; @@ -33,23 +34,33 @@ use Friendica\Model\User; * * @author Hypolite Petovan */ -abstract class BaseModule +abstract class BaseModule implements ICanHandleRequests { + /** @var array */ + protected static $parameters = []; + + public function __construct(array $parameters = []) + { + static::$parameters = $parameters; + } + /** - * Initialization method common to both content() and post() - * - * Extend this method if you need to do any shared processing before both - * content() or post() + * @return array + */ + public static function getParameters(): array + { + return self::$parameters; + } + + /** + * {@inheritDoc} */ public static function init(array $parameters = []) { } /** - * Module GET method to display raw content from technical endpoints - * - * Extend this method if the module is supposed to return communication data, - * e.g. from protocol implementations. + * {@inheritDoc} */ public static function rawContent(array $parameters = []) { @@ -58,46 +69,29 @@ abstract class BaseModule } /** - * Module GET method to display any content - * - * Extend this method if the module is supposed to return any display - * through a GET request. It can be an HTML page through templating or a - * XML feed or a JSON output. - * - * @return string + * {@inheritDoc} */ public static function content(array $parameters = []) { - $o = ''; - - return $o; + return ''; } /** - * Module DELETE method to process submitted data - * - * Extend this method if the module is supposed to process DELETE requests. - * Doesn't display any content + * {@inheritDoc} */ public static function delete(array $parameters = []) { } /** - * Module PATCH method to process submitted data - * - * Extend this method if the module is supposed to process PATCH requests. - * Doesn't display any content + * {@inheritDoc} */ public static function patch(array $parameters = []) { } /** - * Module POST method to process submitted data - * - * Extend this method if the module is supposed to process POST requests. - * Doesn't display any content + * {@inheritDoc} */ public static function post(array $parameters = []) { @@ -105,24 +99,25 @@ abstract class BaseModule } /** - * Called after post() - * - * Unknown purpose + * {@inheritDoc} */ public static function afterpost(array $parameters = []) { } /** - * Module PUT method to process submitted data - * - * Extend this method if the module is supposed to process PUT requests. - * Doesn't display any content + * {@inheritDoc} */ public static function put(array $parameters = []) { } + /** Gets the name of the current class */ + public static function getClassName(): string + { + return static::class; + } + /* * Functions used to protect against Cross-Site Request Forgery * The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key. diff --git a/src/Capabilities/ICanHandleRequests.php b/src/Capabilities/ICanHandleRequests.php new file mode 100644 index 0000000000..0e1582bf64 --- /dev/null +++ b/src/Capabilities/ICanHandleRequests.php @@ -0,0 +1,79 @@ +determineRunMode(false, $module, $server, $mobileDetect); @@ -232,7 +232,7 @@ class ModeTest extends MockedTest public function testIsNotBackend() { $server = []; - $module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); + $module = new Module(Module::DEFAULT, null, false); $mobileDetect = new MobileDetect(); $mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect); @@ -250,7 +250,7 @@ class ModeTest extends MockedTest 'HTTP_X_REQUESTED_WITH' => 'xmlhttprequest', ]; - $module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); + $module = new Module(Module::DEFAULT, null, false); $mobileDetect = new MobileDetect(); $mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect); @@ -264,7 +264,7 @@ class ModeTest extends MockedTest public function testIsNotAjax() { $server = []; - $module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); + $module = new Module(Module::DEFAULT, null, false); $mobileDetect = new MobileDetect(); $mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect); @@ -278,7 +278,7 @@ class ModeTest extends MockedTest public function testIsMobileIsTablet() { $server = []; - $module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); + $module = new Module(Module::DEFAULT, null, false); $mobileDetect = Mockery::mock(MobileDetect::class); $mobileDetect->shouldReceive('isMobile')->andReturn(true); $mobileDetect->shouldReceive('isTablet')->andReturn(true); @@ -296,7 +296,7 @@ class ModeTest extends MockedTest public function testIsNotMobileIsNotTablet() { $server = []; - $module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); + $module = new Module(Module::DEFAULT, null, false); $mobileDetect = Mockery::mock(MobileDetect::class); $mobileDetect->shouldReceive('isMobile')->andReturn(false); $mobileDetect->shouldReceive('isTablet')->andReturn(false); diff --git a/tests/src/App/ModuleTest.php b/tests/src/App/ModuleTest.php index c5d4e11faa..7f5d65fcf1 100644 --- a/tests/src/App/ModuleTest.php +++ b/tests/src/App/ModuleTest.php @@ -21,6 +21,7 @@ namespace Friendica\Test\src\App; +use Dice\Dice; use Friendica\App; use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; @@ -38,7 +39,7 @@ class ModuleTest extends DatabaseTest { self::assertEquals($assert['isBackend'], $module->isBackend()); self::assertEquals($assert['name'], $module->getName()); - self::assertEquals($assert['class'], $module->getClassName()); + self::assertEquals($assert['class'], $module->getClass()); } /** @@ -48,21 +49,25 @@ class ModuleTest extends DatabaseTest { $module = new App\Module(); + $defaultClass = App\Module::DEFAULT_CLASS; + self::assertModule([ 'isBackend' => false, 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], $module); } public function dataModuleName() { + $defaultClass = App\Module::DEFAULT_CLASS; + return [ 'default' => [ 'assert' => [ 'isBackend' => false, 'name' => 'network', - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments('network/data/in', 'network/data/in', @@ -73,7 +78,7 @@ class ModuleTest extends DatabaseTest 'assert' => [ 'isBackend' => false, 'name' => 'with_strike_and_point', - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments('with-strike.and-point/data/in', 'with-strike.and-point/data/in', @@ -84,7 +89,7 @@ class ModuleTest extends DatabaseTest 'assert' => [ 'isBackend' => false, 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments(), ], @@ -92,7 +97,7 @@ class ModuleTest extends DatabaseTest 'assert' => [ 'isBackend' => false, 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments(), ], @@ -100,7 +105,7 @@ class ModuleTest extends DatabaseTest 'assert' => [ 'isBackend' => true, 'name' => App\Module::BACKEND_MODULES[0], - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in', App\Module::BACKEND_MODULES[0] . '/data/in', @@ -111,7 +116,7 @@ class ModuleTest extends DatabaseTest 'assert' => [ 'isBackend' => false, 'name' => 'login', - 'class' => App\Module::DEFAULT_CLASS, + 'class' => new $defaultClass(), ], 'args' => new App\Arguments('users/sign_in', 'users/sign_in', @@ -187,9 +192,12 @@ class ModuleTest extends DatabaseTest $router = (new App\Router([], __DIR__ . '/../../../static/routes.config.php', $l10n, $cache, $lock)); - $module = (new App\Module($name))->determineClass(new App\Arguments('', $command), $router, $config); + $dice = Mockery::mock(Dice::class); + $dice->shouldReceive('create')->andReturn(new $assert()); - self::assertEquals($assert, $module->getClassName()); + $module = (new App\Module($name))->determineClass(new App\Arguments('', $command), $router, $config, $dice); + + self::assertEquals($assert, $module->getClass()::getClassName()); } /**