Antes de ver el cómo, deberíamos ver el porqué. Y antes de eso, ver un ejemplo de anidamiento masivo para entender el problema.
Vaya por delante que esto es algo que todos hemos hecho en alguna ocasión, el motivo de este post no es criticar, simplemente tratar de mostrar una forma mejor de desarrollar código, más mantenible y escalable.
De hecho, circula un meme por las redes que, si eres desarrollador/a, habrás visto en alguna ocasión:
En este ejemplo, con fines didácticos, tenemos un método de una clase que comprueba que el formulario se ha enviado. A continuación verifica en nonce. Después comprueba que recibimos un email válido. Con ese email comprobamos si tenemos al usuario registrado, y por último si es administrador.
<?php
class MyForm {
public function check_form() {
try {
if ( isset( $_POST['btn'] ) ) {
$nonce = $_POST['_wpnonce'] ?? '';
if ( wp_verify_nonce( $nonce, 'my-nonce' ) ) {
$email = $_POST['_email'] ?? '';
if ( is_email( $email ) ) {
if ( email_exists( $email ) ) {
$user = get_userdata( email_exists( $email ) );
if ( in_array( 'administrator', (array) $user->roles ) ) {
//do something
} else {
throw new Exception( 'User is not an administrator' );
}
} else {
throw new Exception( 'User not found' );
}
} else {
throw new Exception( 'Not valid email' );
}
} else {
throw new Exception( 'An error occurred while checking Nonce' );
}
} else {
throw new Exception( 'Form not submitted' );
}
} catch ( Exception $e ) {
wp_die( 'Error: ' . $e->getMessage(), 'Error' );
}
}
}
Como podemos ver, tenemos un montón de condiciones anidadas. Esto se puede complicar en función de las necesidades, y crecer y crecer hasta un punto que sea incluso difícil de leer.
Lo ideal sería encapsular cada comprobación en un método independiente, con un nombre que le de sentido y semántica al código. Además de leerse mejor el código. A esto se le conoce como cláusulas de guarda.
Las cláusulas de guarda son un método que nos permite hacer nuestro código más legible, semántico, escalable, testeable y con menor nivel de indentación.
De este modo, en lugar de anidamientos, tendremos una lista «plana» de condicionales, una tras otra, con early returns (si no se cumple una condición, no seguimos).
Algo como esto:
<?php
class MyForm {
public function validate() {
try {
$this->check_form();
// do something
} catch ( Exception $e ) {
wp_die( 'Error: ' . $e->getMessage(), 'Error' );
}
}
public function check_form() {
if ( ! $this->is_form_submitted() ) {
throw new Exception( 'Form not submitted' );
}
if ( ! $this->is_nonce_ok() ) {
throw new Exception( 'An error occurred while checking Nonce' );
}
$email = $_POST['_email'] ?? '';
if ( ! is_email( $email ) ) {
throw new Exception( 'Not valid email' );
}
if ( ! email_exists( $email ) ) {
throw new Exception( 'User not found' );
}
if ( $this->is_user_an_administrator($email) ) {
throw new Exception( 'User is not an administrator' );
}
}
private function is_form_submitted(): bool {
return isset( $_POST['btn'] );
}
private function is_nonce_ok(): bool {
$nonce = $_POST['_wpnonce'] ?? '';
return (bool) wp_verify_nonce( $nonce, 'my-nonce' );
}
private function is_user_an_administrator( string $email ): bool {
$user = get_userdata( email_exists( $email ) );
return in_array( 'administrator', (array) $user->roles );
}
}
Llamando a los método con nombres descriptivos, nos ayudará a leer mejor el código y a que sea más fácil generar tests unitarios.
No es lo mismo tener en código un isset($_POST[‘btn’]) (donde no sabes qué es btn) que un método que se llame is_form_submitted() (donde estás preguntando si el formulario ha sido enviado).