```php
interface IStorage
{
- public static function get($ref);
- public static function put($data, $ref = "");
- public static function delete($ref);
- public static function getOptions();
- public static function saveOptions($data);
+ public function get(string $reference);
+ public function put(string $data, string $reference = '');
+ public function delete(string $reference);
+ public function getOptions();
+ public function saveOptions(array $data);
+ public function __toString();
+ public static function getName();
}
```
-- `get($ref)` returns data pointed by `$ref`
-- `put($data, $ref)` saves data in `$data` to position `$ref`, or a new position if `$ref` is empty.
-- `delete($ref)` delete data pointed by `$ref`
+- `get(string $reference)` returns data pointed by `$reference`
+- `put(string $data, string $reference)` saves data in `$data` to position `$reference`, or a new position if `$reference` is empty.
+- `delete(string $reference)` delete data pointed by `$reference`
Each storage backend can have options the admin can set in admin page.
- `getOptions()` returns an array with details about each option to build the interface.
-- `saveOptions($data)` get `$data` from admin page, validate it and save it.
+- `saveOptions(array $data)` get `$data` from admin page, validate it and save it.
The array returned by `getOptions()` is defined as:
'type',
define the field used in form, and the type of data.
-one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea', 'yesno'
+one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea'
'label',
- 'select': array `[ value => label ]` of choices
- 'intcheckbox': value of input element
- 'select_raw': prebuild html string of `<option >` tags
-- 'yesno': array `[ 'label no', 'label yes']`
Each label should be translatable
Each backend must be registered in the system when the plugin is installed, to be aviable.
-`Friendica\Core\StorageManager::register($name, $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
-`Friendica\Core\StorageManager::unregister($class)`.
+`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
+
+**Currently testing is limited to core Friendica only, this shows theoretically how tests should work in the future**
+
+Each new Storage class should be added to the test-environment at [Storage Tests](https://github.com/friendica/friendica/tree/develop/tests/src/Model/Storage/).
+
+Add a new test class which's naming convention is `StorageClassTest`, which extend the `StorageTest` in the same directory.
+
+Override the two necessary instances:
+```php
+use Friendica\Model\Storage\IStorage;
+
+abstract class StorageTest
+{
+ // returns an instance of your newly created storage class
+ abstract protected function getInstance();
+
+ // Assertion for the option array you return for your new StorageClass
+ abstract protected function assertOption(IStorage $storage);
+}
+```
## Example
use Friendica\Model\Storage\IStorage;
-use Friendica\Core\Config;
+use Friendica\Core\Config\IConfig;
use Friendica\Core\L10n;
class SampleStorageBackend implements IStorage
{
- public static function get($ref)
+ const NAME = 'Sample Storage';
+
+ /** @var IConfig */
+ private $config;
+ /** @var L10n */
+ private $l10n;
+
+ /**
+ * SampleStorageBackend constructor.
+ * @param IConfig $config The configuration of Friendica
+ *
+ * You can add here every dynamic class as dependency you like and add them to a private field
+ * Friendica automatically creates these classes and passes them as argument to the constructor
+ */
+ public function __construct(IConfig $config, L10n $l10n)
{
- // we return alwais the same image data. Which file we load is defined by
+ $this->config = $config;
+ $this->l10n = $l10n;
+ }
+
+ public function get(string $reference)
+ {
+ // we return always the same image data. Which file we load is defined by
// a config key
- $filename = Config::get("storage", "samplestorage", "sample.jpg");
+ $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
return file_get_contents($filename);
}
- public static function put($data, $ref = "")
+ public function put(string $data, string $reference = '')
{
- if ($ref === "") {
- $ref = "sample";
+ if ($reference === '') {
+ $reference = 'sample';
}
// we don't save $data !
- return $ref;
+ return $reference;
}
- public static function delete($ref)
+ public function delete(string $reference)
{
// we pretend to delete the data
return true;
}
- public static function getOptions()
+ public function getOptions()
{
- $filename = Config::get("storage", "samplestorage", "sample.jpg");
+ $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
return [
- "filename" => [
- "input", // will use a simple text input
- L10n::t("The file to return"), // the label
+ 'filename' => [
+ 'input', // will use a simple text input
+ $this->l10n->t('The file to return'), // the label
$filename, // the current value
- L10n::t("Enter the path to a file"), // the help text
- // no extra data for "input" type..
+ $this->l10n->t('Enter the path to a file'), // the help text
+ // no extra data for 'input' type..
+ ],
];
}
- public static function saveOptions($data)
+ public function saveOptions(array $data)
{
// the keys in $data are the same keys we defined in getOptions()
- $newfilename = trim($data["filename"]);
+ $newfilename = trim($data['filename']);
// this function should always validate the data.
// in this example we check if file exists
if (!file_exists($newfilename)) {
// in case of error we return an array with
- // ["optionname" => "error message"]
- return ["filename" => "The file doesn't exists"];
+ // ['optionname' => 'error message']
+ return ['filename' => 'The file doesn\'t exists'];
}
- Config::set("storage", "samplestorage", $newfilename);
+ $this->config->set('storage', 'samplestorage', $newfilename);
// no errors, return empty array
return [];
}
+
+ public function __toString()
+ {
+ return self::NAME;
+ }
+
+ public static function getName()
+ {
+ return self::NAME;
+ }
}
```
* Author: Alice <https://alice.social/~alice>
*/
-use Friendica\Core\StorageManager;
use Friendica\Addon\samplestorage\SampleStorageBackend;
+use Friendica\DI;
function samplestorage_install()
{
// on addon install, we register our class with name "Sample Storage".
// note: we use `::class` property, which returns full class name as string
// this save us the problem of correctly escape backslashes in class name
- StorageManager::register("Sample Storage", SampleStorageBackend::class);
+ DI::storageManager()->register(SampleStorageBackend::class);
}
function samplestorage_unistall()
{
// when the plugin is uninstalled, we unregister the backend.
- StorageManager::unregister("Sample Storage");
+ DI::storageManager()->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`:
+```php
+use Friendica\Model\Storage\IStorage;
+use Friendica\Test\src\Model\Storage\StorageTest;
+
+class SampleStorageTest extends StorageTest
+{
+ // returns an instance of your newly created storage class
+ protected function getInstance()
+ {
+ // create a new SampleStorageBackend instance with all it's dependencies
+ // Have a look at DatabaseStorageTest or FilesystemStorageTest for further insights
+ return new SampleStorageBackend();
+ }
+ // Assertion for the option array you return for your new StorageClass
+ protected function assertOption(IStorage $storage)
+ {
+ $this->assertEquals([
+ 'filename' => [
+ 'input',
+ 'The file to return',
+ 'sample.jpg',
+ 'Enter the path to a file'
+ ],
+ ], $storage->getOptions());
+ }
+}
+```