Completed
Push — master ( f7e861...136cb7 )
by Nekrasov
02:32
created

BitrixModel::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
3
namespace Arrilot\BitrixModels\Models;
4
5
use Arrilot\BitrixModels\Queries\BaseQuery;
6
use Exception;
7
8
abstract class BitrixModel extends BaseBitrixModel
9
{
10
    /**
11
     * Bitrix entity object.
12
     *
13
     * @var object
14
     */
15
    public static $bxObject;
16
17
    /**
18
     * Corresponding object class name.
19
     *
20
     * @var string
21
     */
22
    protected static $objectClass = '';
23
24
    /**
25
     * Constructor.
26
     *
27
     * @param $id
28
     * @param $fields
29
     */
30
    public function __construct($id = null, $fields = null)
31
    {
32
        static::instantiateObject();
33
34
        $this->id = $id;
35
36
        $this->fill($fields);
37
    }
38
39
    /**
40
     * Activate model.
41
     *
42
     * @return bool
43
     */
44
    public function activate()
45
    {
46
        $this->fields['ACTIVE'] = 'Y';
47
48
        return $this->save(['ACTIVE']);
49
    }
50
51
    /**
52
     * Deactivate model.
53
     *
54
     * @return bool
55
     */
56
    public function deactivate()
57
    {
58
        $this->fields['ACTIVE'] = 'N';
59
60
        return $this->save(['ACTIVE']);
61
    }
62
63
    /**
64
     * Internal part of create to avoid problems with static and inheritance
65
     *
66
     * @param $fields
67
     *
68
     * @throws Exception
69
     *
70
     * @return static|bool
71
     */
72
    protected static function internalCreate($fields)
73
    {
74
        $model = new static(null, $fields);
75
        
76
        if ($model->onBeforeSave() === false || $model->onBeforeCreate() === false) {
77
            return false;
78
        }
79
        
80
        $bxObject = static::instantiateObject();
81
        $id = $bxObject->add($fields);
82
        $model->setId($id);
83
        
84
        $result = $id ? true : false;
85
        
86
        $model->onAfterCreate($result);
87
        $model->onAfterSave($result);
88
        
89
        if (!$result) {
90
            throw new Exception($bxObject->LAST_ERROR);
91
        }
92
        
93
        return $model;
94
    }
95
96
    /**
97
     * Delete model.
98
     *
99
     * @return bool
100
     */
101
    public function delete()
102
    {
103
        if ($this->onBeforeDelete() === false) {
104
            return false;
105
        }
106
107
        $result = static::$bxObject->delete($this->id);
108
109
        $this->onAfterDelete($result);
110
111
        return $result;
112
    }
113
114
    /**
115
     * Save model to database.
116
     *
117
     * @param array $selectedFields save only these fields instead of all.
118
     *
119
     * @return bool
120
     */
121
    public function save($selectedFields = [])
122
    {
123
        $selectedFields = is_array($selectedFields) ? $selectedFields : func_get_args();
124
125
        if ($this->onBeforeSave() === false || $this->onBeforeUpdate() === false) {
126
            return false;
127
        }
128
129
        $fields = $this->normalizeFieldsForSave($selectedFields);
130
        $result = !empty($fields) ? static::$bxObject->update($this->id, $fields) : false;
131
        if ($this instanceof ElementModel) {
132
            $savePropsResult = $this->saveProps($selectedFields);
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...
133
            $result = $result || $savePropsResult;
134
        }
135
136
        $this->onAfterUpdate($result);
137
        $this->onAfterSave($result);
138
139
        return $result;
140
    }
141
142
    /**
143
     * Scope to get only active items.
144
     *
145
     * @param BaseQuery $query
146
     *
147
     * @return BaseQuery
148
     */
149
    public function scopeActive($query)
150
    {
151
        $query->filter['ACTIVE'] = 'Y';
152
153
        return $query;
154
    }
155
156
    /**
157
     * Determine whether the field should be stopped from passing to "update".
158
     *
159
     * @param string $field
160
     * @param mixed  $value
161
     * @param array  $selectedFields
162
     *
163
     * @return bool
164
     */
165
    protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
166
    {
167
        $blacklistedFields = [
168
            'ID',
169
            'IBLOCK_ID',
170
            'PROPERTIES',
171
            'GROUPS',
172
            'PROPERTY_VALUES',
173
        ];
174
175
        return (!empty($selectedFields) && !in_array($field, $selectedFields))
176
            || in_array($field, $blacklistedFields)
177
            || (substr($field, 0, 1) === '~')
178
            || (substr($field, 0, 9) === 'PROPERTY_');
179
    }
180
181
    /**
182
     * Instantiate bitrix entity object.
183
     *
184
     * @throws Exception
185
     *
186
     * @return object
187
     */
188
    public static function instantiateObject()
189
    {
190
        if (static::$bxObject) {
191
            return static::$bxObject;
192
        }
193
194
        if (class_exists(static::$objectClass)) {
195
            return static::$bxObject = new static::$objectClass();
196
        }
197
198
        throw new Exception('Object initialization failed');
199
    }
200
201
    /**
202
     * Destroy bitrix entity object.
203
     *
204
     * @return void
205
     */
206
    public static function destroyObject()
207
    {
208
        static::$bxObject = null;
209
    }
210
}
211