forked from magento/devdocs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmage-for-dev-5.html
462 lines (339 loc) · 22.7 KB
/
mage-for-dev-5.html
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
---
---
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=utf-8>
<link rel="stylesheet" type="text/css" href="{{ site.baseurl }}common/css/stylesheet.css"/>
<link rel="stylesheet" href="{{ site.baseurl }}common/css/stylesheet-fonts.css" type="text/css" charset="utf-8">
<link rel="icon" href="{{ site.baseurl }}common/css/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="{{ site.baseurl }}common/css/favicon.ico" type="image/x-icon">
<title>Magento for Developers: Part 5—Magento Models and ORM Basics</title>
</head>
<body>
<a name="top"></a>
<img src="{{ site.baseurl }}common/images/m1x/m1xheader.png" width="1024" alt="header" />
<body>
<a name="top"></a>
<div id="content">
<h1>Magento for Developers: Part 5—Magento Models and ORM Basics</h1>
<p><em>by Alan Storm</em>, updated for Magento 1.12</p>
<p><a href="{{ site.m1xgithuburl }}/magefordev/mage-for-dev-5.html" target="_blank">Edit this page on GitHub <img src="{{ site.baseurl }}common/images/github_icon.png" height="15px"></a></p>
<!-- Magento for Developers: Part 5—Magento Models and ORM Basics -->
<!-- magento-for-dev-part-5-magento-models-and-orm-basics
<style type="text/css">
#kb-entry .series {float: right; width: 175px; margin-left: 10px; font-size: 12px; }
#kb-entry .series ul {margin-left: 15px;}
</style>
<div class="r-box-rss series"> -->
<h5>Other articles in this series:</h5>
<ul>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-1.html">Part 1—Introduction to Magento</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-2.html">Part 2—The Magento Config</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-3.html">Part 3—Magento Controller Dispatch</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-4.html">Part 4—Magento Layouts, Blocks and Templates</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-5.html">Part 5—Magento Models and ORM Basics</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-6.html">Part 6—Magento Setup Resources</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-7.html">Part 7—Advanced ORM: Entity Attribute Value</a>
<li><a href="{{ site.m1xgdeurl }}magefordev/mage-for-dev-8.html">Part 8—Varien Data Collections</a>
</ul>
</div>
<p class="para1">The implementation of a "Models Tier" is a huge part of any MVC framework. It represents the data of your application, and most applications are useless without data. Magento Models play an even bigger role, as they typically contain the "Business Logic" that's often relegated to the Controller or Helper methods in other PHP MVC frameworks.</p>
<h2>Traditional PHP MVC Models</h2>
<p>If the definition of MVC is somewhat fuzzy, the definition of a Model is even fuzzier. Prior to the wide adoption of the MVC pattern by PHP developers, data access was usually raw SQL statements and/or an SQL abstraction layer. Developers would write queries and not think too much about what objects they were modeling.</p>
<p>In this day and age, raw SQL is mostly frowned upon, but many PHP frameworks are still SQL centric. Models will be objects that provide some layer of abstraction, but behind the scenes developers are still writing SQL and/or calling SQL like abstraction methods to read and write-down their data.</p>
<p>Other frameworks eschew SQL and take the Object Relational Mapping (ORM) approach. Here, a developer is dealing strictly with Objects. Properties are set, and when a save method is called on the Object, the data is automatically written to the database. Some ORMs will attempt to divine object properties from the database, others require the user to specify them in some way, (usually in an abstract data language such as YAML). One of the most famous and popular implementations of this approach is ActiveRecord. </p>
<p>This definition of ORM should suffice for now, but like everything Computer Science these days, the strict definition of ORM has blurred over the years. It's beyond the scope of this article to settle that dispute, but suffice it say we're generalizing a bit.</p>
<h2>Magento Models</h2>
<p>It should be no surprise that Magento takes the ORM approach. While the Zend Framework SQL abstractions are available, most of your data access will be via the built in Magento Models, and Models you build yourself. It should also come as no surprise that Magento has a highly flexible, highly abstract, concept of what a Model is. </p>
<h3>Anatomy of a Magento Model</h3>
<p>Most Magento Models can be categorized in one of two ways. There's a basic, ActiveRecord-like/one-object-one-table Model, and there's also an Entity Attribute Value (EAV) Model. Each Model also gets a Model Collection. Collections are PHP objects used to hold a number of individual Magento Model instances. The Magento team has implemented the PHP Standard Library interfaces of IteratorAggregate and Countable to allow each Model type to have it's own collection type. If you're not familiar with the PHP Standard Library, think of Model Collections as arrays that also have methods attached.</p>
<p>Magento Models don't contain any code for connecting to the database. Instead, each Model uses a <tt>modelResource</tt> class, that is used to communicate with the database server (via one read and one write adapter object). By decoupling the logical Model and the code that talks to the database, it's theoretically possible to write new resource classes for a different database schemas and platforms while keeping your Models themselves untouched. </p>
<h2>Enable developer mode</h2>
<p>Something you should do in development—but <em>never</em> in production—is to enable Magento's <em>developer mode</em> which, among other things, displays exceptions in your browser. It's useful for debugging your code.</p>
<p>Enable developer mode in any of the following ways:</p>
<ul><li><a href="https://magento2.atlassian.net/wiki/display/m1wiki/How+to+Configure+a+Magento+1.x+Error+Page#display_errors_on_the_same_page_developer_mode">developer mode</a></a>
<li>Edit the <code>.htaccess</code> in the Magento root directory file to add <code>SetEnv MAGE_IS_DEVELOPER_MODE "true"</code></p>
<h2>Creating a Basic Model</h2>
<p>To begin, we're going to create a basic Magento Model. PHP MVC tradition insists we model a weblog post. The steps we'll need to take are</p>
<ol>
<li>Create a new "Weblog" module</li>
<li>Create a database table for our Model</li>
<li>Add Model information to the config for a Model named Blogpost</li>
<li>Add Model Resource information to the config for the Blogpost Model</li>
<li>Add a Read Adapter to the config for the Blogpost Model</li>
<li>Add a Write Adapter to the config for the Blogpost Model</li>
<li>Add a PHP class file for the Blogpost Model</li>
<li>Add a PHP class file for the Blogpost Resource Model</li>
<li>Instantiate the Model</li>
</ol>
<h3>Create a Weblog Module</h3>
<p>You should be an old hat at creating empty modules at this point, so we'll skip the details and assume you can create an empty module named Weblog. After you've done that, we'll setup a route for an index Action Controller with an action named "testModel". As always, the following examples assume a Package Name of "Magentotutorial". </p>
<p>In Magentotutorial/Weblog/etc/config.xml, setup the following route</p>
<pre>
<frontend>
<routers>
<weblog>
<use>standard</use>
<args>
<module>Magentotutorial_Weblog</module>
<frontName>weblog</frontName>
</args>
</weblog>
</routers>
</frontend>
</pre>
<p>And then add the following Action Controller in </p>
<pre>
class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action {
public function testModelAction() {
echo 'Setup!';
}
}
</pre>
<p>at <tt>Magentotutorial/Weblog/controllers/IndexController.php</tt>. Clear your Magento cache and load the following URL to ensure everything's been setup correctly. </p>
<pre>http://example.com/weblog/index/testModel</pre>
<p>You should see the word "Setup" on a white background.</p>
<h3>Creating the Database Table</h3>
<p>Magento has a system for automatically creating and changing your database schemas, but for the time being we'll just manually create a table for our Model. </p>
<p>Using the command-line or your favorite MySQL GUI application, create a table with the following schema</p>
<pre>
CREATE TABLE `blog_posts` (
`blogpost_id` int(11) NOT NULL auto_increment,
`title` text,
`post` text,
`date` datetime default NULL,
`timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`blogpost_id`)
)
</pre>
<p>And then populate it with some data </p>
<pre>INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2010-07-01 00:00:00','2010-07-02 23:12:30');</pre>
<h3>The Global Config and Creating The Model</h3>
<p>There are three individual things we need to setup for a Model in our config.</p>
<ol>
<li>Enabling Models in our Module</li>
<li>Enabling Model Resources in our Module</li>
<li>Add an "entity" table configuration to our Model Resource.</li>
</ol>
<p>When you instantiate a Model in Magento, you make a call like this</p>
<pre>$model = Mage::getModel('weblog/blogpost');</pre>
<p>The first part of the URI you pass into get Model is the <a href="http://alanstorm.com/2009/img/magento-book/weblogGetModel.png">Model Group Name</a>. Because it is a good idea to follow conventions, this should be the (lowercase) name of your module, or to be safeguarded agains conflicts use the packagename and modulename (also in lowercase). The second part of the URI is the lowercase version of your Model name.</p>
<p>So, let's add the following XML to our module's config.xml.</p>
<pre>
<global>
<!-- ... -->
<models>
<weblog>
<class>Magentotutorial_Weblog_Model</class>
<!--
need to create our own resource, can't just
use core_resource
-->
<resourceModel>weblog_resource</resourceModel>
</weblog>
</models>
<!-- ... -->
</global>
</pre>
<p>The outer <weblog /> tag is your Group Name, which should match your module name. <class /> is the BASE name all Models in the weblog group will have, also calles <strong>Class Prefix</strong>. The <resourceModel /> tag indicates which Resource Model that weblog group Models should use. There's more on this below, but for now be content to know it's your Group Name, followed by a the literal string "resource".</p>
<p>So, we're not done yet, but let's see what happens if we clear our Magento cache and attempt to instantiate a blogpost Model. In your testModelAction method, use the following code</p>
<pre>
public function testModelAction() {
$blogpost = Mage::getModel('weblog/blogpost');
echo get_class($blogpost);
}
</pre>
<p>and reload your page. You should see an error like the following:</p>
<pre>
include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory
</pre>
<p>By attempting to retrieve a <tt>weblog/blogpost</tt> Model, you told Magento to instantiate a class with the name</p>
<pre>
Magentotutorial_Weblog_Model_Blogpost
</pre>
<p>Magento is trying to __autoload include this Model, but can't find the file. Let's create it! Create the following class at the following location</p>
<pre>File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php</pre>
<pre>
class Magentotutorial_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract
{
protected function _construct()
{
$this->_init('weblog/blogpost');
}
}
</pre>
<p>Reload your page, and the exception should be replaced with the name of your class. </p>
<p>All basic Models that interact with the database should extend the <tt>Mage_Core_Model_Abstract</tt> class. This abstract class forces you to implement a single method named <tt>_construct</tt> (<strong>NOTE:</strong> this is not PHP's constructor <tt>__construct</tt>). This method should call the class's _init method with the same identifying URI you'll be using in the <tt>Mage::getModel</tt> method call.</p>
<h3>The Global Config and Resources</h3>
<p>So, we've setup our Model. Next, we need to setup our Model Resource. Model Resources contain the code that actually talks to our database. In the last section, we included the following in our config.</p>
<pre>
<resourceModel>weblog_resource</resourceModel>
</pre>
<p>The value in <resourceModel /> will be used to instantiate a Model Resource class. Although you'll never need to call it yourself, when any Model in the weblog group needs to talk to the database, Magento will make the following method call to get the Model resource</p>
<pre>
Mage::getResourceModel('weblog/blogpost');
</pre>
<p>Again, weblog is the Group Name, and blogpost is the Model. The <tt>Mage::getResourceModel</tt> method will use the <tt>weblog/blogpost</tt> URI to inspect the global config and pull out the value in <resourceModel> (in this case, <tt>weblog_resource</tt>). Then, a model class will be instantiated with the following URI</p>
<pre>
weblog_resource/blogpost
</pre>
<p>So, if you followed that all the way, what this means is, <strong>resource models are configured in the same section of the XML config as normal Models</strong>. This can be confusing to newcomers and old-hands alike.</p>
<p>So, with that in mind, let's configure our resource. In our <models> section add</p>
<pre>
<global>
<!-- ... -->
<models>
<!-- ... -->
<weblog_resource>
<class>Magentotutorial_Weblog_Model_Resource</class>
</weblog_resource>
</models>
</global>
</pre>
<p>You're adding the <weblog_resource /> tag, which is the value of the <resourceModel /> tag you just setup. The value of <class /> is the base name that all your resource models will have, and should be named with the following format</p>
<pre>
Packagename_Modulename_Model_Resource
</pre>
<p>So, we have a configured resource, let's try loading up some Model data. Change your action to look like the following</p>
<pre>
public function testModelAction() {
$params = $this->getRequest()->getParams();
$blogpost = Mage::getModel('weblog/blogpost');
echo("Loading the blogpost with an ID of ".$params['id']);
$blogpost->load($params['id']);
$data = $blogpost->getData();
var_dump($data);
}
</pre>
<p>And then load the following URL in your browser (after clearing your Magento cache)</p>
<pre>http://example.com/weblog/index/testModel/id/1</pre>
<p>You should see an exception something like the following</p>
<pre>Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost.php) [function.include]: failed to open stream: No such file ....</pre>
<p>As you've likely intuited, we need to add a resource class for our Model. <strong>Every</strong> Model has its own resource class. Add the following class at at the following location</p>
<pre>File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php</pre>
<pre>
class Magentotutorial_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{
protected function _construct()
{
$this->_init('weblog/blogpost', 'blogpost_id');
}
}
</pre>
<p>Again, the first parameter of the init method is the URL used to identify the <strong>Model</strong>. The second parameter is the database field that uniquely identifies any particular column. In most cases, this should be the primary key. Clear your cache, reload, and you should see</p>
<pre>Can't retrieve entity config: weblog/blogpost</pre>
<p>Another exception! When we use the Model URI <tt>weblog/blogpost</tt>, we're telling Magento we want the Model Group <tt>weblog</tt>, and the <tt>blogpost</tt> <em>Entity</em>. In the context of simple Models that extend <tt>Mage_Core_Model_Resource_Db_Abstract</tt>, an entity corresponds to a table. In this case, the table named <tt>blog_post</tt> that we created above. Let's add that entity to our XML config.</p>
<pre>
<models>
<!-- ... --->
<weblog_resource>
<class>Magentotutorial_Weblog_Model_Resource</class>
<entities>
<blogpost>
<table>blog_posts</table>
</blogpost>
</entities>
</weblog_resource>
</models>
</pre>
<p>We've added a new <entities /> section to the resource Model section of our config. This, in turn, has a section named after our entity (<blogpost />) that specifies the name of the database table we want to use for this Model.</p>
<p>Clear your Magento cache, cross your fingers, reload the page and ...</p>
<pre>Loading the blogpost with an ID of 1
array
'blogpost_id' => string '1' (length=1)
'title' => string 'My New Title' (length=12)
'post' => string 'This is a blog post' (length=19)
'date' => string '2009-07-01 00:00:00' (length=19)
'timestamp' => string '2009-07-02 16:12:30' (length=19)
</pre>
<p>Eureka! We've managed to extract our data and, more importantly, completely configure a Magento Model.</p>
<h2>Basic Model Operations</h2>
<p>All Magento Models inherit from the Varien_Object class. This class is part of the Magento system library and <strong>not</strong> part of any Magento core module. You can find this object at</p>
<pre>lib/Varien/Object.php</pre>
<p>Magento Models store their data in a protected _data property. The Varien_Object class gives us several methods we can use to extract this data. You've already seen <tt>getData</tt>, which will return an array of key/value pairs. This method can also be passed a string key to get a specific field.</p>
<pre>
$model->getData();
$model->getData('title');
</pre>
<p>There's also a getOrigData method, which will return the Model data as it was when the object was initially populated, (working with the protected _origData method).</p>
<pre>
$model->getOrigData();
$model->getOrigData('title');
</pre>
<p>The Varien_Object also implements some special methods via PHP's magic <tt>__call</tt> method. You can get, set, unset, or check for the existence of any property using a method that begins with the word get, set, unset or has and is followed by the camel cased name of a property.</p>
<pre>
$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...}
</pre>
<p>For this reason, you'll want to name all your database columns with lower case characters and use underscores to separate characters. </p>
<h2>CRUD, the Magento Way</h2>
<p>Magento Models support the basic Create, Read, Update, and Delete functionality of CRUD with <tt>load</tt>, <tt>save</tt>, and <tt>delete</tt> methods. You've already seen the load method in action. When passed a single parameter, the load method will return a record whose id field (set in the Model's resource) matches the passed in value.</p>
<pre>$blogpost->load(1);</pre>
<p>The save method will allow you to both INSERT a new Model into the database, or UPDATE an existing one. Add the following method to your Controller</p>
<pre>
public function createNewPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->setTitle('Code Post!');
$blogpost->setPost('This post was created from code!');
$blogpost->save();
echo 'post with ID ' . $blogpost->getId() . ' created';
}
</pre>
<p>and then execute your Controller Action by loading the following URL</p>
<pre>http://example.com/weblog/index/createNewPost</pre>
<p>You should now see an additional saved post in you database table. Next, try the following to edit your post</p>
<pre>
public function editFirstPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->load(1);
$blogpost->setTitle("The First post!");
$blogpost->save();
echo 'post edited';
}
</pre>
<p>And finally, you can delete your post using very similar syntax. </p>
<pre>
public function deleteFirstPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->load(1);
$blogpost->delete();
echo 'post removed';
}
</pre>
<h2>Model Collections</h2>
<p>So, having a single Model is useful, but sometimes we want to grab list of Models. Rather than returning a simple array of Models, each Magento Model type has a unique collection object associated with it. These objects implement the PHP IteratorAggregate and Countable interfaces, which means they can be passed to the <tt>count</tt> function, and used in <tt>for each</tt> constructs. </p>
<p>We'll cover Collections in full in a later article, but for now let's look at basic setup and usage. Add the following action method to your Controller, and load it in your browser.</p>
<pre>
public function showAllBlogPostsAction() {
$posts = Mage::getModel('weblog/blogpost')->getCollection();
foreach($posts as $blogpost){
echo '<h3>'.$blogpost->getTitle().'</h3>';
echo nl2br($blogpost->getPost());
}
}
</pre>
<p>Load the action URL, </p>
<pre>http://example.com/weblog/index/showAllBlogPosts</pre>
<p>and you should see a (by now) familiar exception.</p>
<pre>Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php) [function.include]: failed to open stream</pre>
<p>You're not surprised, are you? We need to add a PHP class file that defines our Blogpost collection. Every Model has a protected property named <tt>_resourceCollectionName</tt> that contains a URI that's used to identify our collection.</p>
<pre>
protected '_resourceCollectionName' => string 'weblog/blogpost_collection'
</pre>
<p>By default, this is the same URI that's used to identify our Resource Model, with the string "_collection" appended to the end. Magento considers Collections part of the Resource, so this URI is converted into the class name </p>
<pre>Magentotutorial_Weblog_Model_Resource_Blogpost_Collection</pre>
<p>Add the following PHP class at the following location</p>
<pre>File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php</pre>
<pre>
class Magentotutorial_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract {
protected function _construct()
{
$this->_init('weblog/blogpost');
}
}
</pre>
<p>Just as with our other classes, we need to init our Collection with the Model URI. (weblog/blogpost). Rerun your Controller Action, and you should see your post information.</p>
<h2>Wrapup</h2>
<p>Congratulations, you've created and configured your first Magento Model. In a later article we'll take a look at Magento's Entity Attribute Value Models (EAV), which expand on what we've learned here.</p>