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

  1. Some new file structure loving
    app/
      design/
        frontend/
          default/
            default/
              template/
                twit/
                  - twits_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)
            Twit/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - Edit.php
                  Edit/
                    - Form.php
                  - New.php
                  New/
                    - Form.php
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Twit.php
                Mysql4/
                  - Twit.php
                  Twit/
                    - Collection.php
              sql/
                twit_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_Twit_IndexController extends Mage_Core_Controller_Front_Action
    {
     
        public function indexAction()
        {
    	$this->loadLayout();
    	$this->getLayout()
    		->getBlock('content')->append(
    			$this->getLayout()->createBlock('twit/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_Twit_Block_Index extends Mage_Core_Block_Template
    {
        public function __construct()
        {
            parent::__construct();
            $this->setTemplate('twit/twits_list.phtml');
        }
     
        public function getTwits()
        {
        	$model = Mage::getModel('twit/twit');
        	$collection = $model
        	    	->getCollection()
        	    	->load();
     
            return $collection->getItems();
        }
    }

    What the difference here? We made the block load the template from twits/twit_list.phtml. We can create/find this file in /app/design/frontend/default/default/template/twits/twit_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 twit_list.phtml file:
    <?php
    $_twits = $this->getTwits();
    if ($_twits) {
    	$count = 0;
    	foreach ($_twits as $i=>$twit) {
    ?>
    		<div class="twit">
    			<div class='name'><?= $twit->getName() ?></div>
    			<div class='summary'><?= $twit->getSummary() ?></div>
    		</div>
    <?php
    	}
    }
    ?>

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

  5. There you have it! check your twits list at http://127.0.0.1/magento/index.php/twit

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)
            Twit/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - Edit.php
                  Edit/
                    - Form.php
                  - New.php
                  New/
                    - Form.php
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Twit.php
                Mysql4/
                  - Twit.php
                  Twit/
                    - Collection.php
              sql/
                twit_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_Twit_Block_Admin_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
    {
        public function __construct()
        {
            parent::__construct();
     
            $this->_blockGroup = 'twit';
            $this->_mode = 'edit';
            $this->_controller = 'admin';
     
            if( $this->getRequest()->getParam($this->_objectId) ) {
                $twitData = Mage::getModel('twit/twit')
                    ->load($this->getRequest()->getParam($this->_objectId));
                Mage::register('frozen_twit', $twitData);
            }
        }
     
        public function getHeaderText()
        {
            return Mage::helper('twit')->__("Edit Twit'%s'", $this->htmlEscape(Mage::registry('frozen_twit')->getName()));
        }
    }

    The additional code loads the right Twit 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
    43
    
    <?php
    class SavantDegrees_Twit_Block_Admin_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
    {
        protected function _prepareForm()
        {
            $form = new Varien_Data_Form(array(
                'id'        => 'edit_form',
                'action'    => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
                'method'    => 'post'
            ));
     
            $fieldset = $form->addFieldset('edit_twit', array('legend' => Mage::helper('twit')->__('Twit Details')));
     
            $fieldset->addField('name', 'text', array(
                'name'      => 'name',
                'title'     => Mage::helper('twit')->__('Name'),
                'label'     => Mage::helper('twit')->__('Name'),
                'maxlength' => '50',
                'required'  => true,
            ));
     
            $fieldset->addField('tags', 'text', array(
                'name'      => 'tags',
                'title'     => Mage::helper('twit')->__('Tags'),
                'label'     => Mage::helper('twit')->__('Tags'),
                'maxlength' => '255',
                'required'  => true,
            ));
     
            $fieldset->addField('summary', 'textarea', array(
                'name'      => 'summary',
                'title'     => Mage::helper('twit')->__('Summary'),
                'label'     => Mage::helper('twit')->__('Summary'),
                'style'     => 'width: 98%; height: 200px;',
                'required'  => true,
            ));
     
            $form->setUseContainer(true);
            $form->setValues(Mage::registry('frozen_twit')->getData());
            $this->setForm($form);
            return parent::_prepareForm();
        }
    }
  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_Twit_AdminController extends Mage_Adminhtml_Controller_Action
    {
    	public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twit/admin_main'))
    			->renderLayout();
        }
     
    	public function deleteAction()
        {
            $twitId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twit/twit')->setId($twitId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Page 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('twit/admin_new'))
            ->renderLayout();
        }
     
        public function postAction()
        {
            if ($data = $this->getRequest()->getPost()) {
                $twit = Mage::getModel('twit/twit')->setData($data);
                try {
                    $twit->save();
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Twit 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;
        }
     
        public function editAction()
        {
            $this->loadLayout();
            $this->_addContent($this->getLayout()->createBlock('twit/admin_edit'));
            $this->renderLayout();
        }
     
        public function saveAction()
        {
            $twitId = $this->getRequest()->getParam('id', false);
            if ($data = $this->getRequest()->getPost()) {
                $twit = Mage::getModel('twit/twit')->load($twitId)->addData($data);
                try {
                    $twit->setId($twitId)->save();
     
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Twit was saved successfully'));
                    $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($twitId) 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! =)

Just to be sure, download the Magento tutorial files to see if you’ve made any mistakes here: Magento Tutorial

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)
            Twit/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                  - New.php
                  New/
                    - Form.php
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Twit.php
                Mysql4/
                  - Twit.php
                  Twit/
                    - Collection.php
              sql/
                twit_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_Twit_Block_Admin_New extends Mage_Adminhtml_Block_Widget_Form_Container
    {
        public function __construct()
        {
            parent::__construct();
     
            $this->_blockGroup = 'twit';
            $this->_mode = 'new';
            $this->_controller = 'admin';
        }
     
        public function getHeaderText()
        {
            return Mage::helper('twit')->__('Add New Twit');
        }
    }
  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_Twit_Block_Admin_New_Form extends Mage_Adminhtml_Block_Widget_Form
    {
        protected function _prepareForm()
        {
            $form = new Varien_Data_Form();
     
            $fieldset = $form->addFieldset('new_twit', array('legend' => Mage::helper('twit')->__('Twit Details')));
     
            $fieldset->addField('name', 'text', array(
                'name'      => 'name',
                'title'     => Mage::helper('twit')->__('Name'),
                'label'     => Mage::helper('twit')->__('Name'),
                'maxlength' => '50',
                'required'  => true,
            ));
     
            $fieldset->addField('tags', 'text', array(
                'name'      => 'tags',
                'title'     => Mage::helper('twit')->__('Tags'),
                'label'     => Mage::helper('twit')->__('Tags'),
                'maxlength' => '255',
                'required'  => true,
            ));
     
            $fieldset->addField('summary', 'textarea', array(
                'name'      => 'summary',
                'title'     => Mage::helper('twit')->__('Summary'),
                'label'     => Mage::helper('twit')->__('Summary'),
                '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
    
     
    class SavantDegrees_Twit_AdminController extends Mage_Adminhtml_Controller_Action
    {
    	public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twit/admin_main'))
    			->renderLayout();
        }
     
    	public function deleteAction()
        {
            $twitId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twit/twit')->setId($twitId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Page 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('twit/admin_new'))
            ->renderLayout();
        }
     
        public function postAction()
        {
            if ($data = $this->getRequest()->getPost()) {
                $twit = Mage::getModel('twit/twit')->setData($data);
                try {
                    $twit->save();
                    Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Twit 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 Twit” button from the Grid!

Just 1 more … CRUD – Update

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_Twit_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twit/admin_main'))
    			->renderLayout();
        }
     
        public function deleteAction()
        {
            $twitId = $this->getRequest()->getParam('id', false);
     
            try {
                Mage::getModel('twit/twit')->setId($twitId)->delete();
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('twit')->__('Page successfully deleted'));
                $this->getResponse()->setRedirect($this->getUrl('*/*/'));
     
                return;
            } catch (Exception $e){
                Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
            }
     
            $this->_redirectReferer();
        }
    }
Almost there … CRUD – Create

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)
            Twit/ (Or whatever your module name might be)
              Block/
                Admin/
                  - Main.php
                  Main/
                    - Grid.php
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Twit.php
                Mysql4/
                  - Twit.php
                  Twit/
                    - Collection.php
              sql/
                twit_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0

    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_Twit_Block_Admin_Main extends Mage_Adminhtml_Block_Widget_Grid_Container
    {
        public function __construct()
        {
            $this->_addButtonLabel = Mage::helper('twit')->__('Add New Twit');
            parent::__construct();
     
            $this->_blockGroup = 'twit';
            $this->_controller = 'admin_main';
            $this->_headerText = Mage::helper('twit')->__('Twit(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_Twit_Block_Admin_Main_Grid extends Mage_Adminhtml_Block_Widget_Grid
    {
     
        public function __construct()
        {
            parent::__construct();
            $this->setId('twitGrid');
            $this->_controller = 'twit';
        }
     
        protected function _prepareCollection()
        {
            $model = Mage::getModel('twit/twit');
            $collection = $model->getCollection();
    		$this->setCollection($collection);
     
            return parent::_prepareCollection();
        }
     
     
     
        protected function _prepareColumns()
        {
     
            $this->addColumn('twit_id', array(
                'header'        => Mage::helper('twit')->__('ID'),
                'align'         => 'right',
                'width'         => '50px',
                'filter_index'  => 'dt.twit_id',
                'index'         => 'twit_id',
            ));
     
            $this->addColumn('name', array(
                'header'        => Mage::helper('twit')->__('Name'),
                'align'         => 'left',
                'width'         => '150px',
                'filter_index'  => 'dt.name',
                'index'         => 'name',
                'type'          => 'text',
                'truncate'      => 50,
                'escape'        => true,
            ));
     
            $this->addColumn('summary', array(
                'header'        => Mage::helper('twit')->__('Summary'),
                'align'         => 'left',
                'filter_index'  => 'dt.summary',
                'index'         => 'summary',
                'type'          => 'text',
                'escape'        => false,
            ));
     
            $this->addColumn('tags', array(
                'header'    	=> Mage::helper('twit')->__('Tags'),
                'align'         => 'left',
                'filter_index'  => 'dt.tags',
                'index'    	 	=> 'tags',
                'type'     	 	=> 'text',
                'escape'		=> true,
            ));
     
            $this->addColumn('action',
                array(
                    'header'    => Mage::helper('twit')->__('Action'),
                    'width'     => '150px',
                    'type'      => 'action',
                    'getter'	=> 'getTwitId',
                    'actions'   => array(
                        array(
                            'caption' => Mage::helper('twit')->__('Edit'),
                            'url'     => array(
                                'base'=>'*/*/edit'
                             ),
                             'field'   => 'id'
                        ),
                        array(
                            'caption' => Mage::helper('twit')->__('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->getTwitId(),
            ));
        }
    }

    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_Twit_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twit/admin_main'))
    			->renderLayout();
        }
    }

    Well it says createBlock doesnt it? What do you think it does? twit/admin_main as always refers to the twit 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

Prepare the model

  1. Back to the file structure drawing board
    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)
            Twit/ (Or whatever your module name might be)
              Block/
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Twit.php
                Mysql4/
                  - Twit.php
                  Twit/
                    - Collection.php
              sql/
                twit_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0

    Notice the new folder Model and the associated files inside. Lets clarify:
    Model/Twit.php is the logical model that holds code regarding business logic and relations.
    Model/Mysql4/Twit.php is the data implementation model that associates the model with the table and its primary key.
    Model/Mysql4/Twit/Collection.php is the data implementation model of a collection of Twit. Similar to a recordset/resultset in .Net/Java. This will be used with a Grid later.

  2. Even more config.xml magic. Yay! More hairloss!
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twit>
                <version>0.2.0</version>
            </SavantDegrees_Twit>
        </modules>
        <global>
            <models>
                <twit>
                    <class>SavantDegrees_Twit_Model</class>
                    <resourceModel>twit_mysql4</resourceModel>
                </twit>
                <twit_mysql4>
                    <class>SavantDegrees_Twit_Model_Mysql4</class>
                    <entities>
                        <twit>
                            <table>twit</table>
                        </twit>
                    </entities>
                </twit_mysql4>
            </models>
    	<blocks>
                <twit>
                    <class>SavantDegrees_Twit_Block</class>
                </twit>
            </blocks>
             <helpers>
                <twit><class>SavantDegrees_Twit_Helper</class></twit>
            </helpers>
            <resources>
                <twit_setup>
                    <setup>
                        <module>SavantDegrees_Twit</module>
                    </setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </twit_setup>
                <twit_write>
                    <connection>
                        <use>core_write</use>
                    </connection>
                </twit_write>
                <twit_read>
                    <connection>
                        <use>core_read</use>
                    </connection>
                </twit_read>
            </resources>
        </global>
        <adminhtml>
            <menu>
                <twit translate="title" module="twit">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twit/admin</action>
                </twit>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twit>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twit</module>
                        <frontName>twit</frontName>
                    </args>
                </SavantDegrees_Twit>
            </routers>
        </frontend>
    </config>

    The global > models fragment define the twit table, the twit model and its resourceModel that we will be using shortly.

  3. Now for the model/Twit.php code:
    <?php
    class SavantDegrees_Twit_Model_Twit extends Mage_Core_Model_Abstract
    {
     
        protected function _construct()
        {
            $this->_init('twit/twit');
        }
    }

    Whats the point you ask!? The code does nothing but binds the model to the Twit namespace under the model twit. In Java that line would have been package Twit;. *i assume/think/foretold*
    (quickly moving on…)

  4. Then Model/Mysql4/Twit.php
    <?php
    class SavantDegrees_Twit_Model_Mysql4_Twit extends Mage_Core_Model_Mysql4_Abstract
    {
        protected function _construct()
        {
            $this->_init('twit/twit', 'twit_id');
        }
    }

    The code above tells Magento to hunt for the twit application’s twit table in the configuration. Yes i know the names are confusing now. Lets say instead of twit/twit it is twit/foo. In that case the system will find the configuration file associated with twit (which is our app/code/local/SavantDegrees/Twit/etc/config.xml), and look under config > global > models > twit_mysql4 > entities > foo to figure out its configuration instructions. You’ll see next that we’ll request for the table from that same configuration file. Capiche?
    As a resourceModel, the code binds the model to a underlying Mysql4 table.

  5. Lastly, Model/Mysql4/Twit/Collection.php
    <?php
    class SavantDegrees_Twit_Model_Mysql4_Twit_Collection extends Varien_Data_Collection_Db
    {
        protected $_twitTable;
     
        public function __construct()
        {
            $resources = Mage::getSingleton('core/resource');
            parent::__construct($resources->getConnection('twit_read'));
            $this->_twitTable = $resources->getTableName('twit/twit');
     
            $this->_select->from(
            		array('twit'=>$this->_twitTable),
     		       	array('*')
            		);
            $this->setItemObjectClass(Mage::getConfig()->getModelClassName('twit/twit'));
        }
    }

    Basically the model files created at this phase are simply trying to tie the model (singular and collection) to the underlying Mysql resource. The entire mechanism is half tied in the config.xml table declaration, the model individual instructions (from identifying the namespace to actually declaring the primary key of the table)

  6. Viola. Models are ready
Arent we there yet? Part 5 – CRUD – Retrieve

Prepare the database
Next we’ll create the installation script that installs the twit 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)
            Twit/ (Or whatever your module name might be)
              Block/
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              sql/
                twit_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_Twit>
                <version>0.2.0</version>
            </SavantDegrees_Twit>
        </modules>
        <global>
    	<blocks>
                <twit>
                    <class>SavantDegrees_Twit_Block</class>
                </twit>
            </blocks>
             <helpers>
                <twit><class>SavantDegrees_Twit_Helper</class></twit>
            </helpers>
            <resources>
                <twit_setup>
                    <setup>
                        <module>SavantDegrees_Twit</module>
                    </setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </twit_setup>
                <twit_write>
                    <connection>
                        <use>core_write</use>
                    </connection>
                </twit_write>
                <twit_read>
                    <connection>
                        <use>core_read</use>
                    </connection>
                </twit_read>
            </resources>
        </global>
        <adminhtml>
            <menu>
                <twit translate="title" module="twit">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twit/admin</action>
                </twit>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twit>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twit</module>
                        <frontName>twit</frontName>
                    </args>
                </SavantDegrees_Twit>
            </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('twit')} (
      `twit_id` int(10) unsigned NOT NULL auto_increment,
      `name` varchar(250) NOT NULL default '',
      `tags` varchar(250) default NULL,
      `summary` text,
      PRIMARY KEY  (`twit_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

Create the administrative complement
Next we’ll create the administration controller and configuration to achieve the https://127.0.0.1/magento/index.php/twit/admin reservation. This took me a long time to figure out how to do nicely.

  1. We’ll begin by updating the adding 2 new files (controller and helper) for the purpose of the Hello world in the admin site
    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)
            Twit/ (Or whatever your module name might be)
              Block/
                - Index.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
  2. Then we obviously create the adminController.php
    We’ll make the admin page reuse the same Hello world block that we’ve created in Phase 1.

    <?php
    class SavantDegrees_Twit_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twit/index'))
    			->renderLayout();
        }
    }

    Note that this controller extends Mage_Adminhtml_Controller_Action instead. This enforces security and loads the admin layout for this controller instead of the frontend version. With this done, you can now visit https://127.0.0.1/magento/index.php/twit/admin to see the page in action. Again, this produces our ubiquitous Hello world. You’d probably figured out by now that /twit refers to our frontend controller reservation by our config.xml and then /admin refers to the adminController that we’ve just created. Now you can try https://127.0.0.1/magento/index.php/twit/admin/index to see the indexAction method being called. Try it with other action names!

  3. Next we’ll do the config.xml magic that allows us to use https://127.0.0.1/magento/index.php/admin/twit for the administration.
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twit>
                <version>0.1.0</version>
            </SavantDegrees_Twit>
        </modules>
        <global>
             <helpers>
                <twit><class>SavantDegrees_Twit_Helper</class></twit>
            </helpers>
    	<blocks>
                <twit>
                    <class>SavantDegrees_Twit_Block</class>
                </twit>
            </blocks>
        </global>
        <adminhtml>
            <menu>
                <twit translate="title" module="twit">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twit/admin</action>
                </twit>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twit>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twit</module>
                        <frontName>twit</frontName>
                    </args>
                </SavantDegrees_Twit>
            </routers>
        </frontend>
    </config>

    What’ changed?

    • The global > helper > [module] fragment defines the helper class that will help our future translation. We’ll be seeing the code for that shortly.
    • The adminhtml > menu > [module] fragment allows us to create the menu to access the twit/admin page. This is vital for version 1.3 onwards as the menu generates the necessary url key to access the page.
  4. And finally the helper Data.php file
    <?php
    class SavantDegrees_Twit_Helper_Data extends Mage_Core_Helper_Abstract
    {
    }

    Why bother? Its a quirk of Magento to try to translate the menu. So we NEED this helper. Otherwise Magento goes looking for a Mage_Twit_Helper_Data class. Which obviously doesnt exist! You could use the helper from existing Magento classes, but then you could use ALOT of stuff from Magento classes. And if you’re reading this tutorial, you’re obviously arent ready.

Moving along … Part 3 – Database

Guide to programming with MagentoMagento code is awesome. So awesome that 20 wiki entries and a 100 files open later i’m was still lost. So much that i ordered the PHP|architects Magento book! At first i was disappointed in its coverage but it did help me out along the way. So heres a synthesized description of our efforts. We’re breaking up this HOWTO into a few segments into increasing orders of complexity and integration.

Note: Apparently i’ve been giving our dear readers the misconception that the book is what taught me all these techniques. WRONG. The book’s lack of coverage is what prompted me to write this. I’m not sure i was any better off reading the book! :(

If you have a really good idea of what you’re doing/not doing, you could cut right through the chase and download the entire tutorial’s code here: Magento Tutorial

Otherwise, let us begin …

Do us all a favour and get your cache management right (disabled) from the beginning
Go to your system backend: http://127.0.0.1/magento/index.php/admin
Login and click ok System > Cache Management
Then on the All Cache dropdown menu, select Disable
Save cache settings

Create barebones code, file structure and config
We’re trying to achieve a proof of concept here to describe how the file structure, namespaces and config files interplay to achieve a Hello world message

  1. Create your directory structure! Our NEW company name is Savant Degrees! and our module shall be called….. Twit (for a lack of an imagination). The whole module (well, almost) should rest in the /app/code/local> folder. So lets first create the following folder structure. For a hello world module, you only need a config file, a controller and a block. Ergo…
    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)
            Twit/ (Or whatever your module name might be)
              Block/
                - Index.php
              controllers/
                - IndexController.php
              etc/
                - config.xml
  2. Create your config.xml file. Thats the crux of the whole module.
    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
    
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twit>
                <version>0.1.0</version>
            </SavantDegrees_Twit>
        </modules>
        <global>
            <blocks>
                <twit>
                    <class>SavantDegrees_Twit_Block</class>
                </twit>
            </blocks>
        </global>
        <frontend>
            <routers>
                <SavantDegrees_Twit>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twit</module>
                        <frontName>twit</frontName>
                    </args>
                </SavantDegrees_Twit>
            </routers>
        </frontend>
    </config>

    Let me explain this step a little…

    The modules > [module] > version number allows Magento to track when your module is upgraded. We’ll be seeing a version of that shortly.
    The global > blocks > [module] define the Block resources that are available under your package. We’ll be using this to generate a Hello world shortly
    The frontend > routers fragment define how the module can be access. The current code allows the block to be access via https://127.0.0.1/magento/index.php/twit

  3. Create your IndexController.php file. Its as simple as:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    <?php
    class SavantDegrees_Twit_IndexController extends Mage_Core_Controller_Front_Action
    {
     
        public function indexAction()
        {
    	$this->loadLayout();
    	$this->getLayout()
    		->getBlock('content')->append(
    			$this->getLayout()->createBlock('twit/index')
        	);
            $this->renderLayout();
        }
    }

    More Explanation: The twit/index refers to the index block inside the Twit module, which is defined in the config file above. We’re trying to put its contents inside the layout.

  4. Lastly, we’ll create the Index.php Block:
    1
    2
    3
    4
    5
    6
    7
    8
    
    <?php
    class SavantDegrees_Twit_Block_Index extends Mage_Core_Block_Template
    {
    	protected function _toHtml()
    	{
    		return 'Hello world';
    	}
    }

    You DO understand SOME PHP don’t you?

  5. Finally, we activate the module by creating the file SavantDegrees_All.xml file
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twit>
                <active>true</active>
                <codePool>local</codePool>
            </SavantDegrees_Twit>
        </modules>
    </config>
  6. 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.

Tada! Navigate to https://127.0.0.1/magento/index.php/twit to see your well deserved Hello World!

Feb 7, 2009

HOWTO: PHP image resize, centered and cropped

Author: gaweee | Filed under: development, howto

Sorry for not posting in such a long time folks, i just came back from the Pegasus galaxy. While working on a Joomla! 1.5 project, we had to develop a function to crop and resize images for obvious content management and aesthetic reasons. So i gathered around some good sources on PHP.net and wrote the following class:

class ImageHelper {
 
	static function treatFilename($filename) {
		$newfilename = strtolower($filename);
		$newfilename = str_replace(" ","_",$newfilename);
		return $newfilename;
	}
 
	static function isPotrait($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		return !ImageHelper::isLandscape($srcimage);
	}
 
	static function isLandscape($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$size	= getimagesize( $srcimage );
		return ($size[0] > $size[1]);
	}
 
	static function resizeImage($srcimage, $destimage, $width, $height) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$srcpathinfo 	= pathinfo($srcimage);
		$srcext 		= strtolower($srcpathinfo['extension']);
		$destpathinfo 	= pathinfo($destimage);
		$destext 		= strtolower($destpathinfo['extension']);
		$size 			= getimagesize( $srcimage );				// Get the size of the original image into an array [0]=> width, [1]=> height
		$image			= null;
		$canvas 		= imagecreatetruecolor( $width, $height );	// Prepare canvas
 
		// Create a new image in the memory from the file 
		switch ($srcext) { 
			case 'wbmp':
			case 'bmp':
				$image = imagecreatefromwbmp($srcimage);
				break;
			case 'jpg':
			case 'jpeg':
				$image = imagecreatefromjpeg($srcimage);
				break;
			case 'png':
				$image = imagecreatefrompng($srcimage);
				break;
			case 'gif':
				$image = imagecreatefromgif($srcimage);
				break;
			case 'xpm':
				$image = imagecreatefromxpm($srcimage);
				break;
			default:
				return;
		}
 
		// Calculate dimensions
		$widthratio		= $size[0]/$width;
		$heightratio	= $size[1]/$height;
		$dimensions 	= array(
						'ratio' 			=> $widthratio,
						'source_cropwidth' 	=> 0,
						'source_cropheight' => 0,
						'source_offsetx' 	=> 0,
						'source_offsety' 	=> 0
						);
 
		if ($heightratio < $widthratio)
			$dimensions['ratio'] 			= $heightratio;
 
		// let say image is 1200*800, then:
		// widthratio = 1200/400 = 3
		// heightratio = 800/300 = 2.66
		// since there is less height than width, the max scale we can do is 2.66, then
		// the targetwidth to crop = 400 * 2.66
		// the targetheight to crop = 300 * 2.66
		// the offset width = (1200 - (2.66 * 400))/2 = 68
		// the offset height = (800 - (2.66 * 300))/2 = 1
 
		$dimensions['source_width'] 		= $size[0];
		$dimensions['source_height'] 		= $size[1];
		$dimensions['source_cropwidth'] 	= $width * $dimensions['ratio'];
		$dimensions['source_cropheight'] 	= $height * $dimensions['ratio'];
		$dimensions['source_offsetx'] 		= ($size[0] - $dimensions['source_cropwidth']) / 2;
		$dimensions['source_offsety'] 		= ($size[1] - $dimensions['source_cropheight']) / 2;
 
		imagecopyresampled($canvas, $image, 0, 0, $dimensions['source_offsetx'], $dimensions['source_offsety'], $width, $height, $dimensions['source_cropwidth'], $dimensions['source_cropheight']);
 
		switch ($destext) {
			case 'jpg':
			case 'jpeg':
				imagejpeg( $canvas, $destimage );
				break;
			case 'png':
				imagepng( $canvas, $destimage );
				break;
			case 'gif':
				imagegif( $canvas, $destimage );
				break;
			case 'wbmp':
			case 'bmp':
				imagewbmp( $canvas, $destimage );
				break;
		}
 
		imagedestroy( $canvas );
		imagedestroy( $image );
 
		return true;
	}
}
use treatFilename to remove those pesky spaces, then use isPotrait or isLandscape to determine the orientation of the picture, then perform your resize accordingly. If your uploaded file input was called ulimage, then the code to resize your image would be:

	$width 		= 150;
	$height 		= 150;
	if (is_uploaded_file(@$_FILES['ulimage']['tmp_name'])){
		$targetfilename	= ImageHelper::treatFilename(uniqid() . "_" . $_FILES['ulimage']['name']);
		move_uploaded_file($_FILES['ulimage']['tmp_name'], dirname(__FILE__) . "/tmp/" . $_FILES['ulimage']['name']);
		ImageHelper::resizeImage(dirname(__FILE__) . "/tmp/" . @$_FILES['ulimage']['name'], dirname(__FILE__) . "/tmp/" . $targetfilename, $width, $height);
	}

The reason you need to save the file first is for the imageResize function to infer the image type off its extension. I hadnt bothered to create an image type override. :D

Get the source here: ImageHelper.php
or view the demo here