Completed
Push — master ( 7abe43...7deb80 )
by Askupa
01:24
created

Form::get_component()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 8
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Amarkal\UI;
4
5
/**
6
 * Implements a Form controller.
7
 * 
8
 * The form object is used to encapsulate UI components and the update/validation 
9
 * process into a single entity.
10
 */
11
class Form
12
{
13
    /**
14
     * The list of AbstractComponent type objects to be updated.
15
     * 
16
     * @var AbstractComponent[] objects array.
17
     */
18
    private $component_list;
19
    
20
    /**
21
     * The old component values array. These values are used if the new values
22
     * are invalid.
23
     * Structure: component_name => component_value
24
     * 
25
     * @var array Old values array.
26
     */
27
    private $old_instance;
28
    
29
    /**
30
     * The new component values array.
31
     * Structure: component_name => component_value
32
     * 
33
     * @var array New values array.
34
     */
35
    private $new_instance;
36
    
37
    /**
38
     * The final values array, after filtering and validation.
39
     * This is returned by the update function.
40
     * Structure: component_name => component_value
41
     * 
42
     * @var array Final values array. 
43
     */
44
    private $final_instance = array();
45
    
46
    /**
47
     * Array of names of components that were invalid, and the error message 
48
     * recieved.
49
     * Structure: component_name => error_message
50
     * 
51
     * @var string[] Array of error messages. 
52
     */
53
    private $errors = array();
54
    
55
    /**
56
     * When instantiating a form, a list of component arguments arrays must be 
57
     * provided. Each arguments array must have a 'type' argument, in addition 
58
     * to the component's original arguments.
59
     * 
60
     * @param ComponentList $component_list
61
     */
62
    public function __construct( ComponentList $component_list = null )
63
    {
64
        if(null === $component_list)
65
        {
66
            $component_list = new ComponentList();
67
        }
68
        $this->component_list = $component_list;
0 ignored issues
show
Documentation Bug introduced by
It seems like $component_list of type object<Amarkal\UI\ComponentList> is incompatible with the declared type array<integer,object<Ama...\UI\AbstractComponent>> of property $component_list.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
69
    }
70
    
71
    /**
72
     * Get the updated component values (validated, filtered or ignored).
73
     * 
74
     * Loops through each component and acts according to its type:
75
     * - Disableable components are ignored if they are disabled.
76
     * - Validatable components are validated using their validation function. 
77
     *   If the new value is invalid, the old value will be used.
78
     * - Filterable components are filtered using their filter function.
79
     * - Non-value components are skipped altogether.
80
     * 
81
     * Each component is also set with its new value.
82
     * 
83
     * @param array $new_instance The new component values array.
84
     * @param array $old_instance The old component values array.
85
     * 
86
     * @return array The updated values array.
87
     */
88
    public function update( array $new_instance = array(), array $old_instance = array() )
89
    {
90
        $this->old_instance   = array_merge($this->get_defaults(),$old_instance);
91
        $this->new_instance   = array_merge($this->old_instance,$new_instance);
92
        $this->final_instance = $this->new_instance;
93
        
94
        foreach ( $this->component_list->get_value_components() as $component ) 
0 ignored issues
show
Bug introduced by
The method get_value_components cannot be called on $this->component_list (of type array<integer,object<Ama...\UI\AbstractComponent>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
95
        {
96
            // Update individual fields, as well as the composite parent field.
97
            $this->update_component( $component );
98
        }
99
        
100
        return $this->final_instance;
101
    }
102
    
103
    /**
104
     * Reset all fields to their default values.
105
     * 
106
     * @return array The updated values array.
107
     */
108
    public function reset()
109
    {
110
        foreach( $this->component_list->get_value_components() as $c )
0 ignored issues
show
Bug introduced by
The method get_value_components cannot be called on $this->component_list (of type array<integer,object<Ama...\UI\AbstractComponent>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
111
        {
112
            $c->value = $c->default;
113
            $this->final_instance[$c->name] = $c->default;
114
        }
115
        return $this->final_instance;
116
    }
117
    
118
    /**
119
     * Get the list of error messages for components that could not be validated.
120
     * Structure: components_name => error_message
121
     * 
122
     * @return string[] Array of error messages. 
123
     */
124
    public function get_errors()
125
    {
126
        return $this->errors;
127
    }
128
    
129
    /**
130
     * Get all components.
131
     * 
132
     * @return array
133
     */
134
    public function get_component_list()
135
    {
136
        return $this->component_list;
137
    }
138
    
139
    /**
140
     * Update the component's value with the new value.
141
     * NOTE: this function also updates the $final_instance
142
     * array.
143
     * 
144
     * @param ValueComponentInterface $component The component to validate.
145
     */
146
    private function update_component( ValueComponentInterface $component )
147
    {
148
        $component->value = $this->final_instance[$component->name];
0 ignored issues
show
Bug introduced by
Accessing value on the interface Amarkal\UI\ValueComponentInterface 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...
Bug introduced by
Accessing name on the interface Amarkal\UI\ValueComponentInterface 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...
149
        
150
        // Skip if this field is disabled
151
        if( $this->is_disabled($component) )
0 ignored issues
show
Documentation introduced by
$component is of type object<Amarkal\UI\ValueComponentInterface>, but the function expects a object<Amarkal\UI\UI\AbstractComponent>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
152
        {
153
            return;
154
        }
155
156
        // Apply user-defined filter
157
        $this->filter( $component );
0 ignored issues
show
Documentation introduced by
$component is of type object<Amarkal\UI\ValueComponentInterface>, but the function expects a object<Amarkal\UI\UI\AbstractComponent>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
158
        
159
        // Validate value
160
        $this->validate( $component );
0 ignored issues
show
Documentation introduced by
$component is of type object<Amarkal\UI\ValueComponentInterface>, but the function expects a object<Amarkal\UI\UI\AbstractComponent>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
161
    }
162
    
163
    /**
164
     * Check if the given component is disabled.
165
     * 
166
     * @param UI\AbstractComponent $component
167
     * @return boolean
168
     */
169
    private function is_disabled( $component )
170
    {
171
        return 
172
            $component instanceof DisableableComponentInterface &&
173
            (
174
                true === $component->disabled ||
175
                true === $component->readonly
176
            );
177
    }
178
179
    /**
180
     * Filter the component's value using its filter function (if applicable)
181
     * 
182
     * @param UI\AbstractComponent $component
183
     */
184
    private function filter( $component )
185
    {
186
        if( $component instanceof FilterableComponentInterface )
187
        {
188
            $filter = $component->filter;
189
190
            if( is_callable( $filter ) ) 
191
            {
192
                $component->value = \call_user_func_array($filter, array($this->final_instance[$component->name]));
193
                $this->final_instance[$component->name] = $component->value;
194
            }
195
        }
196
    }
197
    
198
    /**
199
     * Validate the component's value using its validation function.
200
     * 
201
     * If the value is invalid, the old value is used, and an error message is
202
     * saved into the errors array as component_name => error_message.
203
     * 
204
     * @param UI\AbstractComponent $component The component to validate.
205
     */
206
    private function validate( $component )
207
    {
208
        if( !($component instanceof ValidatableComponentInterface) ) return;
209
        
210
        $name     = $component->name;
211
        $validate = $component->validation;
212
        
213
        $component->validity = $component::VALID;
214
        
215
        if(is_callable($validate))
216
        {
217
            $error = '';
218
            $valid = \call_user_func_array($validate, array($this->final_instance[$name], &$error));
219
            
220
            // Invalid input, use old instance or default value
221
            if ( true !== $valid ) 
222
            {
223
                $this->errors[$name]         = $error ? $error : ValidatableComponentInterface::DEFAULT_MESSAGE;
224
                $component->value            = $this->old_instance[$name];
225
                $this->final_instance[$name] = $this->old_instance[$name];
226
                $component->set_validity($component::INVALID);
227
            }
228
        }
229
    }
230
    
231
    /**
232
     * Get the default values for all form components as an array of name => default_value
233
     * 
234
     * @return array
235
     */
236
    private function get_defaults()
237
    {
238
        $defaults = array();
239
        
240
        foreach( $this->component_list->get_value_components() as $component )
0 ignored issues
show
Bug introduced by
The method get_value_components cannot be called on $this->component_list (of type array<integer,object<Ama...\UI\AbstractComponent>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
241
        {
242
            $defaults[$component->name] = $component->default;
243
        }
244
        
245
        return $defaults;
246
    }
247
}