Download the source for this entire series here!

Frontend – List

Let us revisit our frontend controller. Surely by now you’ve gotten a better grasp of the controller and models. So we’ll be revisiting those concepts here. So lets say you want to allow users to view you current Twits as well as create a new Tip.

  1. Some new file structure loving
    app/
      design/
        frontend/
          base/
            default/
              template/
                twits/
                  - tip_list.phtml
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - Edit.php
                  Edit/
                    - Form.php
                  - New.php
                  New/
                    - Form.php
                - HelloWorld.php
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Tip.php
                Mysql4/
                  - Tip.php
                  Tip/
                    - Collection.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    We’ve added a whole nasty branch of subdirectories under app/designs. Read on to understand what its all for…

  2. Let us return to our IndexController.php
    <?php
    class SavantDegrees_Twits_IndexController extends Mage_Core_Controller_Front_Action
    {
     
        public function indexAction()
        {
    		$this->loadLayout();
    		$this->getLayout()->getBlock('content')->append(
    			$this->getLayout()->createBlock('twits/index')
        	);
            $this->renderLayout();
        }
    }

    Hold your horses, this code is EXACTLY the same as the Part 1. Its just a revision. However, that being said, we see that in Part 1, we made the system create a Block called index. That’s an opening…

  3. So let us modify our Index.php block:
    <?php
    class SavantDegrees_Twits_Block_Index extends Mage_Core_Block_Template
    {
        public function __construct()
        {
            parent::__construct();
            $this->setTemplate('twits/tip_list.phtml');
        }
     
        public function getTips()
        {
        	$model = Mage::getModel('twits/tip');
        	$collection = $model
        	    	->getCollection()
        	    	->load();
     
            return $collection->getItems();
        }
    }

    What the difference here? We made the block load the template from twits/tip_list.phtml. We can create/find this file in /app/design/frontend/default/default/template/twits/tip_list.phtml. Some explanation is due here:

    • When you say setTemplate in the block, it means, use this file as the presentation/view
    • This file exists in the some subdirectory of /app/design/frontend/default/default/template. That is the path to your default template. Even though you may have other templates, this is the default 1. So when Magento cant find your template file in the other template directories, it always reverts back to this folder. Its a directory form of inheritance/ancestory/precedence.
  4. Lastly, the tip_list.phtml file:
    <?php
    $_tips = $this->getTips();
    if ($_tips) {
    	$count = 0;
    	foreach ($_tips as $i=>$tip) {
    ?>
    		<div class="tip">
    			<div class='title'><?= $tip->getTitle() ?></div>
    			<div class='author'><?= $tip->getAuthor() ?></div>
    			<p><?= $tip->getContents() ?></p>
    		</div>
    <?php
    	}
    }
    ?>

    Since the whole purpose of tip_list.phtml is to prepare the view for the list of tips, it obviously needs to get the data from somewhere. It turns out, the Template is the extension of the Block. So by calling $this->getTips(), we’re calling the Block's getTips() method.

  5. There you have it! check your tips list at http://127.0.0.1/magento/index.php/twits
  6. If it works for you, leave a comment! =)

Download the source for this entire series here!

CRUD – Update

Very similar to the Create phase, this phase basically requires the Form and Form Container blocks, and the controller to save them. Thats all!

  1. Repeat the file structure goodness:
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - Edit.php
                  Edit/
                    - Form.php
                  - New.php
                  New/
                    - Form.php
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Tip.php
                Mysql4/
                  - Tip.php
                  Tip/
                    - Collection.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    Here we’ve added the block for the Edit container and Form

  2. Looking at the Edit.php code:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    <?php
    class SavantDegrees_Twits_Block_Admin_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
    {
        public function __construct()
        {
            parent::__construct();
     
            $this->_blockGroup = 'twits';
            $this->_mode = 'edit';
            $this->_controller = 'admin';
     
            if( $this->getRequest()->getParam($this->_objectId) ) {
                $tip = Mage::getModel('twits/tip')
                    ->load($this->getRequest()->getParam($this->_objectId));
                Mage::register('frozen_tip', $tip);
            }
        }
     
        public function getHeaderText()
        {
            return Mage::helper('twits')->__("Edit Tip'%s'", $this->htmlEscape(Mage::registry('frozen_tip')->getName()));
        }
    }

    The additional code loads the right Tip data based on the Request parameter, then stores that data in the Mage registry. We’ll be using that data on the Form page again later to populate the form.
    Notice that the saving code is Mage::register and the retrieving code is Mage::registry

  3. Then the accompanying Edit/Form.php code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    <?php
    class SavantDegrees_Twits_Block_Admin_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
    {
    	protected function _prepareForm()
        {
            $form = new Varien_Data_Form();
     
            $fieldset = $form->addFieldset('edit_tip', array('legend' => Mage::helper('twits')->__('Tip Details')));
     
            $fieldset->addField('title', 'text', array(
                'name'      => 'title',
                'title'     => Mage::helper('twits')->__('Title'),
                'label'     => Mage::helper('twits')->__('Title'),
                'maxlength' => '250',
                'required'  => true,
            ));
     
            $fieldset->addField('author', 'text', array(
                'name'      => 'author',
                'title'     => Mage::helper('twits')->__('Author'),
                'label'     => Mage::helper('twits')->__('Author'),
                'maxlength' => '250',
                'required'  => true,
            ));
     
            $fieldset->addField('contents', 'textarea', array(
                'name'      => 'contents',
                'title'     => Mage::helper('twits')->__('Contents'),
                'label'     => Mage::helper('twits')->__('Contents'),
                'style'     => 'width: 98%; height: 200px;',
                'required'  => true,
            ));
     
     		$form->setMethod('post');
            $form->setUseContainer(true);
            $form->setId('edit_form');
            $form->setAction($this->getUrl('*/*/save'));
            $form->setValues(Mage::registry('frozen_tip')->getData());
     
            $this->setForm($form);
        }
    }
  4. Then lastly the adminController with its editAction and saveAction to load and save the form respectively

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    <?php
    class SavantDegrees_Twits_AdminController extends Mage_Adminhtml_Controller_Action
    {
    	public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twits/admin_main'))
    			->renderLayout();
        }
     
    	public function deleteAction()
        {
            $tipId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twits/tip')->setId($tipId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip successfully deleted'));
                $this->getResponse()->setRedirect($this->getUrl('*/*/'));
     
                return;
            } catch (Exception $e){
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
            }
     
            $this->_redirectReferer();
        }
     
        public function newAction()
        {
            $this->loadLayout()
            ->_addContent($this->getLayout()->createBlock('twits/admin_new'))
            ->renderLayout();
        }
     
        public function postAction()
        {
            if ($data = $this->getRequest()->getPost()) {
                $tip = Mage::getModel('twits/tip')->setData($data);
                try {
                    $tip->save();
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip successfully saved'));
                    $this->getResponse()->setRedirect($this->getUrl('*/*/'));
                    return;
                } catch (Exception $e){
                    Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
                }
            }
            $this->getResponse()->setRedirect($this->getUrl('*/*/'));
            return;
        }
     
        public function editAction()
        {
            $this->loadLayout();
            $this->_addContent($this->getLayout()->createBlock('twits/admin_edit'));
            $this->renderLayout();
        }
     
        public function saveAction()
        {
            $tipId = $this->getRequest()->getParam('id', false);
            if ($data = $this->getRequest()->getPost()) {
                $tip = Mage::getModel('twits/tip')->load($tipId)->addData($data);
                try {
                    $tip ->setId($tipId)->save();
     
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip successfully saved'));
                    $this->getResponse()->setRedirect($this->getUrl('*/*/'));
                    return;
                } catch (Exception $e){
                    Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
                }
            }
            $this->_redirectReferer();
        }
    }
  5. Ok i admit the code is a tad inefficient. We have 2 separate functions that both does save. In fact, postAction and saveAction only differ that saveAction loads the right record via the ->load($tipId) function. Thats separates a CREATE from an UPDATE request.

    And we’re finally finally finally done! To test out the edit function, simply click on any row from the Grid! Thats assuming of course that you actually have some data.
    Magento Admin Form

» For frontend stuff, carry on … Fontend – List

Otherwise, if it works for you, leave a comment! =)

Download the source for this entire series here!

CRUD – Create

  1. More file structure goodness:
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - New.php
                  New/
                    - Form.php
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Tip.php
                Mysql4/
                  - Tip.php
                  Tip/
                    - Collection.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    Here we’ve added the block for the New container and Form

  2. Looking at the New.php code:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <?php
    class SavantDegrees_Twits_Block_Admin_New extends Mage_Adminhtml_Block_Widget_Form_Container
    {
        public function __construct()
        {
            parent::__construct();
     
            $this->_blockGroup = 'twits';
            $this->_mode = 'new';
            $this->_controller = 'admin';
        }
     
        public function getHeaderText()
        {
            return Mage::helper('twits')->__('Add New Tip');
        }
    }
  3. Then the New/Form.php code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
    <?php
    class SavantDegrees_Twits_Block_Admin_New_Form extends Mage_Adminhtml_Block_Widget_Form
    {
        protected function _prepareForm()
        {
            $form = new Varien_Data_Form();
     
            $fieldset = $form->addFieldset('new_tip', array('legend' => Mage::helper('twits')->__('Tip Details')));
     
            $fieldset->addField('title', 'text', array(
                'name'      => 'title',
                'title'     => Mage::helper('twits')->__('Title'),
                'label'     => Mage::helper('twits')->__('Title'),
                'maxlength' => '250',
                'required'  => true,
            ));
     
            $fieldset->addField('author', 'text', array(
                'name'      => 'author',
                'title'     => Mage::helper('twits')->__('Author'),
                'label'     => Mage::helper('twits')->__('Author'),
                'maxlength' => '250',
                'required'  => true,
            ));
     
            $fieldset->addField('contents', 'textarea', array(
                'name'      => 'contents',
                'title'     => Mage::helper('twits')->__('Contents'),
                'label'     => Mage::helper('twits')->__('Contents'),
                'style'     => 'width: 98%; height: 200px;',
                'required'  => true,
            ));
     
            $form->setMethod('post');
            $form->setUseContainer(true);
            $form->setId('edit_form');
            $form->setAction($this->getUrl('*/*/post'));
     
            $this->setForm($form);
        }
    }

    Just like your listings/grid page, the New.php and Form.php classes are the Form Container and Form respectively. So the Form Container automagically creates the form. Did I mention why containers are good? They come with nice pretty buttons! (Save and Back).

  4. Then lastly the adminController with its newAction and postAction to load and save the form respectively

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    <?php
    class SavantDegrees_Twits_AdminController extends Mage_Adminhtml_Controller_Action
    {
    	public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twits/admin_main'))
    			->renderLayout();
        }
     
    	public function deleteAction()
        {
            $tipId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twits/tip')->setId($tipId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip successfully deleted'));
                $this->getResponse()->setRedirect($this->getUrl('*/*/'));
     
                return;
            } catch (Exception $e){
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
            }
     
            $this->_redirectReferer();
        }
     
        public function newAction()
        {
            $this->loadLayout()
            ->_addContent($this->getLayout()->createBlock('twits/admin_new'))
            ->renderLayout();
        }
     
        public function postAction()
        {
            if ($data = $this->getRequest()->getPost()) {
                $tip = Mage::getModel('twits/tip')->setData($data);
                try {
                    $tip->save();
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip was successfully saved'));
                    $this->getResponse()->setRedirect($this->getUrl('*/*/'));
                    return;
                } catch (Exception $e){
                    Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
                }
            }
            $this->getResponse()->setRedirect($this->getUrl('*/*/'));
            return;
        }
    }
  5. Reward yourself for your lightning fast copy-and-paste kung foo! Whats happening though?
    When the script receives a call newAction, it’ll show the form to enter data. The data is posted to the postAction method. That method creates an instance of the model, sets the post data into the model, saves it then redirects it back to the indexAction. Did i mention automagically? Yes, thats why we had to learn so much of the Magento code just to write this simple tutorial. The sql is automagically generated and run and the form is automagically created! *sheds a tear*

    Again, your well deserved create form can be found by clicking on the “Add New Tip” button from the Grid!

» Just 1 more … CRUD – Update

Download the source for this entire series here!

CRUD – Delete

  1. The simplest step thus far, add a new function to your AdminController.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    <?php
    class SavantDegrees_Twits_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twits/admin_main'))
    			->renderLayout();
        }
     
        public function deleteAction()
        {
            $tipId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twits/tip')->setId($tipId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twits')->__('Tip successfully deleted'));
                $this->getResponse()->setRedirect($this->getUrl('*/*/'));
     
                return;
            } catch (Exception $e){
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
            }
     
            $this->_redirectReferer();
        }
    }

» Almost there … CRUD – Create

Download the source for this entire series here!

CRUD – Retrieve

  1. Lets add a new block to the file structure
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Tip.php
                Mysql4/
                  - Tip.php
                  Tip/
                    - Collection.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    We’ll create folder called Admin to store all the admin related blocks. Inside we’ll have a Main.php file that contains the Grid Container. Why do we need a grid container? To put the title and Create button!

  2. The Main.php code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    <?php
    class SavantDegrees_Twits_Block_Admin_Main extends Mage_Adminhtml_Block_Widget_Grid_Container
    {
        public function __construct()
        {
            $this->_addButtonLabel = Mage::helper('twits')->__('Add New Tip');
            parent::__construct();
     
            $this->_blockGroup = 'twits';
            $this->_controller = 'admin_main';
            $this->_headerText = Mage::helper('twits')->__('Tips(s)');
        }
    }

    Here you see a first use of the helper, which will assist in translation

  3. The Grid.php itself

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    
    <?php
    class SavantDegrees_Twits_Block_Admin_Main_Grid extends Mage_Adminhtml_Block_Widget_Grid
    {
     
        public function __construct()
        {
            parent::__construct();
            $this->setId('tipsGrid');
            $this->_controller = 'twits';
        }
     
        protected function _prepareCollection()
        {
            $model = Mage::getModel('twits/tip');
            $collection = $model->getCollection();
    		$this->setCollection($collection);
     
            return parent::_prepareCollection();
        }
     
     
     
        protected function _prepareColumns()
        {
     
            $this->addColumn('tip_id', array(
                'header'        => Mage::helper('twits')->__('ID'),
                'align'         => 'right',
                'width'         => '50px',
                'filter_index'  => 'dt.tip_id',
                'index'         => 'tip_id',
            ));
     
            $this->addColumn('name', array(
                'header'        => Mage::helper('twits')->__('Title'),
                'align'         => 'left',
                'width'         => '150px',
                'filter_index'  => 'dt.title',
                'index'         => 'title',
                'type'          => 'text',
                'truncate'      => 50,
                'escape'        => true,
            ));
     
            $this->addColumn('tags', array(
                'header'    	=> Mage::helper('twits')->__('Author'),
                'align'         => 'left',
                'filter_index'  => 'dt.author',
                'index'    	 	=> 'author',
                'type'     	 	=> 'text',
                'escape'		=> true,
            ));
     
            $this->addColumn('summary', array(
                'header'        => Mage::helper('twits')->__('Contents'),
                'align'         => 'left',
                'filter_index'  => 'dt.contents',
                'index'         => 'contents',
                'type'          => 'text',
                'escape'        => false,
            ));
     
            $this->addColumn('action',
                array(
                    'header'    => Mage::helper('twits')->__('Action'),
                    'width'     => '150px',
                    'type'      => 'action',
                    'getter'	=> 'getTipId',
                    'actions'   => array(
                        array(
                            'caption' => Mage::helper('twits')->__('Edit'),
                            'url'     => array(
                                'base'=>'*/*/edit'
                             ),
                             'field'   => 'id'
                        ),
                        array(
                            'caption' => Mage::helper('twits')->__('Delete'),
                            'url'     => array(
                                'base'=>'*/*/delete'
                             ),
                             'field'   => 'id'
                        )
                    ),
                    'filter'    => false,
                    'sortable'  => false
            ));
     
            return parent::_prepareColumns();
        }
     
        public function getRowUrl($row)
        {
            return $this->getUrl('*/*/edit', array(
                'id' => $row->getTipId(),
            ));
        }
    }

    If you scrutinize the code (which i’d advise against), you’ll see most of the code is geared towards the setup of the grid columns and the edit/delete urls i’ll eventually call to perform the other CRUD functions.
    But whats going on? Where do we use this code? The main code (being a Grid_Container automatically adds the child Grid code associated to it. By child we’re talking about the Grid.php file in the Main folder. The folder’s name follows the file. So if you changed the folder’s name to Main2, it wouldnt work. That being said, you can just put any block code in the folder and expect it to run. The reason why this all works now is cause the geniuses at Magento already did alot of work to call the Grid child to run. They have no super time travel powers to tell what else you’re gonna put in there. So, it simply wont work (yet).

  4. Now lets make our adminController.php use that block instead of Hello world.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    class SavantDegrees_Twits_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twits/admin_main'))
    			->renderLayout();
        }
    }

    Well it says createBlock doesnt it? What do you think it does? twits/admin_main as always refers to the twits namespace and the admin/main.php block file.

  5. Viola! Your very very very hard earned grid!
    Magento Admin Grid

» Anytime now … Part 6 – CRUD – Delete

Download the source for this entire series here!

Prepare the database

Next we’ll create the installation script that installs the twits table into the database.

  1. Once again, we begin with updating the file structure
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    We simply included a sql folder that will house the eventual table creation sql code.

  2. Next up, updating the config.xml again. This time to add the setup declarations.
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <version>0.2.0</version>
            </SavantDegrees_Twits>
        </modules>
        <global>
    	<blocks>
                <twits>
                    <class>SavantDegrees_Twits_Block</class>
                </twits>
            </blocks>
             <helpers>
                <twits><class>SavantDegrees_Twits_Helper</class></twits>
            </helpers>
            <resources>
                <twits_setup>
                    <setup>
                        <module>SavantDegrees_Twits</module>
                    </setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </twits_setup>
                <twits_write>
                    <connection>
                        <use>core_write</use>
                    </connection>
                </twits_write>
                <twits_read>
                    <connection>
                        <use>core_read</use>
                    </connection>
                </twits_read>
            </resources>
        </global>
        <adminhtml>
            <menu>
                <twits translate="title" module="twits">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twits/admin</action>
                </twits>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twits>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twits</module>
                        <frontName>twits</frontName>
                    </args>
                </SavantDegrees_Twits>
            </routers>
        </frontend>
    </config>

    Here we’ve added a global > resources section that tells the setup script the folder for installation. We’ve also changed the version number to 0.2.0. When the page is initiated and Magento detects that the version has been upgraded, it will run the appropriate install script.

  3. Lastly the install script itself: mysql4-install-0.2.0.php
    <?php
    $installer = $this;
    $installer->startSetup();
     
    $installer->run("
    CREATE TABLE {$this->getTable('tips')} (
      `tip_id` int(10) unsigned NOT NULL auto_increment,
      `title` varchar(250) NOT NULL default '',
      `author` varchar(250) default NULL,
      `contents` text,
      PRIMARY KEY  (`tip_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      ");
     
    $installer->endSetup();
  4. Copy the same file contents to mysql4-upgrade-0.1.0-0.2.0.php. Why? because the code above is correct. But since this HOWTO is going through the phases in version 0.1, 0.2, and etc. Magento has already a record of twit_setup in its core_resource table bound to the previous version of 0.1 (its first run). So what we need now is a upgrading script instead of an installer. But if/when you do package your extension, you shouldn’t need the upgrade code. Just install straight to version 0.2 (or higher)
  5. Once again, wake up the Magento installer by logging into the admin backend, System > Configuration > Advanced. Click on Save Config. Clear your cache at /var/cache too just to be sure.
  6. Check to see if your table is created.
  7. Once your table is created, you can delete the mysql4-upgrade-0.1.0-0.2.0.php file. As explained above, you dont need it anymore!

» More Intangibles! … Part 4 – Model

Jun 18, 2008

HOWTO: Scheduled Remote MySQL Backups

Author: gaweee | Filed under: development, howto

If you have more access to more than 1 server, a good idea would be to schedule remote backups using a small script and crontab. This way, if 1 server goes down at least SOME data still resides on another. (a friend’s hosting service got prematurely terminted recently because someone else sharing a hosting plan was serving illegal content and the whole system was confiscated by the police in that country!)

of course backing up mysql is the only thing you can do, unless you’ve way too much disk space and way too much bandwidth… in that case please consult a professional on the actual 1001 ways to throw money at redundancy.

The script will first run remote commands on the target server over ssh to dump and gzip the database into a file. The file is then downloaded to the backup server. A final ssh command removes the dump file from the target server. Viola! And here is the shell script…

ssh root@wits.sg 'mysqldump -uroot twits> /tmp/twits.backup.sql && bzip2 -9 /tmp/twits.backup.sql'
scp root@wits.sg:/tmp/twits.backup.sql.bz2 /var/backups/twits/`date "+%Y%m%d-%H%M"`.sql.bz2
ssh root@wits.sg 'rm /tmp/twits.backup.sql.bz2'

Save this few lines to a file on the backup server and give it execute permissions! (I saved it at /root/twits_backup.sh)
Note: Passwordless logins between the 2 servers must have been setup prior to running this script. Otherwise scp and ssh commands will both be prompted for a password.

Next set up the cron job on the backup server to run the script

30 05 * * * /root/twits_backup.sh

This sets the script to run at 5:30am everyday.
Here is a good place to read up more on cron jobs.

Jun 17, 2008

HOWTO: Did you mean…

Author: gaweee | Filed under: development, howto

I always thought it was really cool to have those “Did you mean <some other spelling corrected word>”? Took me long enough to finally chance upon how it works! Basically it uses sound functions: soundex, metaphone or levenshtein distance to match words.

Different database supports different possible implementations of the functions. A sample mySQL query would look like:

SELECT `name` FROM `organizations` WHERE SOUNDEX(`name`) = SOUNDEX('dog');

I’ve also seen some mysql stored procs that hack in a levenshtein distance calculation algorithm. This allows a even more dynamic and accurate match. Unfortunately, i’ve never gotten it to work. Do let me know if anyone has succeeded.