Controller Action mit Events überschreiben

Veröffentlicht am 27.07.2011 von datenbrille in der Schublade Anderes | Ein Kommentar »

In meinem Projekt verwenden wir momentan einen Rewrite des Checkout Controllers um unsere Modifikation vorzunehmen. Irgendwie gefiel mir das nicht und ich habe nach einer anderen Methode gesucht. Nach einigen Hinweisen von den Webguys habe ich eine Event-Observer Idee entwickelt.

Das ganze ist möglich weil Magento in Mage_Core_Controller_Varien_Action in der Methode dispatch ein Flag abfragt ob es überhaupt einen Dispatch geben soll.

//Mage_Core_Controller_Varien_Action->dispatch()
...
if ($this->getRequest()->isDispatched()) {
    /**
     * preDispatch() didn't change the action, so we can continue
     */
    if (!$this->getFlag('', self::FLAG_NO_DISPATCH)) {
        $_profilerKey = self::PROFILER_KEY.'::'.$this->getFullActionName();
        Varien_Profiler::start($_profilerKey);
        $this->$actionMethodName();
        Varien_Profiler::stop($_profilerKey);
        Varien_Profiler::start(self::PROFILER_KEY.'::postdispatch');
        $this->postDispatch();
        Varien_Profiler::stop(self::PROFILER_KEY.'::postdispatch');
    }
}
...

Setzt man nun diesen Flag in Controller oder sonst wo, dann wird Magento nicht die Action des Controller ausführen. Damit war ich meinem Ziel schon etwas näher.
Aber wie kann ich mich in eine beliebige Controller Action einhängen? Auch hier bietet Magento einen Hook. Dieser befindet sich ebenfalls in Mage_Core_Controller_Varien_Action, aber dieses mal in der Methode preDispatch. Dort werden verschiede Events geworfen und das zu einem sehr frühen Zeitpunkt in Request-Response Zyklus von Magento. Erst dadurch ist es möglich eine Action vollständig zu überschreiben.

Mage::dispatchEvent('controller_action_predispatch', array('controller_action'=>$this));
Mage::dispatchEvent(
    'controller_action_predispatch_'.$this->getRequest()->getRouteName(),
    array('controller_action'=>$this)
);
Varien_Autoload::registerScope($this->getRequest()->getRouteName());
Mage::dispatchEvent(
    'controller_action_predispatch_'.$this->getFullActionName(),
    array('controller_action'=>$this)
);

Wie ihr seht werden hier viele verschiedene Events gefeuert. Ich kann einfach auf alle Controller preDispatches hören oder auch nur auf bestimmte. Zur Demonstration habe ich einfach mal alle Actions überschrieben.

//XML in aus der Config XML eines Moduls
<controller_action_predispatch>
  <observers>
    <updateflags>
      <type>singleton</type>
      <class>magelog_teaser/observer</class>
      <method>stopAction</method>
    </updateflags>
  </observers>
</controller_action_predispatch>

Die Methode im Oberserver macht nichts anderes als ein Hello in die Response zu schreiben. Will man nun eine Methode im Checkout überschreiben, ersetzt man das Hello durch eine JSON Rückgabe und wählt den Event weniger allgemein.

public function stopAction(Varien_Event_Observer $observer) {
        //controller aus dem event laden
        /** @var $controller Mage_Core_Controller_Varien_Action */
        $controller = $observer->getData('controller_action');
        //hier passiert die Magie, und wird der Dispatch unterbrochen
        $controller->setFlag(
             $controller->getRequest()->getActionName()
             ,Mage_Core_Controller_Varien_Action::FLAG_NO_DISPATCH
             ,true
         );
        //jetzt kann man beliebigen Output erzeugen
        $controller->getResponse()->setBody("Hello");
}

Viel Spaß damit und ich werde berichten wie weit wir dieses Konzept in unserem neuen Checkout getrieben haben. Ach ja, das ganze hat bei uns auch schon einen Namen: “Das schöne Magento die()”.


Warum man in Update Scripten keine Entitäten löscht!

Veröffentlicht am 27.07.2011 von datenbrille in der Schublade Magento, SQL | Comments Off

Ich habe mit einem neueren Release von meinen Shop auch mal die Datenbank aufgeräumt und überflüssige Kategorien und Attribute gelöscht. Was ich nicht wusste ist, das Magento bei jedem Script oder Durchgang die Foreign Key Contraints abschaltet.

Ich hatte mich bei der Entwicklung auch gefragt warum in Magento 1.5 es nicht mehr erlaubt ist Category zu löschen, wenn man nicht im Backend eingelogt ist. Gibt es da einen Zusammenhang? Ich weiß es leider nicht, aber vielleicht ihr.

Ich konnte das Problem auch nicht mit MySQL-Mitteln lösen.


Translations in der Datenbank und das gute alte Locale

Veröffentlicht am 27.07.2011 von datenbrille in der Schublade Magento, Upgrade | Comments Off

Kleiner Tipp bei der Migration von älteren Magento Versionen auf die Version 1.5.X.X. Die Translations werden nun aus der DB mit einem Locale Parameter geladen. Soweit kein Problem, solange das Locale zu den Übersetzungen passt. Bei mir war es leider nicht so.

Code aus Magento 1.5.1.0:

    protected function _loadDbTranslation($forceReload = false)
    {
        $arr = $this->getResource()->getTranslationArray(null, $this->getLocale());
        $this->_addData($arr, $this->getConfig(self::CONFIG_KEY_STORE), $forceReload);
        return $this;
    }

Code aus Magento 1.3.2.4. Man beachte den fehlenden Locale Parameter bei getTranslationArray().

    protected function _loadDbTranslation($forceReload = false)
    {
        $arr = $this->getResource()->getTranslationArray();
        $this->_addData($arr, $this->getConfig(self::CONFIG_KEY_STORE), $forceReload);
        return $this;
    }

Fehlende Tabellen nach Magento Upgrade nachrüsten.

Veröffentlicht am 04.07.2011 von datenbrille in der Schublade Magento, Upgrade | Tags: | Comments Off

Ich führe gerade ein Upgrade einer Magento 1.3 Installation auf die Version 1.5 durch. Dabei ist mir im System Log folgendes aufgefallen:

PHP Fatal error:  Uncaught exception ‘PDOException’ with message ‘SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘core_file_storage’ doesn’t exist’.

Nach ein wenig suchen im Magento Core ist mir aufgefallen, dass diese Tabellen “on the fly” anlegt werden. Jedoch nur wenn die Datenbank als Cache-Backend verwendet wird. Der Fehler hat bei mir nur das Log voll geschrieben. Der Shop war nicht beeinträchtigt.

Die Lösung ist folgender Schnippsel:

/** @var $model Mage_Core_Model_File_Storage_Directory_Database */
$model = Mage::getModel('core/file_storage_directory_database');
$model->prepareStorage();
/** @var $model Mage_Core_Model_File_Storage_Database */
$model = Mage::getModel('core/file_storage_database');
$model->prepareStorage();

Wichtig ist dabei die Reihenfolge! Die zweite Tabelle erstellt Foreign Keys auf die erste. Das ganze habe ich in ein Update Script eines der Module gepackt und nun werden sie einfach einmalig erstellt.