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

BaseBitrixModel::fieldShouldNotBeSaved()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
nc 1
1
<?php
2
3
namespace Arrilot\BitrixModels\Models;
4
5
use Arrilot\BitrixModels\ModelEventsTrait;
6
use Arrilot\BitrixModels\Queries\BaseQuery;
7
use Exception;
8
9
abstract class BaseBitrixModel extends ArrayableModel
10
{
11
    use ModelEventsTrait;
12
13
    /**
14
     * Have fields been already fetched from DB?
15
     *
16
     * @var bool
17
     */
18
    protected $fieldsAreFetched = false;
19
    
20
    /**
21
     * Internal part of create to avoid problems with static and inheritance
22
     *
23
     * @param $fields
24
     *
25
     * @throws Exception
26
     *
27
     * @return static|bool
28
     */
29
    abstract protected static function internalCreate($fields);
30
    
31
    /**
32
     * Save model to database.
33
     *
34
     * @param array $selectedFields save only these fields instead of all.
35
     *
36
     * @return bool
37
     */
38
    abstract public function save($selectedFields = []);
39
40
    /**
41
     * Determine whether the field should be stopped from passing to "update".
42
     *
43
     * @param string $field
44
     * @param mixed  $value
45
     * @param array  $selectedFields
46
     *
47
     * @return bool
48
     */
49
    abstract protected function fieldShouldNotBeSaved($field, $value, $selectedFields);
50
    
51
    /**
52
     * Get all model attributes from cache or database.
53
     *
54
     * @return array
55
     */
56
    public function get()
57
    {
58
        $this->load();
59
        
60
        return $this->fields;
61
    }
62
63
    /**
64
     * Load model fields from database if they are not loaded yet.
65
     *
66
     * @return $this
67
     */
68
    public function load()
69
    {
70
        if (!$this->fieldsAreFetched) {
71
            $this->refresh();
72
        }
73
        
74
        return $this;
75
    }
76
77
    /**
78
     * Get model fields from cache or database.
79
     *
80
     * @return array
81
     */
82
    public function getFields()
83
    {
84
        if ($this->fieldsAreFetched) {
85
            return $this->fields;
86
        }
87
        
88
        return $this->refreshFields();
89
    }
90
91
    /**
92
     * Refresh model from database and place data to $this->fields.
93
     *
94
     * @return array
95
     */
96
    public function refresh()
97
    {
98
        return $this->refreshFields();
99
    }
100
101
    /**
102
     * Refresh model fields and save them to a class field.
103
     *
104
     * @return array
105
     */
106
    public function refreshFields()
107
    {
108
        if ($this->id === null) {
109
            return $this->fields = [];
110
        }
111
        
112
        $this->fields = static::query()->getById($this->id)->fields;
113
        
114
        $this->fieldsAreFetched = true;
115
        
116
        return $this->fields;
117
    }
118
119
    /**
120
     * Fill model fields if they are already known.
121
     * Saves DB queries.
122
     *
123
     * @param array $fields
124
     *
125
     * @return void
126
     */
127
    public function fill($fields)
128
    {
129
        if (!is_array($fields)) {
130
            return;
131
        }
132
        
133
        if (isset($fields['ID'])) {
134
            $this->id = $fields['ID'];
135
        }
136
        
137
        $this->fields = $fields;
138
        
139
        $this->fieldsAreFetched = true;
140
        
141
        if (method_exists($this, 'afterFill')) {
142
            $this->afterFill();
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\BaseBitrixModel as the method afterFill() does only exist in the following sub-classes of Arrilot\BitrixModels\Models\BaseBitrixModel: Arrilot\BitrixModels\Models\ElementModel, Arrilot\BitrixModels\Models\UserModel. 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...
143
        }
144
    }
145
146
    /**
147
     * Set current model id.
148
     *
149
     * @param $id
150
     */
151
    protected function setId($id)
152
    {
153
        $this->id = $id;
154
        $this->fields['ID'] = $id;
155
    }
156
157
    /**
158
     * Create new item in database.
159
     *
160
     * @param $fields
161
     *
162
     * @throws Exception
163
     *
164
     * @return static|bool
165
     */
166
    public static function create($fields)
167
    {
168
        return static::internalCreate($fields);
169
    }
170
171
    /**
172
     * Get count of items that match $filter.
173
     *
174
     * @param array $filter
175
     *
176
     * @return int
177
     */
178
    public static function count(array $filter = [])
179
    {
180
        return static::query()->filter($filter)->count();
181
    }
182
183
    /**
184
     * Get item by its id.
185
     *
186
     * @param int $id
187
     *
188
     * @return static|bool
189
     */
190
    public static function find($id)
191
    {
192
        return static::query()->getById($id);
193
    }
194
195
    /**
196
     * Update model.
197
     *
198
     * @param array $fields
199
     *
200
     * @return bool
201
     */
202
    public function update(array $fields = [])
203
    {
204
        $keys = [];
205
        foreach ($fields as $key => $value) {
206
            array_set($this->fields, $key, $value);
207
            $keys[] = $key;
208
        }
209
        
210
        return $this->save($keys);
211
    }
212
213
    /**
214
     * Create an array of fields that will be saved to database.
215
     *
216
     * @param $selectedFields
217
     *
218
     * @return array
219
     */
220
    protected function normalizeFieldsForSave($selectedFields)
221
    {
222
        $fields = [];
223
        if ($this->fields === null) {
224
            return [];
225
        }
226
        
227
        foreach ($this->fields as $field => $value) {
228
            if (!$this->fieldShouldNotBeSaved($field, $value, $selectedFields)) {
229
                $fields[$field] = $value;
230
            }
231
        }
232
        
233
        return $fields;
234
    }
235
236
    /**
237
     * Instantiate a query object for the model.
238
     *
239
     * @throws Exception
240
     *
241
     * @return BaseQuery
242
     */
243
    public static function query()
244
    {
245
        throw new Exception('public static function query() is not implemented');
246
    }
247
248
    /**
249
     * Handle dynamic static method calls into a new query.
250
     *
251
     * @param  string  $method
252
     * @param  array  $parameters
253
     * @return mixed
254
     */
255
    public static function __callStatic($method, $parameters)
256
    {
257
        return static::query()->$method(...$parameters);
258
    }
259
}
260