Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Data_Class often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Data_Class, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class Data_Class extends Helper_Class { |
||
23 | |||
24 | /** |
||
25 | * La liste des types de base dans PHP acceptés par EO_Framework pour les champs. |
||
26 | * |
||
27 | * @var array |
||
28 | */ |
||
29 | public static $built_in_types = array( 'string', 'integer', 'float', 'boolean', 'array' ); |
||
30 | |||
31 | /** |
||
32 | * La liste des types de personnalisés acceptés par EO_Framework pour les champs. |
||
33 | * |
||
34 | * @var array |
||
35 | */ |
||
36 | public static $custom_types = array( 'wpeo_date' ); |
||
37 | |||
38 | /** |
||
39 | * La liste complétes des types de champs acceptés pour les champs dans EO_Framework. |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | public static $accepted_types = array(); |
||
44 | |||
45 | /** |
||
46 | * Variable contenant l'ensemble des erreurs rencontrées lors de la création d'un objet. |
||
47 | * |
||
48 | * @var WP_Error |
||
49 | */ |
||
50 | private $wp_errors; |
||
51 | |||
52 | /** |
||
53 | * Variable contenant la méthode actuellement utilisée. Permet de construire l'objet demandé selon la méthode HTTP utilisée. |
||
54 | * |
||
55 | * @var string |
||
56 | */ |
||
57 | private $req_method; |
||
58 | |||
59 | /** |
||
60 | * Appelle la méthode pour dispatcher les données. |
||
61 | * |
||
62 | * @since 1.0.0 |
||
63 | * @version 1.0.0 |
||
64 | * |
||
65 | * @param Array $data Les données non traité. Peut être null, permet de récupérer le schéma. |
||
66 | * @param string $req_method La méthode HTTP actuellement utilisée. |
||
67 | */ |
||
68 | public function __construct( $data = null, $req_method ) { |
||
69 | $this->wp_errors = new \WP_Error(); |
||
70 | $this->req_method = ( null !== $req_method ) ? strtoupper( $req_method ) : null; |
||
71 | |||
72 | // On construit les types autorisés à partir des listes séparées. Permet de ne pas mettre de type en dur dans le code. |
||
73 | self::$accepted_types = wp_parse_args( self::$custom_types, self::$built_in_types ); |
||
74 | |||
75 | // Filtre du schéma. |
||
76 | $this->schema = apply_filters( 'eo_model_handle_schema', $this->schema, $this->req_method ); |
||
77 | |||
78 | if ( null !== $data && null !== $this->req_method ) { |
||
79 | $this->data = $this->handle_data( $data ); |
||
80 | } |
||
81 | |||
82 | if ( ! empty( $this->wp_errors->errors ) ) { |
||
83 | echo wp_json_encode( $this->wp_errors ); |
||
84 | exit; |
||
85 | } |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Dispatches les données selon le modèle. |
||
90 | * |
||
91 | * @since 1.0.0 |
||
92 | * @version 1.0.0 |
||
93 | * |
||
94 | * @param array $data Les données envoyées par l'utilisateur pour construire un objet selon un schéma. |
||
95 | * @param array $schema Optionnal. La définition des données. Ce paramètre est utilisé uniquement dans le cas d'un schéma récursif. |
||
96 | * |
||
97 | * @return object Les données traitées, typées et converties en l'objet demandé. |
||
98 | */ |
||
99 | private function handle_data( $data, $schema = null ) { |
||
100 | $object = null; |
||
101 | $schema = ( null === $schema ) ? $this->schema : $schema; |
||
102 | |||
103 | foreach ( $schema as $field_name => $field_def ) { |
||
104 | // Définie les données par défaut pour l'élément courant par rapport à "default". |
||
105 | $value = null; |
||
106 | if ( isset( $field_def['default'] ) && in_array( $this->req_method, array( 'GET', 'POST' ), true ) ) { |
||
107 | $value = $field_def['default']; |
||
108 | } |
||
109 | |||
110 | // On vérifie si la valeur du champs actuelle est fournie dans les données envoyées pour construction. |
||
111 | if ( isset( $field_def['field'] ) && isset( $data[ $field_def['field'] ] ) ) { // On vérifie si la clé correspondant au champs de la BDD existe. $data['post_date']. |
||
112 | $value = $data[ $field_def['field'] ]; |
||
113 | } elseif ( isset( $data[ $field_name ] ) && isset( $field_def ) && ! isset( $field_def['child'] ) ) { // On vérifie si la clé correspondant au schéma défini existe. $data['date']. |
||
114 | $value = $data[ $field_name ]; |
||
115 | } |
||
116 | |||
117 | $value = apply_filters( 'eo_model_handle_value', $value, $this, $field_def, $this->req_method ); |
||
118 | |||
119 | // Dans le cas ou la méthode actuelle implique un enregistrement dans la base de données. |
||
120 | // On vérifie que le schéma n'indique pas une valeur obligatoire. Si le champs est vide on retourne une erreur. |
||
121 | if ( 'GET' !== $this->req_method && isset( $field_def['required'] ) && $field_def['required'] && null === $value ) { |
||
122 | $this->wp_errors->add( 'eo_model_is_required', get_class( $this ) . ' => ' . $field_name . ' is required' ); |
||
123 | } |
||
124 | |||
125 | // Force le typage de $value en requête mode "GET". |
||
126 | if ( 'GET' === $this->req_method ) { |
||
127 | $value = $this->handle_value_type( $value, $field_def ); |
||
128 | } |
||
129 | |||
130 | // Vérifie le typage de $value. |
||
131 | $this->check_value_type( $value, $field_name, $field_def ); |
||
132 | |||
133 | // On assigne la valeur "construite" au champs dans l'objet en cours de construction. |
||
134 | if ( null !== $value ) { |
||
135 | $object[ $field_name ] = $value; |
||
136 | } |
||
137 | |||
138 | // Dans le cas ou la méthode actuelle implique un enregistrement dans la base de données. |
||
139 | // Si la valeur "construite" est "null" (aucun cas précédent n'a rempli ce champs) et que le champs est requis alors on le supprime pour ne pas supprimer de la BDD. |
||
140 | if ( 'GET' !== $this->req_method ) { |
||
141 | if ( isset( $object[ $field_name ] ) && null === $value && isset( $field_def['required'] ) && $field_def['required'] ) { |
||
142 | unset( $object[ $field_name ] ); |
||
143 | } |
||
144 | } |
||
145 | |||
146 | // Si le champs traité contient l'entrée "child" il s'agit d'un tableau mutli-dimensionnel alors on lance une récursivité. |
||
147 | if ( isset( $field_def['child'] ) ) { |
||
148 | // On vérifie si des données correspondantes au champs en traitement ont été envoyées. |
||
149 | $values = ! empty( $data[ $field_name ] ) ? $data[ $field_name ] : array(); |
||
150 | |||
151 | // Récursivité sur les enfants de la définition courante. |
||
152 | $object[ $field_name ] = $this->handle_data( $values, $field_def['child'] ); |
||
153 | } |
||
154 | } |
||
155 | |||
156 | return $object; |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * Vérification du type de la valeur d'un champs. Si le type n'est pas correct on rempli la variable $this->wp_errors qui sera retournée en fin de traitement. |
||
161 | * |
||
162 | * @param mixed $value La valeur du champs à vérifier. |
||
163 | * @param string $field_name Le nom du champs à vérifier. Utilisé pour le message d'erreur. |
||
164 | * @param array $field_def La définition complète du champs à vérifier. |
||
165 | * |
||
166 | * @return void |
||
167 | */ |
||
168 | public function check_value_type( $value, $field_name, $field_def ) { |
||
169 | // Vérifie le type de $value. |
||
170 | if ( null !== $value ) { |
||
171 | if ( empty( $field_def['type'] ) ) { |
||
172 | $this->wp_errors->add( 'eo_model_invalid_type', get_class( $this ) . ' => ' . $field_name . ': ' . $value . '(' . gettype( $value ) . ') no setted in schema. Type accepted: ' . join( ',', self::$accepted_types ) ); |
||
173 | } else { |
||
174 | $field_type = true; |
||
175 | switch ( $field_def['type'] ) { |
||
176 | case 'string': |
||
177 | if ( ! is_string( $value ) ) { |
||
178 | $field_type = false; |
||
179 | } |
||
180 | break; |
||
181 | case 'integer': |
||
182 | if ( ! is_int( $value ) ) { |
||
183 | $field_type = false; |
||
184 | } |
||
185 | break; |
||
186 | case 'boolean': |
||
187 | if ( ! is_bool( $value ) ) { |
||
188 | $field_type = false; |
||
189 | } |
||
190 | break; |
||
191 | case 'array': |
||
192 | if ( ! is_array( $value ) ) { |
||
193 | $rendered_value = is_object( $value ) ? 'Object item' : $value; |
||
194 | |||
195 | $this->wp_errors->add( 'eo_model_invalid_type', get_class( $this ) . ' => ' . $field_name . ': ' . $rendered_value . '(' . gettype( $value ) . ') is not a ' . $field_def['type'] ); |
||
196 | } elseif ( isset( $field_def['array_type'] ) ) { |
||
197 | if ( ! empty( $value ) ) { |
||
198 | foreach ( $value as $key => $sub_value ) { |
||
199 | $field_def['type'] = $field_def['array_type']; |
||
200 | $this->check_value_type( $sub_value, $field_name, $field_def ); |
||
201 | |||
202 | if ( isset( $field_def['key_type'] ) ) { |
||
203 | $field_def['type'] = $field_def['key_type']; |
||
204 | $this->check_value_type( $key, $field_name, $field_def ); |
||
205 | } |
||
206 | } |
||
207 | } |
||
208 | } |
||
209 | break; |
||
210 | default: |
||
211 | if ( ! in_array( $field_def['type'], self::$accepted_types, true ) ) { |
||
212 | $this->wp_errors->add( 'eo_model_invalid_type', get_class( $this ) . ' => ' . $field_name . ': ' . $value . '(' . gettype( $value ) . ') incorrect type: "' . $field_def['type'] . '". Type accepted: ' . join( ',', self::$accepted_types ) ); |
||
213 | } |
||
214 | break; |
||
215 | } |
||
216 | |||
217 | if ( ! $field_type ) { |
||
218 | // Translators: 1.Current className 2.Field name 3.Given value 4.Field Real type 5.Field expected type. |
||
219 | $this->wp_errors->add( 'eo_model_invalid_type', sprintf( __( '%1$s => %2$s: %3$s ( %4$s ) is not a %5$s', 'eo-framework' ), get_class( $this ), $field_name, $value, gettype( $value ), $field_def['type'] ) ); |
||
220 | } |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Forces le typage des données. |
||
227 | * |
||
228 | * @since 0.1.0 |
||
229 | * @version 1.0.0 |
||
230 | * |
||
231 | * @param mixed $value La valeur courante. |
||
232 | * @param array $field_def La définition du champ. |
||
233 | * |
||
234 | * @return mixed L'objet avec le typage forcé. |
||
235 | */ |
||
236 | public function handle_value_type( $value, $field_def ) { |
||
237 | // Si le type du champs à vérifier est parmis les types personnalisés (non défini par PHP) alors on retourne simplement la valeur, la fonction risque de corrompre les données. |
||
238 | if ( in_array( $field_def['type'], self::$custom_types, true ) ) { |
||
239 | return $value; |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * On type la valeur. |
||
244 | * |
||
245 | * @see self::$accepted_types |
||
246 | */ |
||
247 | settype( $value, $field_def['type'] ); |
||
248 | |||
249 | // On force le typage des enfants uniquement si array_type est défini. |
||
250 | if ( ! empty( $field_def['array_type'] ) && is_array( $value ) && ! empty( $value ) ) { |
||
251 | foreach ( $value as $key => $val ) { |
||
252 | /** |
||
253 | * On type la valeur. |
||
254 | * |
||
255 | * @see self::$accepted_types |
||
256 | */ |
||
257 | settype( $value[ $key ], $field_def['array_type'] ); |
||
258 | } |
||
259 | } |
||
260 | |||
261 | return $value; |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Convertis le modèle en un tableau compatible WordPress. |
||
266 | * |
||
267 | * @since 1.0.0 |
||
268 | * @version 1.0.0 |
||
269 | * |
||
270 | * @return array Tableau compatible avec les fonctions WordPress. |
||
271 | */ |
||
272 | public function convert_to_wordpress() { |
||
273 | $data = array(); |
||
274 | |||
275 | foreach ( $this->schema as $field_name => $field_def ) { |
||
276 | |||
277 | if ( ! empty( $field_def['field'] ) ) { |
||
278 | if ( isset( $this->data[ $field_name ] ) ) { |
||
279 | $value = ( ( isset( $this->data[ $field_name ] ) && null !== $this->data[ $field_name ] ) ) ? $this->data[ $field_name ] : null; |
||
280 | |||
281 | if ( null !== $value ) { |
||
282 | if ( ! in_array( $field_def['type'], self::$custom_types, true ) || ! isset( $value['raw'] ) ) { |
||
283 | $data[ $field_def['field'] ] = $value; |
||
284 | } elseif ( isset( $value['raw'] ) ) { |
||
285 | $data[ $field_def['field'] ] = $value['raw']; |
||
286 | } |
||
287 | } |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | |||
292 | return $data; |
||
293 | } |
||
294 | } |
||
295 | |||
297 |