PluginProbe
Code Snippets / 3.7.0
Code Snippets v3.7.0
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / php / class-data-item.php

class-data-item.php in Code Snippets 3.7.0, at php/class-data-item.php

247 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets;
4
5 use WP_Exception;
6
7 /**
8 * Base class for representing an item of data without needing to use direct access or individual getter and setter functions.
9 *
10 * @package Code_Snippets
11 *
12 * @since 3.4.0
13 */
14 abstract class Data_Item {
15
16 /**
17 * List of data fields keyed to their current values. Will be initialised with default values.
18 *
19 * @var array<string, mixed>
20 */
21 protected array $fields;
22
23 /**
24 * List of default values provided for fields.
25 *
26 * @var array<string, mixed>
27 */
28 protected array $default_values;
29
30 /**
31 * Optional list of field name aliases to map when resolving a field name.
32 *
33 * @var array<string, string> Field alias names keyed to actual field names.
34 */
35 protected array $field_aliases;
36
37 /**
38 * Class constructor.
39 *
40 * @param array<string, mixed> $default_values List of valid fields mapped to their default values.
41 * @param array<string, mixed>|Data_Item $initial_data Optional initial data to populate fields.
42 * @param array<string, string> $field_aliases Optional list of field name aliases to map when resolving a field name.
43 */
44 public function __construct( array $default_values, $initial_data = null, array $field_aliases = [] ) {
45 $this->fields = $default_values;
46 $this->default_values = $default_values;
47 $this->field_aliases = $field_aliases;
48
49 // If we've accidentally passed an existing object, then fetch its fields before constructing the new object.
50 if ( is_object( $initial_data ) && method_exists( $initial_data, 'get_fields' ) ) {
51 $initial_data = $initial_data->get_fields();
52 }
53
54 $this->set_fields( $initial_data );
55 }
56
57
58 /**
59 * Set all data fields from an array or object. Invalid fields will be ignored.
60 *
61 * @param array<string, mixed>|mixed $data List of data.
62 */
63 public function set_fields( $data ) {
64 // Only accept arrays or objects.
65 if ( ! $data || is_string( $data ) ) {
66 return;
67 }
68
69 // Convert objects into arrays.
70 if ( is_object( $data ) ) {
71 $data = get_object_vars( $data );
72 }
73
74 // Loop through the provided fields and set their values.
75 foreach ( $data as $field => $value ) {
76 $this->set_field( $field, $value );
77 }
78 }
79
80 /**
81 * Retrieve list of current data fields.
82 *
83 * @return array<string, mixed> Field names keyed to current values.
84 */
85 public function get_fields(): array {
86 $fields = [];
87
88 foreach ( $this->get_allowed_fields() as $field_name ) {
89 $fields[ $field_name ] = $this->$field_name;
90 }
91
92 return $fields;
93 }
94
95 /**
96 * Retrieve a list of current data fields, excluding values that are unchanged from the default.
97 *
98 * @return array<string, mixed>
99 */
100 public function get_modified_fields(): array {
101 return array_filter(
102 $this->get_fields(),
103 function ( $value, $field ) {
104 return $value && $value !== $this->default_values[ $field ];
105 },
106 ARRAY_FILTER_USE_BOTH
107 );
108 }
109
110 /**
111 * Internal function for resolving the actual name of a field.
112 *
113 * @param string $field A field name, potentially a field alias.
114 *
115 * @return string The resolved field name.
116 */
117 protected function resolve_field_name( string $field ): string {
118 return $this->field_aliases[ $field ] ?? $field;
119 }
120
121 /**
122 * Check if a field is set.
123 *
124 * @param string $field The field name.
125 *
126 * @return bool Whether the field is set.
127 */
128 public function __isset( string $field ) {
129 $field = $this->resolve_field_name( $field );
130 return isset( $this->fields[ $field ] ) || method_exists( $this, 'get_' . $field );
131 }
132
133 /**
134 * Retrieve a field's value.
135 *
136 * @param string $field The field name.
137 *
138 * @return mixed The field value
139 *
140 * @throws WP_Exception If the field name is not allowed.
141 */
142 public function __get( string $field ) {
143 $field = $this->resolve_field_name( $field );
144
145 if ( method_exists( $this, 'get_' . $field ) ) {
146 return call_user_func( array( $this, 'get_' . $field ) );
147 }
148
149 if ( ! $this->is_allowed_field( $field ) ) {
150 if ( function_exists( 'wp_trigger_error' ) ) {
151 // translators: 1: class name, 2: field name.
152 $message = sprintf( 'Trying to access invalid property on "%1$s" class: %2$s', get_class( $this ), $field );
153 wp_trigger_error( __FUNCTION__, $message, E_USER_WARNING );
154 }
155
156 return null;
157 }
158
159 return $this->fields[ $field ];
160 }
161
162 /**
163 * Set the value of a field.
164 *
165 * @param string $field The field name.
166 * @param mixed $value The field value.
167 *
168 * @throws WP_Exception If the field name is not allowed.
169 */
170 public function __set( string $field, $value ) {
171 $field = $this->resolve_field_name( $field );
172
173 if ( ! $this->is_allowed_field( $field ) ) {
174 if ( function_exists( 'wp_trigger_error' ) ) {
175 // translators: 1: class name, 2: field name.
176 $message = sprintf( 'Trying to set invalid property on "%s" class: %s', get_class( $this ), $field );
177 wp_trigger_error( __FUNCTION__, $message, E_USER_ERROR );
178 }
179
180 return;
181 }
182
183 $value = method_exists( $this, 'prepare_' . $field ) ?
184 call_user_func( array( $this, 'prepare_' . $field ), $value ) :
185 $this->prepare_field( $value, $field );
186
187 $this->fields[ $field ] = $value;
188 }
189
190 /**
191 * Prepare a value before it is stored.
192 *
193 * @param mixed $value Value to prepare.
194 * @param string $field Field name.
195 *
196 * @return mixed Value in the correct format.
197 */
198 abstract protected function prepare_field( $value, string $field );
199
200 /**
201 * Retrieve the list of fields that can be written to.
202 *
203 * @return array<string> List of field names.
204 */
205 public function get_allowed_fields(): array {
206 return array_keys( $this->fields ) + array_keys( $this->field_aliases );
207 }
208
209 /**
210 * Determine whether a field is allowed to be written to
211 *
212 * @param string $field The field name.
213 *
214 * @return bool true if the is allowed, false if invalid.
215 */
216 public function is_allowed_field( string $field ): bool {
217 return ( $this->fields && array_key_exists( $field, $this->fields ) ) ||
218 ( $this->field_aliases && array_key_exists( $field, $this->field_aliases ) );
219 }
220
221 /**
222 * Safely set the value for a field.
223 * If the field name is invalid, false will be returned instead of an error thrown.
224 *
225 * @param string $field The field name.
226 * @param mixed $value The field value.
227 *
228 * @return bool true if the field was set successfully, false if the field name is invalid.
229 *
230 * @noinspection PhpDocMissingThrowsInspection
231 */
232 public function set_field( string $field, $value ): bool {
233 if ( ! $this->is_allowed_field( $field ) ) {
234 return false;
235 }
236
237 /**
238 * Above is_allowed_field check should bypass exception.
239 *
240 * @noinspection PhpUnhandledExceptionInspection
241 */
242 $this->__set( $field, $value );
243
244 return true;
245 }
246 }
247