Completed
Branch develop (2aa849)
by Steve
09:08
created

FieldManager::insertFields()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 17
rs 8.2222
cc 7
eloc 8
nc 7
nop 2
1
<?php
2
3
namespace StoutLogic\AcfBuilder;
4
5
/**
6
 * Manages an array of field configs
7
 */
8
class FieldManager
9
{
10
    /**
11
     * Array of fields
12
     * @var array
13
     */
14
    private $fields;
15
16
    /**
17
     * @param array $fields optional default array of field configs
18
     */
19
    public function __construct($fields = [])
20
    {
21
        $this->fields = $fields;
22
    }
23
24
    /**
25
     * @return array field configs
26
     */
27
    public function getFields()
28
    {
29
        return $this->fields;
30
    }
31
32
    /**
33
     * Return int of fields
34
     * @return int field count
35
     */
36
    public function getCount()
37
    {
38
        return count($this->getFields());
39
    }
40
41
    /**
42
     * Add field to end of array
43
     * @param  array|Builder $field Field array config or Builder
44
     * @return void
45
     */
46
    public function pushField($field)
47
    {
48
        $this->insertFields($field, $this->getCount());
0 ignored issues
show
Bug introduced by
It seems like $field defined by parameter $field on line 46 can also be of type object<StoutLogic\AcfBuilder\Builder>; however, StoutLogic\AcfBuilder\FieldManager::insertFields() does only seem to accept array|object<StoutLogic\AcfBuilder\NamedBuilder>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
49
    }
50
51
    /**
52
     * Remove last field from end of array
53
     * @throws \OutOfRangeException if array is empty
54
     * @return array|Builder Field array config or Builder
55
     */
56
    public function popField()
57
    {
58
        if ($this->getCount() > 0) {
59
            $fields = $this->removeFieldAtIndex($this->getCount() - 1);
60
            return $fields[0];
61
        }
62
63
        throw new \OutOfRangeException("Can't call popField when the field count is 0");
64
    }
65
66
    /**
67
     * Insert of field at a specific index
68
     * @param  array|NamedBuilder $fields a single field or an array of fields
69
     * @param  int $index  insertion point
70
     * @return void
71
     */
72
    public function insertFields($fields, $index)
73
    {
74
        if (!$fields instanceof NamedBuilder && !is_array($fields)) {
75
            return;
76
        }
77
78
        // If a singular field config, put into an array of fields
79
        if ($fields instanceof NamedBuilder || array_key_exists('name', $fields)) {
80
            $fields = [$fields];
81
        }
82
83
        foreach ($fields as $i => $field) {
84
            if ($this->validateField($field)) {
85
                array_splice($this->fields, $index + $i, 0, [$field]);
86
            }
87
        }
88
    }
89
90
    /**
91
     * Remove a field at a specific index
92
     * @param  int $index
93
     * @return array  removed field
94
     */
95
    private function removeFieldAtIndex($index)
96
    {
97
        return array_splice($this->fields, $index, 1);
98
    }
99
100
    /**
101
     * Remove a speicifc field by name
102
     * @param  string $name name of the field
103
     * @return void
104
     */
105
    public function removeField($name)
106
    {
107
        $index = $this->getFieldIndex($name);
108
        $this->removeFieldAtIndex($index);
109
    }
110
111
    /**
112
     * Replace a field with a single field or array of fields
113
     * @param  string $name  name of field to replace
114
     * @param  array|Builder $field single or array of fields
115
     * @return void
116
     */
117
    public function replaceField($name, $field)
118
    {
119
        $index = $this->getFieldIndex($name);
120
        $this->removeFieldAtIndex($index);
121
        $this->insertFields($field, $index);
0 ignored issues
show
Bug introduced by
It seems like $field defined by parameter $field on line 117 can also be of type object<StoutLogic\AcfBuilder\Builder>; however, StoutLogic\AcfBuilder\FieldManager::insertFields() does only seem to accept array|object<StoutLogic\AcfBuilder\NamedBuilder>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
122
    }
123
124
    /**
125
     * Check to see if a field name already exists
126
     * @param  string $name field name
127
     * @return bool
128
     */
129
    public function fieldNameExists($name)
130
    {
131
        try {
132
            $this->getFieldIndex($name);
133
        } catch (FieldNotFoundException $e) {
134
            return false;
135
        }
136
137
        return true;
138
    }
139
140
    /**
141
     * Return a field by name
142
     * @param  string $name field name
143
     * @return array|Builder  Field config array or Builder
144
     */
145
    public function getField($name)
146
    {
147
        return $this->fields[$this->getFieldIndex($name)];
148
    }
149
150
    /**
151
     * Return the name given a field
152
     * @param  array|NamedBuilder $field
153
     * @return string|false field name
154
     */
155
    public function getFieldName($field)
156
    {
157
        if ($field instanceof NamedBuilder) {
158
            return $field->getName();
159
        }
160
161
        if (is_array($field) && array_key_exists('name', $field)) {
162
            return $field['name'];
163
        }
164
165
        return false;
166
    }
167
168
    /**
169
     * Modify the configuration of a field
170
     * @param  string $name          field name
171
     * @param  array $modifications  field configuration
172
     * @return void
173
     */
174
    public function modifyField($name, $modifications)
175
    {
176
        $field = $this->getField($name);
177
        $field = array_merge($field, $modifications);
178
        $this->replaceField($name, $field);
179
    }
180
181
    /**
182
     * Validate a field
183
     * @param  array|Builder $field
184
     * @return bool
185
     */
186
    private function validateField($field)
187
    {
188
        return $this->validateFieldName($field);
0 ignored issues
show
Bug introduced by
It seems like $field defined by parameter $field on line 186 can also be of type object<StoutLogic\AcfBuilder\Builder>; however, StoutLogic\AcfBuilder\Fi...er::validateFieldName() does only seem to accept array|object<StoutLogic\AcfBuilder\NamedBuilder>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
189
    }
190
191
    /**
192
     * Validates that a field's name doesn't already exist
193
     * @param  array|NamedBuilder $field
194
     * @throws FieldNameCollisionException when the name already exists
195
     * @return bool
196
     */
197
    private function validateFieldName($field)
198
    {
199
        $fieldName = $this->getFieldName($field);
200
        if (!$fieldName || $this->fieldNameExists($fieldName)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldName of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
201
            throw new FieldNameCollisionException("Field Name: `{$fieldName}` already exists.");
202
        }
203
204
        return true;
205
    }
206
207
    /**
208
     * Return the index in the $this->fields array looked up by the field's name
209
     * @param  string $name Field Name
210
     * @throws FieldNotFoundException if the field name doesn't exist
211
     * @return int Field Index
212
     */
213
    public function getFieldIndex($name)
214
    {
215
        foreach ($this->getFields() as $index => $field) {
216
            if ($this->getFieldName($field) === $name) {
217
                return $index;
218
            }
219
        }
220
221
        throw new FieldNotFoundException("Field `{$name}` not found.");
222
    }
223
}
224