Como posso ter um arquivo XML personalizado nos módulos mesclados como um no Magento 2? (MageStackDay mistério questão 2)

22

Pergunta de bônus do MageStackDay para 500pts Bounty E a possibilidade de ganhar uma licença Z-Ray gratuita por um ano. Mais informações podem ser encontradas >> aqui <<

As perguntas são fornecidas / inspiradas pelo desenvolvedor principal do Magento 2, Anton Kril.

Questão:

Estou criando uma extensão que possui um conjunto separado de configurações.
Isso significa que eu não posso usar config.xmlou routes.xmlou fieldset.xmlou quaisquer outros arquivos de configuração XML magento tem.
Exemplo.

Digamos que estou definindo uma configuração de 'tabela' que possui linhas e colunas. Eu poderia usar este xml abaixo. (chame table.xml)

<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="path/to/table.xsd">
    <row id="row1">
        <column id="col1" sort="10" attr1="val1">
            <label>Col 1</label>
        </column>
    </row>
    <row id="row2">
        <column id="col1" sort="10" attr1="val1">
            <label>Col 1</label>
        </column>
        <column id="col2" sort="20" disabled="true" attr1="val2" >
            <label>Col 2</label>
        </column>
        <column id="col3" sort="15" attr1="val1">
            <label>Col 3</label>
        </column>
    </row>
</table>

Mas se uma outra extensão contiver table.xml, quero que ela seja selecionada pelo leitor de configuração e os 2 ou mais arquivos xml deverão ser mesclados. Quero dizer, se o segundo arquivo for assim

<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="path/to/table.xsd">
    <row id="row1">
        <column id="col2" sort="10" attr1="val2">
            <label>Col 2</label>
        </column>
    </row>
    <row id="row2">
        <column id="col1" sort="10" attr1="val5" />
    </row>
</table>

o resultado será que a segunda coluna será adicionada à primeira linha e o valor de attr1será substituído pelo segundo xml:

<table ....>
    <row id="row1">
        <column id="col1" sort="10" attr1="val1"> <!-- from first xml -->
            <label>Col 1</label>
        </column>
        <column id="col2" sort="10" attr1="val2"><!-- from second xml-->
            <label>Col 2</label>
        </column>
    </row>
    <row id="row2">
        <column id="col1" sort="10" attr1="val5"><!--they apear in both xmls with the same path and id and second one overrides the value for `attr1`-->
            <label>Col 1</label>
        </column>
        <column id="col2" sort="20" disabled="true" attr1="val2"><!-- from first xml -->
            <label>Col 2</label>
        </column>
        <column id="col3" sort="15" attr1="val1"><!-- from first xml -->
            <label>Col 3</label>
        </column>
    </row>
</table>

No Magento 1, eu poderia ter feito isso apenas chamando

 $merged = Mage::getConfig()->loadModulesConfiguration('table.xml')
            ->applyExtends();

Como posso fazer o mesmo com o Magento 2?

Sander Mangel
fonte

Respostas:

15

No Magento 2, isso é tratado pela \Magento\Framework\Config\Reader\Filesystemclasse. Esta classe permite especificar o arquivo xml que você deseja mesclar.

A parte a seguir mesclará todos os arquivos encontrados nos módulos disponíveis e mesclará a saída (snippet from \Magento\Framework\Config\Reader\Filesystem)

/**
 * Load configuration scope
 *
 * @param string|null $scope
 * @return array
 */
public function read($scope = null)
{
    $scope = $scope ?: $this->_defaultScope;
    $fileList = $this->_fileResolver->get($this->_fileName, $scope);
    if (!count($fileList)) {
        return [];
    }
    $output = $this->_readFiles($fileList);

    return $output;
}

/**
 * Read configuration files
 *
 * @param array $fileList
 * @return array
 * @throws \Magento\Framework\Exception
 */
protected function _readFiles($fileList)
{
    /** @var \Magento\Framework\Config\Dom $configMerger */
    $configMerger = null;
    foreach ($fileList as $key => $content) {
        try {
            if (!$configMerger) {
                $configMerger = $this->_createConfigMerger($this->_domDocumentClass, $content);
            } else {
                $configMerger->merge($content);
            }
        } catch (\Magento\Framework\Config\Dom\ValidationException $e) {
            throw new \Magento\Framework\Exception("Invalid XML in file " . $key . ":\n" . $e->getMessage());
        }
    }
    if ($this->_isValidated) {
        $errors = [];
        if ($configMerger && !$configMerger->validate($this->_schemaFile, $errors)) {
            $message = "Invalid Document \n";
            throw new \Magento\Framework\Exception($message . implode("\n", $errors));
        }
    }

    $output = [];
    if ($configMerger) {
        $output = $this->_converter->convert($configMerger->getDom());
    }
    return $output;
}

Na solução que criei, a classe acima é estendida para fornecer o arquivo xml necessário e especificar onde o arquivo xsd a ser validado pode ser encontrado (consulte https://github.com/Genmato/MageStackTable para obter um exemplo completo):

namespace Genmato\TableXml\Model\Table;

class Reader extends \Magento\Framework\Config\Reader\Filesystem
{
    protected $_idAttributes = [
        '/table/row' => 'id',
        '/table/row/column' => 'id',
    ];

    /**
     * @param \Magento\Framework\Config\FileResolverInterface $fileResolver
     * @param \Magento\Framework\Config\ConverterInterface $converter
     * @param \Genmato\TableXml\Model\Table\SchemaLocator $schemaLocator
     * @param \Magento\Framework\Config\ValidationStateInterface $validationState
     * @param string $fileName
     * @param array $idAttributes
     * @param string $domDocumentClass
     * @param string $defaultScope
     */
    public function __construct(
        \Magento\Framework\Config\FileResolverInterface $fileResolver,
        \Magento\Framework\Config\ConverterInterface $converter,
        \Genmato\TableXml\Model\Table\SchemaLocator $schemaLocator,
        \Magento\Framework\Config\ValidationStateInterface $validationState,
        $fileName = 'table.xml',
        $idAttributes = [],
        $domDocumentClass = 'Magento\Framework\Config\Dom',
        $defaultScope = 'global'
    ) {
        parent::__construct(
            $fileResolver,
            $converter,
            $schemaLocator,
            $validationState,
            $fileName,
            $idAttributes,
            $domDocumentClass,
            $defaultScope
        );
    }

Para obter os dados mesclados, você pode ligar para:

$output = $this->_objectManager->get('Genmato\TableXml\Model\Table\Reader')->read();

A saída é uma representação de matriz do xml mesclado.

EDITAR:

Para testar a maneira como os arquivos são lidos, criei um exemplo de trabalho (consulte https://github.com/Genmato/MageStackTable ). Atualização da resposta com a compilação da solução.

Vladimir Kerkhoff
fonte
Vladimir, hoje cedo eu vi sua versão de resposta anterior com Domexemplo de classe. Comecei a trabalhar na resposta utilizando Readerclasse. Enquanto isso
atualizei a
Obrigado pela resposta detalhada completa e pelo módulo POC do github. Deixe-o lá para futuras referências. Aqui ... tenha alguma recompensa.
Marius
Marius, obrigado! Deixará o módulo disponível no GitHub.
Vladimir Kerkhoff