Supprimer la vue du magasin par programme dans le script de mise à niveau

12

Je souhaite supprimer une vue de magasin par programme . En regardant Mage_Adminhtml_System_StoreController::deleteStorePostAction(), c'est assez facile (code abrégé un peu):

$model = Mage::getModel('core/store')->load($id);

if ($model->getId() && $model->isCanDelete()) {
    $model->delete();
    Mage::dispatchEvent('store_delete', array('store' => $model));
}

Je veux mettre ce code dans un script de mise à niveau des données afin que la suppression soit exécutée automatiquement.

Le problème est que lors de l'exécution des scripts de mise à niveau dans data/Magento, seuls les observateurs d'événements configurés dans la globalzone sont appelés (voir Mises à jour de structure Magento vs Mises à jour de données ). Certains observateurs comme enterprise_cmset enterprise_searchpour l'événement store_delete_aftersont définis dans la adminhtmlzone afin qu'ils ne soient pas exécutés. La suppression de la vue du magasin ne sera pas traitée comme une suppression exécutée dans le backend.

Comment gérez-vous des opérations comme celle-ci? Chargez vous-même des zones d'événements supplémentaires dans les scripts de mise à niveau (j'en ai peur)? Ne faites pas de modifications de données comme celle-ci dans le script de mise à niveau, mais placez vos scripts magiques dans un endroit caché sacré et exécutez-les manuellement?

Matthias Zeis
la source
1
Pourquoi devez-vous supprimer la vue du magasin de manière problématique?
oleksii.svarychevskyi
Parce que nous avons plusieurs environnements et qu'il est plus rapide et plus fiable d'effectuer toutes les modifications de configuration par programmation.
Matthias Zeis
Avez-vous déjà trouvé une solution à cela? À quelle fréquence feriez-vous cela? Personnellement, j'irais pour la deuxième option "Ne faites pas de modifications de données comme ça dans le script de mise à niveau mais placez vos scripts magiques dans un endroit caché sacré et exécutez-les manuellement?"
ProxiBlue
J'ai reporté ce problème car il y avait des choses plus importantes à faire. Le propre @philwinkle de Magento SE a répondu sur Twitter: "Je les fais dans une file d'attente; ou j'écoute et déclenche un événement de répartition adminhtml en tandem (aka fake it)" ( twitter.com/philwinkle/status/428183845985210369 ). Je crois que c'est trop risqué pour moi et bien que je n'aime pas cette approche, je le ferai manuellement.
Matthias Zeis
@MatthiasZeis pouvez-vous ajouter cela comme réponse et l'accepter pour que les questions sans réponse soient décomptées?
Sander Mangel

Réponses:

6

Donc, peu de temps après avoir tweeté cela à Matthias, je suis resté silencieux à la radio. J'espère que vous avez ressenti le suspense en attendant cette réponse depuis quelques semaines maintenant.

Ce que je veux dire par «je fais ça dans une file d'attente» est en réponse directe à:

Certains observateurs comme enterprise_cms et enterprise_search pour l'événement store_delete_after sont définis dans la zone adminhtml afin qu'ils ne soient pas exécutés. La suppression de la vue du magasin ne sera pas traitée comme une suppression exécutée dans le backend.

Méthode de file d'attente:

Quand je sais qu'il y a certains événements qui ne se déclencheront pas dans le contexte correct (principalement pour EE, mais peuvent s'appliquer dans d'autres contextes), je pousse généralement la suppression dans une file d'attente afin qu'elle s'exécute dans le contexte dont elle a besoin. .

En d'autres termes, créez une table de file d'attente (ou file d'attente / rubrique dans RabbitMQ, etc.) qui contiendrait les détails de la transaction et les hooks d'événement qu'elle devrait écouter. Cela peut être aussi élégant ou aussi simpliste que vous le souhaitez. Voici une base

$queue = Mage::getModel('yourcompany/queue_job')
         ->setJobType('delete')
         ->setEntityType('core/store')
         ->setEntityId(12)
         ->setDispatchEvent('store_delete')
         ->setDispatchEventDataKey('store')
         ->save();

Et puis travaillez la file d'attente plus tard dans un CRON, où vous avez maintenant le contrôle sur le magasin qui est "en cours d'exécution" (c'est-à-dire que vous l'exécutez comme s'il s'agissait de l'administrateur, le magasin 0):

foreach(Mage::getModel('yourcompany/queue_job')->getCollection() as $job){
    if($job->getJobType()=='delete'){

        $model = Mage::getModel($this->getEntityType())->load($this->getEntityId());

        if ($model->getId() && $model->isCanDelete()) {
            $model->delete();
            Mage::dispatchEvent($job->getDispatchEvent(), array($job->setDispatchEventDataKey() => $model));
        }
    }
}

Évidemment, si vous deveniez fantaisiste, vous encapsulez dans un essai / capture et encapsulez dans une transaction. Je pense que vous obtenez l'essentiel.

C'est probablement le seul moyen de contrôler le contexte dans lequel l'événement se déclenche.

Méthode d'événement en tandem:

Vous pouvez déclencher la méthode "adminhtml" vous-même manuellement - Alan donne une explication assez décente de ce que vous feriez pour affecter cela , mais essentiellement c'est la même chose que ceci:

#File: app/code/core/Mage/Adminhtml/controllers/CustomerController.php
public function saveAction()
{
    //...
    $customer->save();
    //...
    Mage::dispatchEvent('adminhtml_customer_prepare_save', array(
        'customer'  => $customer,
        'request'   => $this->getRequest()
    ));        
    //..
}

La version d'administration de la sauvegarde client appelle la sauvegarde de modèle standard et distribue ensuite l'événement adminhtml. Vous pouvez faire l'inverse chez un observateur si vous le souhaitez.

philwinkle
la source
5

Bon sang, je m'aime un peu, mais je dois être en désaccord avec la complexité / fragilité du transport des paramètres de la tâche et de la zone ( adminhtml | crontab | frontend | global | install ) vers une file d'attente, surtout si cette file d'attente doit être en cours d'exécution un contexte Magento. S'il existe des contextes mixtes qui doivent être traités, la solution de file d'attente est une réimplémentation du "problème" actuel!

Je pense que l'approche de la file d'attente est fragile. Mon argument est que le chargement prématuré des zones d'événements n'est pas vraiment un problème. Pour expliquer cela, revenons en arrière et examinons le problème:

Quel est le danger de charger prématurément une zone d'événements dans une étendue d'exécution?

Pour comprendre cela, nous devons examiner les zones d'événements dans le contexte d'exécution. Matthias, j'imagine que vous le savez déjà, mais pour l'édification des autres:

Les scripts de configuration des données sont exécutés Mage_Core_Model_App::run()avant d'envoyer la demande au contrôleur frontal:

public function run($params)
{
    $options = isset($params['options']) ? $params['options'] : array();
    $this->baseInit($options);
    Mage::register('application_params', $params);

    if ($this->_cache->processRequest()) {
        $this->getResponse()->sendResponse();
    } else {
        $this->_initModules();
//Global event area is loaded here
        $this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS);

        if ($this->_config->isLocalConfigLoaded()) {
            $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : '';
            $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store';
            $this->_initCurrentStore($scopeCode, $scopeType);
            $this->_initRequest();
//Data setup scripts are executed here: 
            Mage_Core_Model_Resource_Setup::applyAllDataUpdates();
        }

        $this->getFrontController()->dispatch();
    }
    return $this;
}

Au moment où les scripts de configuration des données s'exécutent, la zone d'événements globale est chargée. Les zones d'événements contextuelles de routage ( frontend ou adminhtml ) sont chargées plus tard à Mage_Core_Controller_Varien_Action::preDispatch()la suite de la correspondance du routeur avec une action du contrôleur (le areanom est défini via l'héritage):

public function preDispatch()
{
    //...
    Mage::app()->loadArea($this->getLayout()->getArea());
    //...
}

Ainsi, normalement lors de l'initialisation de l'application, seuls les observateurs configurés sous la zone d'événement global seront exécutés. Si le script de configuration fait quelque chose comme

$this->loadAreaPart(Mage_Core_Model_App_Area::AREA_ADMINHTML, Mage_Core_Model_App_Area::PART_EVENTS);

alors il n'y a que deux dangers:

  1. Un observateur a été mal configuré sous adminhtml pour observer un événement sans contexte tel que controller_front_init_beforeoucontroller_front_init_routers
  2. La demande est une demande frontale .

# 1 devrait être facile à comprendre. # 2 est la vraie préoccupation, et je pense que la réflexion peut résoudre le problème (notez que je suis terriblement inexpérimenté avec la réflexion):

<?php

//Start setup script as normal
$installer = $this;
$installer->startSetup()

//Load adminhtml event area
Mage::app()->loadAreaPart(
    Mage_Core_Model_App_Area::AREA_ADMINHTML,
    Mage_Core_Model_App_Area::PART_EVENTS
);

// your setup script logic here

//I hope this isn't a bad idea.
$reflectedApp = new ReflectionClass('Mage_Core_Model_App');
$_areas = $reflectedApp->getProperty('_areas');
$_areas->setAccessible(true);
$areas = $_areas->getValue(Mage::app());
unset($areas['adminhtml']);
$_areas->setValue(Mage::app(),$areas); //reset areas

//End setup script as normal
$installer->endSetup()

Je n'ai pas testé cela, mais cela supprime l'index des événements adminhtml et l' Mage_Core_Model_App_Areaobjet correspondant .

Benmarks
la source
1
Je n'en suis pas digne!! Je n'en suis pas digne!!!!
philwinkle