Create Directories
|
Magento Modules follow a common
naming scheme, Namespace_Module. This is very important to remember as you need
to be careful about how you name your classes. Every custom module will be
created in the directory:
/app/code/local
The first step in creating your
custom module is determining the namespace and module name. The
namespace is simply an extra division that you can create you module in. This
means that you can have two modules named the same thing if they are in two
different namespaces. One way to use namespaces is to use your company name or
initials for all of your modules. If my company name is Acme and I am creating
a module called News the full module name would be Kk_News. Magento uses the
namespace Mage. There is nothing stopping you from using that namespace
under local, i.e. you could create Mage_News and that would work just fine.
Note : You cannot use underscore
within your module name
Note2: It seems that currently, if
you use upper case characters in module names (expecting to show word starts
since neither - nor _ is allowed)... the install will fail or rather the module
will not work. Suggestion: use a single upper case character, at the
beginning of the name.
Let’s setup our directory structure:
/app/code/local/<Namespace>/<Module>/
Block/
controllers/
etc/
Model/
Mysql4/
<Module>/
sql/
<module>_setup/
/app/design/frontend/<interface>/<theme>/
<module>/
Activate Module
|
Magento requires there to be an XML
file that tells Magento to look for and use your custom module.
/app/etc/modules/<Namespace>_<Module>.xml
<config>
<modules>
<[Namespace]_[Module]>
<active>true</active>
<codePool>local</codePool>
</[Namespace]_[Module]>
</modules>
</config>
Also you can disable your module in
the Configuration menu on the backend via the Advanced tab.
NOTE: Due to a bug in Magento,
whitespace is not treated correctly. This means that if you leave space in the
values between node names (anything in angled brackets <> is a node),
Magento will break.
As an explanation of the above code
you will see that all you are changing is the [Namespace]_[Module] text and
leaving everything else the same. Please note the capital P in codePool.
If this is lowercase this module will not be active.
Create Controller
|
/app/code/local/<Namespace>/<Module>/controllers/IndexController.php
<?php
class <Namespace>_<Module>_IndexController extends Mage_Core_Controller_Front_Action
{
public function indexAction()
{
$this->loadLayout();
$this->renderLayout();
}
}
NOTE: You may notice that there is
no closing, ?>, PHP tag in the code. This is a common coding style that
Magento core classes use. Magento Coding Standard is similar (with some
exceptions) to Zend Framework PHP Coding Standard and you can find the detailed
explanations of this rule in Zend Framework Documentation
Create Configuration XML
|
/app/code/local/<Namespace>/<Module>/etc/config.xml
<?xml version="1.0"?>
<config>
<modules>
<[Namespace]_[Module]>
<version>0.1.0</version>
</[Namespace]_[Module]>
</modules>
<frontend>
<routers>
<[module]>
<use>standard</use>
<args>
<module>[Namespace]_[Module]</module>
<frontName>[module]</frontName>
</args>
</[module]>
</routers>
<layout>
<updates>
<[module]>
<file>[module].xml</file>
</[module]>
</updates>
</layout>
</frontend>
<global>
<models>
<[module]>
<class>[Namespace]_[Module]_Model</class>
<resourceModel>[module]_mysql4</resourceModel>
</[module]>
<[module]_mysql4>
<class>[Namespace]_[Module]_Model_Mysql4</class>
<entities>
<[module]>
<table>[module]</table>
</[module]>
</entities>
</[module]_mysql4>
</models>
<resources>
<[module]_setup>
<setup>
<module>[Namespace]_[Module]</module>
</setup>
<connection>
<use>core_setup</use>
</connection>
</[module]_setup>
<[module]_write>
<connection>
<use>core_write</use>
</connection>
</[module]_write>
<[module]_read>
<connection>
<use>core_read</use>
</connection>
</[module]_read>
</resources>
<blocks>
<[module]>
<class>[Namespace]_[Module]_Block</class>
</[module]>
</blocks>
<helpers>
<[module]>
<class>[Namespace]_[Module]_Helper</class>
</[module]>
</helpers>
</global>
</config>
NB : You can use the frontName of
your choice without any link to your module name. IE : Mage_Catalog could have
“mycatalog” as a frontName.
Create Helper
|
/app/code/local/<Namespace>/<Module>/Helper/Data.php
class <Namespace>_<Module>_Helper_Data extends Mage_Core_Helper_Abstract
{
}
Create Models
|
/app/code/local/<Namespace>/<Module>/Model/<Module>.php
<?phpclass <Namespace>_<Module>_Model_<Module> extends Mage_Core_Model_Abstract
{
public function _construct()
{
parent::_construct();
$this->_init('<module>/<module>');
}
}
/app/code/local/<Namespace>/<Module>/Model/Mysql4/<Module>.php
class <Namespace>_<Module>_Model_Mysql4_<Module> extends Mage_Core_Model_Mysql4_Abstract
{
public function _construct()
{
$this->_init('<module>/<module>', '<module>_id');
}
}
NOTE: The ‘<module>_id’ refers
to the PRIMARY KEY in your database table.
/app/code/local/<Namespace>/<Module>/Model/Mysql4/<Module>/Collection.php
class <Namespace>_<Module>_Model_Mysql4_<Module>_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
{
public function _construct()
{
//parent::__construct();
$this->_init('<module>/<module>');
}
}
SQL Setup
|
/app/code/local/<Namespace>/<Module>/sql/<module>_setup/mysql4-install-0.1.0.php
<?php$installer = $this;
$installer->startSetup();
$installer->run("
-- DROP TABLE IF EXISTS {$this->getTable('<module>')};
CREATE TABLE {$this->getTable('<module>')} (
`<module>_id` int(11) unsigned NOT NULL auto_increment,
`title` varchar(255) NOT NULL default '',
`content` text NOT NULL default '',
`status` smallint(6) NOT NULL default '0',
`created_time` datetime NULL,
`update_time` datetime NULL,
PRIMARY KEY (`<module>_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
");
$installer->endSetup();
NOTE: Please note the <module>
text that needs to be replaced. This SQL structure is up to you, this is merely
a starting point.
Template Design
|
/app/design/frontend/<interface>/<theme>/layout/<module>.xml
<layout version="0.1.0">
<[module]_index_index>
<reference name="content">
<block type="[module]/[module]" name="[module]" />
</reference>
</[module]_index_index>
</layout>
NOTE: The block type will
automatically figure out what template file to use based on the second [module]
declaration.
As an alternate way of declaring
what template file to use you can use this: /app/design/frontend/<interface>/<theme>/layout/<module>.xml
<layout version="0.1.0">
<[module]_index_index>
<reference name="content">
<block type="core/template" name="[module]" template="[module]/[module].phtml" />
</reference>
</[module]_index_index>
</layout>
/app/design/frontend/<interface>/<theme>/template/<Module>/<module>.phtml
<?php
/*
This will load one record from your database table.
load(<module>_id) will load whatever ID number you give it.
*/
/*
$news = Mage::getModel('<module>/<module>')->load(1);
echo $news->get<Module>Id();
echo $news->getTitle();
echo $news->getContent();
echo $news->getStatus();
*/
/*
This block of code loads all of the records in the database table.
It will iterate through the collection and the first thing it will do
is set the Title to the current value of $i which is incremented each
iteration and then echo that value back out. At the very end it will
save the entire collection.
*/
/*
$i = 0;
$collection = Mage::getModel('<module>/<module>')->getCollection();
$collection->setPageSize(5);
$collection->setCurPage(2);
$size = $collection->getSize();
$cnt = count($collection);
foreach ($collection as $item) {
$i = $i+1;
$item->setTitle($i);
echo $item->getTitle();
}
$collection->walk('save');
*/
/*
This shows how to load one value, change something and save it.
*/
/*
$object = Mage::getModel('<module>/<module>')->load(1);
$object->setTitle('This is a changed title');
$object->save();
*/
?>
NOTE: Uncomment anything that you
would like to use and this is just a starting point and some common methods for
you to try and pull the data out.
In this section I am utilizing the
built-in Grid Widgets and form capabilities to create a form to allow editing
and creating new items for your custom database.
Directory Additions
|
Here is the revised directory setup
due to the additions and changes we need for the backend module.
/app/code/local/<Namespace>/<Module>/
Adminhtml/
<Module>/
Edit/
Tab/
controllers/
Adminhtml/
etc/
Helper/
Model/
Mysql4/
<Module>/
sql/
<module>_setup/
Blocks
|
These control the setup and
appearance of your grids and the options that they display.
NOTE: Please note the fact that
Block comes before Adminhtml in the class declaration. In any of the Magento
modules in Adminhtml it is the opposite. For your module to work it has to be
Block_Adminhtml otherwise you will get a ‘Cannot redeclare module...’ error.
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>.php
class <Namespace>_<Module>_Block_Adminhtml_<Module> extends Mage_Adminhtml_Block_Widget_Grid_Container
{
public function __construct()
{
$this->_controller = 'adminhtml_<module>';
$this->_blockGroup = '<module>';
$this->_headerText = Mage::helper('<module>')->__('Item Manager');
$this->_addButtonLabel = Mage::helper('<module>')->__('Add Item');
parent::__construct();
}
}
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>/Edit.php
<?php
class <Namespace>_<Module>_Block_Adminhtml_<Module>_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
{
public function __construct()
{
parent::__construct();
$this->_objectId = 'id';
$this->_blockGroup = '<module>';
$this->_controller = 'adminhtml_<module>';
$this->_updateButton('save', 'label', Mage::helper('<module>')->__('Save Item'));
$this->_updateButton('delete', 'label', Mage::helper('<module>')->__('Delete Item'));
}
public function getHeaderText()
{
if( Mage::registry('<module>_data') && Mage::registry('<module>_data')->getId() ) {
return Mage::helper('<module>')->__("Edit Item '%s'", $this->htmlEscape(Mage::registry('<module>_data')->getTitle()));
} else {
return Mage::helper('<module>')->__('Add Item');
}
}
}
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>/Grid.php
<?php
class <Namespace>_<Module>_Block_Adminhtml_<Module>_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
public function __construct()
{
parent::__construct();
$this->setId('<module>Grid');
// This is the primary key of the database
$this->setDefaultSort('<module>_id');
$this->setDefaultDir('ASC');
$this->setSaveParametersInSession(true);
}
protected function _prepareCollection()
{
$collection = Mage::getModel('<module>/<module>')->getCollection();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn('<module>_id', array(
'header' => Mage::helper('<module>')->__('ID'),
'align' =>'right',
'width' => '50px',
'index' => '<module>_id',
));
$this->addColumn('title', array(
'header' => Mage::helper('<module>')->__('Title'),
'align' =>'left',
'index' => 'title',
));
/*
$this->addColumn('content', array(
'header' => Mage::helper('<module>')->__('Item Content'),
'width' => '150px',
'index' => 'content',
));
*/
$this->addColumn('created_time', array(
'header' => Mage::helper('<module>')->__('Creation Time'),
'align' => 'left',
'width' => '120px',
'type' => 'date',
'default' => '--',
'index' => 'created_time',
));
$this->addColumn('update_time', array(
'header' => Mage::helper('<module>')->__('Update Time'),
'align' => 'left',
'width' => '120px',
'type' => 'date',
'default' => '--',
'index' => 'update_time',
));
$this->addColumn('status', array(
'header' => Mage::helper('<module>')->__('Status'),
'align' => 'left',
'width' => '80px',
'index' => 'status',
'type' => 'options',
'options' => array(
1 => 'Active',
0 => 'Inactive',
),
));
return parent::_prepareColumns();
}
public function getRowUrl($row)
{
return $this->getUrl('*/*/edit', array('id' => $row->getId()));
}
}
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>/Edit/Form.php
<?php
class <Namespace>_<Module>_Block_Adminhtml_<Module>_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',
)
);
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
}
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>/Edit/Tabs.php
class <Namespace>_<Module>_Block_Adminhtml_<Module>_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs
{
public function __construct()
{
parent::__construct();
$this->setId('<module>_tabs');
$this->setDestElementId('edit_form');
$this->setTitle(Mage::helper('<module>')->__('News Information'));
}
protected function _beforeToHtml()
{
$this->addTab('form_section', array(
'label' => Mage::helper('<module>')->__('Item Information'),
'title' => Mage::helper('<module>')->__('Item Information'),
'content' => $this->getLayout()->createBlock('<module>/adminhtml_<module>_edit_tab_form')->toHtml(),
));
return parent::_beforeToHtml();
}
}
/app/code/local/<Namespace>/<Module>/Block/Adminhtml/<Module>/Edit/Tab/Form.php
class <Namespace>_<Module>_Block_Adminhtml_<Module>_Edit_Tab_Form extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
$form = new Varien_Data_Form();
$this->setForm($form);
$fieldset = $form->addFieldset('<module>_form', array('legend'=>Mage::helper('<module>')->__('Item information')));
$fieldset->addField('title', 'text', array(
'label' => Mage::helper('<module>')->__('Title'),
'class' => 'required-entry',
'required' => true,
'name' => 'title',
));
$fieldset->addField('status', 'select', array(
'label' => Mage::helper('<module>')->__('Status'),
'name' => 'status',
'values' => array(
array(
'value' => 1,
'label' => Mage::helper('<module>')->__('Active'),
),
array(
'value' => 0,
'label' => Mage::helper('<module>')->__('Inactive'),
),
),
));
$fieldset->addField('content', 'editor', array(
'name' => 'content',
'label' => Mage::helper('<module>')->__('Content'),
'title' => Mage::helper('<module>')->__('Content'),
'style' => 'width:98%; height:400px;',
'wysiwyg' => false,
'required' => true,
));
if ( Mage::getSingleton('adminhtml/session')->get<Module>Data() )
{
$form->setValues(Mage::getSingleton('adminhtml/session')->get<Module>Data());
Mage::getSingleton('adminhtml/session')->set<Module>Data(null);
} elseif ( Mage::registry('<module>_data') ) {
$form->setValues(Mage::registry('<module>_data')->getData());
}
return parent::_prepareForm();
}
}
Controller
|
/app/code/local/<Namespace>/<Module>/controllers/Adminhtml/<Module>Controller.php
NOTE: you need to manually add line
16, which is currently missing in this file.
As per suggestion from mkd at page http://www.magentocommerce.com/boards/viewthread/11228/
class <Namespace>_<Module>_Adminhtml_<Module>Controller extends Mage_Adminhtml_Controller_action
{
protected function _initAction()
{
$this->loadLayout()
->_setActiveMenu('<module>/items')
->_addBreadcrumb(Mage::helper('adminhtml')->__('Items Manager'), Mage::helper('adminhtml')->__('Item Manager'));
return $this;
}
public function indexAction() {
$this->_initAction();
$this->_addContent($this->getLayout()->createBlock('<module>/adminhtml_<module>'));
$this->renderLayout();
}
public function editAction()
{
$<module>Id = $this->getRequest()->getParam('id');
$<module>Model = Mage::getModel('<module>/<module>')->load($<module>Id);
if ($<module>Model->getId() || $<module>Id == 0) {
Mage::register('<module>_data', $<module>Model);
$this->loadLayout();
$this->_setActiveMenu('<module>/items');
$this->_addBreadcrumb(Mage::helper('adminhtml')->__('Item Manager'), Mage::helper('adminhtml')->__('Item Manager'));
$this->_addBreadcrumb(Mage::helper('adminhtml')->__('Item News'), Mage::helper('adminhtml')->__('Item News'));
$this->getLayout()->getBlock('head')->setCanLoadExtJs(true);
$this->_addContent($this->getLayout()->createBlock('<module>/adminhtml_<module>_edit'))
->_addLeft($this->getLayout()->createBlock('<module>/adminhtml_<module>_edit_tabs'));
$this->renderLayout();
} else {
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('<module>')->__('Item does not exist'));
$this->_redirect('*/*/');
}
}
public function newAction()
{
$this->_forward('edit');
}
public function saveAction()
{
if ( $this->getRequest()->getPost() ) {
try {
$postData = $this->getRequest()->getPost();
$<module>Model = Mage::getModel('<module>/<module>');
$<module>Model->setId($this->getRequest()->getParam('id'))
->setTitle($postData['title'])
->setContent($postData['content'])
->setStatus($postData['status'])
->save();
Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('Item was successfully saved'));
Mage::getSingleton('adminhtml/session')->set<Module>Data(false);
$this->_redirect('*/*/');
return;
} catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
Mage::getSingleton('adminhtml/session')->set<Module>Data($this->getRequest()->getPost());
$this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id')));
return;
}
}
$this->_redirect('*/*/');
}
public function deleteAction()
{
if( $this->getRequest()->getParam('id') > 0 ) {
try {
$<module>Model = Mage::getModel('<module>/<module>');
$<module>Model->setId($this->getRequest()->getParam('id'))
->delete();
Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('Item was successfully deleted'));
$this->_redirect('*/*/');
} catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
$this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id')));
}
}
$this->_redirect('*/*/');
}
}
XML Configuration Changes
|
/app/code/local/<Namespace>/<Module>/etc/config.xml
<?xml version="1.0"?>
<config>
<modules>
<[Namespace]_[Module]>
<version>0.1.0</version>
</[Namespace]_[Module]>
</modules>
<frontend>
<routers>
<[module]>
<use>standard</use>
<args>
<module>[Namespace]_[Module]</module>
<frontName>[module]</frontName>
</args>
</[module]>
</routers>
<layout>
<updates>
<[module]>
<file>[module].xml</file>
</[module]>
</updates>
</layout>
</frontend>
<admin>
<routers>
<[module]>
<use>admin</use>
<args>
<module>[Namespace]_[Module]</module>
<frontName>[module]</frontName>
</args>
</[module]>
</routers>
</admin>
<adminhtml>
<menu>
<[module] module="[module]">
<title>[Module]</title>
<sort_order>71</sort_order>
<children>
<items module="[module]">
<title>Manage Items</title>
<sort_order>0</sort_order>
<action>[module]/adminhtml_[module]</action>
</items>
</children>
</[module]>
</menu>
<acl>
<resources>
<all>
<title>Allow Everything</title>
</all>
<admin>
<children>
<[module]>
<title>[Module] Module</title>
<sort_order>200</sort_order>
</[module]>
</children>
</admin>
</resources>
</acl>
<layout>
<updates>
<[module]>
<file>[module].xml</file>
</[module]>
</updates>
</layout>
</adminhtml>
<global>
<models>
<[module]>
<class>[Namespace]_[Module]_Model</class>
<resourceModel>[module]_mysql4</resourceModel>
</[module]>
<[module]_mysql4>
<class>[Namespace]_[Module]_Model_Mysql4</class>
<entities>
<[module]>
<table>[module]</table>
</[module]>
</entities>
</[module]_mysql4>
</models>
<resources>
<[module]_setup>
<setup>
<module>[Namespace]_[Module]</module>
</setup>
<connection>
<use>core_setup</use>
</connection>
</[module]_setup>
<[module]_write>
<connection>
<use>core_write</use>
</connection>
</[module]_write>
<[module]_read>
<connection>
<use>core_read</use>
</connection>
</[module]_read>
</resources>
<blocks>
<[module]>
<class>[Namespace]_[Module]_Block</class>
</[module]>
</blocks>
<helpers>
<[module]>
<class>[Namespace]_[Module]_Helper</class>
</[module]>
</helpers>
</global>
</config>
XML Layout
|
/app/design/adminhtml/<interface>/<theme>/layout/<module>.xml
<?xml version="1.0"?>
<layout version="0.1.0">
<[module]_adminhtml_[module]_index>
<reference name="content">
<block type="[module]/adminhtml_[module]" name="[module]" />
</reference>
</[module]_adminhtml_[module]_index>
</layout>