• This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn more.
  • This forum has been archived. New threads and replies may not be made. All add-ons/resources that are active should be migrated to the Resource Manager. See this thread for more information.

Installer

Robbo

Well-known member
#1
This is the start of an abstract class to make creating installers easier for developers. What I am about to post hasn't even been tested yet but it gives you an idea of where I am going with it, I would like for developers to add their input (and changes when I add to git) to make our lives easier.

PHP:
<?php

abstract class XenTrader_Installer_Abstract
{
    protected $_db;
    protected $_existingAddon;
    protected $_addonData;
    protected $_rebuildContentCache = false;

    public function __construct($existingAddon, $addonData)
    {
        $this->_db = XenForo_Application::get('db');
        $this->_existingAddon = $existingAddon;
        $this->_addonData = $addonData;
    }

    public function __destruct()
    {
        if ($this->_rebuildContentCache)
        {
            XenForo_Model::create('XenForo_Model_ContentType')->rebuildContentTypeCache();
        }
    }

    public static function intall($existingAddon, $addonData)
    {
        $installer = new self($existingAddon, $addonData);
        return $installer->runInstall();
    }

    public static function uninstall($existingAddon, $addonData)
    {
        $installer = new self($existingAddon, $addonData);
        return $installer->runUninstall();
    }

    public function runInstall()
    {
        $start = 1;

        if ($this->_existingAddon)
            $start += $this->_existingAddon['version_id'];

        for ($v = $start; $v <= $this->_addonData['version_id']; ++$v)
            $this->_callVersionMethod($v);

        return true;
    }

    public function runUninstall()
    {
        for ($v = $this->_existingAddon['version_id']; $v >= 0; --$v)
            $this->_callVersionMethod($v, true);

        return true;
    }
 
    protected function _callVersionMethod($version, $uninstall = false)
    {
        if (method_exists($this, '_' . ($uninstall ? 'un' : '') . 'installVersion' . $version))
            $this->{'_' . ($uninstall ? 'un' : '') . 'installVersion' . $version}();
    }

    protected function _bulkAddContentType(array $types)
    {
        if (!is_array($types))
            return;

        foreach ($types as $type => $pairs)
        {
            if (!is_array($pairs))
                continue;

            foreach ($pairs as $name => $value)
                $this->_addContentType($type, $name, $value);
        }
    }

    protected function _addContentType($type, $name, $value)
    {
        $this->_rebuildContentCache = true;

        if (!$this->_db->fetchRow('SELECT * FROM xf_content_type_field WHERE content_type = ? AND field_name = ?', array($type, $name)))
            $this->_db->insert('xf_content_type_field', array(
                'content_type' => $type,
                'field_name' => $name,
                'field_value' => $value)
            );

        if (!$this->_db->fetchRow('SELECT * FROM xf_content_type WHERE content_type = ?', $type))
            $this->db->insert('xf_content_type', array('content_type' => $type, 'addon_id' => $this->_addonData['addon_id'], 'fields' => ''));
    }

    protected function _removeContentType($type, $name = null)
    {
        $this->_rebuildContentCache = true;

        $handlers = array(
            'alert_handler_class' => 'xf_user_alert',
            'news_feed_handler_class' => 'xf_news_feed',
            'report_handler_class' => array('xf_report', '_removeReportComments'),
            // TODO: add the rest of the possible handlers
        );

        $single = false;
        if ($name && isset($handlers[$name]))
            $single = $handlers[$name];
        else
            $name = '*';

        $this->_db->delete('xf_content_type', array('content_type = ?' => $type));
        $this->_db->delete('xf_content_type_field', array('content_type = ? AND field_name = ?' => array($type, $name)));

        if ($single)
        {
            if (is_array($single))
            {
                if (method_exists($this, $single[1]))
                    $this->$single[1]($type);

                $single = $single[0];
            }

            $this->_db->delete($single, array('content_type = ?' => $type));
            return;
        }

        foreach ($handlers as $handle)
        {
            if (is_array($handle))
            {
                if (method_exists($this, $handle[1]))
                    $this->$handle[1]($type);

                $handle = $handle[0];
            }

            $this->_db->delete($handle, array('content_type = ?' => $type));
        }
    }

    protected function _removeReportComments($type)
    {
        $reportIds = $this->_db->fetchCol('SELECT report_id FROM xf_report WHERE content_type = ?', $type);
        if (!empty($reportIds))
            $this->_db->delete('xf_report_comment', array('report_id IN (' . implode(',', $reportIds) . ')'));
    }

    protected function _addTableColumn($table, $field, $info, $after = '')
    {
        $columns = $this->_db->describeTable($table);

        if (isset($columns[$field]))
            return;

        if (isset($columns[$after]))
            $info .= ' AFTER ' . $after;

        $this->_db->query("ALTER TABLE '$table' ADD '$field' $info");
    }

    protected function _removeTableColumn($table, $field)
    {
        $columns = $this->_db->describeTable($table);

        if (isset($columns[$field]))
        {
            $this->_db->query("ALERT TABLE '$table' DROP '$field'");
        }
    }
}
It handles content types nicely (haven't added everything there yet) including cleaning up old data when you remove one. It also has add and remove table column methods which check if columns exist for you.

You extend this class and create a method for each version id. _installVersion1, _installVersion2, _uninstallVersion1 etc. If you ignore the abstract class it is overall a nice OO class to work with (no staticness).
 

Robbo

Well-known member
#2
Here is a before and after of my install script for XenTrader.

PHP:
<?php

[B]Before[/B]
class XenTrader_Installer
{
    public static function install()
    {
        self::_createTables();
        self::_addContentTypeHandler('alert_handler_class', 'XenTrader_AlertHandler_Feedback');
        self::_addContentTypeHandler('news_feed_handler_class', 'XenTrader_NewsFeedHandler_Feedback');
        self::_addContentTypeHandler('report_handler_class', 'XenTrader_ReportHandler_Feedback');

        return true;
    }

    public static function uninstall()
    {
        $db = XenForo_Application::get('db');

        XenForo_Db::beginTransaction();

        $db->query('DROP TABLE `xentrader_feedback`');
        $db->query('DROP TABLE `xentrader_user`');

        $db->delete('xf_user_alert', array("content_type = 'feedback'"));
        $db->delete('xf_news_feed', array("content_type = 'feedback'"));
        $reportIds = $db->fetchCol("SELECT report_id FROM xf_report WHERE content_type = 'feedback'");
        if (!empty($reportIds))
            $db->delete('xf_report_comment', array('report_id IN (' . implode(',', $reportIds) . ')'));
        $db->delete('xf_report', array("content_type = 'feedback'"));

        $db->delete('xf_content_type_field',  array("content_type = 'feedback'"));

        XenForo_Db::commit();

        return true;
    }

    protected static function _createTables()
    {
        $db = XenForo_Application::get('db');

        $db->query('
            CREATE TABLE IF NOT EXISTS `xentrader_feedback` (
                  `feedback_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                  `from_user_id` int(10) unsigned NOT NULL,
                  `to_user_id` int(10) unsigned NOT NULL,
                  `from_username` varchar(50) NOT NULL,
                  `to_username` varchar(50) NOT NULL,
                  `thread_id` int(10) NOT NULL,
                  `thread_title` varchar(150) NOT NULL,
                  `feedback` varchar(80) NOT NULL,
                  `rating` tinyint(1) NOT NULL,
                  `type` enum(\'buy\',\'sell\',\'trade\') NOT NULL,
                  `feedback_date` int(10) unsigned NOT NULL,
                  PRIMARY KEY (`feedback_id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;');

        $db->query('
            CREATE TABLE IF NOT EXISTS `xentrader_user` (
                `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `positive` smallint(10) unsigned NOT NULL,
                `neutral` smallint(10) unsigned NOT NULL,
                `negative` smallint(10) unsigned NOT NULL,
                `total` smallint(10) NOT NULL,
                `rating` tinyint(3) unsigned NOT NULL,oh,
                PRIMARY KEY (`user_id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;');

    }

    protected static function _addContentTypeHandler($name, $value)
    {
        $db = XenForo_Application::get('db');

        if (!$db->fetchOne("SELECT * FROM xf_content_type_field WHERE content_type = 'feedback' AND field_name = '{$name}'"))
            $db->insert('xf_content_type_field', array(
                'content_type' => 'feedback',
                'field_name' => $name,
                'field_value' => $value)
            );
    }
}
After
PHP:
<?php
 
 class XenTrader_Installer_Core extends XenTrader_Installer_Abstract
 {
     protected function _installVersion1()
     {
         $this->_db->query('
             CREATE TABLE IF NOT EXISTS xentrader_feedback (
                   `feedback_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                   `from_user_id` int(10) unsigned NOT NULL,
                   `to_user_id` int(10) unsigned NOT NULL,
                   `from_username` varchar(50) NOT NULL,
                   `to_username` varchar(50) NOT NULL,
                   `thread_id` int(10) NOT NULL,
                   `thread_title` varchar(150) NOT NULL,
                   `feedback` varchar(80) NOT NULL,
                   `rating` tinyint(1) NOT NULL,
                   `type` enum(\'buy\',\'sell\',\'trade\') NOT NULL,
                   `feedback_date` int(10) unsigned NOT NULL,
                   PRIMARY KEY (`feedback_id`)
             ) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci');
 
         $this->_db->query('
             CREATE TABLE IF NOT EXISTS xentrader_user (
                 `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                 `positive` smallint(10) unsigned NOT NULL,
                 `neutral` smallint(10) unsigned NOT NULL,
                 `negative` smallint(10) unsigned NOT NULL,
                 `total` smallint(10) NOT NULL,
                 `rating` tinyint(3) unsigned NOT NULL,
                 PRIMARY KEY (`user_id`)
             ) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci');
 
         $this->_bulkAddContentType(array(
             'feedback' => array(
                 'alert_handler_class' => 'XenTrader_AlertHandler_Feedback',
                 'news_feed_handler_class' => 'XenTrader_NewsFeedHandler_Feedback',
                 'report_handler_class', 'XenTrader_ReportHandler_Feedback'
             )
         ));
     }
 
     protected function _installVersion4()
     {
         if (isset($this->_existingAddon['version_id']))
         {
             $this->_db->query('ALTER TABLE xentrader_feedback CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci');
             $this->_db->query('ALTER TABLE xentrader_user CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci');
         }
     }
 
     protected function _uninstallVersion1()
     {
         XenForo_Db::beginTransaction();
 
         $this->_db->query('DROP TABLE `xentrader_feedback`');
         $this->_db->query('DROP TABLE `xentrader_user`');
 
         $this->_removeContentType('feedback');
 
         XenForo_Db::commit();
     }
 }
I have been attempted to get more developer interest in tools / libraries etc to make developing mods for XenForo a faster process. This is the first thing I have had time for to start pushing for that to happen.