Howto: Repackageable custom extension development in Magento

blahboatsInternet and Web Development

Dec 13, 2013 (3 years and 8 months ago)

299 views

Mar 31, 2009

Howto: Repackageable custom extension development in

Magento

Author: gaweee | Filed under:
development
,
howto


Magento needs no explaning, but it

does need a shitload of proper developers education.
So much that our team spent a good amount of time trying to learn how to develop on the
damn thing. So heres a synthesized description of our efforts. We’re breaking up this
HOWTO into a few segments in
to increasing orders of complexity and integration.

Download the source for this entire series here!

Let us begin …

Setup Module Environment and Helloworld

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 NE
W company name is
Savant Degrees
!

and our module shall be called…..
Twits

(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 mod
ule, you only need a
config

file, a
controller

and a
block
. Ergo…

2.

app/

3.


etc/

4.


modules/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your company name might be
)

9.


Tw
its/
(
Or whatever your module name might be
)

10.


Block/

11.


-

helloWorld.php

12.


controllers/

13.


-

indexController.php

14.


etc/


-

config.xml

15.

Create your
config.xml

file. Thats the crux of the whole module.

16.

<?
xml

version
=
"1.0"
?>

17.

<config>

18.


<modules>

19.


<SavantDegrees_Twits>

20.


<version>
0.1.0
</version>

21.


</SavantDegrees_Twits>

22.


</modules>

23.


<global>

24.


<blocks>

25.


<twits>

26.


<class>
SavantDegrees_Twits_Block
</
class>

27.


</twits>

28.


</blocks>

29.


</global>

30.


<frontend>

31.


<routers>

32.


<SavantDegrees_Twits>

33.


<use>
standard
</use>

34.


<args>

35.


<module>
SavantDegrees_Twits
</module>

36.



<frontName>
twits
</frontName>

37.


</args>

38.


</SavantDegrees_Twits>

39.


</routers>

40.


</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 mo
dule can be access. The
current code allows the block to be access via
https://127.0.0.1/magento/index.php/twits


41.

Create your
IndexController.php

file. Its as simple as:

42.

class

SavantDeg
rees_Twits_IndexController
extends

Mage_Core_Controller_Front_Action

43.

{

44.



45.


public

function

indexAction
()

46.


{

47.


$this
-
>
loadLayout
()
;

48.


$this
-
>
getLayout
()

49.



-
>
getBlock
(
'content'
)
-
>
append
(

50.




$this
-
>
getLayout
()
-
>
createBlock
(
'twits/helloWorld'
)

51.



)
;

52.



$this
-
>
renderLayout
()
;

53.


}

}

More Explanation: The
twits/index

refers to the index block inside the Twits
module, which is defined in the config file above. We’re trying to put its contents
inside the layout.

54.

Lastly, we’ll create the
Index.php

Block:


55.

class

SavantDegrees_Twits_Block_HelloWorld
extends

Mage_Core_Block_Template

56.

{

57.


protected
function

_toHtml
()

58.


{

59.



return

'Hello world'
;

60.


}

}

You DO understand SOME PHP don’t you?

61.

Finally, we activate the module by creating the file
SavantDegrees_All.xml

file

62.

<?xml

version
=
"1.0"
?>

63.

<config>

64.


<modules>

65.


<SavantDegrees_Twits>

66.


<active>
true
</active>

67.


<codePool>
local
</codePool>

68.


</SavantDegrees_Twits>

69.


</modules>

</config>

70.

Wake up the Magento installer by logging into
the
admin

backend,
System >
Configuration > Advanced > 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/inde
x.php/twits

to see your well deserved
Hello World!

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 2


Admin Controller

Author: gaweee | Filed under:
d
evelopment
,
howto


Download the source for this entire series here!

Create the administrative complement

Next we’ll create the administration controller and configuration to achieve the
https://12
7.0.0.1/magento/index.php/twits/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

2.

app/

3.


etc/

4.


module
s/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your company name might be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


-

HelloWorld.ph
p

12.


controllers/

13.


-

AdminController.php

14.


-

IndexController.php

15.


etc/

16.


-

config.xml

17.


Helper/


-

Data.php

18.

Then we obviously create the
adminController.php

We’ll make the admin page reuse t
he same
Hello world block

that we’ve
created in Phase 1.

19.

<?php

20.

class

SavantDegrees_Twits_AdminController
extends

Mage_Adminhtml_Controller_Action

21.

{

22.


public

function

indexAction
()

23.


{

24.



$this
-
>
loadLayout
()

25.




-
>
_addContent
(
$this
-
>
getLayout
()
-
>
createBl
ock
(
'twits/helloWorld'
))

26.




-
>
renderLayout
()
;

27.


}

}

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/twits/admin

to see the page in action. Again,
this produces our ubiquitous Hello world. You’d probably figured out by now that
/twits

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/magen
to/index.php/twits/admin/index

to see the
indexAction

method being called. Try it with other action names!

28.

Next we’ll do the
config.xml

magic that allows us to use
https://127.0.0
.1/magento/index.php/admin/twits

for the administration.

29.

<?xml

version
=
"1.0"
?>

30.

<config>

31.


<modules>

32.


<SavantDegrees_Twits>

33.


<version>
0.1.0
</version>

34.


</SavantDegrees_Twits>

35.


</modules>

36.


<global>

37.


<helpers>

38.



<twits><class>
SavantDegrees_Twits_Helper
</class></twits>

39.


</helpers>

40.


<blocks>

41.


<twits>

42.


<class>
SavantDegrees_Twits_Block
</class>

43.


</twits>

44.


</blocks>

45.


</global>

46.


<adminhtml>

47.


<menu>

48.



<twits

translate
=
"title"

module
=
"twits"
>

49.


<title>
Twits
</title>

50.


<sort_order>
100
</sort_order>

51.


<action>
twits/admin
</action>

52.


</twits>

53.


</menu>

54.


</adminhtml>

55.


<frontend>

56.


<ro
uters>

57.


<SavantDegrees_Twits>

58.


<use>
standard
</use>

59.


<args>

60.


<module>
SavantDegrees_Twits
</module>

61.


<frontName>
twits
</frontName>

62.


</args>

63.


</SavantDegree
s_Twits>

64.


</routers>

65.


</frontend>

</config>

What’ changed?

o

The
global > helper > [module]
fragment defines the helper class
that will help our future translation. We’ll be seeing the code for that
shortly.

o

The
adminhtml > menu > [module]
fragment

allows us to create the
menu to access the twits/admin page.
This is vital for version 1.3
onwards as the menu generates the necessary url key to access the
page.

66.

And finally the helper
Data.php

file

67.

<?php

68.

class

SavantDegrees_Twits_Helper_Data
extends

Ma
ge_Core_Helper_Abstract

69.

{

}

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_Twits_Helper_Data class.
Which obviously doesnt exist! You could use the helper from existing Mag
ento
classes, but then you could use ALOT of stuff from Magento classes. And if
you’re reading this tutorial, you’re obviously arent ready.

70.

When you’re done, go flush the cache by accessing
System > Cache
Management > Flush Magento Cache

in the admin syst
em. Then check out the
new Twit menu item in the administration menu bar!

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 3


Database

Author: gaweee | Filed under:
de
velopment
,
howto


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

2.

app/

3.


etc/

4.


modules/

5.


-

Sava
ntDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your company name might be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


-

HelloWorld.php

12.


con
trollers/

13.


-

AdminController.php

14.


-

IndexController.php

15.


etc/

16.


-

config.xml

17.


Helper/

18.


-

Data.php

19.


sql/

20.


twits_setup/

21.


-

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.

22.

Next up, updating the
config.xml

again. This time to add the setup declarations.

23.

<?xml

version
=
"1.0"
?>

24.

<config>

25.


<modules>

26.


<Sa
vantDegrees_Twits>

27.


<version>
0.2.0
</version>

28.


</SavantDegrees_Twits>

29.


</modules>

30.


<global>

31.


<blocks>

32.


<twits>

33.


<class>
SavantDegrees_Twits_Block
</class>

34.


</twits>

35.


</blocks>

36.


<helpe
rs>

37.


<twits><class>
SavantDegrees_Twits_Helper
</class></twits>

38.


</helpers>

39.


<resources>

40.


<twits_setup>

41.


<setup>

42.


<module>
SavantDegrees_Twits
</module>

43.


</setup>

44.



<connection>

45.


<use>
core_setup
</use>

46.


</connection>

47.


</twits_setup>

48.


<twits_write>

49.


<connection>

50.


<use>
core_write
</use>

51.


</connection>

52.


<
/twits_write>

53.


<twits_read>

54.


<connection>

55.


<use>
core_read
</use>

56.


</connection>

57.


</twits_read>

58.


</resources>

59.


</global>

60.


<adminhtml>

61.


<menu>

62.


<twits

transl
ate
=
"title"

module
=
"twits"
>

63.


<title>
Twits
</title>

64.


<sort_order>
100
</sort_order>

65.


<action>
twits/admin
</action>

66.


</twits>

67.


</menu>

68.


</adminhtml>

69.


<frontend>

70.


<routers>

71.


<S
avantDegrees_Twits>

72.


<use>
standard
</use>

73.


<args>

74.


<module>
SavantDegrees_Twits
</module>

75.


<frontName>
twits
</frontName>

76.


</args>

77.


</SavantDegrees_Twits>

78.


</ro
uters>

79.


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

80.

Lastly the install script itself:
mysql4
-
install
-
0.2.0.php


81.

<?php

82.

$installer

=

$this
;

83.

$installer
-
>
startSetup
()
;

84.



85.

$installer
-
>
run
(
"

86.

CREATE TABLE {
$this
-
>getTable
('tips')} (

87.


`tip_id` int(10) unsigned

NOT NULL auto_increment,

88.


`title` varchar(250) NOT NULL default '',

89.


`author` varchar(250) default NULL,

90.


`contents` text,

91.


PRIMARY KEY (`tip_id`)

92.

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

93.


"
)
;

94.



$installer
-
>
endSetup
()
;

95.

Copy the same file contents to
my
sql4
-
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)

96.

Once again, wake up the Magento installer b
y logging into the
admin

backend,
System > Configuration > Advanced
. Click on
Save Config
. Clear your
cache at
/var/cache

too just to be sure.

97.

Check to see if your table is created.

98.

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!

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 4


Model

Author: gaweee | Filed under:
development
,
howto


Download the source for this entire series here!

Prepare the model

1.

Back to the file structure drawing board

2.

app/

3.


etc/

4.


modules/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your company name mig
ht be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


-

HelloWorld.php

12.


controllers/

13.


-

AdminController.php

14.


-

IndexController.php

15.


etc/

16.


-

config.xml

17.


Helper/

18.


-

Data.php

19.


Model/

20.


-

Tip.php

21.


Mysql4/

22.


-

Tip.php

23.


Tip/

24.


-

Collection.php

25.


sql/

26.


Twits_setup/

27.


-

mysql4
-
install
-
0.2.0.php


-

mysql4
-
upgrade
-
0.1.0
-
0.2.0.php

Notice the new folder
Model

and the associated files inside. Lets clarify:

Model/Tip.php

is the logical model that holds code regarding business logic and
relations.

Model/Mysql4/Tip.php

is the data implementation model that
associates the
model with the table and its primary key.

Model/Mysql4/Tip/Collection.php

is the data implementation model of a
collection of
Tip
. Similar to a
recordset/resultset

in
.Net/Java
. This will be
used with a Grid later.

28.

Even more
config.xml

magi
c. Yay! More hairloss!

29.

<?xml

version
=
"1.0"
?>

30.

<config>

31.


<modules>

32.


<SavantDegrees_Twits>

33.


<version>
0.2.0
</version>

34.


</SavantDegrees_Twits>

35.


</modules>

36.


<global>

37.


<models>

38.


<twits>

39.


<class>
S
avantDegrees_Twits_Model
</class>

40.


<resourceModel>
twits_mysql4
</resourceModel>

41.


</twits>

42.


<twits_mysql4>

43.


<class>
SavantDegrees_Twits_Model_Mysql4
</class>

44.


<entities>

45.


<tip
>

46.


<table>
tips
</table>

47.


</tip>

48.


</entities>

49.


</twits_mysql4>

50.


</models>

51.


<blocks>

52.


<twits>

53.


<class>
SavantDegrees_Twits_Block
</class>

54.


</twits>

55.



</blocks>

56.


<helpers>

57.


<twits><class>
SavantDegrees_Twits_Helper
</class></twits>

58.


</helpers>

59.


<resources>

60.


<twits_setup>

61.


<setup>

62.


<module>
SavantDegrees_Twits
</module>

63.



</setup>

64.


<connection>

65.


<use>
core_setup
</use>

66.


</connection>

67.


</twits_setup>

68.


<twits_write>

69.


<connection>

70.


<use>
core_write
</use>

71.



</connection>

72.


</twits_write>

73.


<twits_read>

74.


<connection>

75.


<use>
core_read
</use>

76.


</connection>

77.


</twits_read>

78.


</resources>

79.


</global>

80.


<adminhtml>

81.


<menu>

82.


<twits

translate
=
"title"

module
=
"twits"
>

83.


<title>
Twits
</title>

84.


<sort_order>
100
</sort_order>

85.


<action>
twits/admin
</action>

86.


</twits>

87.


</menu>

88.


</adminhtml>

89.


<frontend>

90.


<routers>

91.


<SavantDegrees_Twits>

92.


<use>
standard
</use>

93.


<args>

94.


<module>
SavantDegrees_Twits
</module>

95.


<frontName>
twits
</frontName>

96.


</args>

97.


</S
avantDegrees_Twits>

98.


</routers>

99.


</frontend>

</config>

The
global > models

fragment define the
tips

table, the
tip model

and its
resourceModel

that we will be using shortly.

100.

Now for the
model/Tip.php

code:

101.

<?php

102.

class

SavantDegrees_Twits_Model_
Tip
extends

Mage_Core_Model_Abstract

103.

{

104.



105.


protected
function

_construct
()

106.


{

107.


$this
-
>
_init
(
'twits/tip'
)
;

108.


}

}

Whats the point you ask!? The code does nothing but binds the model to the Twits
namespace under the model twits. In Java that lin
e would have been
package
Twits;
. *i assume/think/foretold*

(quickly moving on…)

109.

Then
Model/Mysql4/Tip.php


110.

<?php

111.

class

SavantDegrees_Twits_Model_Mysql4_Tip
extends

Mage_Core_Model_Mysql4_Abstract

112.

{

113.


protected
function

_construct
()

114.


{

115.


$this
-
>
_init
(
'twits/tip'
,

'tip_id'
)
;

116.


}

}

The code above tells Magento to hunt for the twits application’s twits table in the
configuration. Yes i know the names are confusing now. Lets say instead of
twits/tip

it is
twits/foo
. In that case the system will fi
nd the configuration file
associated with twits (which is our
app/code/local/SavantDegrees/Twits/etc/config.xml), and look under
config >
global > models > twits_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
.

117.

Lastly,
Model/Mysql4/Tip/Collection.php


118.

<?php

119.

class

SavantDegrees_Twits_Model_Mysql4_Tip_Collection
extends

Va
rien_Data_Collection_Db

120.

{

121.


protected
$_tipTable
;

122.



123.


public

function

__construct
()

124.


{

125.


$resources

=

Mage
::
getSingleton
(
'core/resource'
)
;

126.


parent
::
__construct
(
$resources
-
>
getConnection
(
'twits_read'
))
;

127.


$this
-
>
_tipTable
=

$resourc
es
-
>
getTableName
(
'twits/tip'
)
;

128.



129.


$this
-
>
_select
-
>
from
(

130.




array
(
'tip'
=>
$this
-
>
_tipTable
)
,

131.






array
(
'*'
)

132.




)
;

133.


$this
-
>
setItemObjectClass
(
Mage
::
getConfig
()
-
>
getModelClassName
(
'twits/tip'
))
;

134.


}

}

Basically the model fi
les 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 namespac
e to actually declaring the primary key
of the table)

135.

Viola. Models are ready

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 5


CRUD


Retrieve

Author: gaweee | Filed under:
development
,
howto


Download the source for this entire series here!

CRUD


Retrieve

1.

Lets add a new block to the file structure

2.

app/

3.


etc/

4.


modules/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.



SavantDegrees/
(
Or what ever your company name might be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


Admin/

12.


-

Main.php

13.


Main/

14.


-

Grid.php

15.


-

HelloWorld.php

16.



controllers/

17.


-

AdminController.php

18.


-

IndexController.php

19.


etc/

20.


-

config.xml

21.


Helper/

22.


-

Data.php

23.


Model/

24.


-

Tip.php

25.


Mysql4/

26.


-

Tip.php

27.



Tip/

28.


-

Collection.php

29.


sql/

30.


twits_setup/

31.


-

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’l
l
have a Main.php file that contains the
Grid Container
. Why do we need a grid
container? To put the title and
Create

button!

32.

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_Wi
dget_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)'
)
;


}

}

33.

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

34.

The
Grid.php

itself

1

2

3

4

5

6

7

8

9

10

11

12

<?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'
;


}



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


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'

=>

'ti
tle'
,


'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
(

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


'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
()
,


))
;


}

}

35.

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 ta
lking 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 al
l 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).

36.

Now lets make our
adminController.php

us
e that block instead of Hello world.

1

2

class

SavantDegrees_Twits_AdminController
extends

Mage_Adminhtml_Controller_Action

3

4

5

6

7

8

9

{


public

function

indexAction
()


{



$this
-
>
loadLayout
()




-
>
_addContent
(
$this
-
>
getLayout
()
-
>
createBlock
(
'twi
ts/admin_main'
))




-
>
renderLayout
()
;


}

}

37.

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.

38.

Viola! Your very very very hard earned grid!


Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 6


CRUD


Delete

Author: gaweee | Filed under:
development
,
howto


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

<?p
hp

class

SavantDegrees_Twits_AdminController
extends

Mage_Adminhtml_Controller_Action

{


public

function

indexAction
()


{



$this
-
>
loadLayout
()

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27




-
>
_addContent
(
$this
-
>
getLayout
()
-
>
createBlock
(
'twits/admin_main'
))




-
>
renderLayout
()
;


}




pub
lic

function

deleteAction
()


{


$tipId

=

$this
-
>
getRequest
()
-
>
getParam
(
'id'
,

false
)
;




try
{


Mage
::
getModel
(
'twits/tip'
)
-
>
setId
(
$tipId
)
-
>
delete
()
;


Mage
::
getSingleton
(
'adminhtml/session'
)
-
>
addSuccess
(
Mage
::
helper
(
't
wits'
)
-
>
__
(
'Tip successfully
deleted'
))
;


$this
-
>
getResponse
()
-
>
setRedirect
(
$this
-
>
getUrl
(
'*/*/'
))
;




return
;


}

catch
(
Exception
$e
){


Mage
::
getSingleton
(
'adminhtml/session'
)
-
>
addError
(
$e
-
>
getMessage
())
;


}




$this
-
>
_redirectReferer
()
;


}

}

» Almost there …

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 7


CRUD


Create

Author: gaweee | Filed under:
d
evelopment
,
howto


Download the source for this entire series here!

CRUD


Create

1.

More file structure goodness:

2.

app/

3.


etc/

4.


modules/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your co
mpany name might be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


Admin/

12.


-

Main.php

13.


Main/

14.


-

Grid.php

15.


-

New.php

16.


New/

17.


-

Form.php

18.



-

HelloWorld.php

19.


controllers/

20.


-

AdminController.php

21.


-

IndexController.php

22.


etc/

23.


-

config.xml

24.


Helper/

25.


-

Data.php

26.


Model/

27.


-

Tip.php

28.


Mysql4
/

29.


-

Tip.php

30.


Tip/

31.


-

Collection.php

32.


sql/

33.


twits_setup/

34.


-

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 a
nd
Form


35.

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'
)
;


}

}

36.

Then the
New/Form.php

code

1

2

3

4

<?php

class

SavantDegrees_Twits_Block_Admin_New_Form
extends

Mage_Adminhtml_Block_Widget_Form

{

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


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
)
;


}

}

37.

Jus
t 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).


38.

Then lastly the
adminController

with its
newAction

and
postAction

to load
and save the form respectively

1

2

3

4

5

<?ph
p

class

SavantDegrees_Twits_AdminController
extends

Mage_Adminhtml_Controller_Action

{


public

function

indexAction
()

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


{



$this
-
>
loadLayout
()




-
>
_addContent
(
$this
-
>
getLayout
()
-
>
createBlock
(
'twits/admin_main'
))




-
>
renderLayout
()
;


}




public

fun
ction

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
;


}

}

39.

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 lear
n 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 f
rom the
Grid
!

Mar 31, 2009

Howto: Repackageable custom extension development in Magento


Part 8


CRUD


Update

Author: gaweee | Filed under:
development
,
howto


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:

2.

app/

3.


etc/

4.


modules/

5.


-

SavantDegrees_All.xml
(
Or what ever your company name
might be
)

6.


code/

7.


local/

8.


SavantDegrees/
(
Or what ever your company name might be
)

9.


Twits/
(
Or whatever your module name might be
)

10.


Block/

11.


Admin/

12.


-

Main.php

13.


Mai
n/

14.


-

Grid.php

15.


-

Edit.php

16.


Edit/

17.


-

Form.php

18.


-

New.php

19.


New/

20.


-

Form.php

21.


-

HelloWorld.php

22.


controllers/

23.


-

AdminController.ph
p

24.


-

IndexController.php

25.


etc/

26.


-

config.xml

27.


Helper/

28.


-

Data.php

29.


Model/

30.


-

Tip.php

31.


Mysql4/

32.


-

Tip.php

33.


Tip/

34.


-

Collection.php

35.



sql/

36.


twits_setup/

37.


-

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


38.

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
::
registe
r
(
'frozen_tip'
,

$tip
)
;


}


}




public

function

getHeaderText
()


{


return

Mage
::
helper
(
'twits'
)
-
>
__
(
"Edit Tip'
%s
'"
,

$this
-
>
htmlEscape
(
Mage
::
registry
(
'frozen_tip'
)
-
>
getName
()))
;


}

}

39.

The additional code loads the right
Tip

data b
ased 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


40.

Then the accompa
nying
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
funct
ion

_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
)
;


}

}

41.

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

2
2

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

<?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
-
>
g
etUrl
(
'*/*/'
))
;




return
;


}

catch
(
Exception
$e
){


Mage
::
getSingleton
(
'adminhtml/session'
)
-
>
addError
(
$e
-
>
getMessage
())
;


}




$this
-
>
_redirectReferer
()
;


}




public

function

newAction
()


{


$thi
s
-
>
loadLayout
()


-
>
_addContent
(
$this
-
>
getLayout
()
-
>
createBlock
(
'twits/admin_new'
))


-
>
renderLayout
()
;


}




public

function

postAction
()


{


if

(
$data

=

$this
-
>
getRequest
()
-
>
getPost
())

{


$tip

=

Mage
::
getModel
(
'twit
s/tip'
)
-
>
setData
(
$data
)
;


try
{


$tip
-
>
save
()
;


Mage
::
getSingleton
(
'adminhtml/session'
)
-
>
addSuccess
(
Mage
::
helper
(
'twits'
)
-
>
__
(
'Tip successfully
saved'
))
;


$this
-
>
getResponse
()
-
>
setRedirect
(
$this
-
>
getU
rl
(
'*/*/'
))
;

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76


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
()


{


$tipI
d

=

$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
()
;


}

}

42.

Ok i admit the code is a tad inefficient. We have 2 separate functions that both
does save. In fact,
postAction

a
nd
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.


Jun 22, 2009

Howto: Repackageable custom extension development in Magento


Part 9


Frontend


List

A
uthor: gaweee | Filed under:
development
,
howto


Download the source for this entire series he
re!

Frontend


List

Let us revisit our frontend controller. Surely by now you’ve gotten a bett
er 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

2.

app/

3.


design/

4.


frontend/

5.


base/

6.


default/

7.


template/

8.


twits/

9.


-

tip_list.phtml