Merge pull request #10165 from nupplaphil/bug/strip_pageinfo
[friendica.git/.git] / doc / Developer-Domain-Driven-Design.md
1 Domain-Driven-Design
2 ==============
3
4 * [Home](help)
5   * [Developer Intro](help/Developers-Intro)
6
7 Friendica uses class structures inspired by Domain-Driven-Design programming patterns.
8 This page is meant to explain what it means in practical terms for Friendica development.
9
10 ## Inspiration
11
12 - https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
13 - https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
14 - https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
15 - https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
16 - https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
17
18 ## Core concepts
19
20 ### Models and Collections
21
22 Instead of anonymous arrays of arrays of database field values, we have Models and collections to take full advantage of PHP type hints.
23
24 Before:
25 ```php
26 function doSomething(array $intros)
27 {
28     foreach ($intros as $intro) {
29         $introId = $intro['id'];
30     }
31 }
32
33 $intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
34
35 doSomething($intros);
36 ```
37
38 After:
39 ```php
40 function doSomething(\Friendica\Collection\Introductions $intros)
41 {
42     foreach ($intros as $intro) {
43         /** @var $intro \Friendica\Model\Introduction */
44         $introId = $intro->id;
45     }
46 }
47
48 /** @var $intros \Friendica\Collection\Introductions */
49 $intros = \Friendica\DI::intro()->select(['uid' => local_user()]);
50
51 doSomething($intros);
52 ```
53
54 ### Dependency Injection
55
56 Under this concept, we want class objects to carry with them the dependencies they will use.
57 Instead of calling global/static function/methods, objects use their own class members.
58
59 Before:
60 ```php
61 class Model
62 {
63     public $id;
64
65     function save()
66     {
67         return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
68     }
69 }
70 ```
71
72 After:
73 ```php
74 class Model
75 {
76     /**
77      * @var \Friendica\Database\Database
78      */
79     protected $dba;
80
81     public $id;
82
83     function __construct(\Friendica\Database\Database $dba)
84     {
85         $this->dba = $dba;
86     }
87     
88     function save()
89     {
90         return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
91     }
92 }
93 ```
94
95 The main advantage is testability.
96 Another one is avoiding dependency circles and avoid implicit initializing.
97 In the first example the method `save()` has to be tested with the `DBA::update()` method, which may or may not have dependencies itself.
98
99 In the second example we can mock `\Friendica\Database\Database`, e.g. overload the class by replacing its methods by placeholders, which allows us to test only `Model::save()` and nothing else implicitly.
100
101 The main drawback is lengthy constructors for dependency-heavy classes.
102 To alleviate this issue we are using [DiCe](https://r.je/dice) to simplify the instantiation of the higher level objects Friendica uses.
103
104 We also added a convenience factory named `\Friendica\DI` that creates some of the most common objects used in modules.
105
106 ### Factories
107
108 Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome.
109 To keep it simple, we are using Factories.
110 Factories are classes used to generate other objects, centralizing the dependencies required in their constructor.
111 Factories encapsulate more or less complex creation of objects and create them redundancy free.
112
113 Before:
114 ```php
115 $model = new Model(\Friendica\DI::dba());
116 $model->id = 1;
117 $model->key = 'value';
118
119 $model->save();
120 ```
121
122 After:
123 ```php
124 class Factory
125 {
126     /**
127      * @var \Friendica\Database\Database
128      */
129     protected $dba;
130
131     function __construct(\Friendica\Database\Database $dba)
132     {
133         $this->dba;
134     }
135
136     public function create()
137     {
138         return new Model($this->dba);    
139     }
140 }
141
142 $model = \Friendica\DI::factory()->create();
143 $model->id = 1;
144 $model->key = 'value';
145
146 $model->save();
147 ```
148
149 Here, `DI::factory()` returns an instance of `Factory` that can then be used to create a `Model` object without having to care about its dependencies.
150
151 ### Repositories
152
153 Last building block of our code architecture, repositories are meant as the interface between models and how they are stored.
154 In Friendica they are stored in a relational database but repositories allow models not to have to care about it.
155 Repositories also act as factories for the Model they are managing.
156
157 Before:
158 ```php
159 class Model
160 {
161     /**
162      * @var \Friendica\Database\Database
163      */
164     protected $dba;
165
166     public $id;
167
168     function __construct(\Friendica\Database\Database $dba)
169     {
170         $this->dba = $dba;
171     }
172     
173     function save()
174     {
175         return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
176     }
177 }
178
179 class Factory
180 {
181     /**
182      * @var \Friendica\Database\Database
183      */
184     protected $dba;
185
186     function __construct(\Friendica\Database\Database $dba)
187     {
188         $this->dba;
189     }
190
191     public function create()
192     {
193         return new Model($this->dba);    
194     }
195 }
196
197
198 $model = \Friendica\DI::factory()->create();
199 $model->id = 1;
200 $model->key = 'value';
201
202 $model->save();
203 ```
204
205 After:
206 ```php
207 class Model {
208     public $id;
209 }
210
211 class Repository extends Factory
212 {
213     /**
214      * @var \Friendica\Database\Database
215      */
216     protected $dba;
217
218     function __construct(\Friendica\Database\Database $dba)
219     {
220         $this->dba;
221     }
222
223     public function create()
224     {
225         return new Model($this->dba);    
226     }
227
228     public function save(Model $model)
229     {
230         return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
231     }
232 }
233
234 $model = \Friendica\DI::repository()->create();
235 $model->id = 1;
236 $model->key = 'value';
237
238 \Friendica\DI::repository()->save($model);
239 ```