Completed
Push — master ( cef71e...189e10 )
by Nate
01:56
created

Relationship::resolve()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 21
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 15
nc 6
nop 3
crap 30
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/domains/license
6
 * @link       https://www.flipboxfactory.com/software/domains/
7
 */
8
9
namespace flipbox\domains\services;
10
11
use Craft;
12
use craft\base\Element;
13
use craft\base\ElementInterface;
14
use craft\events\ModelEvent;
15
use craft\helpers\ArrayHelper;
16
use flipbox\domains\db\DomainsQuery;
17
use flipbox\domains\Domains as DomainsPlugin;
18
use flipbox\domains\fields\Domains;
19
use flipbox\domains\models\Domain;
20
use yii\base\Component;
21
use yii\base\Exception;
22
23
/**
24
 * @author Flipbox Factory <[email protected]>
25
 * @since 1.0.0
26
 */
27
class Relationship extends Component
28
{
29
    /**
30
     * @param Domains $field
31
     * @param string $domain
32
     * @param int $elementId
33
     * @param int|null $siteId
34
     * @return bool
35
     */
36
    public function exists(
37
        Domains $field,
38
        string $domain,
39
        int $elementId,
40
        int $siteId = null
41
    ): bool {
42
        return (new DomainsQuery($field))
43
                ->elementId($elementId)
44
                ->domain($domain)
45
                ->siteId($siteId)
46
                ->count() > 0;
47
    }
48
49
    /**
50
     * @param Domains $field
51
     * @param string $domain
52
     * @param int $elementId
53
     * @param int|null $siteId
54
     * @return Domain|null
55
     */
56
    public function find(
57
        Domains $field,
58
        string $domain,
59
        int $elementId,
60
        int $siteId = null
61
    ) {
62
        return (new DomainsQuery($field))
63
            ->elementId($elementId)
64
            ->domain($domain)
65
            ->siteId($siteId)
66
            ->one();
67
    }
68
69
    /**
70
     * @param Domains $field
71
     * @param string $domain
72
     * @param int $elementId
73
     * @param int|null $siteId
74
     * @return Domain
75
     * @throws Exception
76
     */
77
    public function get(
78
        Domains $field,
79
        string $domain,
80
        int $elementId,
81
        int $siteId = null
82
    ) {
83
84
        if (!$model = $this->find($field, $domain, $elementId, $siteId)) {
85
            throw new Exception(
86
                Craft::t(
87
                    'domains',
88
                    "Unable to find domain '{domain}'",
89
                    [
90
                        'domain' => $domain
91
                    ]
92
                )
93
            );
94
        }
95
96
        return $model;
97
    }
98
99
    /**
100
     * Associate/Dissociate
101
     *
102
     * @param Domains $field
103
     * @param DomainsQuery $query
104
     * @param ElementInterface $element
105
     * @throws Exception
106
     */
107
    public function resolve(Domains $field, DomainsQuery $query, ElementInterface $element)
108
    {
109
        /** @var Element $element */
110
        // If we have a cached result, let's save them
111
        if (($cachedResult = $query->getCachedResult()) !== null) {
112
            $currentDomains = (new DomainsQuery($field))
113
                ->siteId($element->siteId)
1 ignored issue
show
Bug introduced by
Accessing siteId on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
114
                ->elementId($element->getId())
115
                ->indexBy('domain')
116
                ->all();
117
118
            foreach ($cachedResult as $model) {
119
                $model->setElementId($element->getId());
120
                $model->siteId = $element->siteId;
1 ignored issue
show
Bug introduced by
Accessing siteId on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
121
122
                if (!DomainsPlugin::getInstance()->getRelationship()->associate($model)) {
123
                    throw new Exception("Unable to associate domain");
124
                }
125
126
                ArrayHelper::remove($currentDomains, $model->domain);
127
            }
128
129
            foreach ($currentDomains as $domain) {
130
                DomainsPlugin::getInstance()->getRelationship()->dissociate($domain);
131
            }
132
        }
133
    }
134
135
    /**
136
     * @param Domain $model
137
     * @param bool $runValidation
138
     * @param null $attributes
139
     * @return bool
140
     * @throws \Exception
141
     */
142
    public function associate(Domain $model, bool $runValidation = true, $attributes = null)
143
    {
144
        $existingDomain = $this->find(
145
            $model->getField(),
146
            $model->domain,
147
            $model->getElementId(),
148
            $model->siteId
149
        );
150
151
        // Nothing to update
152
        if ($existingDomain &&
153
            $existingDomain->status === $model->status &&
154
            $existingDomain->sortOrder === $model->sortOrder
155
        ) {
156
            return true;
157
        }
158
159
        // Validate
160
        if ($runValidation && !$model->validate($attributes)) {
161
            Craft::info('Domain not saved due to validation error.', __METHOD__);
162
            return false;
163
        }
164
165
        $isNew = (bool)$existingDomain;
166
167
        // Create event
168
        $event = new ModelEvent([
169
            'isNew' => $isNew
170
        ]);
171
172
        // Db transaction
173
        $transaction = Craft::$app->getDb()->beginTransaction();
174
175
        try {
176
            // The 'before' event
177
            if (!$model->beforeSave($event)) {
178
                $transaction->rollBack();
179
180
                return false;
181
            }
182
183
            // Look for an existing domain (that we'll update)
184
            if ($isNew) {
185
                $success = $this->updateDomain($model);
186
            } else {
187
                $success = $this->insertDomain($model);
188
            }
189
190
            // Insert record
191
            if (!$success) {
192
                // Transfer errors to model
193
                $model->addError(
194
                    Craft::t('domains', 'Unable to save domain.')
195
                );
196
197
                $transaction->rollBack();
198
199
                return false;
200
            }
201
202
            // The 'after' event
203
            if (!$model->afterSave($event)) {
204
                $transaction->rollBack();
205
206
                return false;
207
            }
208
        } catch (\Exception $e) {
209
            $transaction->rollBack();
210
211
            throw $e;
212
        }
213
214
        $transaction->commit();
215
216
        return true;
217
    }
218
219
    /**
220
     * @param Domain $model
221
     * @return bool
222
     * @throws \Exception
223
     */
224
    public function dissociate(Domain $model)
225
    {
226
        // The event to trigger
227
        $event = new ModelEvent();
228
229
        // Db transaction
230
        $transaction = Craft::$app->getDb()->beginTransaction();
231
232
        try {
233
            // The 'before' event
234
            if (!$model->beforeDelete($event)) {
235
                $transaction->rollBack();
236
237
                return false;
238
            }
239
240
            // Delete command
241
            Craft::$app->getDb()->createCommand()
242
                ->delete(
243
                    DomainsPlugin::getInstance()->getField()->getTableName($model->getField()),
244
                    [
245
                        'domain' => $model->domain,
246
                        'elementId' => $model->getElementId(),
247
                        'siteId' => $model->siteId
248
                    ]
249
                )->execute();
250
251
            // The 'after' event
252
            if (!$model->afterDelete($event)) {
253
                // Roll back db transaction
254
                $transaction->rollBack();
255
256
                return false;
257
            }
258
        } catch (\Exception $e) {
259
            // Roll back all db actions (fail)
260
            $transaction->rollback();
261
262
            throw $e;
263
        }
264
265
        $transaction->commit();
266
267
        return true;
268
    }
269
270
    /**
271
     * @param Domain $model
272
     * @return bool
273
     */
274
    private function updateDomain(Domain $model): bool
275
    {
276
        return (bool)Craft::$app->getDb()->createCommand()
277
            ->update(
278
                DomainsPlugin::getInstance()->getField()->getTableName($model->getField()),
279
                $this->upsertColumns($model),
280
                [
281
                    'elementId' => $model->getElementId(),
282
                    'domain' => $model->domain,
283
                    'siteId' => $model->siteId
284
                ]
285
            )
286
            ->execute();
287
    }
288
289
    /**
290
     * @param Domain $domain
291
     * @return bool
292
     */
293
    private function insertDomain(Domain $domain): bool
294
    {
295
        return (bool)Craft::$app->getDb()->createCommand()
296
            ->insert(
297
                DomainsPlugin::getInstance()->getField()->getTableName($domain->getField()),
298
                $this->upsertColumns($domain)
299
            )
300
            ->execute();
301
    }
302
303
    /**
304
     * @param Domain $model
305
     * @return array
306
     */
307
    private function upsertColumns(Domain $model): array
308
    {
309
        return [
310
            'domain' => $model->domain,
311
            'elementId' => $model->getElementId(),
312
            'siteId' => $this->resolveSiteId($model->siteId),
0 ignored issues
show
Bug introduced by
It seems like $model->siteId can also be of type integer; however, flipbox\domains\services...onship::resolveSiteId() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
313
            'sortOrder' => $model->sortOrder,
314
            'status' => $model->status
315
        ];
316
    }
317
318
    /**
319
     * @param null $siteId
320
     * @return int
321
     */
322
    private function resolveSiteId($siteId = null): int
323
    {
324
        if ($siteId) {
325
            return $siteId;
326
        }
327
328
        return Craft::$app->getSites()->currentSite->id;
329
    }
330
}
331