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

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)

 



Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *