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

ImportWorkflow::import()   C

Complexity

Conditions 10
Paths 15

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 74
ccs 0
cts 60
cp 0
rs 6.7006
c 0
b 0
f 0
cc 10
nc 15
nop 4
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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