Și cu sula-n dos, și cu sufletul în rai

Contrar titlului, nu vreau să vorbesc despre valori europene. Nu, e vorba că, tot lucrând cu țârâita la Wp Trip Summary, am început să schimb treptat inclusiv convenția folosită pentru denumirea și organizarea claselor: din stilul PEAR A_B_C în structura bazată pe namespace-uri A\B\C.

Foto: Bogdan Stoica

Foto: Bogdan Stoica

Cum a le schimba pe toate deodată este un efort prea mare și-n general fastidios, cum pentru o singură clasă vor coexista o vreme ambele modalități de invocare, trebuie ca procesul să nu impieteze asupra funcționării; deci să pot folosi și A_B_C, și A\B\C. Ca un exemplu concret: și Abp01_Env, și WpTripSummary\Env.

Clasele vor fi redefinite, firește, folosind namespace-uri:

<?php
declare(strict_types=1);

namespace WpTripSummary {
   if (!defined('ABP01_LOADED')) {
      exit;
   }

   use mysqli_driver;
   use MysqliDb;
   use stdClass;

   class Env {
      //...
   }
}

Pentru a putea fi recunoscute la rulare și după vechiul nume, se poate defini un alias cu class_alias(): o clasă existentă este făcută cunoscută printr-un alt nume, ceea ce pentru mine e perfect, că nu trebuie să întrețin sau să spurc locul c-o întreagă armată de fațade. Țin minte că multă vreme m-am tot mirat la ce-o fi bună și cred că e prima oară când o folosesc altcumva decât jucându-mă:

function abp01_init_legacy_class_aliases(): void {
   abp01_legacy_class_alias(\WpTripSummary\Env::class, 
      'Abp01_Env');
   abp01_legacy_class_alias(\WpTripSummary\Exception::class, 
      'Abp01_Exception');
}

function abp01_legacy_class_alias (string $newClass,string $legacyClass): void {
    if (!class_exists($legacyClass, false)) {
        class_alias($newClass, $legacyClass, true);
    }
}

Păstrând al treilea parametru la true, PHP se va ocupa automat de încărcarea automată a clasei reale. Totul e grupat într-o singură funcție și-mi va fi ușor să le mătrășesc odată ce voi termina procesul, destul de rapid zic eu, maxim un an.

Mai rămâne totuși o problemă: analizorul din Intelephense (VS Code) nu știe să urmărească class_alias() și mă va bodogăni în consecință conform prevederilor în vigoare. Ca soluție, am un singur fișier fantomă – neinclus nicăieri, deci nu rulează, doar există – unde clasele reorganizate sunt denumire pe rit vechi și moștenesc din noua lor versiune:

<?php
/**
 * This file is only used to satisfy intelephense 
 * 	and/or other static check support tools
 * 
 * It is not included in the plug-in runtime, 
 * 	nor should it ever be.
 */

if (!defined('ABP01_LOADED')) {
   exit;
}

/**
 * @deprecated Use \WpTripSummary\Env instead.
 */
class Abp01_Env extends \WpTripSummary\Env {}

/**
 * @deprecated Use \WpTripSummary\Exception instead.
 */
class Abp01_Exception extends \WpTripSummary\Exception {}

Fișierul nu reproduce perfect semantica unui alias – pentru analizor, clasa veche moștenește din cea nouă – însă, fiindcă nu participă niciodată la execuție, diferența rămâne în limitele prudenței. Ar putea conta pentru clase final, anumite utilizări self / static sau alte delicatese pe care analizorul încearcă să le deducă.

În schimb, mai există un punct gingaș: la construcția dinamică a modulelor individuale am un soi de injectare a dependințelor mai pentru săraci. Simplă, eficientă și de criză, bazată pe reflecție. Reflecția-mi va raporta denumirile vechi, ce nu vor fi găsite inițial în dicționar, deci obiectul unde-s folosite nu va putea fi construit. Ca să șuntez problema, am definit pur și simplu de două ori modalitatea de obținere a instanței dorite:

private function _getDefaultInjectableServiceFactories(): array {
   return array(
      Env::class 
         => fn() => $this->getEnv(),
      Abp01_Env::class 
         => fn() => $this->getEnv()
   );
}

Cu toate că nu poate obține premiul I cu coroniță pentru eleganță, e concentrat într-un singur loc și, odată șters fișierul fantomă, analizorul static va sublinia drept erori locurile cu denumiri caduce. Așadar, cum se spune în cele mai înalte cercuri academice: l-am făcut pă fraer!