A model represents the data for the better part, and to a certain extent a business logic of your application. Models in Magento take the Object Relational Mapping (ORM) approach, thus having the developer to strictly deal with objects while their data is then automatically persisted to the database. If you are hearing about ORM for the first time, please take some time to familiarize yourself with the concept; you can find good starting material about it at http://en.wikipedia.org/wiki/Object-relational_mapping. Theoretically, you could write and execute raw SQL queries in Magento. However, doing so is not advised, especially if you plan on distributing your extensions.
There are two types of models in Magento:
Basic Data Model: This is a simpler model type, sort of like an Active Record pattern-based model. If you're hearing about Active Record for the first time, please take some time to familiarize yourself with the concept; you can find good starting material about it at https://en.wikipedia.org/wiki/Active_record_pattern.
EAV (Entity-Attribute-Value) Data Model: This is a complex model type, which enables you to dynamically create new attributes on an entity. As EAV Data Model is significantly more complex than Basic Data Model and Basic Data Model will suffice for most of the time, we will focus on Basic Data Model and everything important surrounding it. Each data model you plan to persist to the database, that means models that present an entity, needs to have four files in order for it to work fully:
The model file: This extends the
Mage_Core_Model_Abstract
class. This represents single entity, its properties (fields), and possible business logic within it.The model resource file: This extends the
Mage_Core_Model_Resource_Db_Abstract
class. This is your connection to the database; think of it as the thing that saves your entity properties (fields) database.The model collection file: This extends the
Mage_Core_Model_Resource_Db_Collection_Abstract
class. This is your collection of several entities, a collection that can be filtered, sorted, and manipulated.The installation script file: In its simplest definition this is the PHP file through which you, in an object-oriented way, create your database table(s).
For our example, we will go ahead and create our extensions
User
model. The first thing we need to do is to set up its configuration within theconfig.xml
file as follows:<?xml version="1.0"?> <config> <global> <!-- … other elements ... --> <models> <foggyline_happyhour> <class>Foggyline_HappyHour_Model</class> <resourceModel>foggyline_happyhour_resource</resourceModel> </foggyline_happyhour> <foggyline_happyhour_resource> <class>Foggyline_HappyHour_Model_Resource</class> <entities> <user> <table>foggyline_happyhour_user</table> </user> </entities> </foggyline_happyhour_resource> </models> <resources> <foggyline_happyhour_setup> <setup> <model>Foggyline_HappyHour</model> </setup> </foggyline_happyhour_setup> </resources> <!-- … other elements ... --> </global> </config>
The amount of new elements added to XML might look a bit discouraging, try not to get frightened by it. Let's break it down:
The element
foggyline_happyhour
contains our class group model definition, which actually tells Magento that our Model PHP class files can be found under our extensions directoryapp/code/community/Foggyline/HappyHour/Model/
. Further, thefoggyline_happyhour
element contains the resourceModel element whose value points further to the elementfoggyline_happyhour_resource
.The element
foggyline_happyhour_resource
contains our class group model resource definition, which actually tells Magento that our Model Resource PHP class files can be found under our extensions directoryapp/code/community/Foggyline/HappyHour/Model/Resource/
. Further, thefoggyline_happyhour_resource
element contains the entities element that is a list of all our entities and their mapped database table names.The element
foggyline_happyhour_setup
contains the setup definition for our extension. There is a lot more you can define here, which is not visible in our example due to simplicity. For example, we could have defined completely different read / write database connections here, specific to our extension. The most important thing to keep in mind here, however, is the following: the element namefoggyline_happyhour_setup
must match the folder name for your installation scriptapp/code/community/Foggyline/HappyHour/sql/foggyline_happyhour_setup/
.
Now let us create the four files required for our extensions model entity to work fully.
First we will create a model file app/code/community/Foggyline/HappyHour/Model/User.php
with the following content:
<?php class Foggyline_HappyHour_Model_User extends Mage_Core_Model_Abstract { protected $_eventPrefix = 'foggyline_happyhour_user'; protected $_eventObject = 'user'; protected function _construct() { $this->_init('foggyline_happyhour/user'); } }
All basic data models, such as our Foggyline_HappyHour_Model_User
, should extend the Mage_Core_Model_Abstract
class. This abstract class forces you to implement a single method named _construct
. Please note that this is not PHP's constructor __construct
.
The _construct
method should call the extending class' _init
method with the same identifying URI you will be using in the Mage::getModel
method call. Also, note the class-protected properties $_eventPrefix
and $_eventObject
. It is highly recommended, although not required, for you to define these properties. Values of both the properties can be freely assigned; however, you should follow your extension-naming scheme here as shown earlier.
Once we get to the Magento event/observer system later in the chapters, the meaning of these properties and how they make your code extendible by third-party developers will become more clear.
Every model has its own resource class. When a model in Magento needs to talk to the database, Magento will make the following method call to get the model resource Mage::getResourceModel('class_group/modelname');
. Without resource classes, models would not be able to write to the database. Having that in mind, we create the model resource file app/code/community/Foggyline/HappyHour/Model/Resource/User.php
with the following content:
<?php class Foggyline_HappyHour_Model_Resource_User extends Mage_Core_Model_Resource_Db_Abstract { protected function _construct() { $this->_init('foggyline_happyhour/user', 'user_id'); } }
Again, we have the same pattern: the construct method should call the extending class' init
method with the same identifying URI, with a slight exception of the existing second parameter in this case, which matches the primary key column name in the database. So in this case, the string user_id
matches the primary key column name in the database.
Finally, we address the model collection file. As Magento does not like juggling its model objects through plain PHP arrays, it defines a unique collection object associated with each model. Collection objects implement the PHP IteratorAggregate and Countable interfaces, which means they can be passed to the count function and used for each constructs.
We create the model collection file app/code/community/Foggyline/HappyHour/Model/Resource/User/Collection.php
with the following content:
<?php class Foggyline_HappyHour_Model_Resource_User_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { public function _construct() { $this->_init('foggyline_happyhour/user'); } }
Just as we did with our other classes we define the construct method, which calls the extending class' init
method with the same identifying URI.
Finally, we create an installation script file app/code/community/Foggyline/HappyHour/sql/foggyline_happyhour_setup/install-1.0.0.0.php
with the following content:
<?php /* @var $installer Mage_Core_Model_Resource_Setup */ $installer = $this; $installer->startSetup(); $table = $installer->getConnection() ->newTable($installer->getTable('foggyline_happyhour/user')) ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true, ), 'Id') ->addColumn('firstname', Varien_Db_Ddl_Table::TYPE_VARCHAR, null, array( 'nullable' => false, ), 'User first name') ->addColumn('lastname', Varien_Db_Ddl_Table::TYPE_VARCHAR, null, array( 'nullable' => false, ), 'User last name') ->setComment('Foggyline_HappyHour User Entity'); $installer->getConnection()->createTable($table); $installer->endSetup();
There is one thing we need to pay special attention to here, the naming of the install-1.0.0.0.php
file. The number 1.0.0.0
must be equal to the numbers placed under the version element value, or else Magento won't trigger your installation script.
Ever since version 1.6, Magento (in theory) supports more database backends than only MySQL. Thus, technically, the meaning of code within this install script may vary from database to database depending on the implementation.
Given that MySQL is still the default and far more dominant database backend for Magento, it is worth noting what actually goes on behind this installation script. It starts by calling $installer->startSetup()
, which internally sets SQL_MODE
to NO_AUTO_VALUE_ON_ZERO
, and FOREIGN_KEY_CHECKS
to 0
. The call to $installer->startSetup()
, on the other hand restores the two mentioned values to their previous states. The rest of the code that lies in between is responsible for the actual table definition and creation.
In our preceding example, we defined a table that will be named foggyline_happyhour_user
, and three columns named user_id
, firstname
, and lastname
.
These four files conclude our requirement for a fully persistent entity model. In order to check if everything is functioning, load any Magento URL in the browser and then take a look at the database. If the extension is installed correctly, there should be two changes to the database:
The table
core_resource
should contain an entry with the column code valuefoggyline_happyhour_setup
and column version value1.0.0.0
.The table
foggyline_happyhour_user
should have been successfully created in the database with all the columns as defined within theinstall-1.0.0.0.php
file.
Note, if you experience issues with your installation script during their execution, such as breaking up due to invalid instructions, be sure to remove the core_resource
table entry that your extension might have created. After that, simply open the browser and reload any web page from your shop; this will trigger the installation process again.
Now that we have successfully created single entity (User
) model file, we need to make sure it's working. We can do so by going back to our Foggyline_HappyHour_HelloController
class and adding the following action to it:
<?php class Foggyline_HappyHour_HelloController extends Mage_Core_Controller_Front_Action { /* … other code … */ public function testUserSaveAction() { $user = Mage::getModel('foggyline_happyhour/user'); $user->setFirstname('John'); /* or: $user->setData('firstname', 'John'); */ $user->setLastname('Doe'); /* or: $user->setDatata('lastname', 'Doe'); */ try { $user->save(); echo 'Successfully saved user.'; } catch (Exception $e) { echo $e->getMessage(); Mage::logException($e); /* oror: Mage::log($e->getTraceAsString(), null, 'exception.log', true); */ } } /* … other code … */ }
Models in Magento get called (instantiated) all across the code. Instantiating the model class is done by the statement $model = Mage::getModel('classGroup/modelClassName);
which can be seen in the preceding code.
What confuses most of the Magento newcomers is the fact that our model class Foggyline_HappyHour_Model_User
has absolutely no methods defined other than _construct()
, which is not the default PHP construct (__construct()
).
So how is it then that the statements such as $user->setLastname('Doe');
work? The answer lies in the derived from the Varien_Object
class found in the lib/Varien/Object.php
file. One of the things Varien_Object
provides is Magento's famous getter and setter methods. If you study the class code, you will see that Magento actually uses the class protected $_data
property internally via the help of PHP magic methods. Executing $user->setLastname('Doe');
actually sets $_data['username'] = 'Doe';
. Or to put it differently, it would virtually create a property named 'úsername'
with the value 'Doe'
on a $user
object instance.
The same logic goes for setting values. Executing a statement such as $user->setData('firstname', 'John');
does almost the same as the previous example.
The difference between the two is that setData()
directly changes the value on the protected $_data['username']
property, while setLastname('Doe');
will first try to look for the setLastname()
method within the Foggyline_HappyHour_Model_User
class. If the method is found, the value is passed to the method and the method is in charge of passing the value to the protected $_data['username']
property, possibly doing some modifications on it.
You should take some time to study the inner workings of the Varien_Object
class, as it is the base class for all of your models.
To continue with our preceding example, if you now try to open the URL http://magento1702ce.loc/index.php/happyhour/hello/testUserSave
in your browser, you should be able to see the Successfully saved user message.
Once you confirm that the entity save action is working, you should test and confirm that the model collection is working too. Create a new action under the Foggyline_HappyHour_HelloController
class as follows:
<?php class Foggyline_HappyHour_HelloController extends Mage_Core_Controller_Front_Action { /* … other code … */ public function testUserCollectionAction() { $users = Mage::getModel('foggyline_happyhour/user') ->getCollection(); foreach ($users as $user) { $firstname = $user->getFirstname(); /* or: $user->getData('firstname') */ $lastname = $user->getLastname(); /* or: $user->getData('lastname') */ echo "$firstname $lastname<br />"; } } /* … other code … */ }
If you now try to open the URL http://magento.loc/index.php/happyhour/hello/testUserCollection
in your browser, you should be able to see the list of your users within the foggyline_happyhour_user
database table.
If you were able to follow up and all went well, you should now have a fully working model entity. There is a lot more to be said about models; however, this is enough to get you started.