¿Cómo puedo tener un archivo XML personalizado en módulos combinados como uno en Magento 2? (Pregunta misteriosa MageStackDay 2)

22

Pregunta de bonificación MageStackDay por 500 puntos Bounty Y la posibilidad de ganar una licencia gratuita de Z-Ray por un año. Más información se puede encontrar >> aquí <<

Las preguntas son proporcionadas / inspiradas por el desarrollador principal de Magento 2, Anton Kril.

Pregunta:

Estoy creando una extensión que tiene un conjunto separado de configuraciones.
Esto significa que no pueden utilizar config.xmlo routes.xmlo fieldset.xmlo cualquier otro archivo de configuración XML Magento tiene.
Ejemplo.

Digamos que estoy definiendo una configuración de 'tabla' que tiene filas y columnas. Podría usar este xml a continuación. (llámalo 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>

Pero si otra extensión contiene table.xml, quiero que sea recogida por el lector de configuración y los 2 o más archivos xml deben fusionarse. Quiero decir, si el segundo archivo se ve así

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

el resultado será que la segunda columna se agrega a la primera fila y attr1el segundo xml sobrescribe el valor de :

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

En Magento 1 podría haber hecho esto simplemente llamando

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

¿Cómo puedo hacer lo mismo para Magento 2?

Sander Mangel
fuente

Respuestas:

15

En Magento 2 esto es manejado por la \Magento\Framework\Config\Reader\Filesystemclase. Esta clase le permite especificar el archivo xml que desea fusionar.

La siguiente parte combinará todos los archivos encontrados en los módulos disponibles y combinará la salida (fragmento de \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;
}

En la solución que creé, la clase anterior se amplía para proporcionar el archivo xml necesario y especificar dónde se puede encontrar el archivo xsd para validar (consulte https://github.com/Genmato/MageStackTable para ver un ejemplo 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 obtener los datos combinados, puede llamar a:

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

El resultado es una representación de matriz del xml combinado.

EDITAR:

Para probar la forma en que se leen los archivos, creé un ejemplo de trabajo (consulte https://github.com/Genmato/MageStackTable ). Se actualizó la respuesta con la compilación de la solución.

Vladimir Kerkhoff
fuente
Vladimir, hoy temprano vi tu versión de respuesta anterior con Domun ejemplo de clase. Comencé a trabajar en la respuesta utilizando la Readerclase. Mientras tanto, actualicé la página de preguntas y me di cuenta de que hiciste eso :-) +1
Wojtek Naruniec
Gracias por la respuesta detallada completa y por el módulo POC de github. Déjelo allí para futuras referencias. Aquí ... ten algo de generosidad.
Marius
Marius, gracias! Dejará el módulo disponible en GitHub.
Vladimir Kerkhoff