public function getOptions();
public function saveOptions(array $data);
public function __toString();
+ public static function getName();
}
```
Each backend must be registered in the system when the plugin is installed, to be aviable.
-`DI::facStorage()->register(string $name, string $class)` is used to register the backend class.
-The `$name` must be univocal and will be shown to admin.
+`DI::facStorage()->register(string $class)` is used to register the backend class.
When the plugin is uninstalled, registered backends must be unregistered using
-`DI::facStorage()->unregister(string $name)`.
+`DI::facStorage()->unregister(string $class)`.
+
+You have to register a new hook in your addon, listening on `storage_instance(App $a, array $data)`.
+In case `$data['name']` is your storage class name, you have to instance a new instance of your `Friendica\Model\Storage\IStorage` class.
+Set the instance of your class as `$data['storage']` to pass it back to the backend.
+
+This is necessary because it isn't always clear, if you need further construction arguments.
## Adding tests
// when the plugin is uninstalled, we unregister the backend.
DI::facStorage()->unregister(SampleStorageBackend::class);
}
+
+function samplestorage_storage_instance(\Friendica\App $a, array $data)
+{
+ if ($data['name'] === SampleStorageBackend::getName()) {
+ // instance a new sample storage instance and pass it back to the core for usage
+ $data['storage'] = new SampleStorageBackend(DI::config(), DI::l10n(), DI::cache());
+ }
+}
```
**Theoretically - until tests for Addons are enabled too - create a test class with the name `addon/tests/SampleStorageTest.php`:
Hook::callAll('page_header', DI::page()['nav']);
Hook::callAll('nav_info', $nav);
+### src/Core/Authentication.php
+
+ Hook::callAll('logged_in', $a->user);
+
+### src/Core/StorageManager
+
+ Hook::callAll('storage_instance', $data);
+
### src/Worker/Directory.php
Hook::callAll('globaldir_update', $arr);
### src/Core/Authentication.php
Hook::callAll('logged_in', $a->user);
+
+### src/Core/StorageManager
+
+ Hook::callAll('storage_instance', $data);
### src/Worker/Directory.php
namespace Friendica\Core;
-use Dice\Dice;
use Exception;
use Friendica\Core\Config\IConfiguration;
+use Friendica\Core\L10n\L10n;
use Friendica\Database\Database;
use Friendica\Model\Storage;
use Psr\Log\LoggerInterface;
private $backends = [];
+ /**
+ * @var Storage\IStorage[] A local cache for storage instances
+ */
+ private $backendInstances = [];
+
/** @var Database */
private $dba;
/** @var IConfiguration */
private $config;
/** @var LoggerInterface */
private $logger;
- /** @var Dice */
- private $dice;
+ /** @var L10n */
+ private $l10n;
/** @var Storage\IStorage */
private $currentBackend;
* @param Database $dba
* @param IConfiguration $config
* @param LoggerInterface $logger
- * @param Dice $dice
+ * @param L10n $l10n
*/
- public function __construct(Database $dba, IConfiguration $config, LoggerInterface $logger, Dice $dice)
+ public function __construct(Database $dba, IConfiguration $config, LoggerInterface $logger, L10n $l10n)
{
$this->dba = $dba;
$this->config = $config;
$this->logger = $logger;
- $this->dice = $dice;
+ $this->l10n = $l10n;
$this->backends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
$currentName = $this->config->get('storage', 'name', '');
- if ($this->isValidBackend($currentName)) {
- $this->currentBackend = $this->dice->create($this->backends[$currentName]);
- } else {
- $this->currentBackend = null;
- }
+ $this->currentBackend = $this->getByName($currentName);
}
/**
/**
* @brief Return storage backend class by registered name
*
- * @param string|null $name Backend name
+ * @param string|null $name Backend name
+ * @param boolean $userBackend Just return instances in case it's a user backend (e.g. not SystemResource)
*
* @return Storage\IStorage|null null if no backend registered at $name
+ *
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public function getByName(string $name = null)
+ public function getByName(string $name = null, $userBackend = true)
{
- if (!$this->isValidBackend($name) &&
- $name !== Storage\SystemResource::getName()) {
- return null;
- }
-
- /** @var Storage\IStorage $storage */
- $storage = null;
-
- // If the storage of the file is a system resource,
- // create it directly since it isn't listed in the registered backends
- if ($name === Storage\SystemResource::getName()) {
- $storage = $this->dice->create(Storage\SystemResource::class);
- } else {
- $storage = $this->dice->create($this->backends[$name]);
+ // If there's no cached instance create a new instance
+ if (!isset($this->backendInstances[$name])) {
+ // If the current name isn't a valid backend (or the SystemResource instance) create it
+ if ($this->isValidBackend($name, $userBackend)) {
+ switch ($name) {
+ // Try the filesystem backend
+ case Storage\Filesystem::getName():
+ $this->backendInstances[$name] = new Storage\Filesystem($this->config, $this->logger, $this->l10n);
+ break;
+ // try the database backend
+ case Storage\Database::getName():
+ $this->backendInstances[$name] = new Storage\Database($this->dba, $this->logger, $this->l10n);
+ break;
+ // at least, try if there's an addon for the backend
+ case Storage\SystemResource::getName():
+ $this->backendInstances[$name] = new Storage\SystemResource();
+ break;
+ default:
+ $data = [
+ 'name' => $name,
+ 'storage' => null,
+ ];
+ Hook::callAll('storage_instance', $data);
+ if (($data['storage'] ?? null) instanceof Storage\IStorage) {
+ $this->backendInstances[$data['name'] ?? $name] = $data['storage'];
+ } else {
+ return null;
+ }
+ break;
+ }
+ } else {
+ return null;
+ }
}
- return $storage;
+ return $this->backendInstances[$name];
}
/**
* Checks, if the storage is a valid backend
*
- * @param string|null $name The name or class of the backend
+ * @param string|null $name The name or class of the backend
+ * @param boolean $userBackend True, if just user backend should get returned (e.g. not SystemResource)
*
* @return boolean True, if the backend is a valid backend
*/
- public function isValidBackend(string $name = null)
+ public function isValidBackend(string $name = null, bool $userBackend = true)
{
- return array_key_exists($name, $this->backends);
+ return array_key_exists($name, $this->backends) ||
+ (!$userBackend && $name === Storage\SystemResource::getName());
}
/**
}
if ($this->config->set('storage', 'name', $name)) {
- $this->currentBackend = $this->dice->create($this->backends[$name]);
+ $this->currentBackend = $this->getByName($name);
return true;
} else {
return false;
}
/**
- * @brief Register a storage backend class
+ * Register a storage backend class
+ *
+ * You have to register the hook "storage_instance" as well to make this class work!
*
* @param string $class Backend class name
*
$_SERVER, $_COOKIE
],
],
- StorageManager::class => [
- 'constructParams' => [
- [Dice::INSTANCE => Dice::SELF],
- ]
- ],
IStorage::class => [
- // Don't share this class with other creations, because it's possible to switch the backend
- // and so we wouldn't be possible to update it
- 'shared' => false,
'instanceOf' => StorageManager::class,
'call' => [
['getBackend', [], Dice::CHAIN_CALL],
namespace Friendica\Test\Util;
+use Friendica\App;
+use Friendica\Core\Hook;
use Friendica\Model\Storage\IStorage;
use Friendica\Core\L10n\L10n;
+use Mockery\MockInterface;
/**
* A backend storage example class
{
return self::NAME;
}
+
+ /**
+ * This one is a hack to register this class to the hook
+ */
+ public static function registerHook()
+ {
+ Hook::register('storage_instance', __DIR__ . '/SampleStorageBackendInstance.php', 'create_instance');
+ }
}
+
--- /dev/null
+<?php
+
+// contains a test-hook call for creating a storage instance
+
+use Friendica\App;
+use Friendica\Core\L10n\L10n;
+use Friendica\Test\Util\SampleStorageBackend;
+use Mockery\MockInterface;
+
+function create_instance(App $a, &$data)
+{
+ /** @var L10n|MockInterface $l10n */
+ $l10n = \Mockery::mock(L10n::class);
+
+ if ($data['name'] == SampleStorageBackend::getName()) {
+ $data['storage'] = new SampleStorageBackend($l10n);
+ }
+}
use Dice\Dice;
use Friendica\Core\Config\IConfiguration;
use Friendica\Core\Config\PreloadConfiguration;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n\L10n;
use Friendica\Core\Session\ISession;
use Friendica\Core\StorageManager;
use Friendica\Database\Database;
+use Friendica\DI;
use Friendica\Factory\ConfigFactory;
use Friendica\Model\Config\Config;
use Friendica\Model\Storage;
use Psr\Log\NullLogger;
use Friendica\Test\Util\SampleStorageBackend;
+/**
+ * @todo Rework Hook:: methods to dynamic to remove the separated process annotation
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
class StorageManagerTest extends DatabaseTest
{
/** @var Database */
private $config;
/** @var LoggerInterface */
private $logger;
- /** @var Dice */
- private $dice;
+ /** @var L10n */
+ private $l10n;
use VFSTrait;
$this->setUpVfsDir();
$this->logger = new NullLogger();
- $this->dice = (new Dice())
- ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
- ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
- ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
$profiler = \Mockery::mock(Profiler::class);
$profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
$configModel = new Config($this->dba);
$this->config = new PreloadConfiguration($configCache, $configModel);
+
+ $this->l10n = \Mockery::mock(L10n::class);
}
/**
*/
public function testInstance()
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$this->assertInstanceOf(StorageManager::class, $storageManager);
}
*
* @dataProvider dataStorages
*/
- public function testGetByName($name, $assert, $assertName)
+ public function testGetByName($name, $assert, $assertName, $userBackend)
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
- $storage = $storageManager->getByName($name);
+ $storage = $storageManager->getByName($name, $userBackend);
if (!empty($assert)) {
$this->assertInstanceOf(Storage\IStorage::class, $storage);
*/
public function testIsValidBackend($name, $assert, $assertName, $userBackend)
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$this->assertEquals($userBackend, $storageManager->isValidBackend($name));
}
*/
public function testListBackends()
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$this->assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
}
*/
public function testGetBackend($name, $assert, $assertName, $userBackend)
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$this->assertNull($storageManager->getBackend());
{
$this->config->set('storage', 'name', $name);
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
if ($userBackend) {
$this->assertInstanceOf($assert, $storageManager->getBackend());
*/
public function testRegisterUnregisterBackends()
{
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ /// @todo Remove dice once "Hook" is dynamic and mockable
+ $dice = (new Dice())
+ ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
+ ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
+ ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
+ DI::init($dice);
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$this->assertTrue($storageManager->register(SampleStorageBackend::class));
$this->assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- 'Sample Storage' => SampleStorageBackend::class,
+ SampleStorageBackend::getName() => SampleStorageBackend::class,
]), $storageManager->listBackends());
$this->assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- 'Sample Storage' => SampleStorageBackend::class,
+ SampleStorageBackend::getName() => SampleStorageBackend::class,
]), $this->config->get('storage', 'backends'));
+ // inline call to register own class as hook (testing purpose only)
+ SampleStorageBackend::registerHook();
+ Hook::loadHooks();
+
$this->assertTrue($storageManager->setBackend(SampleStorageBackend::NAME));
$this->assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
$this->loadFixture(__DIR__ . '/../../datasets/storage/database.fixture.php', $this->dba);
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->dice);
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
$storage = $storageManager->getByName($name);
$storageManager->move($storage);