Completed
Push — master ( d9dfd9...4ffba9 )
by
unknown
01:27
created

Aeria/Field/Fields/BaseField.php (1 issue)

strict.coding_against_specific_subtype

Bug Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Aeria\Field\Fields;
4
5
use Aeria\Aeria;
6
use Aeria\Field\FieldError;
7
use Aeria\Validator\Validator;
8
use Aeria\Structure\Node;
9
use Aeria\Field\Interfaces\FieldInterface;
10
11
/**
12
 * BaseField is the class that represents every Aeria field.
13
 *
14
 * @category Field
15
 *
16
 * @author   Andrea Longo <[email protected]>
17
 * @license  https://github.com/caffeinalab/aeria/blob/master/LICENSE  MIT license
18
 *
19
 * @see     https://github.com/caffeinalab/aeria
20
 */
21
class BaseField extends Node implements FieldInterface
22
{
23
    protected $original_config;
24
25
    public $is_multiple_field = false;
26
27
    /**
28
     * Transform the config array; note that this does not operate on
29
     * `$this->config`: this way it can be called from outside.
30
     *
31
     * @param array $config the field's config
32
     *
33
     * @return array the transformed config
34
     */
35
    public static function transformConfig(array $config)
36
    {
37
        return $config;
38
    }
39
40
    /**
41
     * Constructs the field.
42
     *
43
     * @param string $parent_key the field's parent key
44
     * @param array  $config     the field's config
45
     * @param array  $sections   Aeria's sections config
46
     * @param array  $index      index for multiple fields
47
     *
48
     * @since  Method available since Release 3.0.0
49
     */
50
    public function __construct($parent_key, $config, $sections, $index = null)
51
    {
52
        $this->parent_key = $parent_key;
53
        $this->original_config = isset($config['original_config']) ? $config['original_config'] : $config;
54
55
        unset($config['original_config']);
56
57
        $this->config = $config;
58
59
        $this->id = isset($config['id'])
60
            ? $config['id']
61
            : null;
62
        $this->index = $index;
63
        $this->key = $this->getKey();
64
        $this->sections = $sections;
65
    }
66
67
    /**
68
     * Checks whether a field should be child of another.
69
     *
70
     * @param Node $possible_parent the field's possible parent
71
     *
72
     * @return bool whether the field should be child of $possible_parent
73
     *
74
     * @since  Method available since Release 3.0.0
75
     */
76
    public function shouldBeChildOf(Node $possible_parent)
77
    {
78
        if ($possible_parent->is_multiple_field) {
79
            if (preg_match('/^'.$possible_parent->getKey().'.{1,}/', $this->getKey())) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class Aeria\Structure\Node as the method getKey() does only exist in the following sub-classes of Aeria\Structure\Node: Aeria\Field\Fields\BaseField, Aeria\Field\Fields\DateRangeField, Aeria\Field\Fields\FieldsetField, Aeria\Field\Fields\GalleryField, Aeria\Field\Fields\MapField, Aeria\Field\Fields\MediaField, Aeria\Field\Fields\PictureField, Aeria\Field\Fields\PostTypesField, Aeria\Field\Fields\RelationField, Aeria\Field\Fields\RepeaterField, Aeria\Field\Fields\SectionsField, Aeria\Field\Fields\SelectField, Aeria\Field\Fields\SwitchField, Aeria\Field\Fields\TermsField. 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...
80
                return true;
81
            } else {
82
                return false;
83
            }
84
        } elseif (get_class($possible_parent) == 'RootNode') { // Check if possible_parent is root
85
            return true;
86
        } else {
87
            return false;
88
        }
89
    }
90
91
    /**
92
     * Gets the field full key.
93
     *
94
     * @return string the field's key
95
     *
96
     * @since  Method available since Release 3.0.0
97
     */
98
    public function getKey()
99
    {
100
        return $this->parent_key
101
            .(!is_null($this->index) ? '-'.$this->index : '')
102
            .(!is_null($this->id) ? '-'.$this->id : '');
103
    }
104
105
    /**
106
     * Gets the field's value.
107
     *
108
     * @param array $saved_fields the FieldGroup's saved fields
109
     * @param bool  $skip_filter  whether to skip or not WP's filter
110
     *
111
     * @return mixed the field's value
112
     *
113
     * @since  Method available since Release 3.0.0
114
     */
115
    public function get(array $saved_fields, bool $skip_filter = false)
116
    {
117
        if (!isset($saved_fields[$this->key])) {
118
            return null;
119
        }
120
        if (!$skip_filter) {
121
            $saved_fields[$this->key] = apply_filters('aeria_get_base', $saved_fields[$this->key], $this->config);
122
            $saved_fields[$this->key] = apply_filters('aeria_get_'.$this->key, $saved_fields[$this->key], $this->config);
123
        }
124
        if (is_array($saved_fields[$this->key])) {
125
            return $saved_fields[$this->key][0];
126
        } else {
127
            return $saved_fields[$this->key];
128
        }
129
    }
130
131
    /**
132
     * Gets the field's value and its errors.
133
     *
134
     * @param array $saved_fields the FieldGroup's saved fields
135
     * @param array $errors       the saving errors
136
     *
137
     * @return array the field's config, hydrated with values and errors
138
     *
139
     * @since  Method available since Release 3.0.0
140
     */
141
    public function getAdmin(array $saved_fields, array $errors)
142
    {
143
        if (isset($errors[$this->key])) {
144
            $result = [
145
                'value' => $errors[$this->key]['value'],
146
                'error' => $errors[$this->key]['message'],
147
            ];
148
        } else {
149
            $result = [
150
                'value' => $this->get($saved_fields, true),
151
            ];
152
        }
153
154
        $config = array_merge(
155
            $this->config,
156
            $result
157
        );
158
159
        $config = apply_filters('aeria_get_admin_base', $config);
160
        $config = apply_filters('aeria_get_admin_'.$this->id, $config);
161
        $config = apply_filters('aeria_get_admin_'.$this->key, $config);
162
163
        return $config;
164
    }
165
166
    /**
167
     * Saves the new values to the fields.
168
     *
169
     * @param int       $context_ID        the context ID. For posts, post's ID
170
     * @param string    $context_type      the context type. Right now, options|meta
171
     * @param array     $saved_fields      the saved fields
172
     * @param array     $new_values        the values we're saving
173
     * @param Validator $validator_service Aeria's validator service
174
     * @param Query     $query_service     Aeria's query service
175
     *
176
     * @return array the results of the saving
177
     *
178
     * @since  Method available since Release 3.0.0
179
     */
180
    public function set($context_ID, $context_type, array $saved_fields, array $new_values, $validator_service, $query_service)
181
    {
182
        $value = isset($new_values[$this->key]) ? $new_values[$this->key] : null;
183
        $old = isset($saved_fields[$this->key][0]) ? $saved_fields[$this->key][0] : '';
184
        $value = apply_filters('aeria_set_'.$this->key, $value, $this->config);
185
        if ($value == $old) {
186
            return ['value' => $value];
187
        }
188
        if (is_null($value) || $value == '') {
189
            $this->deleteField($context_ID, $context_type, $query_service);
190
191
            return ['value' => $value];
192
        } else {
193
            $validators = (isset($this->config['validators'])) ? $this->config['validators'] : '';
194
            $error = $validator_service->validate($value, $validators);
195
196
            if (!$error['status']) {
197
                $this->saveField($context_ID, $context_type, $value, $old);
198
199
                return ['value' => $value];
200
            } else {
201
                FieldError::make($context_ID)
202
                    ->addError($this->key, $error);
203
204
                return $error;
205
            }
206
        }
207
    }
208
209
    /**
210
     * Saves a single field to the DB.
211
     *
212
     * @param int    $context_ID   the context ID. For posts, post's ID
213
     * @param string $context_type the context type. Right now, options|meta
214
     * @param mixed  $value        the new value
215
     * @param mixed  $old          the old value
216
     *
217
     * @throws Exception if the node context is invalid
218
     *
219
     * @since  Method available since Release 3.0.0
220
     */
221 View Code Duplication
    private function saveField($context_ID, $context_type, $value, $old)
222
    {
223
        switch ($context_type) {
224
        case 'options':
225
            update_option($this->key, $value);
226
            break;
227
        case 'meta':
228
            update_post_meta($context_ID, $this->key, $value, $old);
229
            break;
230
        default:
231
            throw new Exception('Node context is not valid.');
232
            break;
233
        }
234
    }
235
236
    /**
237
     * Deletes a field value.
238
     *
239
     * @param int    $context_ID    the context ID. For posts, post's ID
240
     * @param string $context_type  the context type. Right now, options|meta
241
     * @param Query  $query_service Aeria's query service
242
     *
243
     * @throws Exception if the node context is invalid
244
     *
245
     * @since  Method available since Release 3.0.0
246
     */
247 View Code Duplication
    private function deleteField($context_ID, $context_type, $query_service)
248
    {
249
        switch ($context_type) {
250
        case 'options':
251
            delete_option($this->key);
252
            break;
253
        case 'meta':
254
            delete_post_meta($context_ID, $this->key);
255
            break;
256
257
        default:
258
            throw new Exception('Node context is not valid.');
259
            break;
260
        }
261
    }
262
}
263