Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<com:TContent ID="body" >
2
<!-- $Id $ -->
3
<h1 id="138046">Active Record</h1>
4
<com:SinceVersion Version="3.1a" />
5
<p id="690478" class="block-content">Active Records are objects that wrap a row in a database table or view,
6
    encapsulate the database access and add domain logic on that data.
7
    The basics of an Active Record are business classes, e.g., a
8
    <tt>Products</tt> class,  that match very closely the record structure
9
    of an underlying database table. Each Active Record will be responsible for
10
    saving and loading data to and from the database. </p>
11
<div class="info"><b class="note">Info:</b>
12
    The data structure of an Active Record should  match that of a table
13
    in the database.
14
    Each column of a table should have a corresponding member variable or property in the
15
    Active Record class the represents the table.
16
</div>
17
 
18
<h2 id="138047">When to Use It</h2>
19
<p id="690479" class="block-content">Active Record is a good choice for domain logic that isn't too complex,
20
    such as creates, reads, updates, and deletes. Derivations and validations
21
    based on a single record work well in this structure. Active Record has the
22
    primary advantage of simplicity. It's easy to build
23
    Active Records, and they are easy to understand.</p>
24
 
25
    <p id="690480" class="block-content">However, as your business logic grows in complexity, you'll soon want
26
    to use your object's direct relationships, collections, inheritance, and so
27
    forth. These don't map easily onto Active Record, and adding them piecemeal
28
    gets very messy.
29
    Another argument against Active Record is the fact that it couples the object
30
    design to the database design. This makes it more difficult to refactor as a project goes forward.</p>
31
 
32
    <p id="690481" class="block-content">The alternative is to use a Data Mapper that separates the roles of the
33
        business object and how these objects are stored.
34
        Prado provides a complimentary choice between Active Record and
35
        <a href="?page=Database.SqlMap">SqlMap Data Mapper</a>.
36
        A SqlMap Data Mapper can be used to load Active Record objects, in turn; these
37
        Active Record objects can be used to update the database.
38
        The "relationship" between Active Records and <a href="?page=Database.SqlMap">SqlMap</a> is illustrated in the
39
    following diagram. More details regarding the SqlMap Data Mapper can be found in
40
    the <a href="http://www.pradosoft.com/demos/sqlmap/">SqlMap Manual</a>.
41
    <img src=<%~ sqlmap_active_record.png %> alt="Active Records and SqlMap DataMapper" id="fig:diagram.png" class="figure"/>
42
    </p>
43
 
44
    <p id="690482" class="block-content">
45
        The Active Record class has functionality to perform the following tasks.
46
    </p>
47
    <ul id="u1" class="block-content">
48
        <li>Create, Retrieve, Update and Delete records.</li>
49
        <li>Finder methods to wrap commonly used SQL queries and return Active Record objects.</li>
50
        <li>Fetch relationships (related foreign objects) such as "has many", "has one", "belongs to" and "many to many" via association table.</li>
51
        <li>Lazy loading of relationships.</li>
52
    </ul>
53
<h2>Design Implications</h2>
54
<p>
55
Prado's implementation of Active Record does not maintain referential identity. Each object obtained using
56
Active Record is a copy of the data in the database. For example, If you ask for
57
a particular customer and get back a <tt>Customer</tt> object, the next time you ask for
58
that customer you get back another instance of a <tt>Customer</tt> object. This implies that a strict
59
comparison (i.e., using <tt>===</tt>) will return false, while loose comparison (i.e., using <tt>==</tt>) will
60
return true if the object values are equal by loose comparison.
61
<p>
62
<p>
63
This is design implication related to the following question.
64
<i>"Do you think of the customer as an object, of which there's only one,
65
or do you think of the objects you operate on as <b>copies</b> of the database?"</i>
66
Other O/R mappings will imply that there is only one Customer object with custID 100, and it literally is that customer.
67
If you get the customer and change a field on it, then you have now changed that customer.
68
<i>"That constrasts with: you have changed this copy of the customer, but not that copy.
69
And if two people update the customer on two copies of the object, whoever updates first,
70
or maybe last, wins."</i> [A. Hejlsberg 2003]
71
</p>
72
 
73
<h2 id="142010">Database Supported</h2>
74
<p id="p1" class="block-content">
75
The Active Record implementation utilizes the <a href="?page=Database.DAO">Prado DAO</a> classes for data access.
76
The current Active Record implementation supports the following database.
77
</p>
78
<ul>
79
    <li><a href="http://www.mysql.com">MySQL 4.1 or later</a></li>
80
    <li><a href="http://www.postgres.com">Postgres SQL 7.3 or later</a></li>
81
    <li><a href="http://www.sqlite.org">SQLite 2 and 3</a></li>
82
    <li><a href="#">MS SQL 2000 or later</a></li>
83
    <li><a href="http://www.oracle.com">Oracle Database (alpha)</a></li>
84
</ul>
85
<p id="710009" class="block-content">Support for other databases can be provided when there are sufficient demands.</p>
86
 
87
<h1 id="138048">Defining an Active Record</h1>
88
<p id="690483" class="block-content">Let us
89
    consider the following "<tt>users</tt>" table that contains two columns named "<tt>username</tt>" and "<tt>email</tt>",
90
    where "<tt>username</tt>" is also the primary key.
91
<com:TTextHighlighter Language="sql" CssClass="source block-content" id="code_690147">
92
CREATE TABLE users
93
(
94
    username VARCHAR( 20 ) NOT NULL ,
95
    email VARCHAR( 200 ) ,
96
    PRIMARY KEY ( username )
97
);
98
</com:TTextHighlighter>
99
</p>
100
<p id="690484" class="block-content">Next we define our Active Record class that corresponds to the "<tt>users</tt>" table.
101
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690148">
102
class UserRecord extends TActiveRecord
103
{
104
    const TABLE='users'; //table name
105
 
106
    public $username; //the column named "username" in the "users" table
107
    public $email;
108
 
109
    /**
110
     * @return TActiveRecord active record finder instance
111
     */
112
    public static function finder($className=__CLASS__)
113
    {
114
        return parent::finder($className);
115
    }
116
}
117
</com:TTextHighlighter>
118
</p>
119
<p id="690485" class="block-content">Each column of the "<tt>users</tt>" table must have corresponding
120
    property of the same name as the column name in the <tt>UserRecord</tt> class.
121
    Of course, you also define additional member variables or properties that does not exist in the table structure.
122
    The class constant
123
    <tt>TABLE</tt> is optional when the class name is the same as
124
    the table name in the database, otherwise <tt>TABLE</tt> must
125
    specify the table name that corresponds to your Active Record class.
126
</p>
127
 
128
<div class="tip"><b class="note">Tip:</b>
129
You may specify qualified table names. E.g. for MySQL, <tt>TABLE = "`database1`.`table1`"</tt>.
130
</div>
131
 
132
<div class="note"><b class="note">Note:</b>
133
Since version <b>3.1.3</b> you can also use a method <tt>table()</tt> to define the table name.
134
This allows you to dynamically specify which table should be used by the ActiveRecord.
135
<com:TTextHighlighter Language="php" CssClass="source block-content">
136
class TeamRecord extends TActiveRecord
137
{
138
    public function table() {
139
        return 'Teams';
140
    }
141
}
142
</com:TTextHighlighter>
143
</div>
144
 
145
<p class="block-content" id="ar_as_component">
146
    Since <tt>TActiveRecord</tt> extends <tt>TComponent</tt>, setter and
147
    getter methods can be defined to allow control over how variables
148
    are set and returned. For example, adding a <tt>$level</tt> property to the UserRecord class:
149
</p>
150
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690149">
151
class UserRecord extends TActiveRecord {
152
    ... //existing definitions as above
153
 
154
    private $_level;
155
    public function setLevel($value) {
156
        $this->_level=TPropertyValue::ensureInteger($value,0);
157
    }
158
    public function getLevel($value){
159
        return $this->_level;
160
    }
161
}
162
</com:TTextHighlighter>
163
<p id="710010" class="block-content">More details regarding TComponent can be found in the <a href="?page=Fundamentals.Components">Components documentation</a>.
164
Later we shall use the getter/setters to allow for lazy loading of relationship objects.
165
</p>
166
 
167
<div class="info"><b class="note">Info:</b>
168
<tt>TActiveRecord</tt> can also work with database views by specifying the constant <tt>TABLE</tt>
169
corresponding to the view name. However, objects returned
170
from views are read-only, calling the <tt>save()</tt> or <tt>delete()</tt> method
171
will raise an exception.
172
</div>
173
 
174
<p id="690486" class="block-content">
175
    The static method <tt>finder()</tt> returns an <tt>UserRecord</tt> instance
176
    that can be used to load records from the database. The loading of records
177
    using the finder methods is discussed a little later. The <tt>TActiveRecord::finder()</tt>
178
    static method takes the name of an Active Record class as parameter.
179
</p>
180
 
181
<h2 id="138049">Setting up a database connection</h2>
182
<p id="690487" class="block-content">
183
    A default database connection for Active Record can be set as follows.
184
    See <a href="?page=Database.DAO">Establishing Database Connection</a> for
185
    further details regarding creation of database connection in general.
186
</p>
187
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690150">
188
//create a connection and give it to the Active Record manager.
189
$dsn = 'pgsql:host=localhost;dbname=test'; //Postgres SQL
190
$conn = new TDbConnection($dsn, 'dbuser','dbpass');
191
TActiveRecordManager::getInstance()->setDbConnection($conn);
192
</com:TTextHighlighter>
193
 
194
<p id="710011" class="block-content">Alternatively, you can create a base class and override the <tt>getDbConnection()</tt>
195
method to return a database connection. This is a simple way to permit multiple
196
connections and multiple databases. The following code demonstrates defining
197
the database connection in a base class (not need to set the DB connection anywhere else).
198
</p>
199
<com:TTextHighlighter Language="php" CssClass="source block-content">
200
class MyDb1Record extends TActiveRecord
201
{
202
    public function getDbConnection()
203
    {
204
        static $conn;
205
        if($conn===null)
206
            $conn = new TDbConnection('xxx','yyy','zzz');
207
        return $conn;
208
    }
209
}
210
class MyDb2Record extends TActiveRecord
211
{
212
    public function getDbConnection()
213
    {
214
        static $conn;
215
        if($conn===null)
216
            $conn = new TDbConnection('aaa','bbb','ccc');
217
        return $conn;
218
    }
219
}
220
</com:TTextHighlighter>
221
 
222
 
223
<h3 class="prado-specific">Using <tt>application.xml</tt> within the Prado Framework</h3>
224
<div class="prado-specific">
225
<p id="690488" class="block-content">
226
    The default database connection can also be configured using a <tt>&lt;module&gt;</tt>
227
    tag in the <a href="?page=Configurations.AppConfig">application.xml</a>
228
    or <a href="?page=Configurations.PageConfig">config.xml</a> as follows.
229
<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_690151">
230
<modules>
231
  <module class="System.Data.ActiveRecord.TActiveRecordConfig" EnableCache="true">
232
    <database ConnectionString="pgsql:host=localhost;dbname=test"
233
        Username="dbuser" Password="dbpass" />
234
  </module>
235
</modules>
236
</com:TTextHighlighter>
237
<div class="tip"><b class="note">Tip:</b>
238
    The <tt>EnableCache</tt> attribute when set to "true" will cache the table
239
    meta data, that is, the table columns names, indexes and constraints are
240
    saved in the cache and reused. You must clear or disable the cache if you
241
    wish to see changes made to your table definitions. A <a href="?page=Advanced.Performance#6402">cache
242
    module</a> must also be defined for the cache to function.
243
</div>
244
</p>
245
 
246
<p id="690489" class="block-content">A <tt>ConnectionID</tt> property can be specified with value corresponding
247
    to another <tt>TDataSourceConfig</tt> module configuration's ID value. This allows
248
    the same database connection to be used in other modules such as <a href="?page=Database.SqlMap">SqlMap</a>.
249
<com:TTextHighlighter Language="xml" CssClass="source block-content" id="code_690152">
250
<modules>
251
  <module class="System.Data.TDataSourceConfig" id="db1">
252
    <database ConnectionString="pgsql:host=localhost;dbname=test"
253
        Username="dbuser" Password="dbpass" />
254
  </module>
255
 
256
  <module class="System.Data.ActiveRecord.TActiveRecordConfig"
257
        ConnectionID="db1" EnableCache="true"  />
258
 
259
  <module class="System.Data.SqlMap.TSqlMapConfig"
260
        ConnectionID="db1"  ... />
261
</modules>
262
</com:TTextHighlighter>
263
</p>
264
</div>
265
 
266
<h2 id="138050">Loading data from the database</h2>
267
<p id="690490" class="block-content">
268
    The <tt>TActiveRecord</tt> class provides many convenient methods to find
269
    records from the database. The simplest is finding one record by matching a primary key or a
270
    composite key (primary keys that consists of multiple columns).
271
    See the <com:DocLink ClassPath="System.Data.ActiveRecord.TActiveRecord" /> for
272
    more details.
273
</p>
274
 
275
<div class="info"><b class="note">Info:</b>
276
All finder methods that may return 1 record only will return <tt>null</tt> if no matching data
277
is found. All finder methods that return an array of records will return an empty array if no
278
matching data is found.
279
</div>
280
 
281
	<h3 id="138055"><tt>findByPk()</tt></h3>
282
    <p id="690491" class="block-content">Finds one record using only a primary key or a composite key.
283
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690153">
284
$finder = UserRecord::finder();
285
$user = $finder->findByPk($primaryKey);
286
 
287
//when the table uses a composite key
288
$record = $finder->findByPk($key1, $key2, ...);
289
$record = $finder->findByPk(array($key1, $key2,...));
290
</com:TTextHighlighter>
291
</p>
292
 
293
    <h3 id="138056"><tt>findAllByPks()</tt></h3>
294
    <p id="690492" class="block-content">Finds multiple records using a list of primary keys or composite keys.
295
The following are equivalent for primary keys (primary key consisting of only one column/field).
296
</p>
297
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690154">
298
$finder = UserRecord::finder();
299
$users = $finder->findAllByPks($key1, $key2, ...);
300
$users = $finder->findAllByPks(array($key1, $key2, ...));
301
</com:TTextHighlighter>
302
The following are equivalent for composite keys.
303
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690155">
304
//when the table uses composite keys
305
$record = $finder->findAllByPks(array($key1, $key2), array($key3, $key4), ...);
306
 
307
$keys = array(  array($key1, $key2), array($key3, $key4), ... );
308
$record = $finder->findAllByPks($keys);
309
</com:TTextHighlighter>
310
 
311
 
312
<h3 id="138057"><tt>find()</tt></h3>
313
<p id="690493" class="block-content">Finds <b>one single record</b> that matches the criteria. The criteria
314
    can be a partial SQL string or a <tt>TActiveRecordCriteria</tt> object.</p>
315
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690156">
316
$finder = UserRecord::finder();
317
 
318
//:name and :pass are place holders for specific values of $name and $pass
319
$finder->find('username = :name AND password = :pass',
320
                          array(':name'=>$name, ':pass'=>$pass));
321
 
322
//using position place holders
323
$finder->find('username = ? AND password = ?', array($name, $pass));
324
//same as above
325
$finder->find('username = ? AND password = ?', $name, $pass);
326
 
327
//$criteria is of TActiveRecordCriteria
328
$finder->find($criteria); //the 2nd parameter for find() is ignored.
329
</com:TTextHighlighter>
330
 
331
<p id="690494" class="block-content">The <tt>TActiveRecordCriteria</tt> class has the following properties:
332
</p>
333
    <ul id="u2" class="block-content">
334
        <li><tt>Parameters</tt> -- name value parameter pairs.</li>
335
        <li><tt>OrdersBy</tt> -- column name and ordering pairs.</li>
336
        <li><tt>Condition</tt> -- parts of the WHERE SQL conditions.</li>
337
        <li><tt>Limit</tt> -- maximum number of records to return.</li>
338
        <li><tt>Offset</tt> -- record offset in the table.</li>
339
    </ul>
340
 
341
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690157">
342
$criteria = new TActiveRecordCriteria;
343
$criteria->Condition = 'username = :name AND password = :pass';
344
$criteria->Parameters[':name'] = 'admin';
345
$criteria->Parameters[':pass'] = 'prado';
346
$criteria->OrdersBy['level'] = 'desc';
347
$criteria->OrdersBy['name'] = 'asc';
348
$criteria->Limit = 10;
349
$criteria->Offset = 20;
350
</com:TTextHighlighter>
351
 
352
<div class="note"><b class="note">Note:</b>
353
For MSSQL and when <tt>Limit</tt> and <tt>Offset</tt> are positive integer values. The
354
actual query to be executed is modified by the
355
<com:DocLink ClassPath="System.Data.ActiveRecord.Common.Mssql.TMssqlCommandBuilder"
356
Text="TMssqlCommandBuilder"
357
/>
358
class according to
359
<a href="http://troels.arvin.dk/db/rdbms/#select-limit-offset">http://troels.arvin.dk/db/rdbms/</a>
360
to emulate the <tt>Limit</tt> and <tt>Offset</tt> conditions.
361
</div>
362
 
363
<h3 id="138058"><tt>findAll()</tt></h3>
364
<p id="690495" class="block-content">Same as <tt>find()</tt> but returns an array of objects.</p>
365
 
366
<h3 id="138059"><tt>findBy*()</tt> and <tt>findAllBy*()</tt></h3>
367
<p id="690496" class="block-content">Dynamic find method using parts of the method name as search criteria.
368
Method names starting with <tt>findBy</tt> return 1 record only
369
and method names starting with <tt>findAllBy</tt> return an array of records.
370
The condition is taken as part of the method name after <tt>findBy</tt> or <tt>findAllBy</tt>.
371
 
372
The following blocks of code are equivalent:
373
</p>
374
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690158">
375
$finder->findByName($name)
376
$finder->find('Name = ?', $name);
377
</com:TTextHighlighter>
378
 
379
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690159">
380
$finder->findByUsernameAndPassword($name,$pass);
381
$finder->findBy_Username_And_Password($name,$pass);
382
$finder->find('Username = ? AND Password = ?', $name, $pass);
383
</com:TTextHighlighter>
384
 
385
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690160">
386
$finder->findAllByAge($age);
387
$finder->findAll('Age = ?', $age);
388
</com:TTextHighlighter>
389
 
390
<div class="tip"><b class="note">Tip:</b>
391
You may also use a combination of <tt>AND</tt> and <tt>OR</tt> as a condition in the dynamic methods.
392
</div>
393
 
394
<h3 id="138060"><tt>findBySql()</tt> and <tt>findAllBySql()</tt></h3>
395
<p id="690497" class="block-content">Finds records using full SQL where <tt>findBySql()</tt>
396
return an Active Record and <tt>findAllBySql()</tt>returns an array of record objects.
397
For each column returned, the corresponding Active Record class must define a member variable or
398
property for each corresponding column name.
399
<com:TTextHighlighter Language="php" CssClass="source block-content">
400
class UserRecord2 extends UserRecord
401
{
402
    public $another_value;
403
}
404
$sql = "SELECT users.*, 'hello' as another_value FROM users";
405
$users = TActiveRecord::finder('UserRecord2')->findAllBySql($sql);
406
</com:TTextHighlighter>
407
</p>
408
<h3 id="138061"><tt>count()</tt></h3>
409
<p id="690498" class="block-content">Find the number of matchings records, accepts same parameters as the <tt>findAll()</tt> method.</p>
410
 
411
<h2 id="138051">Inserting and updating records</h2>
412
<p id="690499" class="block-content">
413
Add a new record using TActiveRecord is very simple, just create a new Active
414
Record object and call the <tt>save()</tt> method. E.g.
415
</p>
416
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690161">
417
$user1 = new UserRecord();
418
$user1->username = "admin";
419
$user1->email = "admin@example.com";
420
$user1->save(); //insert a new record
421
 
422
$data = array('username'=>'admin', 'email'=>'admin@example.com');
423
$user2 = new UserRecord($data); //create by passing some existing data
424
$user2->save(); //insert a new record
425
</com:TTextHighlighter>
426
<div class="tip"><b class="note">Tip:</b>
427
If you insert a new record into a MySQL table that has columns
428
defined with "autoincrement", the Active Record objects will be updated with the new
429
incremented value.</div>
430
 
431
<p id="690500" class="block-content">
432
To update a record in the database, just change one or more properties of
433
the Active Record object that has been loaded from the database and then
434
call the <tt>save()</tt> method.
435
 
436
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690162">
437
$user = UserRecord::finder()->findByName('admin');
438
$user->email="test@example.com"; //change property
439
$user->save(); //update it.
440
</com:TTextHighlighter>
441
</p>
442
 
443
<p id="710012" class="block-content">
444
Active Record objects have a simple life-cycle illustrated in the following diagram.
445
</p>
446
<img src=<%~ object_states.png %> alt="Active Records Life Cycle" id="fig:cycle.png" class="figure"/>
447
<p id="690501" class="block-content">
448
We see that new TActiveRecord objects are created by either using one of the <tt>find*()</tt>
449
methods or using creating a new instance by using PHP's <tt>new</tt> keyword. Objects
450
created by a <tt>find*()</tt> method starts with <tt>clean</tt> state. New instance of
451
TActiveRecord created other than by a <tt>find*()</tt> method starts with <tt>new</tt> state.
452
Whenever you
453
call the <tt>save()</tt> method on the TActiveRecord object, the object enters the <tt>clean</tt>
454
state. Objects in the <tt>clean</tt> becomes <tt>dirty</tt> whenever one of more of its
455
internal states are changed. Calling the <tt>delete()</tt> method on the object
456
ends the object life-cycle, no further actions can be performed on the object.
457
</p>
458
 
459
<h2 id="138052">Deleting existing records</h2>
460
<p id="690502" class="block-content">
461
    To delete an existing record that is already loaded, just call the <tt>delete()</tt> method.
462
    You can also delete records in the database by primary keys without
463
    loading any records using the <tt>deleteByPk()</tt> method (and equivalently the <tt>deleteAllByPks()</tt> method).
464
    For example, to delete one or several records with tables using one or more primary keys.
465
</p>
466
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690163">
467
$finder->deleteByPk($primaryKey); //delete 1 record
468
$finder->deleteAllByPks($key1,$key2,...); //delete multiple records
469
$finder->deleteAllByPks(array($key1,$key2,...)); //delete multiple records
470
</com:TTextHighlighter>
471
 
472
<p id="690503" class="block-content">
473
For composite keys (determined automatically from the table definitions):
474
</p>
475
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690164">
476
$finder->deleteByPk(array($key1,$key2)); //delete 1 record
477
 
478
//delete multiple records
479
$finder->deleteAllByPks(array($key1,$key2), array($key3,$key4),...);
480
 
481
//delete multiple records
482
$finder->deleteAllByPks(array( array($key1,$key2), array($key3,$key4), .. ));
483
</com:TTextHighlighter>
484
 
485
<h3 id="138052a"><tt>deleteAll()</tt> and <tt>deleteBy*()</tt></h3>
486
<p id="690502a" class="block-content">
487
To delete by a criteria, use <tt>deleteAll($criteria)</tt> and <tt>deleteBy*()</tt>
488
with similar syntax to <tt>findAll($criteria)</tt> and <tt>findAllBy*()</tt> as
489
described above.
490
</p>
491
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690163a">
492
//delete all records with matching Name
493
$finder->deleteAll('Name = ?', $name);
494
$finder->deleteByName($name);
495
 
496
//delete by username and password
497
$finder->deleteBy_Username_And_Password($name,$pass);
498
</com:TTextHighlighter>
499
 
500
<h2 id="138053">Transactions</h2>
501
<p id="690504" class="block-content">All Active Record objects contain the property <tt>DbConnection</tt>
502
    that can be used to obtain a transaction object.
503
<com:TTextHighlighter Language="php" CssClass="source block-content" id="code_690165">
504
$finder = UserRecord::finder();
505
$finder->DbConnection->Active=true; //open if necessary
506
$transaction = $finder->DbConnection->beginTransaction();
507
try
508
{
509
    $user = $finder->findByPk('admin');
510
    $user->email = 'test@example.com'; //alter the $user object
511
    $user->save();
512
    $transaction->commit();
513
}
514
catch(Exception $e) // an exception is raised if a query fails
515
{
516
    $transaction->rollBack();
517
}
518
</com:TTextHighlighter>
519
 
520
<h2 id="142011">Events</h2>
521
<p id="710013" class="block-content">
522
The TActiveRecord offers two events, <tt>OnCreateCommand</tt> and <tt>OnExecuteCommand</tt>.
523
</p>
524
 
525
<p id="710014" class="block-content">The <tt>OnCreateCommand</tt> event is raised when a command is prepared and
526
parameter binding is completed. The parameter object is <tt>TDataGatewayEventParameter</tt> of which the
527
<tt>Command</tt> property can be inspected to obtain the SQL query to be executed.
528
</p>
529
 
530
<p id="710015" class="block-content">
531
The <tt>OnExecuteCommand</tt> event is raised when a command is executed and the
532
result from the database was returned. The parameter object is <tt>TDataGatewayResultEventParameter</tt>
533
of which the <tt>Result</tt> property contains the data return from the database.
534
The data returned can be changed by setting the <tt>Result</tt> property.
535
</p>
536
 
537
<h3 id="142016">Logging Example</h3>
538
<p id="710016" class="block-content">Using the <tt>OnExecuteCommand</tt> we can attach an event handler to log
539
the entire SQL query executed for a given TActiveRecord class or instance. For example, we define
540
a base class and override either the <tt>getDbConnection()</tt> or the constructor.
541
</p>
542
 
543
<com:TTextHighlighter Language="php" CssClass="source block-content">
544
class MyDb1Record extends TActiveRecord
545
{
546
    public function getDbConnection()
547
    {
548
        static $conn;
549
        if($conn===null)
550
        {
551
            $conn = new TDbConnection('xxx','yyy','zzz');
552
            $this->OnExecuteCommand[] = array($this,'logger');
553
        }
554
        return $conn;
555
    }
556
    public function logger($sender,$param)
557
    {
558
        var_dump($param->Command->Text);
559
    }
560
}
561
//alternatively as per instance of per finder object
562
function logger($sender,$param)
563
{
564
    var_dump($param->Command->Text);
565
}
566
TActiveRecord::finder('MyRecord')->OnExecuteCommand[] = 'logger';
567
$obj->OnExecuteCommand[] = array($logger, 'log'); //any valid PHP callback.
568
</com:TTextHighlighter>
569
 
570
<h1 id="ar_relations">Active Record Relationships</h1>
571
<com:SinceVersion Version="3.1rc1" />
572
<p id="690504a" class="block-content">
573
The Prado Active Record implementation supports the foreign key mappings for database
574
that supports foreign key constraints. For Active Record relationships to function the
575
underlying database must support foreign key constraints (e.g. MySQL using InnoDB).
576
</p>
577
 
578
<p id="710017" class="block-content">
579
In the following sections we will consider the following table relationships between
580
<tt>Teams</tt>, <tt>Players</tt>, <tt>Skills</tt> and <tt>Profiles</tt>.
581
</p>
582
<img src=<%~ ar_relations.png %> class="figure" />
583
 
584
 
585
<p id="710018" class="block-content">The goal is to obtain object models that represent to some degree the entity
586
relationships in the above figure.
587
</p>
588
 
589
<img src=<%~ ar_objects.png %> class="figure" />
590
 
591
<p class="block-content">
592
There is a mismatch between relationships with objects and table relationships.
593
First there's a difference in representation. Objects handle links by storing references
594
that are held by the runtime memory-managed environment. Relational databases handle
595
links by forming a key into another table. Second, objects can easily use collections
596
to handle multiple references from a single field, while normalization forces
597
all entity relation links to be single valued. This leads to reversals of the data
598
structure between objects and tables. The approach taken in the Prado Active Record
599
design is to use the table foreign key constraints to derive object relationships. This implies
600
that the underlying database must support foreign key constraints.
601
</p>
602
<div class="tip"><b class="note">Tip:</b>
603
For SQLite database, you may create tables that defines the foreign key
604
constraints such as the example below. However, these constraints are <b>NOT</b>
605
enforced by the SQLite database itself.
606
<com:TTextHighlighter Language="sql" CssClass="source block-content">
607
CREATE TABLE foo
608
(
609
    id INTEGER NOT NULL PRIMARY KEY,
610
    id2 CHAR(2)
611
);
612
CREATE TABLE bar
613
(
614
    id INTEGER NOT NULL PRIMARY KEY,
615
    foo_id INTEGER
616
        CONSTRAINT fk_foo_id REFERENCES foo(id) ON DELETE CASCADE
617
);
618
</com:TTextHighlighter>
619
</div>
620
 
621
<h2 id="142012">Foreign Key Mapping</h2>
622
<p class="block-content">The entity relationship between the <tt>Teams</tt> and <tt>Players</tt> table is what is known
623
as an 1-M relationship. That is, one Team may contain 0 or more Players. In terms of
624
object relationships, we say that a <tt>TeamRecord</tt> object <b>has many</b> <tt>PlayerRecord</tt> objects.
625
(Notice the reversal of the direction of relationships between tables and objects.)
626
</p>
627
 
628
<h3 id="142017">Has Many Relationship</h3>
629
<p id="710020" class="block-content">
630
We model the <tt>Team</tt> object as the following Active Record classes.
631
</p>
632
<com:TTextHighlighter Language="php" CssClass="source block-content">
633
class TeamRecord extends TActiveRecord
634
{
635
    const TABLE='Teams';
636
    public $name;
637
    public $location;
638
 
639
    public $players=array();  // this declaration is no longer needed since v3.1.2
640
 
641
    //define the $player member having has many relationship with PlayerRecord
642
    public static $RELATIONS=array
643
    (
644
        'players' => array(self::HAS_MANY, 'PlayerRecord', 'team_name'),
645
    );
646
 
647
    public static function finder($className=__CLASS__)
648
    {
649
        return parent::finder($className);
650
    }
651
}
652
</com:TTextHighlighter>
653
<p id="710021" class="block-content">
654
The static <tt>$RELATIONS</tt> property of <tt>TeamRecord</tt> defines that the
655
property <tt>$players</tt> <b>has many</b> <tt>PlayerRecord</tt>s. Multiple relationships
656
is permitted by defining each relationship with an entry in the <tt>$RELATIONS</tt>
657
array where array key for the entry corresponds to the property name.
658
In <tt>array(self::HAS_MANY, 'PlayerRecord')</tt>, the first element defines the
659
relationship type, the valid types are <tt>self::HAS_MANY</tt>, <tt>self::HAS_ONE</tt>,
660
<tt>self::BELONGS_TO</tt> and <tt>self::MANY_TO_MANY</tt>.
661
The second element is a string <tt>'PlayerRecord'</tt> that corresponds to the
662
class name of the <tt>PlayerRecord</tt> class.
663
And the third element 'team_name' refers to the foreign key column in the Players table that
664
references to the Teams table.
665
</p>
666
 
667
<div class="note"><b class="note">Note:</b>
668
As described in the code comment above, since version <b>3.1.2</b>, related properties no longer
669
need to be explicitly declared. By default, they will be implicitly declared according to
670
keys of the <tt>$RELATIONS</tt> array. A major benefit of declared related properties implicitly
671
is that related objects can be automatically loaded in a lazy way. For example, assume we have
672
a <tt>TeamRecord</tt> instance <tt>$team</tt>. We can access the players via <tt>$team->players</tt>,
673
even if we have never issued fetch command for players. If <tt>$players</tt> is explicitly declared,
674
we will have to use the <tt>with</tt> approach described in the following to fetch the player records.
675
</div>
676
 
677
<p id="710022" class="block-content">
678
The foreign key constraint of the <tt>Players</tt> table is used to determine the corresponding
679
<tt>Teams</tt> table's corresponding key names. This is done automatically handled
680
in Active Record by inspecting the <tt>Players</tt> and <tt>Teams</tt> table definitions.
681
</p>
682
 
683
<div class="info"><b class="note">Info:</b>
684
Since version <b>3.1.2</b>, Active Record supports multiple foreign key
685
references of the same table. Ambiguity between multiple foreign key references to the same table is
686
resolved by providing the foreign key column name as the 3rd parameter in the relationship array.
687
For example, both of the following foreign keys <tt>owner_id</tt> and <tt>reporter_id</tt>
688
references to the same table defined in <tt>UserRecord</tt>.
689
<com:TTextHighlighter Language="php" CssClass="source block-content">
690
class TicketRecord extends TActiveRecord
691
{
692
     public $owner_id;
693
	 public $reporter_id;
694
 
695
     public $owner;     // this declaration is no longer needed since v3.1.2
696
	 public $reporter;  // this declaration is no longer needed since v3.1.2
697
 
698
	 public static $RELATION=array
699
	 (
700
	     'owner' => array(self::BELONGS_TO, 'UserRecord', 'owner_id'),
701
		 'reporter' => array(self::BELONGS_TO, 'UserRecord', 'reporter_id'),
702
	 );
703
}
704
</com:TTextHighlighter>
705
This is applicable to relationships including <tt>BELONGS_TO</tt>, <tt>HAS_ONE</tt> and
706
<tt>HAS_MANY</tt>. See section <a href="#142021">Self Referenced Association Tables</a> for solving ambiguity of <tt>MANY_TO_MANY</tt>
707
relationships.
708
</div>
709
 
710
<p id="710023" class="block-content">The "has many" relationship is not fetched automatically when you use any of the Active Record finder methods.
711
You will need to explicitly fetch the related objects as follows. In the code below, both lines
712
are equivalent and the method names are case insensitive.
713
</p>
714
<com:TTextHighlighter Language="php" CssClass="source block-content">
715
$team = TeamRecord::finder()->withPlayers()->findAll();
716
$team = TeamRecord::finder()->with_players()->findAll(); //equivalent
717
</com:TTextHighlighter>
718
<p id="710024" class="block-content">
719
The method <tt>with_xxx()</tt> (where <tt>xxx</tt> is the relationship property
720
name, in this case, <tt>players</tt>) fetches the corresponding PlayerRecords using
721
a second query (not by using a join). The <tt>with_xxx()</tt> accepts the same
722
arguments as other finder methods of TActiveRecord, e.g. <tt>with_players('age = ?', 35)</tt>.
723
</p>
724
 
725
<div class="note"><b class="note">Note:</b>
726
It is essential to understand that the related objects are fetched using additional
727
queries. The first query fetches the source object, e.g. the <tt>TeamRecord</tt> in the above example code.
728
A second query is used to fetch the corresponding related <tt>PlayerRecord</tt> objects.
729
The usage of the two query is similar to a single query using Left-Outer join with the
730
exception that null results on the right table
731
are not returned. The consequence of using two or more queries is that the aggregates
732
and other join conditions are not feasible using Active Records. For queries outside the
733
scope of Active Record the <a href="?page=Database.SqlMap">SqlMap Data Mapper</a> may be considered.
734
</div>
735
 
736
<div class="info"><b class="info">Info:</b>
737
The above <tt>with</tt> approach also works with implicitly declared related properties (introduced
738
in version 3.1.2). So what is the difference between the <tt>with</tt> approach and the lazy loading
739
approach? Lazy loading means we issue an SQL query if a related object is initially accessed and not ready,
740
while the <tt>with</tt> approach queries for the related objects once for all, no matter the related objects
741
are accessed or not. The lazy loading approach is very convenient since we do not need to explictly
742
load the related objects, while the <tt>with</tt> approach is more efficient if multiple records are
743
returned, each with some related objects.
744
</div>
745
 
746
<h3 id="142019">Has One Relationship</h3>
747
<p id="710030" class="block-content">The entity relationship between <tt>Players</tt> and <tt>Profiles</tt> is one to one. That is,
748
each <tt>PlayerRecord</tt> object <b>has one</b> <tt>ProfileRecord</tt> object (may be none or null).
749
A <b>has one</b> relationship is nearly identical to a <b>has many</b> relationship with the exception
750
that the related object is only one object (not a collection of objects).
751
</p>
752
 
753
<h3 id="142018">Belongs To Relationship</h3>
754
<p id="710025" class="block-content">The "has many" relationship in the above section defines a collection of foreign
755
objects. In particular, we have that a <tt>TeamRecord</tt> has many (zero or more)
756
<tt>PlayerRecord</tt> objects. We can also add a back pointer by adding a property
757
in the <tt>PlayerRecord</tt> class that links back to the <tt>TeamRecord</tt> object,
758
effectively making the association bidirectional.
759
We say that the <tt>$team</tt> property in <tt>PlayerRecord</tt> class <tt>belongs to</tt> a <tt>TeamRecord</tt> object.
760
The following code defines the complete <tt>PlayerRecord</tt> class with 3 relationships.
761
</p>
762
<com:TTextHighlighter Language="php" CssClass="source block-content">
763
class PlayerRecord extends TActiveRecord
764
{
765
    const TABLE='Players';
766
    public $player_id;
767
    public $age;
768
    public $team_name;
769
 
770
    public $team;              // this declaration is no longer needed since v3.1.2
771
    public $skills=array();    // this declaration is no longer needed since v3.1.2
772
    public $profile;           // this declaration is no longer needed since v3.1.2
773
 
774
    public static $RELATIONS=array
775
    (
776
        'team' => array(self::BELONGS_TO, 'TeamRecord', 'team_name'),
777
        'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'),
778
        'profile' => array(self::HAS_ONE, 'ProfileRecord', 'player_id'),
779
    );
780
 
781
    public static function finder($className=__CLASS__)
782
    {
783
        return parent::finder($className);
784
    }
785
}
786
</com:TTextHighlighter>
787
<p id="710026" class="block-content">
788
The static <tt>$RELATIONS</tt> property of <tt>PlayerRecord</tt> defines that the
789
property <tt>$team</tt> <b>belongs to</b> a <tt>TeamRecord</tt>.
790
The <tt>$RELATIONS</tt> array also defines two other relationships that we
791
shall examine in later sections below.
792
In <tt>array(self::BELONGS_TO, 'TeamRecord', 'team_name')</tt>, the first element defines the
793
relationship type, in this case <strong><tt>self::BELONGS_TO</tt></strong>;
794
the second element is a string <tt>'TeamRecord'</tt> that corresponds to the
795
class name of the <tt>TeamRecord</tt> class; and the third element 'team_name' refers
796
to the foreign key of Players referencing Teams.
797
A player object with the corresponding team object may be fetched as follows.
798
</p>
799
<com:TTextHighlighter Language="php" CssClass="source block-content">
800
$players = PlayerRecord::finder()->with_team()->findAll();
801
</com:TTextHighlighter>
802
 
803
<p id="710027" class="block-content">
804
 The method <tt>with_xxx()</tt> (where <tt>xxx</tt> is the relationship property
805
 name, in this case, <tt>team</tt>) fetches the corresponding <tt>TeamRecords</tt> using
806
 a second query (not by using a join). The <tt>with_xxx()</tt> accepts the same
807
arguments as other finder methods of <tt>TActiveRecord</tt>, e.g.
808
<tt>with_team('location = ?', 'Madrid')</tt>.
809
</p>
810
 
811
<div class="tip"><b class="note">Tip:</b>
812
Additional relationships may be fetched by chaining the <tt>with_xxx()</tt> together as the following
813
example demonstrates.
814
<com:TTextHighlighter Language="php" CssClass="source block-content">
815
$players = PlayerRecord::finder()->with_team()->with_skills()->findAll();
816
</com:TTextHighlighter>
817
Each <tt>with_xxx()</tt> method will execute an additional SQL query. Every
818
<tt>with_xxx()</tt> accepts arguments similar to those in the <tt>findAll()</tt> method and is only
819
applied to that particular relationship query.
820
</div>
821
 
822
<p id="710028" class="block-content">The "belongs to" relationship of <tt>ProfileRecord</tt> class is defined similarly.</p>
823
<com:TTextHighlighter Language="php" CssClass="source block-content">
824
class ProfileRecord extends TActiveRecord
825
{
826
    const TABLE='Profiles';
827
    public $player_id;
828
    public $salary;
829
 
830
    public $player;  // this declaration is no longer needed since v3.1.2
831
 
832
    public static $RELATIONS=array
833
    (
834
        'player' => array(self::BELONGS_TO, 'PlayerRecord'),
835
    );
836
 
837
    public static function finder($className=__CLASS__)
838
    {
839
        return parent::finder($className);
840
    }
841
}
842
</com:TTextHighlighter>
843
 
844
<p id="710029" class="block-content">In essence, there exists a "<b>belongs to</b>" relationship for objects corresponding to
845
entities that has column which are foreign keys. In particular, we see that
846
the <tt>Profiles</tt> table has a foreign key constraint on the column <tt>player_id</tt>
847
that relates to the <tt>Players</tt> table's <tt>player_id</tt> column. Thus, the <tt>ProfileRecord</tt>
848
object has a property (<tt>$player</tt>) that <b>belongs to</b> a <tt>PlayerRecord</tt> object.
849
Similarly, the <tt>Players</tt> table has a foreign key constraint on the column <tt>team_name</tt> that relates to the
850
<tt>Teams</tt> table's <tt>name</tt> column.
851
Thus, the <tt>PlayerRecord</tt> object has a property (<tt>$team</tt>) that <b>belongs to</b> a
852
<tt>TeamRecord</tt> object.
853
</p>
854
 
855
<h3 id="142020">Parent Child Relationships</h3>
856
<p id="710031" class="block-content">A parent child relationship can be defined using a combination of <tt>has many</tt> and <tt>belongs to</tt>
857
relationship that refers to the same class. The following example shows a parent children relationship between
858
"categories" and a "parent category".
859
</p>
860
 
861
<com:TTextHighlighter Language="php" CssClass="source block-content">
862
class Category extends TActiveRecord
863
{
864
    public $cat_id;
865
    public $category_name;
866
    public $parent_cat_id;
867
 
868
    public $parent_category;            // this declaration is no longer needed since v3.1.2
869
    public $child_categories=array();   // this declaration is no longer needed since v3.1.2
870
 
871
    public static $RELATIONS=array
872
    (
873
        'parent_category' => array(self::BELONGS_TO, 'Category', 'parent_cat_id'),
874
        'child_categories' => array(self::HAS_MANY, 'Category', 'parent_cat_id'),
875
    );
876
}
877
</com:TTextHighlighter>
878
 
879
<h3>Query Criteria for Related Objects</h3>
880
<p>
881
In the above, we show that an Active Record object can reference to its related objects by
882
declaring a static class member $RELATIONS which specifies a list of relations. Each relation
883
is specified as an array consisting of three elements: relation type, related AR class name,
884
and the foreign key(s). For example, we use <tt>array(self::HAS_MANY, 'PlayerRecord', 'team_name')</tt>
885
to specify the players in a team. There are two more optional elements that can be specified
886
in this array: query condition (the fourth element) and parameters (the fifth element).
887
They are used to control how to query for the related objects. For example, if we want to obtain
888
the players ordered by their age, we can specify <tt>array(self::HAS_MANY, 'PlayerRecord', 'team_name', 'ORDER BY age')</tt>.
889
If we want to obtain players whose age is smaller than 30, we could use
890
<tt>array(self::HAS_MANY, 'PlayerRecord', 'team_name', 'age<:age', array(':age'=>30))</tt>. In general,
891
these two additional elements are similar as the parameters passed to the <tt>find()</tt> method in AR.
892
</p>
893
 
894
 
895
 
896
<h2 id="142013">Association Table Mapping</h2>
897
<p id="710032" class="block-content">
898
Objects can handle multivalued fields quite easily by using collections as field values.
899
Relational databases don't have this feature and are constrained to single-valued fields only.
900
When you're mapping a one-to-many association you can handle this using <b>has many</b> relationships,
901
essentially using a foreign key for the single-valued end of the association.
902
But a many-to-many association can't do this because there is no single-valued end to
903
hold the foreign key.
904
</p>
905
<p id="710033" class="block-content">
906
The answer is the classic resolution that's been used by relational data people
907
for decades: create an extra table (an association table) to record the relationship.
908
The basic idea is using an association table to store the association. This table
909
has only the foreign key IDs for the two tables that are linked together, it has one
910
row for each pair of associated objects.
911
</p>
912
<p id="710034" class="block-content">
913
The association table has no corresponding in-memory object and its primary key is the
914
compound of the two primary keys of the tables that are associated.
915
In simple terms, to load data from the association table you perform two queries (in general, it may also be achieved using one query consisting of joins).
916
Consider loading the <tt>SkillRecord</tt> collection for a list <tt>PlayerRecord</tt> objects.
917
In this case, you do queries in two stages.
918
The first stage queries the <tt>Players</tt> table to find all the rows of the players you want.
919
The second stage finds the <tt>SkillRecord</tt> object for the related player ID for each row
920
in the <tt>Player_Skills</tt> association table using an inner join.
921
</p>
922
 
923
<p id="710035" class="block-content">The Prado Active Record design implements the two stage approach. For the
924
<tt>Players</tt>-<tt>Skills</tt> M-N (many-to-many) entity relationship, we
925
define a <b>many-to-many</b> relationship in the <tt>PlayerRecord</tt> class and
926
in addition we may define a <b>many-to-many</b> relationship in the <tt>SkillRecord</tt> class as well.
927
The following sample code defines the complete <tt>SkillRecord</tt> class with a
928
many-to-many relationship with the <tt>PlayerRecord</tt> class. (See the <tt>PlayerRecord</tt>
929
class definition above to the corresponding many-to-many relationship with the <tt>SkillRecord</tt> class.)
930
</p>
931
 
932
<com:TTextHighlighter Language="php" CssClass="source block-content">
933
class SkillRecord extends TActiveRecord
934
{
935
    const TABLE='Skills';
936
    public $skill_id;
937
    public $name;
938
 
939
    public $players=array();    // this declaration is no longer needed since v3.1.2
940
 
941
    public static $RELATIONS=array
942
    (
943
        'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'),
944
    );
945
 
946
    public static function finder($className=__CLASS__)
947
    {
948
        return parent::finder($className);
949
    }
950
}
951
</com:TTextHighlighter>
952
 
953
<p id="710036" class="block-content">
954
The static <tt>$RELATIONS</tt> property of SkillRecord defines that the
955
property <tt>$players</tt> has many <tt>PlayerRecord</tt>s via an association table '<tt>Player_Skills</tt>'.
956
In <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the
957
relationship type, in this case <strong><tt>self::MANY_TO_MANY</tt></strong>,
958
the second element is a string <tt>'PlayerRecord'</tt> that corresponds to the
959
class name of the <tt>PlayerRecord</tt> class, and the third element is the name
960
of the association table name.
961
</p>
962
 
963
<div class="note"><b class="note">Note:</b>
964
Prior to version <b>3.1.2</b> (versions up to 3.1.1), the many-to-many relationship was
965
defined using <tt>self::HAS_MANY</tt>. For version <b>3.1.2</b> onwards, this must be changed
966
to <tt>self::MANY_TO_MANY</tt>. This can be done by searching for the <tt>HAS_MANY</tt> in your
967
source code and carfully changing the appropriate definitions.
968
</div>
969
 
970
<p id="710037" class="block-content">
971
A list of player objects with the corresponding collection of skill objects may be fetched as follows.
972
</p>
973
<com:TTextHighlighter Language="php" CssClass="source block-content">
974
$players = PlayerRecord::finder()->withSkills()->findAll();
975
</com:TTextHighlighter>
976
<p id="710038" class="block-content">
977
The method <tt>with_xxx()</tt> (where <tt>xxx</tt> is the relationship property
978
name, in this case, <tt>Skill</tt>) fetches the corresponding <tt>SkillRecords</tt> using
979
a second query (not by using a join). The <tt>with_xxx()</tt> accepts the same
980
arguments as other finder methods of <tt>TActiveRecord</tt>.
981
</p>
982
 
983
<h3 id="142021">Self Referenced Association Tables</h3>
984
<p id="710039" class="block-content">
985
For self referenced association tables, that is, the association points to the same
986
table. For example, consider the <tt>items</tt> table with M-N related
987
item via the <tt>related_items</tt> association table. The syntax in the following
988
example is valid for a PostgreSQL database. For other database, consult their respective documentation for
989
defining the foreign key constraints.
990
<com:TTextHighlighter Language="sql" CssClass="source block-content">
991
CREATE TABLE items
992
(
993
  "item_id" SERIAL,
994
  "name" VARCHAR(128) NOT NULL,
995
  PRIMARY KEY("item_id")
996
);
997
CREATE TABLE "related_items"
998
(
999
  "item_id" INTEGER NOT NULL,
1000
  "related_item_id" INTEGER NOT NULL,
1001
  CONSTRAINT "related_items_pkey" PRIMARY KEY("item_id", "related_item_id"),
1002
  CONSTRAINT "related_items_item_id_fkey" FOREIGN KEY ("item_id")
1003
    REFERENCES "items"("item_id")
1004
    ON DELETE CASCADE
1005
    ON UPDATE NO ACTION
1006
    NOT DEFERRABLE,
1007
  CONSTRAINT "related_items_related_item_id_fkey" FOREIGN KEY ("related_item_id")
1008
    REFERENCES "items"("item_id")
1009
    ON DELETE CASCADE
1010
    ON UPDATE NO ACTION
1011
    NOT DEFERRABLE
1012
);
1013
</com:TTextHighlighter>
1014
 
1015
<p id="710040" class="block-content">The association table name in third element of the relationship array may
1016
contain the foreign table column names. The columns defined in the association
1017
table must also be defined in the record class (e.g. the <tt>$related_item_id</tt> property
1018
corresponds to the <tt>related_item_id</tt> column in the <tt>related_items</tt> table).
1019
</p>
1020
<com:TTextHighlighter Language="php" CssClass="source block-content">
1021
class Item extends TActiveRecord
1022
{
1023
    const TABLE="items";
1024
    public $item_id;
1025
    public $details;
1026
 
1027
    //additional foreign item id defined in the association table
1028
    public $related_item_id;
1029
    public $related_items=array();    // this declaration is no longer needed since v3.1.2
1030
 
1031
    public static $RELATIONS=array
1032
    (
1033
        'related_items' => array(self::MANY_TO_MANY,
1034
            'Item', 'related_items.related_item_id'),
1035
    );
1036
}
1037
</com:TTextHighlighter>
1038
<div class="tip"><b class="note">Tip:</b>
1039
Compound keys in the foreign table can
1040
be specified as comma separated values between brackets. E.g.
1041
<tt>'related_items.(id1,id2)'</tt>.
1042
</div>
1043
 
1044
<!---
1045
<h2 id="142014">Adding/Removing/Updating Related Objects</h2>
1046
 
1047
<p id="710041" class="block-content">Related objects can be simply inserted/updated by first adding those related objects to
1048
the current source object (i.e. the object currently been worked on) and then call
1049
the <tt>save()</tt> method on the source object. The related object's references
1050
and the association reference (if required) will be added and/or updated.
1051
For example, to add two new players to the team (assuming that 'Team A' exists), we can simply do the following.
1052
</p>
1053
 
1054
<com:TTextHighlighter Language="php" CssClass="source block-content">
1055
$team = TeamRecord::finder()->findByPk('Team A');
1056
$team->players[] = new PlayerRecord(array('age'=>20));
1057
$team->players[] = new PlayerRecord(array('age'=>25));
1058
$team->save();
1059
</com:TTextHighlighter>
1060
<p id="710042" class="block-content">
1061
Since the <tt>TeamRecord</tt> class contains a <b>has many</b> relationship with the <tt>PlayerRecord</tt>,
1062
then saving a <tt>TeamRecord</tt> object will also update the corresponding foreign objects in <tt>$players</tt> array.
1063
That is, the objects in <tt>$players</tt> are inserted/updated in the database and the
1064
<tt>$team_name</tt> property of those objects will contain the foreign key value that corresponds to the <tt>$team</tt> object's primary key value.
1065
</p>
1066
 
1067
<p id="710043" class="block-content">To delete a particular foreign object (or any Active Record object), simply call
1068
the object's <tt>delete()</tt> method. You may setup the database table's foreign key constraints such that
1069
when deleting a particular data in the database it will delete the referenced data as well (it may also be achieved using database
1070
triggers). E.g. such as having a "<tt>ON DELETE CASCADE</tt>" constraint.
1071
Deleting foreign objects by either setting the property value to null or removing the object from an array will <b>NOT</b>
1072
remove the corresponding data in the database.
1073
</p>
1074
 
1075
<p id="710044" class="block-content">To remove associations for the many-to-many relationships via an association table, an Active Record
1076
that corresponds to the association table can be used. Then the association can be removed by calling the <tt>deleteByPk()</tt> method, for example:
1077
</p>
1078
<com:TTextHighlighter Language="php" CssClass="source block-content">
1079
PlayerSkillAssocation::finder()->deleteByPk(array('fk1','fk2'));
1080
//where 'fk1' is the primary key value of a player
1081
// and 'fk2' is the primary key value of a skill
1082
</com:TTextHighlighter>
1083
--->
1084
 
1085
<h2 id="142015">Lazy Loading Related Objects</h2>
1086
 
1087
<div class="note"><b class="note">Note:</b>
1088
Implicitly declared related properties introduced in version 3.1.2 automatically have lazy
1089
loading feature. Therefore, the lazy loading technique described in the following is no longer
1090
needed in most of the cases, unless you want to manipulate the related objects through getter/setter.
1091
</div>
1092
 
1093
<p id="710045" class="block-content">Using the <tt>with_xxx()</tt> methods will load the relationship record on demand. Retrieving the
1094
related record using lazy loading (that is, only when those related objects are accessed) can be
1095
achieved by using a feature of the <tt>TComponent</tt> that provides accessor methods. In particular,
1096
we define a pair of getter and setter methods where the getter method will retrieve the relationship
1097
conditionally. The following example illustrates that the <tt>PlayerRecord</tt> can retrieve its
1098
<tt>$skills</tt> foreign objects conditionally.
1099
</p>
1100
<com:TTextHighlighter Language="php" CssClass="source block-content">
1101
class PlayerRecord extends BaseFkRecord
1102
{
1103
    //... other properties and methods as before
1104
 
1105
    private $_skills; //change to private and default as null
1106
 
1107
    public function getSkills()
1108
    {
1109
        if($this->_skills===null && $this->player_id !==null)
1110
        {
1111
            //lazy load the skill records
1112
            $this->setSkills($this->withSkills()->findByPk($this->player_id)->skills);
1113
        }
1114
        else if($this->_skills===null)
1115
        {
1116
            //create new TList;
1117
            $this->setSkills(new TList());
1118
        }
1119
 
1120
        return $this->_skills;
1121
    }
1122
 
1123
    public function setSkills($value)
1124
    {
1125
        $this->_skills = $value instanceof TList ? $value : new TList($value);
1126
    }
1127
}
1128
</com:TTextHighlighter>
1129
<p id="710046" class="block-content">We first need to change the <tt>$skills=array()</tt> declaration to a private property
1130
<tt>$_skills</tt> (notice the underscore) and set it to null instead. This allows us
1131
to define the <tt>skills</tt> property using getter/setter methods
1132
(see <a href="?page=Fundamentals.Components">Components</a> for details). The <tt>getSkills()</tt>
1133
getter method for the <tt>skills</tt> property will lazy load the corresponding skills foreign record
1134
when it is used as follows. Notice that we only do a lazy load when its <tt>$player_id</tt> is
1135
not null (that is, when the record is already fetched from the database or player id was already set).
1136
</p>
1137
<com:TTextHighlighter Language="php" CssClass="source block-content">
1138
$player = PlayerRecord::finder()->findByPk(1);
1139
var_dump($player->skills); //lazy load it on first access
1140
var_dump($player->skills[0]); //already loaded skills property
1141
$player->skills[] = new SkillRecord(); //add skill
1142
</com:TTextHighlighter>
1143
 
1144
<p id="710047" class="block-content">The <tt>setSkills()</tt> ensures that the <tt>skills</tt> property will always be a TList.
1145
Using a TList allows us to set the elements of the <tt>skills</tt> property as if they were
1146
arrays. E.g. <tt>$player->skills[] = new SkillRecord()</tt>. If <tt>array</tt> was used, a PHP error
1147
will be thrown.
1148
</p>
1149
 
1150
<h2>Column Mapping</h2>
1151
<p>
1152
Since v3.1.1, Active Record starts to support column mapping. Column mapping allows developers
1153
to address columns in Active Record using a more consistent naming convention. In particular,
1154
using column mapping, one can access a column using whatever name he likes, rather than limited by
1155
the name defined in the database schema.
1156
</p>
1157
<p>
1158
To use column mapping, declare a static array named <tt>COLUMN_MAPPING</tt> in the Active Record class.
1159
The keys of the array are column names (called <i>physical column names</i>) as defined in the database
1160
schema, while the values are corresponding property names (called <i>logical column names</i>) defined
1161
in the Active Record class. The property names can be either public class member variable names or
1162
component property names defined via getters/setters. If a physical column name happens to be the same
1163
as the logical column name, they do not need to be listed in <tt>COLUMN_MAPPING</tt>.
1164
</p>
1165
<com:TTextHighlighter Language="php" CssClass="source block-content">
1166
class UserRecord extends TActiveRecord
1167
{
1168
	const TABLE='users';
1169
	public static $COLUMN_MAPPING=array
1170
	(
1171
		'user_id'=>'id',
1172
		'email_address'=>'email',
1173
		'first_name'=>'firstName',
1174
		'last_name'=>'lastName',
1175
	);
1176
	public $id;
1177
	public $username; // the physical and logical column names are the same
1178
	public $email;
1179
	public $firstName;
1180
	public $lastName;
1181
	//....
1182
}
1183
</com:TTextHighlighter>
1184
<p>
1185
With the above column mapping, we can address <tt>first_name</tt> using <tt>$userRecord->firstName</tt>
1186
instead of <tt>$userRecord->first_name</tt>. This helps separation of logic and model.
1187
</p>
1188
 
1189
<h2 id="138054">References</h2>
1190
<ul id="u3" class="block-content">
1191
    <li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>,
1192
    Addison Wesley, 2002.</li>
1193
	<li>B. Venners with B. Eckel. <i><a href="http://www.artima.com/intv/abstract3.html">Inappropriate Abstractions - A Conversation with Anders Hejlsberg, Part VI.</a></i>
1194
	Artima Developer, 2003.
1195
	</li>
1196
 
1197
</ul>
1198
 
1199
<div class="last-modified">$Id: ActiveRecord.page 2530 2008-10-16 09:03:39Z mikl $</div></com:TContent>