Completed
Pull Request — master (#114)
by Bart
02:14
created

Base::getRecordDefinition()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 0
cts 15
cp 0
rs 8.439
c 0
b 0
f 0
cc 6
eloc 20
nc 16
nop 1
crap 42
1
<?php
2
3
namespace NerdsAndCompany\Schematic\Services;
4
5
use Craft;
6
use craft\base\Model;
7
use craft\helpers\ArrayHelper;
8
use yii\base\Component as BaseComponent;
9
use NerdsAndCompany\Schematic\Behaviors\FieldLayoutBehavior;
10
use NerdsAndCompany\Schematic\Behaviors\SourcesBehavior;
11
use NerdsAndCompany\Schematic\Interfaces\MappingInterface;
12
use NerdsAndCompany\Schematic\Schematic;
13
14
/**
15
 * Schematic Base Service.
16
 *
17
 * Sync Craft Setups.
18
 *
19
 * @author    Nerds & Company
20
 * @copyright Copyright (c) 2015-2018, Nerds & Company
21
 * @license   MIT
22
 *
23
 * @see      http://www.nerds.company
24
 */
25
abstract class Base extends BaseComponent implements MappingInterface
26
{
27
    /**
28
     * Load fieldlayout and sources behaviors.
29
     *
30
     * @return array
31
     */
32
    public function behaviors()
33
    {
34
        return [
35
          FieldLayoutBehavior::className(),
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
36
          SourcesBehavior::className(),
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
37
        ];
38
    }
39
40
    /**
41
     * Get all records.
42
     *
43
     * @return Model[]
44
     */
45
    abstract protected function getRecords();
46
47 57
    /**
48
     * Get all record definitions.
49 57
     *
50 57
     * @return array
51
     */
52
    public function export(array $records = null)
53
    {
54
        $records = $records ?: $this->getRecords();
55
        $result = [];
56
        foreach ($records as $record) {
57
            $result[$record->handle] = $this->getRecordDefinition($record);
58
        }
59
60
        return $result;
61
    }
62
63
    /**
64
     * Get single record definition.
65
     *
66
     * @param Model $record
67
     *
68
     * @return array
69
     */
70
    protected function getRecordDefinition(Model $record)
71
    {
72
        $definition = [
73
          'class' => get_class($record),
74
          'attributes' => $record->getAttributes(),
75
        ];
76
        unset($definition['attributes']['id']);
77
        unset($definition['attributes']['structureId']);
78
        unset($definition['attributes']['dateCreated']);
79
        unset($definition['attributes']['dateUpdated']);
80
81
        // Define sources
82
        if (isset($definition['attributes']['sources'])) {
83
            $definition['sources'] = $this->getSources($definition['class'], $definition['attributes']['sources'], 'id', 'handle');
0 ignored issues
show
Documentation Bug introduced by
The method getSources does not exist on object<NerdsAndCompany\Schematic\Services\Base>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
84
        }
85
86
        if (isset($definition['attributes']['source'])) {
87
            $definition['source'] = $this->getSource($definition['class'], $definition['attributes']['sources'], 'id', 'handle');
0 ignored issues
show
Documentation Bug introduced by
The method getSource does not exist on object<NerdsAndCompany\Schematic\Services\Base>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
88
        }
89
90
        // Define field layout
91
        if (isset($definition['attributes']['fieldLayoutId'])) {
92
            $definition['fieldLayout'] = $this->getFieldLayoutDefinition($record->getFieldLayout());
0 ignored issues
show
Documentation Bug introduced by
The method getFieldLayoutDefinition does not exist on object<NerdsAndCompany\Schematic\Services\Base>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
93
            unset($definition['attributes']['fieldLayoutId']);
94
        }
95
96
        // Define site settings
97
        if (isset($record->siteSettings)) {
98
            $definition['siteSettings'] = [];
99
            foreach ($record->getSiteSettings() as $siteSetting) {
100
                $definition['siteSettings'][$siteSetting->site->handle] = $this->getRecordDefinition($siteSetting);
101
            }
102
        }
103
104
        return $definition;
105
    }
106
107
    /**
108
     * Import records.
109
     *
110
     * @param array $definitions
111
     * @param Model $records           The existing records
112
     * @param array $defaultAttributes Default attributes to use for each record
113 4
     */
114
    public function import(array $definitions, array $records = null, array $defaultAttributes = [])
115 4
    {
116 4
        $records = $records ?: $this->getRecords();
117
        $recordsByHandle = ArrayHelper::index($records, 'handle');
118
        foreach ($definitions as $handle => $definition) {
119
            $record = new $definition['class']();
120
            if (array_key_exists($handle, $recordsByHandle)) {
121
                $existing = $recordsByHandle[$handle];
122
                if (get_class($record) == get_class($existing)) {
123
                    $record = $existing;
124 11
                } else {
125
                    $record->id = $existing->id;
126 11
                    $record->setAttributes($existing->getAttributes());
127 11
                }
128
129
                if ($this->getRecordDefinition($record) === $definition) {
130
                    Schematic::info('- Skipping '.get_class($record).' '.$handle);
131
                    unset($recordsByHandle[$handle]);
132
                    continue;
133
                }
134
            }
135
136
            Schematic::info('- Saving '.get_class($record).' '.$handle);
137
            $this->setRecordAttributes($record, $definition, $defaultAttributes);
138
            if (!$this->saveRecord($record, $definition)) {
139
                $this->importError($record, $handle);
140
            }
141
            unset($recordsByHandle[$handle]);
142
        }
143
144
        if (Schematic::$force) {
145
            // Delete records not in definitions
146
            foreach ($recordsByHandle as $handle => $record) {
147
                Schematic::info('- Deleting '.get_class($record).' '.$handle);
148
                $this->deleteRecord($record);
149
            }
150
        }
151
    }
152
153
    /**
154
     * Log an import error.
155
     *
156
     * @param Model  $record
157
     * @param string $handle
158
     */
159
    protected function importError($record, $handle)
160
    {
161
        Schematic::warning('- Error importing '.get_class($record).' '.$handle);
162
        foreach ($record->getErrors() as $errors) {
163
            foreach ($errors as $error) {
164
                Schematic::error('   - '.$error);
165
            }
166
        }
167
    }
168
169
    /**
170
     * Set record attributes from definition.
171
     *
172
     * @param Model $record
173
     * @param array $definition
174
     */
175
    private function setRecordAttributes(Model &$record, array $definition, array $defaultAttributes)
176
    {
177
        $attributes = array_merge($definition['attributes'], $defaultAttributes);
178
        $record->setAttributes($attributes);
179
180
        // Set field layout
181
        if (array_key_exists('fieldLayout', $definition)) {
182
            $record->setFieldLayout($this->getFieldLayout($definition['fieldLayout']));
0 ignored issues
show
Documentation Bug introduced by
The method getFieldLayout does not exist on object<NerdsAndCompany\Schematic\Services\Base>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
183
        }
184
185
        // Set site settings
186
        if (array_key_exists('siteSettings', $definition)) {
187
            $siteSettings = [];
188
            foreach ($definition['siteSettings'] as $handle => $siteSettingDefinition) {
189
                $siteSetting = new $siteSettingDefinition['class']($siteSettingDefinition['attributes']);
190
                $site = Craft::$app->sites->getSiteByHandle($handle);
191
                if ($site) {
192
                    $siteSetting->siteId = $site->id;
193
                    $siteSettings[$site->id] = $siteSetting;
194
                } else {
195
                    Schematic::warning('  - Site '.$handle.' could not be found');
196
                }
197
            }
198
            $record->setSiteSettings($siteSettings);
199
        }
200
    }
201
202
    /**
203
     * Save a record.
204
     *
205
     * @param Model $record
206
     * @param array $definition
207
     *
208
     * @return bool
209
     */
210
    abstract protected function saveRecord(Model $record, array $definition);
211
212
    /**
213
     * Delete a record.
214
     *
215
     * @param Model $record
216
     *
217
     * @return bool
218
     */
219
    abstract protected function deleteRecord(Model $record);
220
}
221