Completed
Pull Request — master (#114)
by Bart
07:15
created

ModelProcessor::import()   C

Complexity

Conditions 11
Paths 26

Size

Total Lines 54
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 54
rs 6.6153
c 0
b 0
f 0
cc 11
eloc 37
nc 26
nop 4

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
namespace NerdsAndCompany\Schematic\Services;
4
5
use Craft;
6
use craft\base\Model;
7
use craft\helpers\ArrayHelper;
8
use NerdsAndCompany\Schematic\Schematic;
9
use NerdsAndCompany\Schematic\Converters\Base as BaseConverter;
10
use NerdsAndCompany\Schematic\Interfaces\MappingInterface;
11
use yii\base\Component as BaseComponent;
12
13
/**
14
 * Schematic Base Service.
15
 *
16
 * Sync Craft Setups.
17
 *
18
 * @author    Nerds & Company
19
 * @copyright Copyright (c) 2015-2018, Nerds & Company
20
 * @license   MIT
21
 *
22
 * @see      http://www.nerds.company
23
 */
24
class ModelProcessor extends BaseComponent implements MappingInterface
25
{
26
    /**
27
     * Get all record definitions.
28
     *
29
     * @return array
30
     */
31
    public function export(array $records): array
32
    {
33
        $result = [];
34
        foreach ($records as $record) {
35
            $modelClass = get_class($record);
36
            $converter = $this->getConverter($modelClass);
37
            if ($converter == false) {
38
                Schematic::error('No converter found for '.$modelClass);
39
                continue;
40
            }
41
            $result[$record->handle] = $converter->getRecordDefinition($record);
42
        }
43
44
        return $result;
45
    }
46
47
    /**
48
     * Import records.
49
     *
50
     * @param array $definitions
51
     * @param Model $records           The existing records
52
     * @param array $defaultAttributes Default attributes to use for each record
53
     * @param bool  $persist           Whether to persist the parsed records
54
     */
55
    public function import(array $definitions, array $records, array $defaultAttributes = [], $persist = true): array
56
    {
57
        $imported = [];
58
        $recordsByHandle = ArrayHelper::index($records, 'handle');
59
        foreach ($definitions as $handle => $definition) {
60
            $modelClass = $definition['class'];
61
            $converter = $this->getConverter($modelClass);
62
            if ($converter == false) {
63
                Schematic::error('No converter found for '.$modelClass);
64
                continue;
65
            }
66
67
            $record = new $modelClass();
68
            if (array_key_exists($handle, $recordsByHandle)) {
69
                $existing = $recordsByHandle[$handle];
70
                if (get_class($record) == get_class($existing)) {
71
                    $record = $existing;
72
                } else {
73
                    $record->id = $existing->id;
74
                    $record->setAttributes($existing->getAttributes());
75
                }
76
77
                if ($converter->getRecordDefinition($record) === $definition) {
78
                    Schematic::info('- Skipping '.get_class($record).' '.$handle);
79
                    unset($recordsByHandle[$handle]);
80
                    continue;
81
                }
82
            }
83
84
            $converter->setRecordAttributes($record, $definition, $defaultAttributes);
85
            if (!$persist) {
86
                $imported[] = $record;
87
            } else {
88
                Schematic::info('- Saving '.get_class($record).' '.$handle);
89
                if ($converter->saveRecord($record, $definition)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
90
                } else {
91
                    $this->importError($record, $handle);
92
                }
93
            }
94
            unset($recordsByHandle[$handle]);
95
        }
96
97
        if (Schematic::$force && $persist) {
98
            // Delete records not in definitions
99
            foreach ($recordsByHandle as $handle => $record) {
100
                $modelClass = get_class($record);
101
                Schematic::info('- Deleting '.get_class($record).' '.$handle);
102
                $converter = $this->getConverter($modelClass);
103
                $converter->deleteRecord($record);
104
            }
105
        }
106
107
        return $imported;
108
    }
109
110
    /**
111
     * Find converter class for model.
112
     *
113
     * @param string $modelClass
114
     *
115
     * @return BaseConverter
116
     */
117
    protected function getConverter(string $modelClass)
118
    {
119
        if ($modelClass) {
120
            $converterClass = 'NerdsAndCompany\\Schematic\\Converters\\'.ucfirst(str_replace('craft\\', '', $modelClass));
121
            if (class_exists($converterClass)) {
122
                return new $converterClass();
123
            }
124
125
            return $this->getConverter(get_parent_class($modelClass));
126
        }
127
128
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by NerdsAndCompany\Schemati...Processor::getConverter of type NerdsAndCompany\Schematic\Converters\Base.

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...
129
    }
130
131
    /**
132
     * Log an import error.
133
     *
134
     * @param Model  $record
135
     * @param string $handle
136
     */
137
    public function importError(Model $record, string $handle)
138
    {
139
        Schematic::warning('- Error importing '.get_class($record).' '.$handle);
140
        foreach ($record->getErrors() as $errors) {
141
            foreach ($errors as $error) {
142
                Schematic::error('   - '.$error);
143
            }
144
        }
145
    }
146
}
147