]> git.mxchange.org Git - friendica.git/blob - doc/AddonStorageBackend.md
- Fixing SystemResource
[friendica.git] / doc / AddonStorageBackend.md
1 Friendica Storage Backend Addon development
2 ===========================================
3
4 * [Home](help)
5
6 Storage backends can be added via addons.
7 A storage backend is implemented as a class, and the plugin register the class to make it avaiable to the system.
8
9 ## The Storage Backend Class
10
11 The class must live in `Friendica\Addon\youraddonname` namespace, where `youraddonname` the folder name of your addon.
12
13 The class must implement `Friendica\Model\Storage\IStorage` interface. All method in the interface must be implemented:
14
15 namespace Friendica\Model\Storage;
16
17 ```php
18 interface IStorage
19 {
20         public function get(string $reference);
21         public function put(string $data, string $reference = '');
22         public function delete(string $reference);
23         public function getOptions();
24         public function saveOptions(array $data);
25         public function __toString();
26 }
27 ```
28
29 - `get(string $reference)` returns data pointed by `$reference`
30 - `put(string $data, string $reference)` saves data in `$data` to position `$reference`, or a new position if `$reference` is empty.
31 - `delete(string $reference)` delete data pointed by `$reference`
32
33 Each storage backend can have options the admin can set in admin page.
34
35 - `getOptions()` returns an array with details about each option to build the interface.
36 - `saveOptions(array $data)` get `$data` from admin page, validate it and save it.
37
38 The array returned by `getOptions()` is defined as:
39
40         [
41                 'option1name' => [ ..info.. ],
42                 'option2name' => [ ..info.. ],
43                 ...
44         ]
45
46 An empty array can be returned if backend doesn't have any options.
47
48 The info array for each option is defined as:
49
50         [
51                 'type',
52
53 define the field used in form, and the type of data.
54 one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea', 'yesno'
55
56                 'label',
57
58 Translatable label of the field. This label will be shown in admin page
59
60                 value,
61
62 Current value of the option
63
64                 'help text',
65
66 Translatable description for the field. Will be shown in admin page
67
68                 extra data
69
70 Optional. Depends on which 'type' this option is:
71
72 - 'select': array `[ value => label ]` of choices
73 - 'intcheckbox': value of input element
74 - 'select_raw': prebuild html string of `<option >` tags
75 - 'yesno': array `[ 'label no', 'label yes']`
76
77 Each label should be translatable
78
79         ];
80
81
82 See doxygen documentation of `IStorage` interface for details about each method.
83
84 ## Register a storage backend class
85
86 Each backend must be registered in the system when the plugin is installed, to be aviable.
87
88 `DI::facStorage()->register(string $name, string $class)` is used to register the backend class.
89 The `$name` must be univocal and will be shown to admin.
90
91 When the plugin is uninstalled, registered backends must be unregistered using
92 `DI::facStorage()->unregister(string $name)`.
93
94 ## Adding tests
95
96 **Currently testing is limited to core Friendica only, this shows theoretically how tests should work in the future**
97
98 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/).
99
100 Add a new test class which's naming convention is `StorageClassTest`, which extend the `StorageTest` in the same directory.
101
102 Override the two necessary instances:
103 ```php
104 use Friendica\Model\Storage\IStorage;
105
106 abstract class StorageTest 
107 {
108         // returns an instance of your newly created storage class
109         abstract protected function getInstance();
110
111         // Assertion for the option array you return for your new StorageClass
112         abstract protected function assertOption(IStorage $storage);
113
114 ```
115
116 ## Example
117
118 Here an hypotetical addon which register an unusefull storage backend.
119 Let's call it `samplestorage`.
120
121 This backend will discard all data we try to save and will return always the same image when we ask for some data.
122 The image returned can be set by the administrator in admin page.
123
124 First, the backend class.
125 The file will be `addon/samplestorage/SampleStorageBackend.php`:
126
127 ```php
128 <?php
129 namespace Friendica\Addon\samplestorage;
130
131 use Friendica\Model\Storage\IStorage;
132
133 use Friendica\Core\Config;
134 use Friendica\Core\L10n;
135
136 class SampleStorageBackend implements IStorage
137 {
138         const NAME = 'Sample Storage';
139
140         /** @var Config\IConfiguration */
141         private $config;
142         /** @var L10n\L10n */
143         private $l10n;
144
145         /**
146           * SampleStorageBackend constructor.
147           * @param Config\IConfiguration $config The configuration of Friendica
148           *                                                                       
149           * You can add here every dynamic class as dependency you like and add them to a private field
150           * Friendica automatically creates these classes and passes them as argument to the constructor                                                                           
151           */
152         public function __construct(Config\IConfiguration $config, L10n\L10n $l10n) 
153         {
154                 $this->config = $config;
155                 $this->l10n   = $l10n;
156         }
157
158         public function get(string $reference)
159         {
160                 // we return always the same image data. Which file we load is defined by
161                 // a config key
162                 $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
163                 return file_get_contents($filename);
164         }
165         
166         public function put(string $data, string $reference = '')
167         {
168                 if ($reference === '') {
169                         $reference = 'sample';
170                 }
171                 // we don't save $data !
172                 return $reference;
173         }
174         
175         public function delete(string $reference)
176         {
177                 // we pretend to delete the data
178                 return true;
179         }
180         
181         public function getOptions()
182         {
183                 $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
184                 return [
185                         'filename' => [
186                                 'input',        // will use a simple text input
187                                 $this->l10n->t('The file to return'),   // the label
188                                 $filename,      // the current value
189                                 $this->l10n->t('Enter the path to a file'), // the help text
190                                 // no extra data for 'input' type..
191                         ],
192                 ];
193         }
194         
195         public function saveOptions(array $data)
196         {
197                 // the keys in $data are the same keys we defined in getOptions()
198                 $newfilename = trim($data['filename']);
199                 
200                 // this function should always validate the data.
201                 // in this example we check if file exists
202                 if (!file_exists($newfilename)) {
203                         // in case of error we return an array with
204                         // ['optionname' => 'error message']
205                         return ['filename' => 'The file doesn\'t exists'];
206                 }
207                 
208                 $this->config->set('storage', 'samplestorage', $newfilename);
209                 
210                 // no errors, return empty array
211                 return [];
212         }
213
214         public function __toString()
215         {
216                 return self::NAME;
217         }
218
219         public static function getName()
220         {
221                 return self::NAME;
222         }
223 }
224 ```
225
226 Now the plugin main file. Here we register and unregister the backend class.
227
228 The file is `addon/samplestorage/samplestorage.php`
229
230 ```php
231 <?php
232 /**
233  * Name: Sample Storage Addon
234  * Description: A sample addon which implements an unusefull storage backend
235  * Version: 1.0.0
236  * Author: Alice <https://alice.social/~alice>
237  */
238
239 use Friendica\Addon\samplestorage\SampleStorageBackend;
240 use Friendica\DI;
241
242 function samplestorage_install()
243 {
244         // on addon install, we register our class with name "Sample Storage".
245         // note: we use `::class` property, which returns full class name as string
246         // this save us the problem of correctly escape backslashes in class name
247         DI::facStorage()->register(SampleStorageBackend::class);
248 }
249
250 function samplestorage_unistall()
251 {
252         // when the plugin is uninstalled, we unregister the backend.
253         DI::facStorage()->unregister(SampleStorageBackend::class);
254 }
255 ```
256
257 **Theoretically - until tests for Addons are enabled too - create a test class with the name `addon/tests/SampleStorageTest.php`:
258
259 ```php
260 use Friendica\Model\Storage\IStorage;
261 use Friendica\Test\src\Model\Storage\StorageTest;
262
263 class SampleStorageTest extends StorageTest 
264 {
265         // returns an instance of your newly created storage class
266         protected function getInstance()
267         {
268                 // create a new SampleStorageBackend instance with all it's dependencies
269                 // Have a look at DatabaseStorageTest or FilesystemStorageTest for further insights
270                 return new SampleStorageBackend();
271         }
272
273         // Assertion for the option array you return for your new StorageClass
274         protected function assertOption(IStorage $storage)
275         {
276                 $this->assertEquals([
277                         'filename' => [
278                                 'input',
279                                 'The file to return',
280                                 'sample.jpg',
281                                 'Enter the path to a file'
282                         ],
283                 ], $storage->getOptions());
284         }
285
286 ```