Cómo crear una clase para el Shell en Magento

Cuando pensamos en módulos para Magento nos quedamos, normalmente, con agregar funcionalidad para el frontend o para el backend.

Creo que cuando pensamos en un módulo debemos imaginarnos los cuatro posibles entornos para su aplicación. Por los cuatro entornos me refiero:

  • Frontend o tienda propiamente dicha.
  • Backend o administración.
  • API.
  • Consola.

Si bien ésta división puede parecer arbitraria, éstas serán las posibles puertas de entrada que normalmente utilicemos (dependiendo sobre si nos toca ser usuario, administrador, desarrollador o el encargado del mantenimiento; o todo).

Claro está que no todos los módulos requieren funcionalidad en los cuatro entornos, pero en muchos casos deberíamos cuidar las formas y proveer de herramientas para cada caso.

En mi caso, desde hace ya un buen tiempo, me ha tocado desarrollar unas cuantas integraciones que importan o exportan información. Normalmente, con procesos manejados a través del Cron de Magento.

Uno de las situaciones con las que nos vamos a encontrar cuando trabajemos con módulos que funcionan con un cronjob, es la de la prueba de ejecución. Está claro que podemos programar la tarea para que se ejecute y esperar a que suceda. Luego de la quinta prueba es muy probable que empecemos a perder un poco la paciencia.

Lo que vamos a ver hoy es cómo crear una clase para el shell respetando el estilo de la plataforma. Si bien podemos hacerlo por fuera de éstos lineamientos, vamos a tratar de ser lo más respetuosos posible. La idea es lograr tener siempre extensiones prolijas para que puedan ser reutilizadas y no nos generen conflictos con otras extensiones (o al menos que esos casos sean los menos posibles).

Para seguir con los ejemplos, y partiendo de un punto que cualquiera podría replicar para partir desde el mismo lugar, voy a seguir con mi módulo Dc_Test, creado con el generador de módulos.

Ahora bien, nuestro modulito nos provee ya de la grilla y el formulario para cargar datos en el backend.

Más allá que en éste caso no tenemos motivo aparente para tener funcionalidad en la consola, vamos a pasar por alto ésta cuestión y pensemos que necesitamos poder tener información sobre los datos manejados por éste módulo.

Lo primero que tenemos que hacer es crear nuestra clase que, para mantener la lógica, vamos a llamar Dc_Shell_Test y la vamos a guardar en el archivo /shell/test.php.

La versión más simple de nuestra clase contará con dos métodos: run() y usageHelp(). Más allá de lo obvio, el método usageHelp es el que usaremos para brindar información sobre el uso de la clase.

Un ejemplo de su contenido podría ser:

public function usageHelp() {
    return <<<USAGE
Usage:  php -f test.php -- [options]
 
  --help              This help
 
USAGE;
}

Hasta ahora, dado que nuestra clase no tiene acciones posibles, sólo podemos mostrar que la ayuda nos permite invocar a la ayuda (si, muy Inception style).

El otro método posible, en nuestra adolescente clase, es run(). De más está decir que éste es el método que será invocado y que será el responsable de ejecutar la o las acciones que desarrollemos.

Aquí podemos aplicar dos formas para invocar a las acciones.

La primera, para el caso más sencillo, sería la siguiente.

public function run() {
    if ($this->getArg('my_action')) {
        //Tareas a realizar por la acción my_action
    } else {
        echo $this->usageHelp();
        exit(1);
    }
}

En éste momento, nuestra clase se debe ver de la siguiente forma. (Nótese que agregué información en el método usageHelp() para describir nuestra acción).

class Dc_Shell_Test {
    public function run() {
        if ($this->getArg('my_action')) {
            //Tareas a realizar por la acción my_action
        } else {
            echo $this->usageHelp();
            exit(1);
        }
    }
 
    public function usageHelp() {
        return <<<USAGE
Usage:  php -f test.php -- [options]
 
  --myaction        Test action
  --help            This help
 
USAGE;
    }
}

La clase ya es funcional, pero a nuestro archivo le faltan aún tres líneas.

Primero, debemos hacer un include para la clase abstracta que Magento utiliza para el Shell.

Luego, al final de nuestro archivo otras dos líneas que nos van a instanciar la clase y luego a ejecutarla.

El ejemplo completo debe verse así.

require_once 'abstract.php';
 
class Dc_Shell_Test extends Mage_Shell_Abstract {
 
    public function run() {
        if ($this->getArg('my_action')) {
            echo "Acción de my_action\n";
        } else {
            echo $this->usageHelp();
            exit(1);
        }
    }
 
    public function usageHelp() {
        return <<<USAGE
Usage:  php -f test.php -- [options]
 
  --myaction        Test action
  --help            This help
 
USAGE;
    }
}
 
$shell = new Dc_Shell_Test();
$shell->run();

Ahora si, nos vamos a la consola, y dando por sentado que estamos en el directorio correcto, hacemos nuestra primera prueba.

php test --my_action

Nos devolverá.

Acción de my_action

Si en cambio pasamos un parámetro no soportado.

php test --my_test

Tendríamos que obtener lo siguiente.

Usage:  php -f test.php -- [options]
  --myaction          Test action
  --help              This help

Como ven, es una lógica muy sencilla. Básicamente, si el nombre del argumento coincide con uno de los definidos como acción, se ejecutará. Caso contrario, se muestra la ayuda.

Si tuviéramos varias acciones, podemos, o bien tener varios if else if y toda la lógica dentro del método run(), o bien hacer un poco más ordenada nuestra clase y separar cada acción en métodos independientes y modificar un poquito a run().

A manera de ejemplo, vamos a suponer que tenemos 5 acciones posibles, a las que llamaremos: accion1, accion2, accion3, accion4 y accion5.

Para cada acción vamos a crear un método privado. Sólo para el ejemplo, probemos con:

private function accion1() {
    echo "Acción 1\n";
}

private function accion2() {
    echo "Acción 2\n";
}

private function accion3() {
    echo "Acción 3\n";
}

private function accion4() {
    echo "Acción 4\n";
}

private function accion5() {
    echo "Acción 5\n";
}

Ahora vamos a cambiar un poquito a run() para evitarnos una lista interminable de if else if.

No sólo vamos a pasar un argumento, sino que vamos a asignarle un valor.

public function run() {
    $action = $this->getArg('my_action');
    if (!empty($action)) {
        $method = $action;
        if (method_exists($this, $method)) {
            $this->$method();
        } else {
            echo "Action $action not exists\n";
            echo $this->usageHelp();
            exit(1);
        }
    } else {
        echo $this->usageHelp();
        exit(1);
    }
}

Lo que cambia ahora es que además de validar que exista el parámetro, vamos a tomar el valor del argumento y lo vamos a utilizar para invocar uno de nuestros métodos.

Si el método no existe, notificamos y mostramos la ayuda. (Siempre que agreguemos nuevas acciones tendríamos que agregar la correspondiente descripción en la ayuda).

La clase completa debería verse así.

class Dc_Shell_Test extends Mage_Shell_Abstract {
 
    public function run() {
        $action = $this->getArg('my_action');
        if (!empty($action)) {
            $method = $action;
            if (method_exists($this, $method)) {
                $this->$method();
            } else {
                echo "Action $action not exists\n";
                echo $this->usageHelp();
                exit(1);
            }
        } else {
            echo $this->usageHelp();
            exit(1);
        }
    }
 
    private function accion1() {
        echo "Acción 1\n";
    }
 
    private function accion2() {
        echo "Acción 2\n";
    }
 
    private function accion3() {
        echo "Acción 3\n";
    }
 
    private function accion4() {
        echo "Acción 4\n";
    }
 
    private function accion5() {
        echo "Acción 5\n";
    }
 
    public function usageHelp() {
        return <<<USAGE
Usage:  php -f test.php -- [options]
 
  --my_action accion1     Acción 1
  --my_action accion2     Acción 2
  --my_action accion3     Acción 3
  --my_action accion4     Acción 4
  --my_action accion5     Acción 5
  --help                  This help
 
USAGE;
    }
}

Con ésta modificación, vamos a cambiar la forma de pasar los parámetros. Si quisiéramos llamar a la Acción 3, lo haríamos de ésta forma.

php test.php --my_action accion3

Y obtendríamos éste resultado.

Acción 3

Y si enviáramos un parámetro incorrecto, por ejemplo: php test.php –my_action accion6, deberíamos obtener el mensaje de nuestra ayuda.

Action accion6 not exists
Usage:  php -f test.php -- [options]
 
  --my_action accion1     Acción 1
  --my_action accion2     Acción 2
  --my_action accion3     Acción 3
  --my_action accion4     Acción 4
  --my_action accion5     Acción 5
  --help                  This help

Ahora si, ya tenemos una forma prolija y ordenada de realizar acciones en Magento desde la consola.