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