Completed
Pull Request — master (#114)
by Bart
05:42
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->attributes,
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
                $record = $recordsByHandle[$handle];
122
                if ($this->getRecordDefinition($record) === $definition) {
123
                    Schematic::info('- Skipping '.get_class($record).' '.$handle);
124 11
                    unset($recordsByHandle[$handle]);
125
                    continue;
126 11
                }
127 11
            }
128
129
            Schematic::info('- Saving '.get_class($record).' '.$handle);
130
            $this->setRecordAttributes($record, $definition, $defaultAttributes);
131
            if (!$this->saveRecord($record, $definition)) {
132
                $this->importError($record, $handle);
133
            }
134
            unset($recordsByHandle[$handle]);
135
        }
136
137
        if (Schematic::$force) {
138
            // Delete records not in definitions
139
            foreach ($recordsByHandle as $handle => $record) {
140
                Schematic::info('- Deleting '.get_class($record).' '.$handle);
141
                $this->deleteRecord($record);
142
            }
143
        }
144
    }
145
146
    /**
147
     * Log an import error.
148
     *
149
     * @param Model  $record
150
     * @param string $handle
151
     */
152
    protected function importError($record, $handle)
153
    {
154
        Schematic::warning('- Error importing '.get_class($record).' '.$handle);
155
        foreach ($record->getErrors() as $errors) {
156
            foreach ($errors as $error) {
157
                Schematic::error('   - '.$error);
158
            }
159
        }
160
    }
161
162
    /**
163
     * Set record attributes from definition.
164
     *
165
     * @param Model $record
166
     * @param array $definition
167
     */
168
    private function setRecordAttributes(Model &$record, array $definition, array $defaultAttributes)
169
    {
170
        $attributes = array_merge($definition['attributes'], $defaultAttributes);
171
        $record->setAttributes($attributes);
172
173
        // Set field layout
174
        if (array_key_exists('fieldLayout', $definition)) {
175
            $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...
176
        }
177
178
        // Set site settings
179
        if (array_key_exists('siteSettings', $definition)) {
180
            $siteSettings = [];
181
            foreach ($definition['siteSettings'] as $handle => $siteSettingDefinition) {
182
                $siteSetting = new $siteSettingDefinition['class']($siteSettingDefinition['attributes']);
183
                $site = Craft::$app->sites->getSiteByHandle($handle);
184
                if ($site) {
185
                    $siteSetting->siteId = $site->id;
186
                    $siteSettings[] = $siteSetting;
187
                } else {
188
                    Schematic::warning('  - Site '.$handle.' could not be found');
189
                }
190
            }
191
            $record->setSiteSettings($siteSettings);
192
        }
193
    }
194
195
    /**
196
     * Save a record.
197
     *
198
     * @param Model $record
199
     * @param array $definition
200
     *
201
     * @return bool
202
     */
203
    abstract protected function saveRecord(Model $record, array $definition);
204
205
    /**
206
     * Delete a record.
207
     *
208
     * @param Model $record
209
     *
210
     * @return bool
211
     */
212
    abstract protected function deleteRecord(Model $record);
213
}
214