Create interface for static Module calls

This commit is contained in:
Philipp Holzer 2021-11-14 20:28:36 +01:00
parent 96996790f1
commit 018275919c
Signed by: nupplaPhil
GPG key ID: 24A7501396EB5432
8 changed files with 170 additions and 93 deletions

View file

@ -46,5 +46,6 @@ $a->runFrontend(
$dice->create(\Friendica\Core\PConfig\Capability\IManagePersonalConfigValues::class), $dice->create(\Friendica\Core\PConfig\Capability\IManagePersonalConfigValues::class),
$dice->create(\Friendica\Security\Authentication::class), $dice->create(\Friendica\Security\Authentication::class),
$dice->create(\Friendica\App\Page::class), $dice->create(\Friendica\App\Page::class),
$dice,
$start_time $start_time
); );

View file

@ -21,6 +21,7 @@
namespace Friendica; namespace Friendica;
use Dice\Dice;
use Exception; use Exception;
use Friendica\App\Arguments; use Friendica\App\Arguments;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
@ -575,7 +576,7 @@ class App
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException * @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($start_time, 'start');
$this->profiler->set(microtime(true), 'classinit'); $this->profiler->set(microtime(true), 'classinit');
@ -702,11 +703,11 @@ class App
$page['page_title'] = $moduleName; $page['page_title'] = $moduleName;
if (!$this->mode->isInstall() && !$this->mode->has(App\Mode::MAINTENANCEDISABLED)) { if (!$this->mode->isInstall() && !$this->mode->has(App\Mode::MAINTENANCEDISABLED)) {
$module = new Module('maintenance', Maintenance::class); $module = new Module('maintenance', new Maintenance());
} else { } else {
// determine the module class and save it to the module instance // 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) // @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, ...) // Let the module run it's internal process (init, get, post, ...)

View file

@ -21,8 +21,9 @@
namespace Friendica\App; namespace Friendica\App;
use Dice\Dice;
use Friendica\App; use Friendica\App;
use Friendica\BaseModule; use Friendica\Capabilities\ICanHandleRequests;
use Friendica\Core; use Friendica\Core;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\LegacyModule; use Friendica\LegacyModule;
@ -80,15 +81,10 @@ class Module
private $module; private $module;
/** /**
* @var BaseModule The module class * @var ICanHandleRequests The module class
*/ */
private $module_class; private $module_class;
/**
* @var array The module parameters
*/
private $module_parameters;
/** /**
* @var bool true, if the module is a backend module * @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 $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 * @return bool True, if the current module is a backend module
* @see Module::BACKEND_MODULES for a list * @see Module::BACKEND_MODULES for a list
@ -132,11 +120,12 @@ class Module
return $this->isBackend; 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 = $module;
$this->module_class = $moduleClass; $this->module_class = $module_class ?? new $defaultClass();
$this->module_parameters = $moduleParameters;
$this->isBackend = $isBackend; $this->isBackend = $isBackend;
$this->printNotAllowedAddon = $printNotAllowedAddon; $this->printNotAllowedAddon = $printNotAllowedAddon;
} }
@ -164,21 +153,22 @@ class Module
$isBackend = in_array($module, Module::BACKEND_MODULES);; $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 * Determine the class of the current module
* *
* @param Arguments $args The Friendica execution arguments * @param Arguments $args The Friendica execution arguments
* @param Router $router The Friendica routing instance * @param Router $router The Friendica routing instance
* @param IManageConfigValues $config The Friendica Configuration * @param IManageConfigValues $config The Friendica Configuration
* @param Dice $dice The Dependency Injection container
* *
* @return Module The determined module of this call * @return Module The determined module of this call
* *
* @throws \Exception * @throws \Exception
*/ */
public function determineClass(Arguments $args, Router $router, IManageConfigValues $config) public function determineClass(Arguments $args, Router $router, IManageConfigValues $config, Dice $dice)
{ {
$printNotAllowedAddon = false; $printNotAllowedAddon = false;
@ -222,7 +212,10 @@ class Module
$module_class = $module_class ?: PageNotFound::class; $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); 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'); $profiler->set(microtime(true) - $timestamp, 'init');
if ($server['REQUEST_METHOD'] === Router::DELETE) { 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) { 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) { if ($server['REQUEST_METHOD'] === Router::POST) {
Core\Hook::callAll($this->module . '_mod_post', $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) { 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); 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. // "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff. // 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());
} }
} }

View file

@ -347,13 +347,13 @@ class Page implements ArrayAccess
$content = ''; $content = '';
try { try {
$moduleClass = $module->getClassName(); $moduleClass = $module->getClass();
$arr = ['content' => $content]; $arr = ['content' => $content];
Hook::callAll($moduleClass . '_mod_content', $arr); Hook::callAll( $moduleClass::getClassName() . '_mod_content', $arr);
$content = $arr['content']; $content = $arr['content'];
$arr = ['content' => call_user_func([$moduleClass, 'content'], $module->getParameters())]; $arr = ['content' => $moduleClass::content($moduleClass::getParameters())];
Hook::callAll($moduleClass . '_mod_aftercontent', $arr); Hook::callAll($moduleClass::getClassName() . '_mod_aftercontent', $arr);
$content .= $arr['content']; $content .= $arr['content'];
} catch (HTTPException $e) { } catch (HTTPException $e) {
$content = ModuleHTTPException::content($e); $content = ModuleHTTPException::content($e);

View file

@ -21,6 +21,7 @@
namespace Friendica; namespace Friendica;
use Friendica\Capabilities\ICanHandleRequests;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Model\User; use Friendica\Model\User;
@ -33,23 +34,33 @@ use Friendica\Model\User;
* *
* @author Hypolite Petovan <hypolite@mrpetovan.com> * @author Hypolite Petovan <hypolite@mrpetovan.com>
*/ */
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() * @return array
* */
* Extend this method if you need to do any shared processing before both public static function getParameters(): array
* content() or post() {
return self::$parameters;
}
/**
* {@inheritDoc}
*/ */
public static function init(array $parameters = []) public static function init(array $parameters = [])
{ {
} }
/** /**
* Module GET method to display raw content from technical endpoints * {@inheritDoc}
*
* Extend this method if the module is supposed to return communication data,
* e.g. from protocol implementations.
*/ */
public static function rawContent(array $parameters = []) public static function rawContent(array $parameters = [])
{ {
@ -58,46 +69,29 @@ abstract class BaseModule
} }
/** /**
* Module GET method to display any content * {@inheritDoc}
*
* 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
*/ */
public static function content(array $parameters = []) public static function content(array $parameters = [])
{ {
$o = ''; return '';
return $o;
} }
/** /**
* Module DELETE method to process submitted data * {@inheritDoc}
*
* Extend this method if the module is supposed to process DELETE requests.
* Doesn't display any content
*/ */
public static function delete(array $parameters = []) public static function delete(array $parameters = [])
{ {
} }
/** /**
* Module PATCH method to process submitted data * {@inheritDoc}
*
* Extend this method if the module is supposed to process PATCH requests.
* Doesn't display any content
*/ */
public static function patch(array $parameters = []) public static function patch(array $parameters = [])
{ {
} }
/** /**
* Module POST method to process submitted data * {@inheritDoc}
*
* Extend this method if the module is supposed to process POST requests.
* Doesn't display any content
*/ */
public static function post(array $parameters = []) public static function post(array $parameters = [])
{ {
@ -105,24 +99,25 @@ abstract class BaseModule
} }
/** /**
* Called after post() * {@inheritDoc}
*
* Unknown purpose
*/ */
public static function afterpost(array $parameters = []) public static function afterpost(array $parameters = [])
{ {
} }
/** /**
* Module PUT method to process submitted data * {@inheritDoc}
*
* Extend this method if the module is supposed to process PUT requests.
* Doesn't display any content
*/ */
public static function put(array $parameters = []) 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 * 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. * 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.

View file

@ -0,0 +1,79 @@
<?php
namespace Friendica\Capabilities;
/**
* This interface provides the capability to handle requests from clients and returns the desired outcome
*/
interface ICanHandleRequests
{
/**
* Initialization method common to both content() and post()
*
* Extend this method if you need to do any shared processing before both
* content() or post()
*/
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.
*/
public static function rawContent(array $parameters = []);
/**
* 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
*/
public static function content(array $parameters = []);
/**
* Module DELETE method to process submitted data
*
* Extend this method if the module is supposed to process DELETE requests.
* Doesn't display any content
*/
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
*/
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
*/
public static function post(array $parameters = []);
/**
* Called after post()
*
* Unknown purpose
*/
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
*/
public static function put(array $parameters = []);
public static function getClassName(): string;
public static function getParameters(): array;
}

View file

@ -218,7 +218,7 @@ class ModeTest extends MockedTest
public function testIsBackendButIndex() public function testIsBackendButIndex()
{ {
$server = []; $server = [];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], true); $module = new Module(Module::DEFAULT, null, true);
$mobileDetect = new MobileDetect(); $mobileDetect = new MobileDetect();
$mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect); $mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect);
@ -232,7 +232,7 @@ class ModeTest extends MockedTest
public function testIsNotBackend() public function testIsNotBackend()
{ {
$server = []; $server = [];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); $module = new Module(Module::DEFAULT, null, false);
$mobileDetect = new MobileDetect(); $mobileDetect = new MobileDetect();
$mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect); $mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect);
@ -250,7 +250,7 @@ class ModeTest extends MockedTest
'HTTP_X_REQUESTED_WITH' => 'xmlhttprequest', 'HTTP_X_REQUESTED_WITH' => 'xmlhttprequest',
]; ];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); $module = new Module(Module::DEFAULT, null, false);
$mobileDetect = new MobileDetect(); $mobileDetect = new MobileDetect();
$mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect); $mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect);
@ -264,7 +264,7 @@ class ModeTest extends MockedTest
public function testIsNotAjax() public function testIsNotAjax()
{ {
$server = []; $server = [];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); $module = new Module(Module::DEFAULT, null, false);
$mobileDetect = new MobileDetect(); $mobileDetect = new MobileDetect();
$mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect); $mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect);
@ -278,7 +278,7 @@ class ModeTest extends MockedTest
public function testIsMobileIsTablet() public function testIsMobileIsTablet()
{ {
$server = []; $server = [];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); $module = new Module(Module::DEFAULT, null, false);
$mobileDetect = Mockery::mock(MobileDetect::class); $mobileDetect = Mockery::mock(MobileDetect::class);
$mobileDetect->shouldReceive('isMobile')->andReturn(true); $mobileDetect->shouldReceive('isMobile')->andReturn(true);
$mobileDetect->shouldReceive('isTablet')->andReturn(true); $mobileDetect->shouldReceive('isTablet')->andReturn(true);
@ -296,7 +296,7 @@ class ModeTest extends MockedTest
public function testIsNotMobileIsNotTablet() public function testIsNotMobileIsNotTablet()
{ {
$server = []; $server = [];
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false); $module = new Module(Module::DEFAULT, null, false);
$mobileDetect = Mockery::mock(MobileDetect::class); $mobileDetect = Mockery::mock(MobileDetect::class);
$mobileDetect->shouldReceive('isMobile')->andReturn(false); $mobileDetect->shouldReceive('isMobile')->andReturn(false);
$mobileDetect->shouldReceive('isTablet')->andReturn(false); $mobileDetect->shouldReceive('isTablet')->andReturn(false);

View file

@ -21,6 +21,7 @@
namespace Friendica\Test\src\App; namespace Friendica\Test\src\App;
use Dice\Dice;
use Friendica\App; use Friendica\App;
use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
@ -38,7 +39,7 @@ class ModuleTest extends DatabaseTest
{ {
self::assertEquals($assert['isBackend'], $module->isBackend()); self::assertEquals($assert['isBackend'], $module->isBackend());
self::assertEquals($assert['name'], $module->getName()); 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(); $module = new App\Module();
$defaultClass = App\Module::DEFAULT_CLASS;
self::assertModule([ self::assertModule([
'isBackend' => false, 'isBackend' => false,
'name' => App\Module::DEFAULT, 'name' => App\Module::DEFAULT,
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], $module); ], $module);
} }
public function dataModuleName() public function dataModuleName()
{ {
$defaultClass = App\Module::DEFAULT_CLASS;
return [ return [
'default' => [ 'default' => [
'assert' => [ 'assert' => [
'isBackend' => false, 'isBackend' => false,
'name' => 'network', 'name' => 'network',
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], ],
'args' => new App\Arguments('network/data/in', 'args' => new App\Arguments('network/data/in',
'network/data/in', 'network/data/in',
@ -73,7 +78,7 @@ class ModuleTest extends DatabaseTest
'assert' => [ 'assert' => [
'isBackend' => false, 'isBackend' => false,
'name' => 'with_strike_and_point', 'name' => 'with_strike_and_point',
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], ],
'args' => new App\Arguments('with-strike.and-point/data/in', 'args' => new App\Arguments('with-strike.and-point/data/in',
'with-strike.and-point/data/in', 'with-strike.and-point/data/in',
@ -84,7 +89,7 @@ class ModuleTest extends DatabaseTest
'assert' => [ 'assert' => [
'isBackend' => false, 'isBackend' => false,
'name' => App\Module::DEFAULT, 'name' => App\Module::DEFAULT,
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], ],
'args' => new App\Arguments(), 'args' => new App\Arguments(),
], ],
@ -92,7 +97,7 @@ class ModuleTest extends DatabaseTest
'assert' => [ 'assert' => [
'isBackend' => false, 'isBackend' => false,
'name' => App\Module::DEFAULT, 'name' => App\Module::DEFAULT,
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], ],
'args' => new App\Arguments(), 'args' => new App\Arguments(),
], ],
@ -100,7 +105,7 @@ class ModuleTest extends DatabaseTest
'assert' => [ 'assert' => [
'isBackend' => true, 'isBackend' => true,
'name' => App\Module::BACKEND_MODULES[0], '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', 'args' => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in',
App\Module::BACKEND_MODULES[0] . '/data/in', App\Module::BACKEND_MODULES[0] . '/data/in',
@ -111,7 +116,7 @@ class ModuleTest extends DatabaseTest
'assert' => [ 'assert' => [
'isBackend' => false, 'isBackend' => false,
'name' => 'login', 'name' => 'login',
'class' => App\Module::DEFAULT_CLASS, 'class' => new $defaultClass(),
], ],
'args' => new App\Arguments('users/sign_in', 'args' => new App\Arguments('users/sign_in',
'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)); $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());
} }
/** /**