MongoSagaRepository::associationValueQuery()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4286
cc 1
eloc 6
nc 1
nop 2
crap 1
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $sagaClass; (integer|double|string|null|array|boolean|resource) is incompatible with the return type declared by the abstract method Governor\Framework\Saga\...tSagaRepository::typeOf of type string.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
}