|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file is part of the SmartGecko(c) business platform. |
|
4
|
|
|
* |
|
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
6
|
|
|
* file that was distributed with this source code. |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
namespace Governor\Framework\Saga\Repository\Mongo; |
|
10
|
|
|
|
|
11
|
|
|
use Governor\Framework\Saga\AssociationValue; |
|
12
|
|
|
use Governor\Framework\Saga\SagaInterface; |
|
13
|
|
|
use Governor\Framework\Serializer\SerializerInterface; |
|
14
|
|
|
use Governor\Framework\Saga\ResourceInjectorInterface; |
|
15
|
|
|
use Governor\Framework\Saga\Repository\AbstractSagaRepository; |
|
16
|
|
|
use Governor\Framework\Serializer\SimpleSerializedObject; |
|
17
|
|
|
use Governor\Framework\Serializer\SimpleSerializedType; |
|
18
|
|
|
use Psr\Log\LoggerAwareInterface; |
|
19
|
|
|
use Psr\Log\LoggerInterface; |
|
20
|
|
|
use Governor\Framework\Common\Logging\NullLogger; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* Implementations of the SagaRepository that stores Sagas and their associations in a Mongo Database. Each Saga and |
|
24
|
|
|
* its associations is stored as a single document. |
|
25
|
|
|
* |
|
26
|
|
|
* @author Jettro Coenradie |
|
27
|
|
|
* @author Allard Buijze |
|
28
|
|
|
* @since 2.0 |
|
29
|
|
|
*/ |
|
30
|
|
|
class MongoSagaRepository extends AbstractSagaRepository implements LoggerAwareInterface |
|
31
|
|
|
{ |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* @var LoggerInterface |
|
35
|
|
|
*/ |
|
36
|
|
|
private $logger; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* @var MongoTemplateInterface |
|
40
|
|
|
*/ |
|
41
|
|
|
private $mongoTemplate; |
|
42
|
|
|
/** |
|
43
|
|
|
* @var SerializerInterface |
|
44
|
|
|
*/ |
|
45
|
|
|
private $serializer; |
|
46
|
|
|
/** |
|
47
|
|
|
* @var ResourceInjectorInterface |
|
48
|
|
|
*/ |
|
49
|
|
|
private $injector; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Initializes the Repository, using given <code>mongoTemplate</code> to access the collections containing the |
|
53
|
|
|
* stored Saga instances. |
|
54
|
|
|
* |
|
55
|
|
|
* @param MongoTemplateInterface $mongoTemplate the template providing access to the collections |
|
56
|
|
|
* @param ResourceInjectorInterface $injector |
|
57
|
|
|
* @param SerializerInterface $serializer |
|
58
|
|
|
*/ |
|
59
|
12 |
|
public function __construct( |
|
60
|
|
|
MongoTemplateInterface $mongoTemplate, |
|
61
|
|
|
ResourceInjectorInterface $injector, |
|
62
|
|
|
SerializerInterface $serializer |
|
63
|
|
|
) { |
|
64
|
12 |
|
$this->mongoTemplate = $mongoTemplate; |
|
65
|
12 |
|
$this->serializer = $serializer; |
|
66
|
12 |
|
$this->injector = $injector; |
|
67
|
|
|
|
|
68
|
12 |
|
$this->logger = new NullLogger(); |
|
69
|
12 |
|
} |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* Finds the identifiers of the sagas of given <code>type</code> associated with the given |
|
73
|
|
|
* <code>associationValue</code>. |
|
74
|
|
|
* |
|
75
|
|
|
* @param string $type The type of saga to find identifiers for |
|
76
|
|
|
* @param AssociationValue $associationValue The value the saga must be associated with |
|
77
|
|
|
* @return array The identifiers of sagas associated with the given <code>associationValue</code> |
|
78
|
|
|
*/ |
|
79
|
7 |
|
protected function findAssociatedSagaIdentifiers( |
|
80
|
|
|
$type, |
|
81
|
|
|
AssociationValue $associationValue |
|
82
|
|
|
) { |
|
83
|
7 |
|
$value = $this->associationValueQuery($type, $associationValue); |
|
84
|
|
|
|
|
85
|
7 |
|
$dbCursor = $this->mongoTemplate->sagaCollection()->find($value, ["sagaIdentifier" => 1]); |
|
86
|
7 |
|
$found = []; |
|
87
|
|
|
|
|
88
|
7 |
|
while ($dbCursor->hasNext()) { |
|
89
|
4 |
|
$found[] = $dbCursor->getNext()['sagaIdentifier']; |
|
90
|
4 |
|
} |
|
91
|
|
|
|
|
92
|
7 |
|
return $found; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* Returns the type identifier to use for the given <code>sagaClass</code>. This information is typically provided |
|
97
|
|
|
* by the Serializer, if the repository stores serialized instances. |
|
98
|
|
|
* |
|
99
|
|
|
* @param mixed $sagaClass The type of saga to get the type identifier for. |
|
100
|
|
|
* @return string The type identifier to use for the given sagaClass |
|
101
|
|
|
*/ |
|
102
|
8 |
|
protected function typeOf($sagaClass) |
|
103
|
|
|
{ |
|
104
|
8 |
|
if (is_object($sagaClass)) { |
|
105
|
8 |
|
return $this->serializer->typeForClass($sagaClass)->getName(); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
7 |
|
return $sagaClass; |
|
|
|
|
|
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Remove the given saga as well as all known association values pointing to it from the repository. If no such |
|
113
|
|
|
* saga exists, nothing happens. |
|
114
|
|
|
* |
|
115
|
|
|
* @param SagaInterface $saga The saga instance to remove from the repository |
|
116
|
|
|
*/ |
|
117
|
2 |
|
protected function deleteSaga(SagaInterface $saga) |
|
118
|
|
|
{ |
|
119
|
2 |
|
$this->mongoTemplate->sagaCollection()->remove(SagaEntry::queryByIdentifier($saga->getSagaIdentifier())); |
|
120
|
2 |
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Update a stored Saga, by replacing it with the given <code>saga</code> instance. |
|
124
|
|
|
* |
|
125
|
|
|
* @param SagaInterface $saga The saga that has been modified and needs to be updated in the storage |
|
126
|
|
|
*/ |
|
127
|
2 |
|
protected function updateSaga(SagaInterface $saga) |
|
128
|
|
|
{ |
|
129
|
2 |
|
$sagaEntry = new SagaEntry($saga, $this->serializer); |
|
130
|
|
|
|
|
131
|
2 |
|
$this->mongoTemplate->sagaCollection()->findAndModify( |
|
132
|
2 |
|
SagaEntry::queryByIdentifier($saga->getSagaIdentifier()), |
|
133
|
2 |
|
$sagaEntry->asDBObject() |
|
134
|
2 |
|
); |
|
135
|
2 |
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* Stores a newly created Saga instance. |
|
139
|
|
|
* |
|
140
|
|
|
* @param SagaInterface $saga The newly created Saga instance to store. |
|
141
|
|
|
*/ |
|
142
|
7 |
|
protected function storeSaga(SagaInterface $saga) |
|
143
|
|
|
{ |
|
144
|
7 |
|
$sagaEntry = new SagaEntry($saga, $this->serializer); |
|
145
|
7 |
|
$sagaObject = $sagaEntry->asDBObject(); |
|
146
|
|
|
|
|
147
|
7 |
|
$this->mongoTemplate->sagaCollection()->insert($sagaObject); |
|
148
|
7 |
|
} |
|
149
|
|
|
|
|
150
|
|
|
/** |
|
151
|
|
|
* Store the given <code>associationValue</code>, which has been associated with given <code>sagaIdentifier</code>. |
|
152
|
|
|
* |
|
153
|
|
|
* @param AssociationValue $associationValue The association value to store |
|
154
|
|
|
* @param string $sagaType Type type of saga the association value belongs to |
|
155
|
|
|
* @param string $sagaIdentifier The saga related to the association value |
|
156
|
|
|
*/ |
|
157
|
7 |
View Code Duplication |
protected function storeAssociationValue( |
|
|
|
|
|
|
158
|
|
|
AssociationValue $associationValue, |
|
159
|
|
|
$sagaType, |
|
160
|
|
|
$sagaIdentifier |
|
161
|
|
|
) { |
|
162
|
7 |
|
$this->mongoTemplate->sagaCollection()->update( |
|
163
|
7 |
|
['sagaIdentifier' => $sagaIdentifier, 'sagaType' => $sagaType], |
|
164
|
|
|
[ |
|
165
|
|
|
'$push' => [ |
|
166
|
|
|
'associations' => [ |
|
167
|
7 |
|
'key' => $associationValue->getPropertyKey(), |
|
168
|
7 |
|
'value' => $associationValue->getPropertyValue() |
|
169
|
7 |
|
] |
|
170
|
7 |
|
] |
|
171
|
7 |
|
] |
|
172
|
7 |
|
); |
|
173
|
7 |
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Removes the association value that has been associated with Saga, identified with the given |
|
177
|
|
|
* <code>sagaIdentifier</code>. |
|
178
|
|
|
* |
|
179
|
|
|
* @param AssociationValue $associationValue The value to remove as association value for the given saga |
|
180
|
|
|
* @param string $sagaType The type of the Saga to remove the association from |
|
181
|
|
|
* @param string $sagaIdentifier The identifier of the Saga to remove the association from |
|
182
|
|
|
*/ |
|
183
|
1 |
View Code Duplication |
protected function removeAssociationValue( |
|
|
|
|
|
|
184
|
|
|
AssociationValue $associationValue, |
|
185
|
|
|
$sagaType, |
|
186
|
|
|
$sagaIdentifier |
|
187
|
|
|
) { |
|
188
|
1 |
|
$this->mongoTemplate->sagaCollection()->update( |
|
189
|
1 |
|
['sagaIdentifier' => $sagaIdentifier, 'sagaType' => $sagaType], |
|
190
|
|
|
[ |
|
191
|
|
|
'$pull' => [ |
|
192
|
|
|
'associations' => [ |
|
193
|
1 |
|
'key' => $associationValue->getPropertyKey(), |
|
194
|
1 |
|
'value' => $associationValue->getPropertyValue() |
|
195
|
1 |
|
] |
|
196
|
1 |
|
] |
|
197
|
1 |
|
] |
|
198
|
1 |
|
); |
|
199
|
1 |
|
} |
|
200
|
|
|
|
|
201
|
|
|
/** |
|
202
|
|
|
* Loads a known Saga instance by its unique identifier. Returned Sagas must be {@link #commit(Saga) committed} |
|
203
|
|
|
* after processing. |
|
204
|
|
|
* Due to the concurrent nature of Sagas, it is not unlikely for a Saga to have ceased to exist after it has been |
|
205
|
|
|
* found based on associations. Therefore, a repository should return <code>null</code> in case a Saga doesn't |
|
206
|
|
|
* exists, as opposed to throwing an exception. |
|
207
|
|
|
* |
|
208
|
|
|
* @param string $sagaIdentifier The unique identifier of the Saga to load |
|
209
|
|
|
* @return SagaInterface The Saga instance, or <code>null</code> if no such saga exists |
|
210
|
|
|
*/ |
|
211
|
9 |
|
public function load($sagaIdentifier) |
|
212
|
|
|
{ |
|
213
|
9 |
|
$dbSaga = $this->mongoTemplate->sagaCollection()->findOne(SagaEntry::queryByIdentifier($sagaIdentifier)); |
|
214
|
|
|
|
|
215
|
9 |
|
if (null === $dbSaga) { |
|
216
|
1 |
|
return null; |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
8 |
|
$serializedSaga = new SimpleSerializedObject( |
|
220
|
8 |
|
$dbSaga[SagaEntry::SERIALIZED_SAGA], |
|
221
|
8 |
|
new SimpleSerializedType($dbSaga[SagaEntry::SAGA_TYPE]) |
|
222
|
8 |
|
); |
|
223
|
|
|
|
|
224
|
8 |
|
$saga = $this->serializer->deserialize($serializedSaga); |
|
225
|
|
|
|
|
226
|
8 |
|
if (null !== $this->injector) { |
|
227
|
8 |
|
$this->injector->injectResources($saga); |
|
228
|
8 |
|
} |
|
229
|
|
|
|
|
230
|
8 |
|
return $saga; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
|
|
234
|
7 |
|
private function associationValueQuery($type, AssociationValue $associationValue) |
|
235
|
|
|
{ |
|
236
|
|
|
return [ |
|
237
|
7 |
|
'sagaType' => $this->typeOf($type), |
|
238
|
|
|
'associations' => [ |
|
239
|
7 |
|
'key' => $associationValue->getPropertyKey(), |
|
240
|
7 |
|
'value' => $associationValue->getPropertyValue() |
|
241
|
7 |
|
] |
|
242
|
7 |
|
]; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* Sets a logger instance on the object |
|
247
|
|
|
* |
|
248
|
|
|
* @param LoggerInterface $logger |
|
249
|
|
|
* @return null |
|
250
|
|
|
*/ |
|
251
|
|
|
public function setLogger(LoggerInterface $logger) |
|
252
|
|
|
{ |
|
253
|
|
|
$this->logger = $logger; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
|
|
257
|
|
|
} |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.