Псевдо-інкапсуляція легасі include-ів коли немає часу рефакторіть

Сьогодні хочу розглянути міграцію коду з далекого минулого в сучасний фреймворк.

Найбільш часта ситуація, яку я можу привести в приклад str_repeat('дуже', 20) старий код, який не знає навіть класів, планується перенести або частково використовувати в сучасному фреймворку, але переписувати тисячі рядків і десятки залежностей немає часу. Таке буває, коли замовник раптом вирішує суттєво модернізувати або розвивати проект, який 10+ років працював без змін, а сапортил його один парттайм-олдскул-програміст зрідка перезавантажуючи пару-трійку сервісів і відновлюючи паролі.

Повинен відзначити, що на цю статтю мене наштовхнула опис «Garbage Wrapper» від search коментарях до моєї попередньої статті.

Отже, уявимо, що ви вже вийшли з депресії після побаченого коду, кава закінчилося, і ось настав момент коли ви готові почати і вже навіть встановили ваш улюблений підходящий фреймворк, але…
Після недовгого дебага з'ясовується, що весь проект побудований на сотнях ланцюжків инклудов і «висмикнути» потрібний шматок коду щоб зробити з нього сервіс/модель неможливо.

Візьмемо для прикладу класичний файл тієї чудної noPSR-епохи:

// legacy_lib.php

include("settings.inc.php");
require("functions.php");
require_once("database.connection.php");

define('SOME_CONST', 'value');

$var1 = funcName(CONST_2);

function get_Var2A($param1, $param2) {
return functionFromAnotherInclude($param2, $param1);
}

class myClass
{
var $data = ";
function getData() {
global $var1;
// do somethig
return get_Var2A($var1, SOME_CONST);
}
}

include_once("specialCode.php");

function needThis() {
$obj = new myClass();
return unknownFunctionFromInclude() + $obj->getData();
}
$var2 = needThis();
printr('{"param":' . $var2 . '; "var": '. $var1 . '}');


Насправді такий файл часто може досягати 1000+ рядків і залежностей в рази більше.
Можна спробувати рознести цей код у класи, сервіси і тд. Але ймовірність того, що він буде працювати так само — спрямується до нуля.
Потрібно швидке рішення, яке дасть можливість запустити задачу і зробити рефактор «плавніше» ну або зовсім забити відкласти його на деякий час.

Я не буду застосовувати тут канонічні шаблони проектування бо в таких ситуаціях це дуже суб'єктивно. Запропоную лише скористатися підходами з цих двох: пристосуванець (flyweight) і адаптер (adapter).

Пристосуванець нам знадобиться для запуску і псевдо-інкапсуляції легасі коду, а адаптер — для універсального доступу до нього.
Я свідомо не використовую (термін|шаблон)и: «фасад», «маппер», «декоратор» і тп. Безсумнівно, в залежності від того, що містить і яку структуру має легасі-файл(и) — ті або інші (термін|шаблон)и можуть бути більш придатними.
Я ставлю метою відносну універсальність, тому розумію, що буду «адаптувати» результат пристосуванця під потреби сервісу/моделі.

Тепер докладніше про кожен.

Завдання пристосуванця у моєму випадку полягають в наступному:
  1. Підмінити при необхідності директорію инклудов;
  2. Підключити потрібний файл;
  3. Буферізіровать результат;
  4. Инкапсулировать глобальні змінні;
  5. Псевдо-инкапсулировать глобальні функції;
  6. Надати можливість доступу до всього вищепереліченого
Завдання адаптера:
  1. Налаштувати і створити пристосуванця;
  2. Дати можливість працювати з пристосуванцем як із звичайним об'єктом;
  3. Дати можливість долати будь-які методи і властивості;
  4. Бути супер-класом для «фасаду», «маппера», «декоратора» та інших структурних шаблонів
Що отримуємо в результаті:

class MyLib extends LegacyAbstractAdapter
{
/**
* Configure flyweight
*/
protected function configure()
{
$this
->setLegacyFile('legacy_lib.php')
->setLegacyPath('/path/to/includes')
;
}
}

$myLib = new MyLib();

// отримуємо змінні
$var1 = $myLib->var1;
$var2 = $myLib->var2;

// перезаписуємо їх
$myLib->var1 = 'some new value';

// доступ до функцій
$res1 = $myLib->get_Var2A($param1, $param2);
$res2 = $myLib->needThis();

// отримання результату виконання файлу
$content = $myLib->getFlyweight()->getContent();


Тепер нам також доступна можливість декорувати, робити композиції і тд.

class MyLib extends LegacyAbstractAdapter
{
/**
* Configure flyweight
*/
protected function configure()
{
$this
->setLegacyFile('legacy_lib.php')
->setLegacyPath('/path/to/includes')
;
}

// переопределенная функція
public function needThis()
{
return 'dummy value';
}

// декорування функції
public function get_Var2A($param1, $param2)
{
return '<font>' . $this->getFlyweight()->call('get_Var2A', [$param1, $param2]); . '</font>';
}

// і тд.
}


І, на мій погляд, тільки в залежності від вмісту кінцевого класу «MyLib» — «адаптер» можна назвати як-то більш слушно.

Так само є можливість доступу до оголошеного всередині файлу класу: створення инстанса, отримання констант і виклик його статичних методів.
Хоча це можна зробити безпосередньо звернувшись до нього по імені» — така можливість є для абстракції. На той випадок, якщо після рефактору такий клас перестане існувати — досить лише замінити один метод доступу до нього, а не всі виклики.

І, звичайно ж, є ряд недоліків, про які варто сказати:
  • Глобальні функції і класи продовжують бути глобальними і доступними «напряму», цей підхід тільки регламентує доступ до них, щоб не «плодити» ще більше залежного коду;
  • Швидкість роботи. Провівши тест і звернувшись до функцій 10 млн. разів — результат був отриманий за час, що удвічі перевищує «нативний» спосіб. Тут потрібно враховувати навантаження та виправданість. Хоча, на мій погляд, в більшості випадків це не буде суттєвою проблемою;
  • «Синглтонность». Неможливо створити одночасно 2 пристосуванця зважаючи на те, що инклуд можна виконати тільки 1 раз


Резюме: якщо у вас немає кількох місяців на рефактор, але є невеликий запас продуктивності — думаю вам це може знадобитися: github
Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.