Completed
Push — master ( a5acb0...57b2d8 )
by Raffael
22:45 queued 18:41
created

ExportWorkflow::ensureExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 16
cp 0
rs 9.6
c 0
b 0
f 0
cc 1
nc 1
nop 4
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee
7
 *
8
 * @copyright   Copryright (c) 2017-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Tubee\Workflow;
13
14
use Generator;
15
use MongoDB\BSON\UTCDateTimeInterface;
16
use Tubee\DataObject\DataObjectInterface;
17
use Tubee\Endpoint\Exception as EndpointException;
18
use Tubee\EndpointObject\EndpointObjectInterface;
19
use Tubee\Helper;
20
use Tubee\Workflow;
21
22
class ExportWorkflow extends Workflow
23
{
24
    /**
25
     * {@inheritdoc}
26
     */
27
    public function export(DataObjectInterface $object, UTCDateTimeInterface $ts, bool $simulate = false): bool
28
    {
29
        $attributes = $object->toArray();
30
        $attributes['relations'] = iterator_to_array($this->getRelations($object));
31
        if ($this->checkCondition($attributes) === false) {
32
            return false;
33
        }
34
35
        $map = $this->attribute_map->map($attributes, $ts);
0 ignored issues
show
Unused Code introduced by
The call to AttributeMap::map() has too many arguments starting with $ts.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
36
        $this->logger->info('mapped object attributes [{map}] for write', [
37
            'category' => get_class($this),
38
            'map' => array_keys($map),
39
        ]);
40
41
        $exists = $this->getExportObject([
42
            'map' => $map,
43
            'object' => $attributes,
44
        ]);
45
46
        $map = Helper::pathArrayToAssociative($map);
47
        $ensure = $this->ensure;
48
49
        if ($exists === null && $this->ensure === WorkflowInterface::ENSURE_ABSENT) {
50
            $this->logger->info('skip object which is already absent from endpoint ['.$this->endpoint->getIdentifier().']', [
51
                'category' => get_class($this),
52
            ]);
53
54
            return true;
55
        }
56
        if ($exists !== null && $this->ensure === WorkflowInterface::ENSURE_EXISTS) {
57
            return false;
58
        }
59
60
        if ($exists === null && $this->ensure === WorkflowInterface::ENSURE_LAST) {
61
            $ensure = WorkflowInterface::ENSURE_EXISTS;
62
        }
63
64
        switch ($ensure) {
65
            case WorkflowInterface::ENSURE_ABSENT:
66
                return $this->ensureAbsent($exists, $map, $simulate);
0 ignored issues
show
Bug introduced by
It seems like $exists defined by $this->getExportObject(a...bject' => $attributes)) on line 41 can be null; however, Tubee\Workflow\ExportWorkflow::ensureAbsent() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
67
            case WorkflowInterface::ENSURE_EXISTS:
68
                return $this->ensureExists($object, $map, $ts, $simulate);
69
            default:
70
            case WorkflowInterface::ENSURE_LAST:
0 ignored issues
show
Unused Code introduced by
case \Tubee\Workflow\Wor... $map, $ts, $simulate); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
71
                return $this->ensureLast($object, $exists, $map, $ts, $simulate);
0 ignored issues
show
Bug introduced by
It seems like $exists defined by $this->getExportObject(a...bject' => $attributes)) on line 41 can be null; however, Tubee\Workflow\ExportWorkflow::ensureLast() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
72
        }
73
74
        return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
75
    }
76
77
    /**
78
     * Update object on endpoint.
79
     */
80
    protected function ensureLast(DataObjectInterface $object, EndpointObjectInterface $exists, array $map, UTCDateTimeInterface $ts, bool $simulate = false): bool
81
    {
82
        $this->logger->info('change object on endpoint ['.$this->endpoint->getIdentifier().']', [
83
            'category' => get_class($this),
84
        ]);
85
86
        $diff = $this->attribute_map->getDiff($map, $exists->getData());
87
88
        $endpoints = [$this->endpoint->getName() => [
89
            'last_sync' => $ts,
90
            'garbage' => false,
91
        ]];
92
93
        if (count($diff) > 0) {
94
            $this->logger->info('update object on endpoint ['.$this->endpoint->getIdentifier().'] with attributes [{attributes}]', [
95
                'category' => get_class($this),
96
                'attributes' => $diff,
97
            ]);
98
99
            $diff = $this->endpoint->getDiff($this->attribute_map, $diff);
100
101
            $this->logger->debug('execute diff [{diff}] on endpoint ['.$this->endpoint->getIdentifier().']', [
102
                'category' => get_class($this),
103
                'diff' => $diff,
104
            ]);
105
106
            $result = $this->endpoint->change($this->attribute_map, $diff, $map, $exists->getData(), $simulate);
107
108
            if ($result !== null) {
109
                $endpoints[$this->endpoint->getName()]['result'] = $result;
110
            }
111
        } else {
112
            $this->logger->debug('object on endpoint ['.$this->endpoint->getIdentifier().'] is already up2date', [
113
                'category' => get_class($this),
114
            ]);
115
        }
116
117
        if (!isset($endpoints[$this->endpoint->getName()]['result'])) {
118
            if (isset($exists->getData()[$this->endpoint->getResourceIdentifier()])) {
119
                $endpoints[$this->endpoint->getName()]['result'] = $exists->getData()[$this->endpoint->getResourceIdentifier()];
120
            } else {
121
                $endpoints[$this->endpoint->getName()]['result'] = null;
122
            }
123
        }
124
125
        $this->endpoint->getCollection()->changeObject($object, $object->toArray(), $simulate, $endpoints);
126
127
        return true;
128
    }
129
130
    /**
131
     * Create object on endpoint.
132
     */
133
    protected function ensureExists(DataObjectInterface $object, array $map, UTCDateTimeInterface $ts, bool $simulate = false)
134
    {
135
        $this->logger->info('create new object on endpoint ['.$this->endpoint->getIdentifier().']', [
136
            'category' => get_class($this),
137
        ]);
138
139
        $result = $this->endpoint->create($this->attribute_map, $map, $simulate);
140
141
        $endpoints = [
142
            $this->endpoint->getName() => [
143
                'last_sync' => $ts,
144
                'result' => $result,
145
                'garbage' => false,
146
            ],
147
        ];
148
149
        $this->endpoint->getCollection()->changeObject($object, $object->toArray(), $simulate, $endpoints);
150
151
        return true;
152
    }
153
154
    /**
155
     * Remove object from endpoint.
156
     */
157
    protected function ensureAbsent(EndpointObjectInterface $exists, array $map, bool $simulate = false)
158
    {
159
        $this->logger->info('delete existing object from endpoint ['.$this->endpoint->getIdentifier().']', [
160
            'category' => get_class($this),
161
        ]);
162
163
        $this->endpoint->delete($this->attribute_map, $map, $exists->getData(), $simulate);
164
165
        return true;
166
    }
167
168
    /**
169
     * Transform relations to array.
170
     */
171
    protected function getRelations(DataObjectInterface $object): Generator
172
    {
173
        foreach ($object->getRelations() as $relation) {
174
            $resource = $relation->toArray();
175
            $resource['object'] = $relation->getDataObject()->toArray();
176
            yield $resource;
177
        }
178
    }
179
180
    /**
181
     * Get export object.
182
     */
183
    protected function getExportObject(array $map): ?EndpointObjectInterface
184
    {
185
        try {
186
            if ($this->endpoint->flushRequired()) {
187
                $exists = null;
188
            } else {
189
                $exists = $this->endpoint->getOne($map, $this->attribute_map->getAttributes());
190
            }
191
192
            $this->logger->debug('found existing object on destination endpoint with provided filter_one', [
193
                'category' => get_class($this),
194
            ]);
195
196
            return $exists;
197
        } catch (EndpointException\ObjectMultipleFound $e) {
198
            throw $e;
199
        } catch (EndpointException\ObjectNotFound $e) {
200
            $this->logger->debug('object does not exists yet on destination endpoint', [
201
                'category' => get_class($this),
202
                'exception' => $e,
203
            ]);
204
        } catch (EndpointException\AttributeNotResolvable $e) {
205
            $this->logger->debug('object filter can not be resolved, leading to non existing object', [
206
                'category' => get_class($this),
207
                'exception' => $e,
208
            ]);
209
        }
210
211
        return null;
212
    }
213
}
214