Completed
Push — master ( e1c530...93bd80 )
by Nekrasov
01:25
created

BitrixModel::save()   C

Complexity

Conditions 7
Paths 14

Size

Total Lines 26
Code Lines 19

Duplication

Lines 6
Ratio 23.08 %

Importance

Changes 0
Metric Value
dl 6
loc 26
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 19
nc 14
nop 1
1
<?php
2
3
namespace Arrilot\BitrixModels\Models;
4
5
use Arrilot\BitrixModels\Exceptions\ExceptionFromBitrix;
6
use Arrilot\BitrixModels\Queries\BaseQuery;
7
use LogicException;
8
9
abstract class BitrixModel extends BaseBitrixModel
10
{
11
    /**
12
     * Bitrix entity object.
13
     *
14
     * @var object
15
     */
16
    public static $bxObject;
17
18
    /**
19
     * Corresponding object class name.
20
     *
21
     * @var string
22
     */
23
    protected static $objectClass = '';
24
25
    /**
26
     * Fetch method and parameters.
27
     *
28
     * @var array|string
29
     */
30
    public static $fetchUsing = [
31
        'method' => 'Fetch',
32
        'params' => [],
33
    ];
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param $id
39
     * @param $fields
40
     */
41
    public function __construct($id = null, $fields = null)
42
    {
43
        static::instantiateObject();
44
45
        $this->id = $id;
46
47
        $this->fill($fields);
48
    }
49
50
    /**
51
     * Activate model.
52
     *
53
     * @return bool
54
     */
55
    public function activate()
56
    {
57
        $this->fields['ACTIVE'] = 'Y';
58
59
        return $this->save(['ACTIVE']);
60
    }
61
62
    /**
63
     * Deactivate model.
64
     *
65
     * @return bool
66
     */
67
    public function deactivate()
68
    {
69
        $this->fields['ACTIVE'] = 'N';
70
71
        return $this->save(['ACTIVE']);
72
    }
73
74
    /**
75
     * Internal part of create to avoid problems with static and inheritance
76
     *
77
     * @param $fields
78
     *
79
     * @throws ExceptionFromBitrix
80
     *
81
     * @return static|bool
82
     */
83
    protected static function internalCreate($fields)
84
    {
85
        $model = new static(null, $fields);
86
        
87
        if ($model->onBeforeSave() === false || $model->onBeforeCreate() === false) {
88
            return false;
89
        }
90
        
91
        $bxObject = static::instantiateObject();
92
        $id = $bxObject->add($model->fields);
93
        $model->setId($id);
94
        
95
        $result = $id ? true : false;
96
97
        $model->setEventErrorsOnFail($result, $bxObject);
98
        $model->onAfterCreate($result);
99
        $model->onAfterSave($result);
100
        $model->resetEventErrors();
101
        $model->throwExceptionOnFail($result, $bxObject);
102
103
        return $model;
104
    }
105
    
106
    /**
107
     * Delete model.
108
     *
109
     * @return bool
110
     * @throws ExceptionFromBitrix
111
     */
112
    public function delete()
113
    {
114
        if ($this->onBeforeDelete() === false) {
115
            return false;
116
        }
117
118
        $result = static::$bxObject->delete($this->id);
119
120
        $this->setEventErrorsOnFail($result, static::$bxObject);
121
        $this->onAfterDelete($result);
122
        $this->resetEventErrors();
123
        $this->throwExceptionOnFail($result, static::$bxObject);
124
125
        return $result;
126
    }
127
    
128
    /**
129
     * Save model to database.
130
     *
131
     * @param array $selectedFields save only these fields instead of all.
132
     * @return bool
133
     * @throws ExceptionFromBitrix
134
     */
135
    public function save($selectedFields = [])
136
    {
137
        $fieldsSelectedForSave = is_array($selectedFields) ? $selectedFields : func_get_args();
138
        $this->fieldsSelectedForSave = $fieldsSelectedForSave;
139 View Code Duplication
        if ($this->onBeforeSave() === false || $this->onBeforeUpdate() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140
            $this->fieldsSelectedForSave = [];
141
            return false;
142
        } else {
143
            $this->fieldsSelectedForSave = [];
144
        }
145
146
        $fields = $this->normalizeFieldsForSave($fieldsSelectedForSave);
147
        $result = !empty($fields) ? static::$bxObject->update($this->id, $fields) : false;
148
        if ($this instanceof ElementModel) {
149
            $savePropsResult = $this->saveProps($fieldsSelectedForSave);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Arrilot\BitrixModels\Models\BitrixModel as the method saveProps() does only exist in the following sub-classes of Arrilot\BitrixModels\Models\BitrixModel: Arrilot\BitrixModels\Models\ElementModel. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
150
            $result = $result || $savePropsResult;
151
        }
152
153
        $this->setEventErrorsOnFail($result, static::$bxObject);
154
        $this->onAfterUpdate($result);
155
        $this->onAfterSave($result);
156
        $this->resetEventErrors();
157
        $this->throwExceptionOnFail($result, static::$bxObject);
158
159
        return $result;
160
    }
161
162
    /**
163
     * Scope to get only active items.
164
     *
165
     * @param BaseQuery $query
166
     *
167
     * @return BaseQuery
168
     */
169
    public function scopeActive($query)
170
    {
171
        $query->filter['ACTIVE'] = 'Y';
172
173
        return $query;
174
    }
175
176
    /**
177
     * Determine whether the field should be stopped from passing to "update".
178
     *
179
     * @param string $field
180
     * @param mixed  $value
181
     * @param array  $selectedFields
182
     *
183
     * @return bool
184
     */
185
    protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
186
    {
187
        $blacklistedFields = [
188
            'ID',
189
            'IBLOCK_ID',
190
            'PROPERTIES',
191
            'GROUPS',
192
            'PROPERTY_VALUES',
193
        ];
194
195
        return (!empty($selectedFields) && !in_array($field, $selectedFields))
196
            || in_array($field, $blacklistedFields)
197
            || (substr($field, 0, 1) === '~')
198
            || (substr($field, 0, 9) === 'PROPERTY_');
199
    }
200
201
    /**
202
     * Instantiate bitrix entity object.
203
     *
204
     * @throws LogicException
205
     *
206
     * @return object
207
     */
208
    public static function instantiateObject()
209
    {
210
        if (static::$bxObject) {
211
            return static::$bxObject;
212
        }
213
214
        if (class_exists(static::$objectClass)) {
215
            return static::$bxObject = new static::$objectClass();
216
        }
217
218
        throw new LogicException('Object initialization failed');
219
    }
220
221
    /**
222
     * Destroy bitrix entity object.
223
     *
224
     * @return void
225
     */
226
    public static function destroyObject()
227
    {
228
        static::$bxObject = null;
229
    }
230
231
    /**
232
     * Set eventErrors field on error.
233
     *
234
     * @param bool $result
235
     * @param object $bxObject
236
     */
237
    protected function setEventErrorsOnFail($result, $bxObject)
238
    {
239
        if (!$result) {
240
            $this->eventErrors = (array) $bxObject->LAST_ERROR;
241
        }
242
    }
243
244
    /**
245
     * Throw bitrix exception on fail
246
     *
247
     * @param bool $result
248
     * @param object $bxObject
249
     * @throws ExceptionFromBitrix
250
     */
251
    protected function throwExceptionOnFail($result, $bxObject)
252
    {
253
        if (!$result) {
254
            throw new ExceptionFromBitrix($bxObject->LAST_ERROR);
255
        }
256
    }
257
}
258