Parte 7 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Implementazione: gestione degli hook di WordPress

Pubblicato: 2022-02-04

Fino a questo punto, interagire con l'API Plugin significava chiamare add_action() e add_filters() nel costruttore di ogni classe.

Finora questo approccio è stato abbastanza buono, in quanto ha mantenuto le cose semplici e ci ha permesso di concentrarci sull'apprendimento di più sulla programmazione orientata agli oggetti con WordPress. Tuttavia, non è l'ideale.

Se un oggetto registra tutti i suoi hook quando viene creato, cose come lo unit test diventano complicate.

NOTA: i test unitari dovrebbero testare ciascuna "unità" in isolamento. Anche se al momento non stai scrivendo unit test, scrivere codice testabile ti farà risparmiare molto tempo nel refactoring in seguito, se decidessi di scrivere test.

Il gestore dei ganci

Facciamo un ulteriore passo avanti e introduciamo una nuova classe per gestire i nostri hook, la chiameremo Hooks_Manager . Questa classe sarà responsabile della registrazione di tutti i nostri ganci. Quindi, creeremo una nuova classe con un metodo register() .

 class Hooks_Manager { /** * Register the hooks of the given object. * * @param object $object */ public function register( $object ) { // Register the hooks the specified object needs } }

Abbiamo bisogno di un'interfaccia per ogni classe che deve registrare gli hook da implementare.

 interface Hooks { /** * Return the actions to register. * * @return array */ public function get_actions(); }

Puoi pensare a un'interfaccia come a un contratto , in cui una classe che implementa quell'interfaccia è "vincolata contrattualmente" per implementare tutti i metodi definiti in quell'interfaccia.

Ad esempio, una classe Login_Error che si aggancia all'azione login_head , deve implementare il metodo get_actions() della nostra interfaccia Hooks .

 class Login_Error implements Hooks { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } }

Il metodo register() di Hooks_Manager accetta un oggetto, chiama il suo metodo get_actions() e registra tutte le sue azioni.

 public function register( $object ) { $actions = $object->get_actions(); foreach ( $actions as $action_name => $action_details ) { $method = $action_details[0]; $priority = $action_details[1]; $accepted_args = $action_details[2]; add_action( $action_name, array( $object, $method ), $priority, $accepted_args ); } }

Aggiungiamo un metodo get_filters() alla nostra interfaccia, così possiamo registrare sia le azioni che i filtri.

 interface Hooks { /** * Return the actions to register. * * @return array */ public function get_actions(); /** * Return the filters to register. * * @return array */ public function get_filters(); }

Tornando alla nostra classe Login_Error , dobbiamo implementare questo nuovo metodo get_filters() .

 class Login_Error implements Hooks { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } public function get_filters() { return array( 'authenticate' => array( 'track_credentials', 10, 3 ), 'shake_error_code' => array( 'add_error_code', 10, 1 ), 'login_errors' => array( 'format_error_message', 10, 1 ), ); } }

Rinomineremo il metodo register() del nostro Hooks_Manager in register_actions() . Aggiungeremo anche un metodo register_filters() . Questi due metodi saranno responsabili rispettivamente della registrazione di azioni e filtri.

 class Hooks_Manager { /** * Register the actions of the given object. * * @param object $object */ private function register_actions( $object ) { $actions = $object->get_actions(); foreach ( $actions as $action_name => $action_details ) { $method = $action_details[0]; $priority = $action_details[1]; $accepted_args = $action_details[2]; add_action( $action_name, array( $object, $method ), $priority, $accepted_args ); } } /** * Register the filters of the given object. * * @param object $object */ private function register_filters( $object ) { $filters = $object->get_filters(); foreach ( $filters as $filter_name => $filter_details ) { $method = $filter_details[0]; $priority = $filter_details[1]; $accepted_args = $filter_details[2]; add_filter( $filter_name, array( $object, $method ), $priority, $accepted_args ); } } }

Ora possiamo aggiungere di nuovo un metodo register() , che chiamerà semplicemente sia register_actions() che register_filters() .

 class Hooks_Manager { /** * Register an object. * * @param object $object */ public function register( $object ) { $this->register_actions( $object ); $this->register_filters( $object ); } // ...

Cosa succede se una classe non ha bisogno di registrare sia le azioni che i filtri? L'interfaccia Hooks contiene due metodi: get_actions() e get_filters() . Tutte le classi che implementano quell'interfaccia saranno obbligate a implementare entrambi i metodi.

 class Cookie_Login implements Hooks { public function get_actions() { return array( 'auth_cookie_bad_username' => array( 'handle_bad_username', 10, 1 ), 'auth_cookie_bad_hash' => array( 'handle_bad_hash', 10, 1 ), 'auth_cookie_valid' => array( 'handle_valid', 10, 2 ), ); } public function get_filters() { return array(); } }

Ad esempio, la classe Cookie_Login deve registrare solo le azioni, ma ora è obbligata a implementare il metodo get_filters() solo per restituire un array vuoto.

L' Interface Segregation Principle (ISP) , la "I" in SOLID, afferma:

"Nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza".

Ciò significa che ciò che stiamo facendo ora è esattamente ciò che non dovremmo fare.

Segregazione dell'interfaccia

Possiamo risolvere questo problema suddividendo la nostra interfaccia in interfacce più piccole e più specifiche in modo che le nostre classi debbano solo conoscere i metodi che sono di loro interesse.

 interface Actions { /** * Return the actions to register. * * @return array */ public function get_actions(); }
 interface Filters { /** * Return the filters to register. * * @return array */ public function get_filters(); }

Non abbiamo più bisogno sia get_actions() che get_filters() , possiamo implementare solo l'interfaccia Actions ed eliminare get_filters()

 class Cookie_Login implements Actions { public function get_actions() { return array( 'auth_cookie_bad_username' => array( 'handle_bad_username', 10, 1 ), 'auth_cookie_bad_hash' => array( 'handle_bad_hash', 10, 1 ), 'auth_cookie_valid' => array( 'handle_valid', 10, 2 ), ); } }

D'altra parte, Login_Error , che necessita di azioni e filtri, deve solo implementare entrambe le interfacce. Le classi possono implementare più di un'interfaccia separandole con una virgola.

 class Login_Error implements Actions, Filters { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } public function get_filters() { return array( 'authenticate' => array( 'track_credentials', 10, 3 ), 'shake_error_code' => array( 'add_error_code', 10, 1 ), 'login_errors' => array( 'format_error_message', 10, 1 ), ); } }

Ora che abbiamo separato la nostra interfaccia, dobbiamo solo aggiornare il metodo register() di Hooks_Manager per riflettere le nostre modifiche.

 class Hooks_Manager { /** * Register an object. * * @param object $object */ public function register( $object ) { if ( $object instanceof Actions ) { $this->register_actions( $object ); } if ( $object instanceof Filters ) { $this->register_filters( $object ); } } // ...

In questo modo, chiamiamo condizionatamente solo register_actions() , only register_filters() o entrambi, in base alle interfacce implementate dall'oggetto specificato.

Per utilizzare effettivamente il gestore degli hook:

 $hooks_manager = new Hooks_Manager(); $hooks_manager->register( $login_error ); $hooks_manager->register( $cookie_login );

Questo è tutto! Ora possiamo usare quell'oggetto per gestire gli hook sull'intera codebase.

Conclusione

Naturalmente, ci sono diversi modi per gestire i tuoi hook in modo orientato agli oggetti, te ne abbiamo appena mostrato uno. Dovresti sperimentare e trovarne uno adatto alle tue esigenze.

Resta con noi per l'ultima parte di questa serie, dove vedremo come gestire le opzioni in modo orientato agli oggetti, parlare di incapsulamento, astrazione e come disaccoppiare le classi per creare un plugin flessibile e facile da estendere!

Fare clic qui per leggere la parte 8 della nostra serie di programmazione orientata agli oggetti

Guarda anche

  • WordPress e programmazione orientata agli oggetti: una panoramica
  • Parte 2 – WordPress e programmazione orientata agli oggetti: un esempio del mondo reale
  • Parte 3 – WordPress e programmazione orientata agli oggetti: Α Esempio di WordPress – Definizione dell'ambito
  • Parte 4 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Design
  • Parte 5 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Implementazione: il menu di amministrazione
  • Parte 6 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Implementazione: registrazione delle sezioni