Extend Magerun by own commands

I already told you about the great Magento – commandline tool Magerun from netz98 in an earlier post. And my opinion didn’t change in the last years. It is one of the most useful tools working with Magento and it made my daily developer live so much easier. Magerun became a de facto standard commandline tool for Magento. That’s why I thought it is a good idea to write this article about how to extend Magerun by own commands.

Problem description

Recently I worked on an extension for a automatic deployment tool called Mina (will write about this very soon), which I’m using to deploy big crossover applications (f.e.: Magento + Typo3) to server clusters.

Extend Magerun with own cleanup commands

On of the main features of this tool should have been the cleanup of the Magento database before export it for delivering releases (short story). Therefore I had to run multiple cleanup task for Magento.

(Image source: https://commons.wikimedia.org/wiki/File:Template_cleanup_icon.svg)

These where:

  • remove all orders
  • remove all quotes
  • remove all customers
  • remove all admin users
  • create a new dummy admin user
  • delete sensitive data from Magento configuration (core_config_data)
  • reindex all indexes
  • clean all log tables
  • clean all session data (truncate session table)
  • clean all caches

So I could have solved this problem by writing a Magento shell script, that does all this cleanup stuff. But I realized that many of the tasks I had to do where already implemented in Magerun.

That’s why I decided to extend Magerun with my own cleanup commands.

Versions:

I worked with the following versions of the involved applications:

  • Magento CE v. 1.9.2.4
  • n98-Magerun v. 1.97.19:
    Unfortunately in version 1.9.2.4 of Magerun there was a bug making the command customer:delete unusable. (@see https://github.com/netz98/n98-magerun/pull/644)
    That’s why I had to switch back to version 1.95.0. The described bugfix will be included in the next release. (at the moment only available in dev branch)

Special adjustments

As described in my article https://www.ask-sheldon.com/magento-magerun/, I use the bash alias magrun in my projects involving n98-magerun.  Its shorter and without special characters.

alias magerun="php path/to/n98-magerun.phar --root-dir=/path/to/magento/root/folder/"

Keep that in mind when following this tutorial.

Already existing commands

The following features of my “wishlist” where already implemented or could be resolved by using existing Magerun commands:

  • remove all customers:
    $> magerun customer:delete --all --force
  • delete single admin user:
    $> magerun admin:user:delete

    Unfortunately this command doesn’t support an –all parameter. So we had to extend this command.

  • create a new dummy admin user:
    $> magerun admin:user:create username email password firstname lastname
  • delete sensitive data from Magento configuration (core_config_data):
    $> magerun config:delete --all path/in/magento/config
  • reindex all indexes:
    $> magerun index:reindex:all
  • clean all log tables
    $> magerun db:query "TRUNCATE TABLE log_customer;\
    TRUNCATE TABLE log_quote;\
    TRUNCATE TABLE log_summary;\
    TRUNCATE TABLE log_summary_type;\
    TRUNCATE TABLE log_url;\
    TRUNCATE TABLE log_url_info;\
    TRUNCATE TABLE log_visitor;\
    TRUNCATE TABLE log_visitor_info;\
    TRUNCATE TABLE log_visitor_online;"
  • clean all session data (truncate session table)
    $> magerun db:query "TRUNCATE core_session;"

    BE CAREFUL WITH THIT COMMAND -> ALL MAGENTO SESSIONS ARE LOST!!!

  • clean all caches:
    $> magerun cache:clean
    $> magerun cache:flush

What had to be done:

So what remained was:

  • remove all orders
  • remove all quotes
  • extend build in Magerun functionality to delete all admin users at once

The next chapter shows how I got these features or modifications implemented (extend Magerun).

Extend Magerun:

Magerun has already a build in functionality to extend its feature range by own commands (@see: https://github.com/netz98/n98-magerun/wiki/Add-custom-commands). But there where a few stumbling blocks I’ve taken.  That’s why I wrote this article 😉

For example, it isn’t explained clearly in the mentioned wiki article, that you  can place your extension as a module under MAGENTO_ROOT/lib/n98-magerun/modules/. Indeed you can place your modules under those paths on Linux (which is describes under https://github.com/netz98/n98-magerun/wiki/Modules):

  • MAGENTO_ROOT/lib/n98-magerun/modules
  • /usr/local/share/n98-magerun/modules
  • ~/.n98-magerun/modules

So here are the steps I’ve taken to extend Magerun by own commands:

  1. Upgrade to the latest Magrun version:
    $> magerun self-update
    

    As I already mentioned above, I had to revert Magerun to version 1.95.0 afterwards, because of a bug in version 1.9.2.4 (@see https://github.com/netz98/n98-magerun/pull/644).

  2. Add Symphony sources for code completion:
    1. Download latest Symphony sources to a central folder (for me this is a folder Symphony in the IntelliJ/PHPStorm projects root folder):
      $> cd path/to/symphony/folder && wget https://github.com/symfony/symfony/archive/master.zip
    2. Extract the zip to that folder:
      $> unzip symfony-master.zip
    3. Add sources to IntelliJ/PHPStorm‘s  PHP includes (File -> Settings -> Languages & Frameworks -> PHP):
  3. Add n98-magerun sources for code completion:
    1. Download latest Magerun sources to a central folder (for me this is a folder Magerun in the IntelliJ/PHPStorm  projects root folder):
      $> cd path/to/magerun/folder && wget https://github.com/netz98/n98-magerun/archive/master.zip
    2. Extract the zip to that folder:
      $> unzip n98-magerun-master.zip
    3. Add sources to IntelliJ/PHPStorm‘s PHP includes (File -> Settings -> Languages & Frameworks -> PHP):
  4. I decided to place my module under MAGENTO_ROOT/lib/n98-magerun/modules/asksheldon to keep it in the project structure it was made for.
  5. Created the folder above and placed a n98-magerun.yaml in it with the following content:
    ##################################################################
    # Magerun
    #
    # NOTICE OF LICENSE
    #
    # This source file is subject to the Open Software License (OSL 3.0)
    # It is  available through the world-wide-web at this URL:
    # http://opensource.org/licenses/osl-3.0.php
    #
    #
    # @category   Magerun Command
    # @package    AskSheldonMisc
    # @copyright  Copyright (c) 2016 Marcel Lange (https://www.ask-sheldon.com)
    # @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
    # @author     Marcel Lange <info@ask-sheldon.com>
    #
    # Date:       20/04/16
    # Time:       14:36
    #
    # Command registration file for n98-magerun
    ##
    # @package AskSheldonMisc
    #
    # Created by IntelliJ IDEA.
    #
    ##################################################################
    
    # register mudule for autoloader
    autoloaders:
      AskSheldonMisc: %module%/src
    
    # define commands (syntax: ModulenameOrFolder\ClassnameOfCommandClass)
    commands:
      customCommands:
        - AskSheldonMisc\DeleteQuotes
        - AskSheldonMisc\DeleteOrders
        - AskSheldonMisc\DeleteAllAdmins

    As you can see this file registers my module sources for the autoloader of Magerun and defines my custom Magerun commands in the form ModulenameOrFoldername\ClassnameOfCommandClass. So Magerun knows where to find the code for my own command.

  6. Placed a class called AbstractBase in MAGENTO_ROOT/lib/n98-magerun/modules/asksheldon/src/

    <?php
    /**
     *
     * Magerun
     *
     * NOTICE OF LICENSE
     *
     * This source file is subject to the Open Software License (OSL 3.0)
     * It is  available through the world-wide-web at this URL:
     * http://opensource.org/licenses/osl-3.0.php
     *
     *
     * @category   Magerun Command
     * @package    AskSheldonMisc
     * @copyright  Copyright (c) 2016 Marcel Lange (https://www.ask-sheldon.com)
     * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
     * @author     Marcel Lange <info@ask-sheldon.com>
     *
     * Date:       20/04/16
     * Time:       16:40
     *
     * Base class for all Magerun command classes
     *
     * Class AbstractBase
     *
     *  * @package AskSheldonMisc
     *  *
     * Created by IntelliJ IDEA.
     *
     */
    
    namespace AskSheldonMisc;
    
    use N98\Magento\Command\AbstractMagentoCommand;
    
    abstract class AbstractBase extends AbstractMagentoCommand
    {
        /**
         * Recommended in https://github.com/netz98/n98-magerun/wiki/Modules#module-best-practices
         *
         * @return bool
         */
        public function isEnabled()
        {
            return version_compare($this->getApplication()->getVersion(), '1.74.1', '>=');
        }
    
        /**
         * Enables all child commands to reset increment ids in eav_entity_store
         *
         * @param array|string $mEntityTypeCode array of entity_type_code's or single entity_type_code
         *                                      (@see table eav_entity_type)
         *
         * @return Zend_Db_Statement_Interface
         */
        protected function _resetIncrementIds($mEntityTypeCode)
        {
            if (is_array($mEntityTypeCode)) {
                $mEntityTypeCode = implode(',', $mEntityTypeCode);
            }
            /* @var Varien_Db_Adapter_Mysqli $oWriter */
            $oWriter = \Mage::getSingleton('core/resource')->getConnection('core_write');
    
            $sQuery = "
                UPDATE eav_entity_store AS ees
                INNER JOIN eav_entity_type AS eet ON eet.entity_type_id = ees.entity_type_id
                SET ees.increment_last_id = CONCAT(LEFT(CONCAT(ees.increment_prefix, '00000000'), 8), '1')
                WHERE FIND_IN_SET(eet.entity_type_code, ?)
            ";
            $sQuery = $oWriter->quoteInto($sQuery, $mEntityTypeCode);
    
            return $oWriter->query($sQuery);
        }
    
        /**
         * @return bool
         */
        protected function _shouldRemove()
        {
            $shouldRemove = $this->input->getOption('force');
            if (!$shouldRemove) {
                $shouldRemove = $this->dialog->askConfirmation(
                    $this->output,
                    $this->_getQuestion('Are you sure?', 'n'),
                    false
                );
            }
    
            return $shouldRemove;
        }
    
        /**
         * @param string $message
         * @param string $default [optional]
         *
         * @return string
         */
        protected function _getQuestion($message, $default = null)
        {
            $params = [$message];
            $pattern = '%s: ';
            if (null !== $default) {
                $params[] = $default;
                $pattern .= '[%s] ';
            }
    
            return vsprintf($pattern, $params);
        }
    }

    This class should serve as the base-class for all my command classes. It encapsulates functions, that are used by all my Magerun command classes. The class is inherited from AbstractMagentoCommand from the n98-magerun core. That is necessary, because all my command classes have to be derived from this class and all my command classes will extend this base class.
    As you can see, it  has only a few function by now:

    1. isEnabled have to be inherited to every command child class. It only checks if the current n98-magerun version is higher than 1.72.0, because this is the first version that allows to extend Magerun  by own modules
    2. _resetIncrementIds is used by child classes to reset Magento‘s increment ids for certain entities in eav_entity_store back to their default values  (f.e. order increment id)
    3. the other functions are just copies of helper functions from the n98-magerun core which I reuse. For example _shouldRemove checks if the –force option was added to the respective Magerun command. Otherwise a confirmation dialog is shown before executing the Magerun command.
      Generally it is a good idea to have a look at the n98-margerun core commands to see how to implement your own once (@see https://github.com/netz98/n98-magerun/tree/master/src/N98/Magento/Command).
  7. The command logic itself was implemented in the command classes declared in the configuration yaml file (n98-magerun.yaml ) . For example, here is the implementation of the command to delete all orders at once, implemented in the class DeleteOrders:
    <?php
    /**
     *
     * Magerun
     *
     * NOTICE OF LICENSE
     *
     * This source file is subject to the Open Software License (OSL 3.0)
     * It is  available through the world-wide-web at this URL:
     * http://opensource.org/licenses/osl-3.0.php
     *
     *
     * @category   Magerun Command
     * @package    AskSheldonMisc
     * @copyright  Copyright (c) 2016 Marcel Lange (https://www.ask-sheldon.com)
     * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
     * @author     Marcel Lange <info@ask-sheldon.com>
     *
     * Date:       20/04/16
     * Time:       14:36
     *
     * Order delete command for Magerun
     *
     * Class DeleteOrders
     *
     *  * @package AskSheldonMisc
     *  *
     * Created by IntelliJ IDEA.
     *
     */
    
    namespace AskSheldonMisc;
    
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Input\InputOption;
    use Symfony\Component\Console\Output\OutputInterface;
    
    
    class DeleteOrders extends AbstractBase
    {
        protected function configure()
        {
            $this
                ->setName('order:delete:all')
                ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force delete')
                ->setDescription('Deletes all orders from magento');
    
            $help = <<<HELP
    This will delete  all orders.
    <comment>Example Usage:</comment>
    n98-magerun order:delete:all                    <info># Will delete all orders</info>
    n98-magerun order:delete:all --force || -f      <info># Will delete all orders without any confirmation</info>
    HELP;
            $this->setHelp($help);
        }
    
        /**
         * @param \Symfony\Component\Console\Input\InputInterface   $input
         * @param \Symfony\Component\Console\Output\OutputInterface $output
         *
         * @return int|void
         */
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            try {
                $this->detectMagento($output);
                if ($this->initMagento()) {
    
                    $this->input = $input;
                    $this->output = $output;
    
                    /** @var DialogHelper dialog */
                    $this->dialog = $this->getHelperSet()->get('dialog');
    
                    if ($this->_shouldRemove()) {
                        $this->_deleteAllOrders();
                        $this->output->writeln("<warning>All orders have been deleted!</warning>");
    
                        /* @var Zend_Db_Statement_Interface $oResult */
                        $oResult = $this->_resetIncrementIds(['order', 'invoice', 'creditmemo', 'shipment']);
                        $this->output->writeln("Order increment have been reset! (Result count: {$oResult->rowCount()})");
                    } else {
                        $this->output->writeln("Nothing was removed!");
                    }
                }
            } catch (\Exception $oEx) {
                $output->writeln("<error>Something got terrible wrong!!!:</error>");
                $output->writeln("<error>" . $oEx->getMessage() . "</error>");
            }
        }
    
        /**
         *
         * Deletes all orders by collection (triggers deletion of related entities)
         *
         * @throws \Exception
         */
        private function _deleteAllOrders()
        {
            $oOrderCollection = \Mage::getModel('sales/order')->getCollection();
            foreach ($oOrderCollection as $oQuote) {
                $oQuote->delete();
            }
        }
    
    }

    As you can see, Magerun already uses PHP namespaces. So I crated my own one (AskSheldonMisc) to contain my classes.
    The most important methods of all command classes are configure and execute.
    configure tells Magerun how your command should be called as the first parameter of the magerun command (here order:delete:all).  It also defines, which parameters are allowed for the particular command (addOption) and sets a description that is shown in the Magerun command list (setDescription).
    With the help of the setHelp function call the help output, shown when the command is called with the –help parameter, is specified.
    The execute method is the starting point of the actual command logic.  It has to be implemented by any command class, because otherwise Symfony\Component\Console\Command::execute will be called which raises a LogicException.  In the function the Magento version is determined and a Magento instance is bootstrapped.  After checking if the command needs a confirmation (_shouldRemove of parent class), the actual order deletion logic takes place in the function _deleteAllOrders. As you can see, you can call Magento‘s superclass Mage by using the public namespace (\). Finally the order increment ids of all stores are reset by calling _resetIncrementIds, which is defined in my command base class above.

  8. That’s it! The other commands where implemented the same way. You can watch and download the complete source code on GitHub (https://github.com/Bravehartk2/Magerun-Modules).

Module structure:

Through the described steps above I got to the following module structure:

  • MAGENTO_ROOT
    • lib
      • n98-magerun
        • modules
          • asksheldon
            • src
              • AskSheldonMisc
                • AbstractBase.php
                • DeleteAllAdmins.php
                • DeleteOrders.php
                • DeleteQuotes.php
            • n98-magerun.yaml

Installation:

The complete code can be downloaded here: https://github.com/Bravehartk2/Magerun-Modules/archive/master.zip

Just put the included n98-magerun folder into your Magento‘s lib folder (MAGENTOROOT/lib/).

Result:

With the few steps I’ve mentioned above, it is really easy to extend Magerun to get your own logic into your own Magerun commands.

After installation you should see the following additional commands in the Magerun commandlist (just run magerun on shell)

  • admin:user:delete:all
  • order:delete:all
  • quote:delete:all

You can run them like any native Magerun command.
For example:

$> magerun order:delete:all --help
$> magerun order:delete:all --force

Further information:

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.