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 |