Magento для PHP MVC розробників – Перевизначення і оновлюваність системи (ч.10/11)

Дуже популярна функція Мадженто системи, та функція, якою часто зловживають – це можливість перевизначити поведінку основної системи. Інша популярна та часто обговорювана тема для розробників Magento – це можливість для оновлення, і як перевизначення може з цим співіснувати. Сьогодні ми подивимося на різні варіанти перевизначення, які роблять процес перемикання версій (оновлюваність) важким.

 

Перш ніж почати, важливо зазначити, що ми говоримо про зміну core “Бізнес-логіки” Magento. Зміни в шаблонах phtml і очікувані, і поширені в усіх магазинах, окрім найпростіших.

 

 

Злам джерела

“Найменш оновлюваний” спосіб змінити поведінку Magento (або будь-якої PHP-системи) полягає в тому, щоб безпосередньо змінювати вихідний код джерела. Якщо ви хочете змінити поведінку Моделі товару, відредагуйте файл Моделі товару:

app/code/core/Mage/Catalog/Model/Product.php  

 

Коли ви це зробите, ви хакнете базовий код Magento. Щоразу, коли ви будете оновлювати систему, вам потрібно буде знову повторювати такі зміни в потрібних файлах. Це рідко виходить добре.

Крім того, ви ризикуєте змінити очікувану поведінку методів, коли вони будуть повертати різні значення, не запускаючи екшенів, від яких може залежати система, або змінити дані несподіваними (а не очікуваними) шляхами. Ми поговоримо про це нижче.

 

На жаль, незважаючи на свою неприйнятність, це найпростіший і найбільш зрозумілий спосіб для багатьох розробників PHP почати роботу з Magento. Перш ніж запускати будь-який новий проект, я завжди завантажую чисту версію CMS і запускаю порівняння як і з папкою lib, так і з папкою app/code/core, щоб побачити, які зміни було внесено до ядра.



 

 

Включення різних класів

Magento, а точніше – PHP, виконує пошук файлів класу в наступних теках Magento:

 

lib/*  app/code/core/*  app/code/community/*  app/code/local/*  

 

Через це, а також через те, що Magento конструює шляхи включення PHP, розміщуючи копію core файлу у теці app/code/local, PHP спочатку включить його. Отже, якщо ви хочете змінити функціональність Моделі товару, додайте свою власну копію:

 

Ваша копія:    app/code/local/Mage/Catalog/Model/Product.php  Оригінал: app/code/core/Mage/Catalog/Model/Product.php  

 

Ваш файл визначає клас замість основного (core) файлу, і тому основний файл ніколи не потрібно буде включати. Це уникає проблеми з об’єднанням файлів, а також централізує всі ваші налаштування в одній структурі каталогів.

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

 

Наприклад, розгляньте метод getName у вказаній Моделі товару:

 

/**  * Отримуємо назву товару  *  * @return string  */  public function getName() {    return $this->_getData('name');  }  

 

Під час перевизначення цього методу ви можете випадково додати шлях коду до вашого перевизначення, якщо цей метод повертає null:

 

/**  * Локальний перезапис! Отримуємо назву товару  *  * @return string  */    public function getName($param=false) {    if($param == self:NICKNAME) {      return $this->_getData('nickname');    }    else if($param == self::BAR) {      return $this->_getData('name')    }  // забули про повернення, тому що важко працювали  }  

 

Якщо інші частини системи покладаються на цей метод, щоб повернути такий рядок, ваші налаштування можуть зламати ці інші частини системи. Це стає ще гіршим, коли методи повертають об’єкти, тому що намагання викликати метод на null призведе до фатальної помилки (це є частиною того, що програмісти Java та C# сперечаються про безпеку типу w/r/t у PHP)

 

Далі розглянемо метод валідації в тій же Моделі:

 

public function validate() {    Mage::dispatchEvent($this->_eventPrefix.'_validate_before', array($this->_eventObject=>$this));    $this->_getResource()->validate($this);    Mage::dispatchEvent($this->_eventPrefix.'_validate_after', array($this->_eventObject=>$this));    return $this;  }  

 

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

 

//Моє перевизначення!  public function validate() {    $this->_getResource()->validate($this);    $this->myCustomValidation($this);    return $this;  }  

 

Інші частини системи, які покладаються на ці події, не будуть працювати.

 

І нарешті, ви ще не розв’язали проблему із оновленнями. Якщо під час оновлення змінюються методи будь-якого класу, Magento все ще буде включати в себе колишній, застарілий клас із колишніми, застарілими методами. Практично це означає, що вам доведеться виконувати вручну оптимізацію під час кожного оновлення.

 

 

Використання системи Перевизначення/Перезапису

Система класів Перевизначення/Перезапису Magento спирається на використання фабричного шаблону для створення Моделей, Помічників та Блоків. Коли ви використовуєте:

 

Mage::getModel('catalog/product');  

 

ви цим самим кажете Magento:

 

Отже, оглянь клас для використання 'catalog/product' і зроби його екземпляр для мене.

 

У свою чергу, Magento оглядає свої файли конфігурації системи і питає:

 

Отже, створимо дерево з файлів config.xml. Який клас я повинен використовувати для 'catalog/product'?

 

Потім Magento робить екземпляр і повертає вам Модель.

 

Коли ви перевизначаєте клас у Magento, ви змінюєте файли конфігурації, щоб сказати:

Якщо Модель 'catalog/product' інстанційована, використовуй мій клас (Myp_Mym_Model_Product) замість core класу

 

Потім, коли ви визначаєте свій клас, ви маєте розширити його початковим класом

 

class Myp_Mym_Model_Product extends Mage_Catalog_Model_Product {  }  

 

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

 

Проте, все ще залишається питання зміни поведінки методу. Розглянемо знову ж таки метод getName і метод валідації. Ваші нові методи можуть так само легко забути/змінити тип повернутого значення або втратити критичну частину функціональних можливостей з вихідних методів:

 

class Myp_Mym_Model_Product extends Mage_Catalog_Model_Product {    public function validate() {      $this->_getResource()->validate($this);      $this->myCustomValidation($this);      return $this;    }      public function getName($param=false) {      if($param == self:NICKNAME) {        return $this->_getData('nickname');      } else if($param == self::BAR) {        return $this->_getData('name')      }      //забули про повернення, тому що важко працювали    }  }  

 

Система Перевизначення/Перезапису не захистить вас від цього, але дасть вам спосіб уникнути помилки. Оскільки ми насправді розширюємо початковий клас, ми можемо викликати оригінальний метод, використовуючи конструктор parent:: PHP:

 

class Myp_Mym_Model_Product extends Mage_Catalog_Model_Product {    public function validate() {      //тут кастомний код валідації      return parent::validate();    }      public function getName($param=false) {      $original_return = parent::getName();      if($param == self::SOMECONST) {        $original_return = $this->getSomethingElse();      }      return $original_return;    }  }  

 

Викликаючи оригінальні методи, ви забезпечуєте, що усі дії, які мають відбутися, відбудуться. Також, отримавши оригінальне повернене значення, ви зменшите шанси того, що ваш метод поверне щось несподіване.

 

Це якщо коротко, але не виключено в повному обсязі. Зараз вам, як розробнику, потрібно, щоб ваш спеціальний код повернув об’єкти або примітиви, які збігаються з оригінальним методом. Це означає, що навіть якщо ви використовуєте таку систему перевизначення, вона все-таки може зламати систему.

 

Через це, коли я роблю контроль архітектури рішення, я намагаюся звести моє перевизначення до мінімуму. Коли мені потрібне перевизначення, я завжди намагаюся завершувати свої методи з:

return parent::originalMethod();  

 

Якщо для моїх змін потрібно спочатку запустити оригінальний метод, я використовую конструкцію на кшталт:

 

public function someMethod() {    $original_return = Mage::getModel('mymodule/immutable')->setValue('this is a test');    //мій користувацький код тут    return $original_return->getValue();  }  

 

URI 'mymodule/immutable' вказує на просту реалізацію незмінного об’єкта:

 

class Alanstormdotcom_Mymodule_Model_Immutable {    protected $_value=null;    public function setValue($thing) {      if(is_null($this->_value)) {        $this->_value = $thing;        return $this;      }      // якщо ми спробуємо знову встановити значення, видамо виняток.      throw new Exception('Already Set');     }      public function getValue() {       return $this->_value;    }  }  

 

Це не заважає комусь (включаючи мене) перезаписати $original_return чимось іншим, але це може багато чого знівелювати. Якщо у моєму класі перевизначення є метод, який не завершується або $original_return->getValue(); або parent::method, мій погляд негайно падає на нього як на можливого винуватця будь-якої проблеми, яку я налагоджую.

 

Нарешті, є моменти, коли ви хочете (або думаєте, що хочете) змінити повернення значення core методу. Коли виникає в цьому потреба, я вважаю, що набагато безпечніше буде визначити новий метод, який викликає оригінал, а потім змінити свою тему, щоб викликати цей новий метод:

 

class Mage_Catalog_Model_Original extends Mage_Core_Model_Abstract {    protected function getSomeCollectionOriginal() {       return Mage::getModel('foo/bar')->getCollection()->addFieldToFilter('some_field', '42');    }  }    class Myp_Mym_Model_New extends Mage_Catalog_Model_Original {    public function getSomeCollectionWithAdditionalItems() {      $collection = $this->getSomeCollectionOriginal();      //тепер змініть або переоцініть $collection власним кодом      return $collection;    }  }  

 

Це гарантує, що якщо будь-які додаткові побічні ефекти, створені за допомогою оригінального методу, все ще виникають, оригінальне повернення типу/результату буде однакове, і ви все одно можете додавати свою спеціальну логіку до певних частин системи.

 

 

Підсумок

Можливість модернізації будь-якої великої або навіть незначної версії програмного пакета, якою ви не керуєте, завжди буде досить складною. Apple та Microsoft витрачають мільйони доларів на випробування своїх шляхів вдосконалення під час випуску нових ОС, а Інтернет все ще сповнений жахливими історіями про випадки поганого кодування.

Як користувач Magento системи ваша робота полягає в тому, щоб забезпечити мінімальні зміни core системи, а якщо такі зміни будуть конче необхідні, вони будуть зроблені в чистому, легко діагностованому вигляді. І використання системи Перевизначення/Перезапису Magento є потужним інструментом для досягнення цієї мети.

 

 

 

Автор: Alan Storm (http://alanstorm.com/magento_upgrade_rewrite_override/)

Переклад українською: SebWeo

 

 

Всі статті даної серії:

  1. Magento для PHP MVC розробників (Alan Storm) – ч.1/11
  2. Magento для PHP MVC розробників – розбір Контролера (ч.2/11)
  3. Magento для PHP MVC розробників – Макети, Блоки та Шаблони (ч.3/11)
  4. Magento для PHP MVC розробників – Моделі та основи ORM (ч.4/11)
  5. Magento для PHP MVC розробників – Інсталювання Ресурсу (ч.5/11)
  6. Magento для PHP MVC розробників – Розширений ORM – EAV (ч.6/11)
  7. Magento для PHP MVC розробників – Особлива конфігурація системи (ч.7/11)
  8. Magento для PHP MVC розробників – Поглиблене налаштування системи (ч.8/11)
  9. Magento для PHP MVC розробників – Колекції Varien Data (ч.9/11)
  10. Magento для PHP MVC розробників – Перевизначення і оновлюваність системи (ч.10/11)
  11. Magento для PHP MVC розробників – Конфігурація системи за замовчуванням (ч.11/11)