Completed
Pull Request — master (#114)
by Bart
10:52 queued 57s
created

Base::setRecordAttributes()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 0
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 8
nop 3
crap 30
1
<?php
2
3
namespace NerdsAndCompany\Schematic\Services;
4
5
use craft\base\Model;
6
use craft\helpers\ArrayHelper;
7
use yii\base\Component as BaseComponent;
8
use NerdsAndCompany\Schematic\Behaviors\FieldLayoutBehavior;
9
use NerdsAndCompany\Schematic\Behaviors\SourcesBehavior;
10
use NerdsAndCompany\Schematic\Interfaces\MappingInterface;
11
use NerdsAndCompany\Schematic\Schematic;
12
use LogicException;
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
        return $result;
60
    }
61
62
    /**
63
     * Get single record definition
64
     *
65
     * @param  Model $record
66
     * @return array
67
     */
68
    protected function getRecordDefinition(Model $record)
69
    {
70
        $definition = [
71
          'class' => get_class($record),
72
          'attributes' => $record->attributes,
73
        ];
74
        unset($definition['attributes']['id']);
75
        unset($definition['attributes']['dateCreated']);
76
        unset($definition['attributes']['dateUpdated']);
77
78
        // Define sources
79
        if (isset($definition['attributes']['sources'])) {
80
            $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...
81
        }
82
83
        if (isset($definition['attributes']['source'])) {
84
            $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...
85
        }
86
87
        // Define field layout
88
        if (isset($definition['attributes']['fieldLayoutId'])) {
89
            $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...
90
            unset($definition['attributes']['fieldLayoutId']);
91
        }
92
93
        // Define site settings
94
        if (isset($record->siteSettings)) {
95
            $definition['siteSettings'] = [];
96
            foreach ($record->getSiteSettings() as $siteSetting) {
97
                $definition['siteSettings'][$siteSetting->site->handle] = $this->getRecordDefinition($siteSetting);
98
            }
99
        }
100
101
        return $definition;
102
    }
103
104
    /**
105
     * Import asset volumes.
106
     *
107
     * @param array $definitions
108
     * @param Model $records The existing records
109
     * @param array $defaultAttributes Default attributes to use for each record
110
     */
111
    public function import(array $definitions, array $records = [], array $defaultAttributes = [])
112
    {
113 4
        $records = $records ?: $this->getRecords();
114
        $recordsByHandle = ArrayHelper::index($records, 'handle');
115 4
        foreach ($definitions as $handle => $definition) {
116 4
            $record = new $definition['class']();
117
            if (array_key_exists($handle, $recordsByHandle)) {
118
                $record = $recordsByHandle[$handle];
119
                if ($this->getRecordDefinition($record) === $definition) {
120
                    Schematic::info('- Skipping '.get_class($record).' '.$handle);
121
                    unset($recordsByHandle[$handle]);
122
                    continue;
123
                }
124 11
            }
125
126 11
            Schematic::info('- Saving '.get_class($record).' '.$handle);
127 11
            $this->setRecordAttributes($record, $definition, $defaultAttributes);
128
            if (!$this->saveRecord($record, $definition)) {
129
                $this->importError($record, $handle);
130
            }
131
            unset($recordsByHandle[$handle]);
132
        }
133
134
        if (Schematic::$force) {
135
            // Delete volumes not in definitions
136
            foreach ($recordsByHandle as $handle => $record) {
137
                Schematic::info('- Deleting '.get_class($record).' '.$handle);
138
                $this->deleteRecord($record);
139
            }
140
        }
141
    }
142
143
    /**
144
     * Log an import error
145
     *
146
     * @param  Model $record
147
     * @param  string $handle
148
     */
149
    protected function importError($record, $handle)
150
    {
151
        Schematic::warning('- Error importing '.get_class($record).' '.$handle);
152
        foreach ($record->getErrors() as $errors) {
153
            foreach ($errors as $error) {
154
                Schematic::error('   - '.$error);
155
            }
156
        }
157
    }
158
159
    /**
160
     * Set record attributes from definition
161
     *
162
     * @param Model $record
163
     * @param array $definition
164
     */
165
    private function setRecordAttributes(Model &$record, array $definition, array $defaultAttributes)
166
    {
167
        $attributes = array_merge($definition['attributes'], $defaultAttributes);
168
        $record->setAttributes($attributes);
169
170
        // Set field layout
171
        if (array_key_exists('fieldLayout', $definition)) {
172
            $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...
173
        }
174
175
        // Set site settings
176
        if (array_key_exists('siteSettings', $definition)) {
177
            $siteSettings = [];
178
            foreach ($definition['siteSettings'] as $handle => $siteSettingDefinition) {
179
                $siteSetting = new $siteSettingDefinition['class']($siteSettingDefinition['attributes']);
180
                $site = Craft::$app->sites->getSiteByHandle($handle);
181
                if ($site) {
182
                    $siteSetting->siteId = $site->id;
183
                    $siteSettings[] = $siteSetting;
184
                } else {
185
                    Schematic::warning('  - Site '.$handle.' could not be found');
186
                }
187
            }
188
            $record->setSiteSettings($siteSettings);
189
        }
190
    }
191
192
    /**
193
     * Save a record
194
     *
195
     * @param Model $record
196
     * @param array $definition
197
     * @return boolean
198
     */
199
    abstract protected function saveRecord(Model $record, array $definition);
200
201
    /**
202
     * Delete a record
203
     *
204
     * @param Model $record
205
     * @return boolean
206
     */
207
    abstract protected function deleteRecord(Model $record);
208
}
209