Excluir a exibição da loja programaticamente no script de atualização

12

Quero excluir uma visão da loja programaticamente . Olhando Mage_Adminhtml_System_StoreController::deleteStorePostAction(), isso é bastante fácil (código reduzido um pouco):

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

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

Quero colocar esse código em um script de atualização de dados para que a exclusão seja executada automaticamente.

O problema é que, ao executar os scripts de atualização no data/Magento, apenas chama observadores de eventos configurados na globalárea (consulte Atualizações de estrutura do Magento vs. Atualizações de dados ). Certos observadores como enterprise_cmse enterprise_searchpara o evento store_delete_aftersão definidos na adminhtmlárea para que não sejam executados. A exclusão da visualização da loja não será tratada como uma exclusão executada no back-end.

Como você lida com operações como esta? Carregue você mesmo áreas de eventos adicionais nos scripts de atualização (eu tenho medo disso)? Não faça modificações de dados como essa no script de atualização, mas coloque seus scripts mágicos em um local sagrado e oculto e execute-os manualmente?

Matthias Zeis
fonte
1
Por que você precisa excluir a visualização da loja com problemas?
Oleksii.svarychevskyi
Como temos vários ambientes e é mais rápido e confiável fazer todas as alterações na configuração programaticamente.
Matthias Zeis
Já encontrou uma solução para isso? Quantas vezes você faria isso? Pessoalmente, eu optaria pela segunda opção "Não faça modificações de dados como essa no script de atualização, mas coloque seus scripts mágicos em um local sagrado e oculto e execute-os manualmente?"
ProxiBlue
Adiei esta questão porque havia coisas mais importantes a fazer. O @philwinkle da Magento SE respondeu no Twitter: "Eu faço isso em uma fila; ou ouço e aciono um evento de despacho adminhtml em tandem (também conhecido como falso)" ( twitter.com/philwinkle/status/428183845985210369 ). Acredito que isso seja muito arriscado para mim e, embora eu não ame essa abordagem, farei isso manualmente.
Matthias Zeis
@MatthiasZeis, você pode adicionar isso como resposta e aceitá-lo para manter a contagem das perguntas não respondidas?
Sander Mangel

Respostas:

6

Então, pouco depois de tuitá-lo para Matthias, fiquei em silêncio no rádio. Espero que tenha sentido o suspense, pois aguarda esta resposta há algumas semanas.

O que quero dizer com "faço isso em uma fila" está em resposta direta a:

Certos observadores, como enterprise_cms e enterprise_search, para o evento store_delete_after, são definidos na área adminhtml, para que não sejam executados. A exclusão da visualização da loja não será tratada como uma exclusão executada no back-end.

Método da fila:

Quando eu sei que existem certos eventos que não são acionados no contexto correto (principalmente para EE, mas podem ser aplicados em outros contextos), normalmente empurro a exclusão para uma fila para que ela seja executada no contexto necessário. .

Em outras palavras, crie uma tabela de filas (ou fila / tópico no RabbitMQ etc.) que contenha detalhes da transação e os ganchos de eventos que devem estar ouvindo. Isso pode ser tão elegante ou simplista quanto você deseja. Aqui está um básico

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

E trabalhe a fila posteriormente em um CRON, onde agora você tem controle sobre qual loja está "executando" (ou seja, você está executando apenas como se fosse o administrador, loja 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));
        }
    }
}

Obviamente, se você estava começando a gostar de uma tentativa / captura e quebra em uma transação. Eu acho que você entendeu.

Essa é a única maneira viável de controlar o contexto em que o evento é disparado.

Método de evento tandem:

Você pode disparar o método "adminhtml" manualmente - Alan dá uma explicação bastante decente do que você faria para afetar isso , mas essencialmente é o mesmo:

#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()
    ));        
    //..
}

A versão administrativa do save do cliente chama o modelo regular de save e despacha o evento adminhtml posteriormente. Você pode fazer o inverso disso em um observador, se assim o desejar.

philwinkle
fonte
5

Droga, eu amo algumas coisas, mas tenho que discordar da complexidade / fragilidade de transportar os parâmetros da tarefa e a área ( adminhtml | crontab | frontend | global | install ) para uma fila, especialmente se essa fila estiver em execução um contexto Magento. Se houver contextos mistos que precisam ser manipulados, a solução da fila é uma reimplementação do "problema" atual!

Eu acho que a abordagem da fila é quebradiça. Meu argumento é que carregar áreas de eventos prematuramente não é realmente um problema. Para explicar isso, vamos voltar e analisar o problema:

Qual é o perigo de carregar uma área de eventos prematuramente em um escopo de execução?

Para entender isso, devemos examinar as áreas de eventos no contexto de execução. Matthias, imagino que você já saiba disso, mas para a edificação de outras pessoas:

Os scripts de configuração de dados são executados Mage_Core_Model_App::run()antes do envio da solicitação ao Front Controller:

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;
}

No momento em que os scripts de configuração de dados estão sendo executados, a área de eventos globais é carregada. As áreas de eventos contextuais de roteamento ( frontend ou adminhtml ) são carregadas posteriormente Mage_Core_Controller_Varien_Action::preDispatch()como resultado do roteador correspondente a uma ação do controlador (o areanome é definido por herança):

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

Portanto, normalmente durante a inicialização do aplicativo, apenas os observadores configurados na área de eventos globais serão executados. Se o script de instalação fizer algo como

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

então existem apenas dois perigos:

  1. Um observador foi configurado incorretamente em adminhtml para observar um evento sem contexto, como controller_front_init_beforeoucontroller_front_init_routers
  2. A solicitação é uma solicitação de front - end .

O número 1 deve ser fácil de saudar. O número 2 é a verdadeira preocupação, e acho que o Reflection pode resolver o problema (note que sou extremamente inexperiente em usar o reflexo):

<?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()

Eu não testei isso, mas ele remove o índice de eventos adminhtml e o Mage_Core_Model_App_Areaobjeto correspondente .

benmarks
fonte
1
Eu não sou digno!! Eu não sou digno!!!!
19414 philwinkle