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

ImportWorkflow   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 10
dl 0
loc 203
ccs 0
cts 143
cp 0
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanup() 0 32 4
C import() 0 74 10
B importRelations() 0 53 6
A getImportObject() 0 26 5
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 MongoDB\BSON\UTCDateTimeInterface;
15
use Tubee\Collection\CollectionInterface;
16
use Tubee\DataObject\DataObjectInterface;
17
use Tubee\DataObject\Exception as DataObjectException;
18
use Tubee\EndpointObject\EndpointObjectInterface;
19
use Tubee\Helper;
20
use Tubee\Workflow;
21
22
class ImportWorkflow extends Workflow
23
{
24
    /**
25
     * {@inheritdoc}
26
     */
27
    public function cleanup(DataObjectInterface $object, UTCDateTimeInterface $ts, bool $simulate = false): bool
28
    {
29
        $attributes = $object->toArray();
30
        if ($this->checkCondition($attributes) === false) {
31
            return false;
32
        }
33
34
        $attributes = Helper::associativeArrayToPath($attributes);
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 cleanup', [
37
            'category' => get_class($this),
38
            'map' => array_keys($map),
39
        ]);
40
41
        switch ($this->ensure) {
42
            case WorkflowInterface::ENSURE_ABSENT:
43
                return $this->endpoint->getCollection()->deleteObject($object->getId(), $simulate);
44
45
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
46
            default:
47
            case WorkflowInterface::ENSURE_LAST:
0 ignored issues
show
Unused Code introduced by
case \Tubee\Workflow\Wor...return true; break; 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...
48
                $resource = Map::map($this->attribute_map, $map, ['data' => $object->getData()], $ts);
49
                $object->getCollection()->changeObject($object, $resource, $simulate);
50
                $this->importRelations($object, $map, $simulate);
51
52
                return true;
53
54
            break;
55
        }
56
57
        return false;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function import(CollectionInterface $collection, EndpointObjectInterface $object, UTCDateTimeInterface $ts, bool $simulate = false): bool
64
    {
65
        $object = $object->getData();
66
        if ($this->checkCondition($object) === false) {
67
            return false;
68
        }
69
70
        $map = $this->attribute_map->map($object, $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...
71
        $this->logger->info('mapped object attributes [{map}] for import', [
72
            'category' => get_class($this),
73
            'map' => array_keys($map),
74
        ]);
75
76
        $exists = $this->getImportObject($collection, $map, $object, $ts);
77
78
        if ($exists === null) {
79
            $this->logger->info('found no existing data object for given import attributes', [
80
                'category' => get_class($this),
81
            ]);
82
        } else {
83
            $this->logger->info('identified existing data object ['.$exists->getId().'] for import', [
84
                'category' => get_class($this),
85
            ]);
86
        }
87
88
        $ensure = $this->ensure;
89
        if ($exists !== null && $this->ensure === WorkflowInterface::ENSURE_EXISTS) {
90
            return false;
91
        }
92
        if ($exists === null && $this->ensure === WorkflowInterface::ENSURE_LAST) {
93
            $ensure = WorkflowInterface::ENSURE_EXISTS;
94
        }
95
96
        switch ($ensure) {
97
            case WorkflowInterface::ENSURE_ABSENT:
98
                $collection->deleteObject($exists->getId(), $simulate);
99
100
                return true;
101
102
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
103
            case WorkflowInterface::ENSURE_EXISTS:
104
                $endpoints = [
105
                    $this->endpoint->getName() => [
106
                        'last_sync' => $ts,
107
                        'garbage' => false,
108
                    ],
109
                ];
110
111
                $id = $collection->createObject(Helper::pathArrayToAssociative($map), $simulate, $endpoints);
112
                $this->importRelations($collection->getObject(['_id' => $id]), $map, $simulate, $endpoints);
113
114
                return true;
115
116
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
117
            default:
118
            case WorkflowInterface::ENSURE_LAST:
0 ignored issues
show
Unused Code introduced by
case \Tubee\Workflow\Wor...return true; break; 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...
119
                $object = Map::map($this->attribute_map, $map, ['data' => $exists->getData()], $ts);
120
                $endpoints = [
121
                    $this->endpoint->getName() => [
122
                        'last_sync' => $ts,
123
                        'garbage' => false,
124
                    ],
125
                ];
126
127
                $collection->changeObject($exists, $object, $simulate, $endpoints);
0 ignored issues
show
Bug introduced by
It seems like $exists defined by $this->getImportObject($...on, $map, $object, $ts) on line 76 can be null; however, Tubee\Collection\Collect...terface::changeObject() 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...
128
                $this->importRelations($exists, $map, $simulate, $endpoints);
0 ignored issues
show
Bug introduced by
It seems like $exists defined by $this->getImportObject($...on, $map, $object, $ts) on line 76 can be null; however, Tubee\Workflow\ImportWorkflow::importRelations() 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...
129
130
                return true;
131
132
            break;
133
        }
134
135
        return false;
136
    }
137
138
    /**
139
     * Create object relations.
140
     */
141
    protected function importRelations(DataObjectInterface $object, array $data, bool $simulate, array $endpoints = []): bool
142
    {
143
        $this->logger->debug('find relationships to be imported for object ['.$object->getId().']', [
144
            'category' => get_class($this),
145
        ]);
146
147
        foreach ($this->attribute_map->getMap() as $definition) {
148
            if (!isset($definition['map'])) {
149
                continue;
150
            }
151
152
            if (!isset($data[$definition['name']])) {
153
                $this->logger->debug('relation attribute ['.$definition['map']['collection'].':'.$definition['map']['to'].'] not found in mapped data object', [
154
                    'category' => get_class($this),
155
                ]);
156
157
                continue;
158
            }
159
160
            $this->logger->debug('find related object from ['.$object->getId().'] to ['.$definition['map']['collection'].':'.$definition['map']['to'].'] => ['.$data[$definition['name']].']', [
161
                'category' => get_class($this),
162
            ]);
163
164
            $namespace = $this->endpoint->getCollection()->getResourceNamespace();
165
            $collection = $namespace->getCollection($definition['map']['collection']);
166
            $relative = $collection->getObject([
167
                $definition['map']['to'] => $data[$definition['name']],
168
            ]);
169
170
            $this->logger->debug('ensure relation state ['.$definition['map']['ensure'].'] for relation to ['.$relative->getId().']', [
171
                'category' => get_class($this),
172
            ]);
173
174
            switch ($definition['map']['ensure']) {
175
                case WorkflowInterface::ENSURE_EXISTS:
176
                case WorkflowInterface::ENSURE_LAST:
177
                    $namespace = $this->endpoint->getCollection()->getResourceNamespace()->getName();
178
                    $collection = $this->endpoint->getCollection()->getName();
179
                    $ep = $this->endpoint->getName();
180
181
                    $endpoints = [
182
                        join('/', [$namespace, $collection, $ep]) => $endpoints[$ep],
183
                    ];
184
185
                    $object->createOrUpdateRelation($relative, [], $simulate, $endpoints);
186
187
                break;
188
                default:
189
            }
190
        }
191
192
        return true;
193
    }
194
195
    /**
196
     * Get import object.
197
     */
198
    protected function getImportObject(CollectionInterface $collection, array $map, array $object, UTCDateTimeInterface $ts): ?DataObjectInterface
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $ts is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
199
    {
200
        $filter = array_intersect_key($map, array_flip($this->endpoint->getImport()));
201
        //TODO: debug import line here
202
203
        if (empty($filter) || count($filter) !== count($this->endpoint->getImport())) {
204
            throw new Exception\ImportConditionNotMet('import condition attributes are not available from mapping');
205
        }
206
207
        try {
208
            $exists = $collection->getObject($filter, false);
209
        } catch (DataObjectException\MultipleFound $e) {
210
            throw $e;
211
        } catch (DataObjectException\NotFound $e) {
212
            return null;
213
        }
214
215
        /*$endpoints = $exists->getEndpoints();
216
217
        if ($exists !== false && isset($endpoints[$this->endpoint->getName()])
218
        && $endpoints[$this->endpoint->getName()]['last_sync']->toDateTime() >= $ts->toDateTime()) {
219
            throw new Exception\ImportConditionNotMet('import filter matched multiple source objects');
220
        }*/
221
222
        return $exists;
223
    }
224
}
225