MT::Object - Movable Type base class for database-backed objects
Creating an MT::Object subclass:
package MT::Foo;
use strict;
use MT::Object;
@MT::Foo::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
columns => [
'id', 'foo',
],
indexes => {
foo => 1,
},
datasource => 'foo',
});
Using an MT::Object subclass:
use MT;
use MT::Foo;
## Create an MT object to load the system configuration and
## initialize an object driver.
my $mt = MT->new;
## Create an MT::Foo object, fill it with data, and save it;
## the object is saved using the object driver initialized above.
my $foo = MT::Foo->new;
$foo->foo('bar');
$foo->save
or die $foo->errstr;
MT::Object is the base class for all Movable Type objects that will be serialized/stored to some location for later retrieval; this location could be a DBM file, a relational database, etc.
Movable Type objects know nothing about how they are stored--they know only of what types of data they consist, the names of those types of data (their columns), etc. The actual storage mechanism is in the MT::ObjectDriver class and its driver subclasses; MT::Object subclasses, on the other hand, are essentially just standard in-memory Perl objects, but with a little extra self-knowledge.
This distinction between storage and in-memory representation allows objects to be serialized to disk in many different ways--for example, an object could be stored in a MySQL database, in a DBM file, etc. Adding a new storage method is as simple as writing an object driver--a non-trivial task, to be sure, but one that will not require touching any other Movable Type code.
Creating a subclass of MT::Object is very simple; you simply need to define the properties and metadata about the object you are creating. Start by declaring your class, and inheriting from MT::Object:
package MT::Foo;
use strict;
use MT::Object;
@MT::Foo::ISA = qw( MT::Object );
Then call the install_properties method on your class name; an easy way to get your class name is to use the special __PACKAGE__ variable:
__PACKAGE__->install_properties({
columns => [
'id', 'foo',
],
indexes => {
foo => 1,
},
datasource => 'foo',
});
install_properties performs the necessary magic to install the metadata about your new class in the MT system. The method takes one argument, a hash reference containing the metadata about your class. That hash reference can have the following keys:
The value for the columns key should be a reference to an array containing the names of your columns.
The value for the indexes key should be a reference to a hash containing
column names as keys, and the value 1 for each key--each key represents
a column that should be indexed.
NOTE: with the DBM driver, if you do not set up an index on a column you will not be able to select objects with values matching that column using the load and load_iter interfaces (see below).
NOTE: created_by and modified_by are not currently used.
Before using (loading, saving, removing) an MT::Object class and its objects, you must always initialize the Movable Type system. This is done with the following lines of code:
use MT;
my $mt = MT->new;
Constructing a new MT objects loads the system configuration from the mt.cfg configuration file, then initializes the object driver that will be used to manage serialized objects.
To create a new object of an MT::Object class, use the new method:
my $foo = MT::Foo->new;
new takes no arguments, and simply initializes a new in-memory object. In fact, you need not ever save this object to disk; it can be used as a purely in-memory object.
To set the column value of an object, use the name of the column as a method name, and pass in the value for the column:
$foo->foo('bar');
The return value of the above call will be bar, the value to which you have
set the column.
To retrieve the existing value of a column, call the same method, but without an argument:
$foo->foo
This returns the value of the foo column from the $foo object.
To save an object using the object driver, call the save method:
$foo->save;
On success, save will return some true value; on failure, it will return
undef, and you can retrieve the error message by calling the errstr
method on the object:
$foo->save
or die "Saving foo failed: ", $foo->errstr;
If you are saving objects in a loop, take a look at the Note on Object Locking.
You can load an object from the datastore using the load method. load is by far the most complicated method, because there are many different ways to load an object: by ID, by column value, by using a join with another type of object, etc.
In addition, you can load objects either into an array (load), or by using an iterator to step through the objects (load_iter).
load has the following general form:
my @objects = CLASS->load(\%terms, \%arguments);
load_iter has the following general form:
my $iter = CLASS->load(\%terms, \%arguments);
Both methods share the same parameters; the only difference is the manner in which they return the matching objects.
If you call load in scalar context, only the first row of the array @objects will be returned; this works well when you know that your load call can only ever result in one object returned--for example, when you load an object by ID.
\%terms should be either:
bar, you could do this:
my @foo = MT::Foo->load({ foo => 'bar' });
In addition to a simple scalar, the hash value can be a reference to an array; combined with the range setting in the \%arguments list, you can use this to perform range searches. If the value is a reference, the first element in the array specifies the low end of the range, and the second element the high end.
\%arguments should be a reference to a hash containing parameters for the search. The following parameters are allowed:
column; column must be an
indexed column (see indexes, above).
ascend.
N objects.
N
matches (the default), return matches M through N + M.
N matches, return the first N matches where column (the sort
column) is greater than value.
The value of range should be a hash reference, where the keys are column
names, and the values are all 1; each key specifies a column that should
be interpreted as a range.
N
entries most recently commented-upon; the sorting is based on MT::Comment
objects, but the objects returned are actually MT::Entry objects. Using
join in this situation is faster than loading the most recent
MT::Comment objects, then loading each of the MT::Entry objects
individually.
Note that join is not a normal SQL join, in that the objects returned are always of only one type--in the above example, the objects returned are only MT::Entry objects, and cannot include columns from MT::Comment objects.
join has the following general syntax:
join => [ CLASS, JOIN_COLUMN, I<\%terms>, I<\%arguments> ]
CLASS is the class with which you are performing the join; JOIN_COLUMN is the column joining the two object tables. \%terms and \%arguments have the same meaning as they do in the outer load or load_iter argument lists: they are used to select the objects with which the join is performed.
For example, to select the last 10 most recently commmented-upon entries, you could use the following statement:
my @entries = MT::Entry->load(undef, {
'join' => [ 'MT::Comment', 'entry_id',
{ blog_id => $blog_id },
{ 'sort' => 'created_on',
direction => 'descend',
unique => 1,
limit => 10 } ]
});
In this statement, the unique setting ensures that the MT::Entry objects returned are unique; if this flag were not given, two copies of the same MT::Entry could be returned, if two comments were made on the same entry.
This is really only useful when used within a join, because when loading data out of a single object datastore, the objects are always going to be unique.
To remove an object from the datastore, call the remove method on an object that you have already loaded using load:
$foo->remove;
On success, remove will return some true value; on failure, it will return
undef, and you can retrieve the error message by calling the errstr
method on the object:
$foo->remove
or die "Removing foo failed: ", $foo->errstr;
If you are removing objects in a loop, take a look at the Note on Object Locking.
To quickly remove all of the objects of a particular class, call the remove_all method on the class name in question:
MT::Foo->remove_all;
On success, remove_all will return some true value; on failure, it will
return undef, and you can retrieve the error message by calling the
errstr method on the class name:
MT::Foo->remove_all
or die "Removing all foo objects failed: ", MT::Foo->errstr;
To determine how many objects meeting a particular set of conditions exist, use the count method:
my $count = MT::Foo->count({ foo => 'bar' });
count takes the same arguments (\%terms and \%arguments) as load and load_iter, above.
To check an object for existence in the datastore, use the exists method:
if ($foo->exists) {
print "Foo $foo already exists!";
}
When you read objects from the datastore, the object table is locked with a shared lock; when you write to the datastore, the table is locked with an exclusive lock.
Thus, note that saving or removing objects in the same loop where you are loading them from an iterator will not work--the reason is that the datastore maintains a shared lock on the object table while objects are being loaded from the iterator, and thus the attempt to gain an exclusive lock when saving or removing an object will cause deadlock.
For example, you cannot do the following:
my $iter = MT::Foo->load_iter({ foo => 'bar ');
while (my $foo = $iter->()) {
$foo->remove;
}
Instead you should do either this:
my @foo = MT::Foo->load({ foo => 'bar' );
for my $foo (@foo) {
$foo->remove;
}
or this:
my $iter = MT::Foo->load_iter({ foo => 'bar');
my @to_remove;
while (my $foo = $iter->()) {
push @to_remove, $foo
if SOME CONDITION;
}
for my $foo (@to_remove) {
$foo->remove;
}
This last example is useful if you will not be removing every MT::Foo
object where foo equals bar, because it saves memory--only the
MT::Foo objects that you will be deleting are kept in memory at the same
time.
Please see the MT manpage for author, copyright, and license information.