root/branches/release-34/lib/MT/Object.pm @ 1846

Revision 1846, 60.1 kB (checked in by bchoate, 20 months ago)

Handle dbi driver selection with multiple levels of caching drivers.

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Object;
8
9use strict;
10use base qw( Data::ObjectDriver::BaseObject MT::ErrorHandler );
11
12use MT;
13use MT::Util qw(offset_time_list);
14
15my (@PRE_INIT_PROPS, @PRE_INIT_META);
16
17sub install_pre_init_properties {
18    # Just in case; to prevent any weird recursion
19    local $MT::plugins_installed = 1;
20
21    foreach my $def (@PRE_INIT_PROPS) {
22        my ($class, $props) = @$def;
23        $class->install_properties($props);
24    }
25    @PRE_INIT_PROPS = ();
26
27    foreach my $def (@PRE_INIT_META) {
28        my ($class, $meta) = @$def;
29        $class->install_meta($meta);
30    }
31    @PRE_INIT_META = ();
32}
33
34sub install_properties {
35    my $class = shift;
36    my ($props) = @_;
37
38    if ( ( $class ne 'MT::Config') && ( !$MT::plugins_installed ) ) {
39        # We're too early in the phase of MT's bootstrapping to
40        # be installing properties; we can't query the registry yet
41        # since plugins are not all accounted for. So save this
42        # set of properties to install it later (odds are, the
43        # package has been loaded to afford installing callbacks
44        # or accessing constants and isn't being used to load
45        # actual data.)
46        #
47        # The only exception to this rule is MT::Config; we must
48        # have access to the MT configuration table in order to
49        # bootstrap MT.
50
51        push @PRE_INIT_PROPS, [$class, $props];
52        return;
53    }
54
55    my $super_props = $class->SUPER::properties();
56    if ($super_props) {
57        # subclass; merge hash
58        for (qw(primary_key meta_column class_column datasource driver audit meta)) {
59            $props->{$_} = $super_props->{$_}
60                if exists $super_props->{$_} && !(exists $props->{$_});
61        }
62        for my $p (qw(column_defs defaults indexes meta_columns)) {
63            if (exists $super_props->{$p}) {
64                foreach my $k (keys %{ $super_props->{$p} }) {
65                    if (!exists $props->{$p}{$k}) {
66                        $props->{$p}{$k} = $super_props->{$p}{$k};
67                    }
68                }
69                if ($p eq 'column_defs') {
70                    $class->__parse_defs($props->{column_defs});
71                }
72            }
73        }
74        if ($super_props->{class_type}) {
75            # copy reference of class_to_type/type_to_class hashes
76            $props->{__class_to_type} = $super_props->{__class_to_type};
77            $props->{__type_to_class} = $super_props->{__type_to_class};
78        }
79    }
80
81    # Legacy MT::Object types only define 'columns'; we still support that
82    # but they aren't handled properly with the upgrade system as a result.
83    if (exists $props->{column_defs}) {
84        $props->{columns} = [ keys %{ $props->{column_defs} } ];
85    } else {
86        map { $props->{column_defs}{$_} = () } @{ $props->{columns} };
87    }
88
89    # Support audit flags
90    if ($props->{audit}) {
91        unless (exists $props->{column_defs}{created_on}) {
92            $props->{column_defs}{created_on} = 'datetime';
93            $props->{column_defs}{created_by} = 'integer';
94            $props->{column_defs}{modified_on} = 'datetime';
95            $props->{column_defs}{modified_by} = 'integer';
96            push @{ $props->{columns} }, qw( created_on created_by modified_on modified_by );
97        }
98    }
99
100    # Metadata column
101    $props->{meta_column} ||= 'meta' if exists $props->{meta};
102    if (my $col = $props->{meta_column}) {
103        if (!$props->{column_defs}{$col}) {
104            $props->{column_defs}{$col} = 'blob';
105            push @{ $props->{columns} }, $col;
106        }
107        no strict 'refs'; ## no critic
108        *{$class . '::' . $col} = \&__meta_column;
109        $class->add_trigger( pre_save => \&pre_save_serialize_metadata );
110    }
111
112    # Classed object types
113    $props->{class_column} ||= 'class' if exists $props->{class_type};
114    if (my $col = $props->{class_column}) {
115        if (!$props->{column_defs}{$col}) {
116            $props->{column_defs}{$col} = 'string(255)';
117            push @{$props->{columns}}, $col;
118            $props->{indexes}{$col} = 1;
119        }
120        if (!$super_props || !$super_props->{class_column}) {
121            $class->add_trigger( pre_search => \&pre_search_scope_terms_to_class );
122            $class->add_trigger( post_load => \&post_load_rebless_object );
123        }
124        if (my $type = $props->{class_type}) {
125            $props->{defaults}{$col} = $type;
126            $props->{__class_to_type}{$class} = $type;
127            $props->{__type_to_class}{$type} = $class;
128        }
129    }
130
131    my $type_id;
132    if ($type_id = $props->{class_type}) {
133        if ($type_id ne $props->{datasource}) {
134            $type_id = $props->{datasource} . '.' . $type_id;
135        }
136    } else {
137        $type_id = $props->{datasource};
138    }
139
140    $class->SUPER::install_properties($props);
141
142    # check for any supplemental columns from other components
143    my $more_props = MT->registry('object_types', $type_id);
144    if ($more_props && (ref($more_props) eq 'ARRAY')) {
145        my $cols = {};
146        for my $prop (@$more_props) {
147            next if ref($prop) ne 'HASH';
148            MT::__merge_hash($cols, $prop, 1);
149        }
150        my @classes = grep { !ref($_) } @$more_props;
151        foreach my $isa_class (@classes) {
152            next if UNIVERSAL::isa($class, $isa_class);
153            eval "require $isa_class;" or die;
154            no strict 'refs'; ## no critic
155            push @{$class . '::ISA'}, $isa_class;
156        }
157        if (%$cols) {
158            # special case for 'plugin' key...
159            delete $cols->{plugin} if exists $cols->{plugin};
160            for my $name (keys %$cols) {
161                next if exists $props->{column_defs}{$name};
162                $class->install_column($name, $cols->{$name});
163                $props->{indexes}{$name} = 1
164                    if $cols->{$name} =~ m/\bindexed\b/;
165                if ($cols->{$name} =~ m/\bdefault (?:'([^']+?)'|(\d+))\b/) {
166                    $props->{defaults}{$name} = defined $1 ? $1 : $2;
167                }
168            }
169        }
170    }
171
172    my $pk = $props->{primary_key} || '';
173    @{$props->{columns}} = sort { $a eq $pk ? -1 : $b eq $pk ? 1 : $a cmp $b }
174        @{$props->{columns}};
175
176    # Child classes are declared as an array;
177    # convert them to a hashref for easier lookup.
178    if ((ref $props->{child_classes}) eq 'ARRAY') {
179        my $classes = $props->{child_classes};
180        $props->{child_classes} = {};
181        @{$props->{child_classes}}{@$classes} = ();
182    }
183
184    # We're declared as a child of some other class; associate ourselves
185    # with that package (the invoking class should have already use'd it.)
186    if (exists $props->{child_of}) {
187        my $parent_classes = $props->{child_of};
188        if (!ref $parent_classes) {
189            $parent_classes = [ $parent_classes ];
190        }
191        foreach my $pc (@$parent_classes) {
192            my $pp = $pc->properties;
193            $pp->{child_classes} ||= {};
194            $pp->{child_classes}{$class} = ();
195        }
196    }
197
198    # Special handling for 'Taggable' objects; automatic saving
199    # and removal of tags.
200    my @isa;
201    {
202        no strict 'refs';
203        @isa = @{ $class . '::ISA' };
204    }
205    foreach my $isa_pkg ( @isa ) {
206        next unless $isa_pkg =~ /able$/;
207        next if $isa_pkg eq $class;
208        if ($isa_pkg->can('install_properties')) {
209            $isa_pkg->install_properties($class);
210        }
211    }
212
213    # install legacy date translation
214    if (0 < scalar @{ $class->columns_of_type('datetime', 'timestamp') }) {
215        if ($props->{audit}) {
216            $class->add_trigger( pre_save  => \&assign_audited_fields);
217            $class->add_trigger( post_save => \&translate_audited_fields );
218        }
219
220        $class->add_trigger( pre_save  => get_date_translator(\&ts2db, 1) );
221        $class->add_trigger( post_load => get_date_translator(\&db2ts, 0) );
222    }
223
224    if ( exists($props->{cacheable}) && !$props->{cacheable} ) {
225        no warnings 'redefine';
226        no strict 'refs'; ## no critic
227        *{$class . '::driver'} = sub { $_[0]->dbi_driver(@_) };
228    }
229
230    return $props;
231}
232
233# A post-load trigger for classed objects
234sub post_load_rebless_object {
235    my $obj = shift;
236    my $props = $obj->properties;
237    if (my $col = $props->{class_column}) {
238        my $type = $obj->column($col);
239        my $pkg = ref($obj);
240        if ($pkg->class_type ne $type) {
241            if (my $class = $props->{__type_to_class}{$type}) {
242                bless $obj, $class;
243            } else {
244                my %models = map { $_ => 1 } MT->models($props->{datasource});
245                if (exists $models{ $props->{datasource} . '.' . $type}) {
246                    $class = MT->model($props->{datasource} . '.' . $type);
247                } elsif (exists $models{$type}) {
248                    $class = MT->model($type);
249                }
250                bless $obj, $class if $class;
251            }
252        }
253    }
254}
255
256# A pre-search trigger for classed objects
257sub pre_search_scope_terms_to_class {
258    my ($class, $terms, $args) = @_;
259    # scope search terms to class
260
261    $terms ||= {};
262    return if (ref $terms eq 'HASH') && exists($terms->{id});
263
264    my $props = $class->properties;
265    my $col = $props->{class_column}
266        or return;
267    if (ref $terms eq 'HASH') {
268        if (exists $terms->{$col}) {
269            if ($terms->{$col} eq '*') {
270                # class term is '*', which signifies filtering for all classes.
271                # simply delete the term in this case.
272                delete $terms->{$col} ;
273            } elsif ($terms->{$col} =~ m/^(\w+:)\*$/) {
274                # class term is in form "foo:*"; translate to a sql-compatible
275                # syntax of "like 'foo:%'"
276                $terms->{$col} = \"like '$1%'";
277            }
278            # term has been explicitly given or explictly removed. make
279            # no further changes.
280            return;
281        }
282        $terms->{$col} = $props->{class_type};
283    }
284    elsif (ref $terms eq 'ARRAY') {
285        if (my @class_terms = grep { ref $_ eq 'HASH' && 1 == scalar keys %$_ && $_->{$col} } @$terms) {
286            # Filter out any unlimiting class terms (class = *).
287            @$terms = grep { ref $_ ne 'HASH' || 1 != scalar keys %$_ || !$_->{$col} || $_->{$col} ne '*' } @$terms;
288
289            # The class column has been explicitly given or removed, so don't
290            # add one.
291            return;
292        }
293        @$terms = ( { $col => $props->{class_type} } => 'AND' => [ @$terms ] );
294    }
295}
296
297sub class_label {
298    my $pkg = shift;
299    return MT->translate($pkg->datasource);
300}
301
302sub class_label_plural {
303    my $pkg = shift;
304    my $label = $pkg->datasource;
305    $label =~ s/y$/ie/;
306    $label .= 's';
307    return MT->translate($label);
308}
309
310sub class_labels {
311    my $pkg = shift;
312    my @all_types = MT->models($pkg->properties->{datasource});
313    my %names;
314    foreach my $type (@all_types) {
315        my $class = $pkg->class_handler($type);
316        $names{$type} = $class->class_label;
317    }
318    return \%names;
319}
320
321# Returns a hashref of asset identifiers mapped to the localized string
322# used to name them. (Ie, image => 'Image').
323sub class_type {
324    my $pkg = shift;
325    if (ref $pkg) {
326        return $pkg->column($pkg->properties->{class_column});
327    } else {
328        return $pkg->properties->{class_type};
329    }
330}
331
332sub class_handler {
333    my $pkg = shift;
334    my $props = $pkg->properties;
335    my ($type) = @_;
336    my $package = $props->{__type_to_class}{$type};
337    unless ($package) {
338        my $ds = $props->{datasource};
339        if (($type eq $ds) || ($type =~ m/\./)) {
340            $package = MT->model($type);
341        } else {
342            $package = MT->model($ds . '.' . $type);
343        }
344    }
345    if ($package) {
346        if (defined *{$package.'::new'}) {
347            return $package;
348        } else {
349            eval "use $package;";
350            return $package unless $@;
351            eval "use $pkg; $package->new;";
352            return $package unless $@;
353        }
354    }
355    return $pkg;
356}
357
358sub add_class {
359    my $pkg = shift;
360    my ($type, $class) = @_;
361    my $props = $pkg->properties;
362    if ($type =~ m/::/) {
363        ($type, $class) = ($class, $type);
364    }
365
366    if (my $old_class = $props->{__type_to_class}{$type}) {
367        delete $props->{__class_to_type}{$old_class};
368    }
369    $props->{__type_to_class}{$type} = $class;
370    $props->{__class_to_type}{$class} = $type;
371}
372
373# 'meta' metadata column support
374
375sub install_meta {
376    my $class = shift;
377    my ($props) = @_;
378    if ( ( $class ne 'MT::Config' ) && (!$MT::plugins_installed) ) {
379        push @PRE_INIT_META, [$class, $props];
380        return;
381    }
382    my $cprops = $class->properties;
383    my $fields = $cprops->{meta_columns} ||= {};
384    my $meta_col = $cprops->{meta_column};
385    foreach my $name (@{ $props->{columns} }) {
386        $fields->{$name} = ();
387        # Skip adding this method if the class overloads it.
388        # this lets the SUPER::columnname magic do it's thing
389        unless ($class->can($name)) {
390            no strict 'refs'; ## no critic
391            *{"${class}::$name"} = sub { shift->$meta_col($name, @_) };
392        }
393    }
394}
395
396sub has_meta {
397    my $props = $_[0]->properties;
398    return $props->{meta} && (@_ > 1 ? exists $props->{meta_columns}{$_[1]} : 1);
399}
400
401sub pre_save_serialize_metadata {
402    my ($obj) = shift;
403    my $meta_col = $obj->properties->{meta_column};
404    if ($obj->{changed_cols}{$meta_col}) {
405        require MT::Serialize;
406        my $meta = $obj->$meta_col;
407        $obj->$meta_col(MT::Serialize->serialize(\$meta));
408    }
409}
410
411sub __thaw_meta {
412    my ($meta) = @_;
413    $$meta = '' unless defined $$meta;
414    require MT::Serialize;
415    my $out = MT::Serialize->unserialize($$meta);
416    if (ref $out eq 'REF') {
417        return $$out;
418    } else {
419        return {};
420    }
421}
422
423# $obj->meta returns a hashref of metadata information
424# $obj->meta($scalar) allows assignment of a serialized value
425# $obj->meta('name', 'value') assigns an individual metadata element
426# $obj->meta('name') returns an individual metadata value
427# $obj->save will automatically serialize the metadata back to the database
428sub __meta_column {
429    my $obj = shift;
430    my $meta_col = $obj->properties->{meta_column} or return;
431
432    if (@_) {
433        my $var = shift;
434        if ((defined $var) && ($var =~ m/^SERG\0\0\0\0/)) {
435            return $obj->column($meta_col, $var);
436        }
437        my $meta = $obj->column($meta_col);
438        if (!defined($meta)) {
439            $obj->{column_values}{$meta_col} = $meta = {};
440        } elsif (!ref $meta) {
441            $obj->{column_values}{$meta_col} = $meta = __thaw_meta(\$meta);
442        }
443        if (@_) {
444            $meta->{$var} = shift if @_;
445            $obj->{changed_cols}{$meta_col}++;
446        }
447        return $meta->{$var};
448    } else {
449        my $meta = $obj->column($meta_col);
450        if (!ref $meta) {
451            $meta = __thaw_meta(\$meta);
452            $obj->{column_values}{$meta_col} = $meta;
453        }
454        # we should assume changes are going to be made, since
455        # we can't really monitor the hash once it has left us
456        $obj->{changed_cols}{$meta_col}++;
457        return $meta;
458    }
459}
460
461sub ts2db { 
462    return unless $_[0]; 
463    if($_[0] =~ m{ \A \d{4} - }xms) { 
464        return $_[0]; 
465    } 
466    my $ret = sprintf '%04d-%02d-%02d %02d:%02d:%02d', unpack 'A4A2A2A2A2A2', $_[0]; 
467    return $ret; 
468} 
469 
470sub db2ts { 
471    my $ts = $_[0]; 
472    $ts =~ s/(?:\+|-)\d{2}$//; 
473    $ts =~ tr/\- ://d; 
474    return $ts; 
475} 
476
477sub get_date_translator {
478    my $translator = shift;
479    my $change = shift;
480    return sub {
481        my $obj = shift;
482        my $dbd = $obj->driver->dbd;
483        FIELD: for my $field (@{$obj->columns_of_type('datetime', 'timestamp')}) {
484            my $value = $obj->column($field);
485            next FIELD if !defined $value;
486            my $new_val = $translator->($value); 
487            if((defined $new_val) && ($new_val ne $value)) {
488                $obj->column($field, $new_val, { no_changed_flag => !$change });
489            }
490        }
491    };
492}
493
494sub translate_audited_fields {
495    my ($obj, $orig_obj) = @_;
496    my $dbd = $obj->driver->dbd;
497    FIELD: for my $field (qw( created_on modified_on )) {
498        my $value = $orig_obj->column($field);
499        next FIELD if !defined $value;
500        my $new_val = db2ts($value); 
501        if((defined $new_val) && ($new_val ne $value)) {
502            $orig_obj->column($field, $new_val);
503        }
504    }
505    return;
506}
507
508sub nextprev {
509    my $obj = shift;
510    my $class = ref($obj);
511    my %param = @_;
512    my ($direction, $terms, $args, $by_field)
513        = @param{qw( direction terms args by )};
514    return undef unless ($direction eq 'next' || $direction eq 'previous');
515    my $next = $direction eq 'next';
516
517    if (!$by_field) {
518        return if !$class->properties->{audit};
519        $by_field = 'created_on';
520    }
521
522    # Selecting the adjacent object can be tricky since timestamps
523    # are not necessarily unique for entries. If we find that the
524    # next/previous object has a matching timestamp, keep selecting entries
525    # to select all entries with the same timestamp, then compare them using
526    # id as a secondary sort column.
527
528    my ($id, $ts) = ($obj->id, $obj->$by_field());
529    local @$args{qw( sort range_incl )}
530        = ( [ { column => $by_field, desc => $next ? 'ASC' : 'DESC' },
531            { column => 'id', desc => $next ? 'ASC' : 'DESC' } ],
532            { $by_field => 1 });
533
534    my $sibling = $class->load({
535        $by_field => ($next ? [ $ts, undef ] : [ undef, $ts ]),
536        'id' => $id,
537        %{$terms}
538    }, { not => { 'id' => 1 }, limit => 1, %$args });
539
540    return $sibling;
541}
542
543## Drivers.
544
545# Note: Removed methods: set_driver
546
547sub count          { shift->_proxy('count',          @_) }
548sub count_group_by { shift->_proxy('count_group_by', @_) }
549sub sum_group_by   { shift->_proxy('sum_group_by',   @_) }
550sub avg_group_by   { shift->_proxy('avg_group_by',   @_) }
551sub remove_all     { shift->_proxy('remove_all',     @_) }
552
553sub remove {
554    my $obj = shift;
555    my(@args) = @_;
556    if (!ref $obj) {
557        return $obj->driver->direct_remove($obj, @args);
558    } else {
559        return $obj->driver->remove($obj, @args);
560    }
561}
562
563sub load {
564    my $self = shift;
565    if (defined $_[0] && (!ref $_[0] || (ref $_[0] ne 'HASH' && ref $_[0] ne 'ARRAY'))) {
566        return $self->lookup($_[0]);
567    } else {
568        if (wantarray) {
569            ## MT::Object::load returns a list in list context, just like
570            ## a D::OD search.
571            return $self->search(@_);
572        } else {
573            ## MT::Object::load returns the first result in scalar context.
574            my $iter = $self->search(@_);
575            return if !defined $iter;
576            return $iter->();
577        }
578    }
579}
580
581# More or less replacing Data::ObjectDriver::Driver::DBI::search here
582# to provide an 'early-finish' iterator as MT::ObjectDriver had provided.
583
584sub load_iter   {
585    my $class = shift;
586    my($terms, $args) = @_;
587
588    my $driver = $class->driver;
589    my $dbi_driver = $driver;
590
591    while ( $dbi_driver->isa('Data::ObjectDriver::Driver::BaseCache') ) {
592        $dbi_driver = $dbi_driver->fallback;
593    }
594
595    if ($dbi_driver->dbd eq 'MT::ObjectDriver::Driver::SQLite') {
596        # for SQLite, use search method, since this technique
597        # will cause it to lock the table
598        return scalar $class->search(@_);
599    }
600
601    my $rec = {};
602    my $sth = $dbi_driver->fetch($rec, $class, $terms, $args);
603
604    my $iter = sub {
605        ## This is kind of a hack--we need $driver to stay in scope,
606        ## so that the DESTROY method isn't called. So we include it
607        ## in the scope of the closure.
608        my $d = $dbi_driver;
609        my $d2 = $driver;
610
611        if (@_ && ($_[0] eq 'finish')) {
612            if ($sth) {
613                $sth->finish;
614                $dbi_driver->end_query($sth);
615            }
616            undef $sth;
617            return;
618        }
619
620        unless ($sth->fetch) {
621            $sth->finish;
622            $dbi_driver->end_query($sth);
623            return;
624        }
625        my $obj;
626        $obj = $class->new;
627        $obj->set_values_internal($rec);
628        ## Don't need a duplicate as there's no previous version in memory
629        ## to preserve.
630        $obj->call_trigger('post_load') unless $args->{no_triggers};
631        $driver->cache_object($obj) if $obj && (!$args->{fetchonly});
632        $obj;
633    };
634    return $iter;
635}
636
637## Callbacks
638
639sub assign_audited_fields {
640    my ($obj, $orig_obj) = @_;
641    if ($obj->properties->{audit}) {
642        my $blog_id;
643        if ($obj->has_column('blog_id')) {
644            $blog_id = $obj->blog_id;
645        }
646        my @ts = offset_time_list(time, $blog_id);
647        my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
648            $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
649
650        my $app = MT->instance;
651        if ($app && $app->can('user')) {
652            if (my $user = $app->user) {
653                if (!defined $obj->created_on) {
654                    $obj->created_by($user->id);
655                    $orig_obj->created_by($obj->created_by);
656                }
657            }
658        }
659        unless ($obj->created_on) {
660            $obj->created_on($ts);
661            $orig_obj->created_on($ts);
662            # intentionally not calling modified_by to distinguish
663            $obj->modified_on($ts);
664            $orig_obj->modified_on($ts);
665        }
666    }
667}
668
669sub modified_by {
670    my $obj = shift;
671    my ($user_id) = @_;
672    if ($user_id) {
673        if ($obj->properties->{audit}) {
674            my $res = $obj->SUPER::modified_by($user_id);
675
676            my $blog_id;
677            if ($obj->has_column('blog_id')) {
678                $blog_id = $obj->blog_id;
679            }
680            my @ts = offset_time_list(time, $blog_id);
681            my $ts = sprintf '%04d%02d%02d%02d%02d%02d',
682                $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0];
683            $obj->modified_on($ts);
684            return $res;
685        }
686    }
687    return $obj->SUPER::modified_by(@_);
688}
689
690# D::OD uses Class::Trigger. Map the call_trigger calls to also invoke
691# MT's callbacks (but internal Class::Trigger routines should be invoked
692# first in the case of pre-triggers, and last in the case of post-triggers).
693
694sub call_trigger {
695    my $obj = shift;
696    my $name = shift;
697    my $class = ref $obj || $obj;
698    my $pre_trigger = $name =~ m/^pre_/;
699    $obj->SUPER::call_trigger($name, @_) if $pre_trigger;
700    MT->run_callbacks($class . '::' . $name, $obj, @_);
701    $obj->SUPER::call_trigger($name, @_) unless $pre_trigger;
702}
703
704# Support for MT-based callbacks.
705
706sub add_callback {
707    my $class = shift;
708    my $meth = shift;
709    MT->add_callback($class . '::' . $meth, @_);
710}
711
712## Construction/initialization.
713
714sub init {
715    my $obj = shift;
716    $obj->SUPER::init(@_);
717    $obj->set_defaults();
718    return $obj;
719}
720
721sub set_defaults {
722    my $obj = shift;
723    my $defaults = $obj->properties->{'defaults'};
724    $obj->{'column_values'} = $defaults ? {%$defaults} : {};
725}
726
727sub __properties { }
728
729our $DRIVER;
730sub driver {
731    require MT::ObjectDriverFactory;
732    return $DRIVER ||= MT::ObjectDriverFactory->new;
733}
734
735# ref to the fallback driver for non-cacheable classes
736our $DBI_DRIVER;
737sub dbi_driver {
738    unless ($DBI_DRIVER) {
739        my $driver = driver(@_);
740        while ( $driver->can('fallback') ) {
741            if ($driver->fallback) {
742                $driver = $driver->fallback;
743            } else {
744                last;
745            }
746        }
747        $DBI_DRIVER = $driver;
748    }
749    return $DBI_DRIVER;
750}
751
752sub table_name {
753    my $obj = shift;
754    return $obj->driver->table_for($obj);
755}
756
757sub clone {
758    my $obj = shift;
759    my($param) = @_;
760    my $clone = $obj->SUPER::clone_all;
761
762    ## If the caller has listed a set of columns not to copy to the clone,
763    ## delete them from the clone.
764    if ($param && ($param->{Except} || $param->{except})) {
765        for my $col (keys %{ $param->{Except} || $param->{except} }) {
766            $clone->$col(undef);
767        }
768    }
769    return $clone;
770}
771
772sub columns_of_type {
773    my $obj = shift;
774    my(@types) = @_;
775    my $props = $obj->properties;
776    my $cols = $props->{columns};
777    my $col_defs = $obj->column_defs;
778    my @cols;
779    my %types = map { $_ => 1 } @types;
780    for my $col (@$cols) {
781        push @cols, $col
782            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
783    }
784    \@cols;
785}
786
787sub created_on_obj {
788    my $obj = shift;
789    return $obj->column_as_datetime('created_on');
790}
791
792sub column_as_datetime {
793    my $obj = shift;
794    my ($col) = @_;
795    if (my $ts = $obj->column($col)) {
796        my $blog;
797        if ($obj->isa('MT::Blog')) {
798            $blog = $obj;
799        } else {
800            if (my $blog_id = $obj->blog_id) {
801                require MT::Blog;
802                $blog = MT::Blog->lookup($blog_id);
803            }
804        }
805        my($y, $mo, $d, $h, $m, $s) = $ts =~ /(\d\d\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)[^\d]?(\d\d)/;
806        require MT::DateTime;
807        my $four_digit_offset;
808        if ($blog) {
809            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
810                                        60 * abs($blog->server_offset
811                                                 - int($blog->server_offset)));
812        }
813        return new MT::DateTime(year => $y, month => $mo, day => $d,
814                                hour => $h, minute => $m, second => $s,
815                                time_zone => $four_digit_offset);
816    }
817    undef;
818}
819
820sub join_on {
821    return [ @_ ];
822}
823
824sub remove_children {
825    my $obj = shift;
826    return 1 unless ref $obj;
827
828    my ($param) = @_;
829    my $child_classes = $obj->properties->{child_classes} || {};
830    my @classes = keys %$child_classes;
831    return 1 unless @classes;
832
833    $param ||= {};
834    my $key = $param->{key} || $obj->datasource . '_id';
835    my $obj_id = $obj->id;
836    for my $class (@classes) {
837        eval "use $class;";
838        $class->remove({ $key => $obj_id });
839    }
840    1;
841}
842
843sub get_by_key {
844    my $class = shift;
845    my ($key) = @_;
846    my($obj) = $class->search($key);
847    $obj ||= new $class;
848    $obj->set_values($key);
849    return $obj;
850}
851
852sub set_by_key {
853    my $class = shift;
854    my ($key, $value) = @_;
855    my ($obj) = $class->search($key);
856    unless ($obj) {
857        $obj = new $class;
858        $obj->set_values($key);
859    }
860    $obj->set_values($value) if $value;
861    $obj->save or return;
862    return $obj;
863}
864
865# This method is overridden since D::OD uses column_values to retrieve
866# the content to cache if caching is enabled. Thus, we must ensure any
867# metadata is serialized prior to caching.
868sub column_values {
869    my $props = $_[0]->properties;
870    if ($props->{meta_column}
871        && $_[0]->{changed_cols}{$props->{meta_column}}) {
872        $_[0]->pre_save_serialize_metadata;
873    }
874    return $_[0]->SUPER::column_values(@_);
875}
876
877# We override D::OD's set_values method here only allowing the
878# assignment of a column if the value given is defined. There are
879# some legacy reasons for doing this, mostly for backward
880# compatibility.
881sub set_values {
882    my $obj = shift;
883    my ($values) = @_;
884    for my $col (keys %$values) {
885        unless ( $obj->has_column($col) ) {
886            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
887        }
888        $obj->$col($values->{$col}) if defined $values->{$col};
889    }
890}
891
892sub column_def {
893    my $obj = shift;
894    my ($name) = @_;
895    my $defs = $obj->column_defs;
896    my $def = $defs->{$name};
897    if (!ref($def)) {
898        $defs->{$name} = $def = $obj->__parse_def($name, $def);
899    }
900    return $def;
901}
902
903sub index_defs {
904    my $obj = shift;
905    my $props = $obj->properties;
906    $props->{indexes};
907}
908
909sub column_defs {
910    my $obj = shift;
911    my $props = $obj->properties;
912    my $defs = $props->{column_defs};
913    return undef if !$defs;
914    my ($key) = keys %$defs;
915    if (!(ref $defs->{$key})) {
916        $obj->__parse_defs($props->{column_defs});
917    }
918    $props->{column_defs};
919}
920
921sub __parse_defs {
922    my $obj = shift;
923    my ($defs) = @_;
924    foreach my $col ( keys %$defs ) {
925        next if ref($defs->{$col});
926        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
927    }
928}
929
930sub __parse_def {
931    my $obj = shift;
932    my ($col, $def) = @_;
933    return undef if !defined $def;
934    my $props = $obj->properties;
935    my %def;
936    if ($def =~ s/^([^( ]+)\s*//) {
937        $def{type} = $1;
938    }
939    if ($def =~ s/^\((\d+)\)\s*//) {
940        $def{size} = $1;
941    }
942    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
943    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
944    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
945    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
946    $def{default} = $props->{defaults}{$col} if exists $props->{defaults}{$col};
947    \%def;
948}
949
950sub cache_property {
951    my $obj = shift;
952    my $key = shift;
953    my $code = shift;
954    if (ref $key eq 'CODE') {
955        ($key, $code) = ($code, $key);
956    }
957    $key ||= (caller(1))[3];
958
959    my $r = MT->request;
960    my $oc = $r->cache('object_cache');
961    unless ($oc) {
962        $oc = {};
963        $r->cache('object_cache', $oc);
964    }
965    $oc = $oc->{"$obj"} ||= {};
966    if (@_) {
967        $oc->{$key} = $_[0];
968    } else {
969        if ((!exists $oc->{$key}) && $code) {
970            $oc->{$key} = $code->($obj, @_);
971        }
972    }
973    return exists $oc->{$key} ? $oc->{$key} : undef;
974}
975
976sub clear_cache {
977    my $obj = shift;
978    my $oc = MT->request('object_cache') or return;
979    if (@_) {
980        $oc = $oc->{"$obj"};
981        delete $oc->{shift} if $oc;
982    } else {
983        delete $oc->{"$obj"};
984    }
985}
986
987sub to_hash {
988    my $obj = shift;
989    my $hash = {};
990    my $props = $obj->properties;
991    my $pfx = $obj->datasource;
992    my $values = $obj->column_values;
993    foreach (keys %$values) {
994        $hash->{"${pfx}.$_"} = $values->{$_};
995    }
996    if (my $meta = $props->{meta_columns}) {
997        foreach (keys %$meta) {
998            $hash->{"${pfx}.$_"} = $obj->meta($_);
999        }
1000    }
1001    if ($obj->has_column('blog_id')) {
1002        my $blog_id = $obj->blog_id;
1003        require MT::Blog;
1004        if (my $blog = MT::Blog->lookup($blog_id)) {
1005            my $blog_hash = $blog->to_hash;
1006            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1007        }
1008    }
1009    $hash;
1010}
1011
10121;
1013__END__
1014
1015=head1 NAME
1016
1017MT::Object - Movable Type base class for database-backed objects
1018
1019=head1 SYNOPSIS
1020
1021Creating an I<MT::Object> subclass:
1022
1023    package MT::Foo;
1024    use strict;
1025
1026    use base 'MT::Object';
1027
1028    __PACKAGE__->install_properties({
1029        columns_defs => {
1030            'id'  => 'integer not null auto_increment',
1031            'foo' => 'string(255)',
1032        },
1033        indexes => {
1034            foo => 1,
1035        },
1036        primary_key => 'id',
1037        datasource => 'foo',
1038    });
1039
1040Using an I<MT::Object> subclass:
1041
1042    use MT;
1043    use MT::Foo;
1044
1045    ## Create an MT object to load the system configuration and
1046    ## initialize an object driver.
1047    my $mt = MT->new;
1048
1049    ## Create an MT::Foo object, fill it with data, and save it;
1050    ## the object is saved using the object driver initialized above.
1051    my $foo = MT::Foo->new;
1052    $foo->foo('bar');
1053    $foo->save
1054        or die $foo->errstr;
1055
1056=head1 DESCRIPTION
1057
1058I<MT::Object> is the base class for all Movable Type objects that will be
1059serialized/stored to some location for later retrieval; this location could
1060be a DBM file, a relational database, etc.
1061
1062Movable Type objects know nothing about how they are stored--they know only
1063of what types of data they consist, the names of those types of data (their
1064columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1065class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1066are essentially just standard in-memory Perl objects, but with a little extra
1067self-knowledge.
1068
1069This distinction between storage and in-memory representation allows objects
1070to be serialized to disk in many different ways--for example, an object could
1071be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1072is as simple as writing an object driver--a non-trivial task, to be sure, but
1073one that will not require touching any other Movable Type code.
1074
1075=head1 SUBCLASSING
1076
1077Creating a subclass of I<MT::Object> is very simple; you simply need to
1078define the properties and metadata about the object you are creating. Start
1079by declaring your class, and inheriting from I<MT::Object>:
1080
1081    package MT::Foo;
1082    use strict;
1083
1084    use base 'MT::Object';
1085
1086=item * __PACKAGE__->install_properties($args)
1087
1088Then call the I<install_properties> method on your class name; an easy way
1089to get your class name is to use the special I<__PACKAGE__> variable:
1090
1091    __PACKAGE__->install_properties({
1092        column_defs => {
1093            'id' => 'integer not null auto_increment',
1094            'foo' => 'string(255)',
1095        },
1096        indexes => {
1097            foo => 1,
1098        },
1099        primary_key => 'id',
1100        datasource => 'foo',
1101    });
1102
1103I<install_properties> performs the necessary magic to install the metadata
1104about your new class in the MT system. The method takes one argument, a hash
1105reference containing the metadata about your class. That hash reference can
1106have the following keys:
1107
1108=over 4
1109
1110=item * column_defs
1111
1112The definition of the columns (fields) in your object. Column names are also
1113used for method names for your object, so your column name should not
1114contain any strange characters. (It could also be used as part of the name of
1115the column in a relational database table, so that is another reason to keep
1116column names somewhat sane.)
1117
1118The value for the I<columns> key should be a reference to an hashref
1119containing the key/value pairs that are names of your columns matched with
1120their schema definition.
1121
1122The type declaration of a column is pseudo-SQL. The data types loosely match
1123SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1124to appropriate types for the database it services. The format of a column
1125type is as follows:
1126
1127    'column_name' => 'type(size) options'
1128
1129The 'type' part of the declaration can be any one of:
1130
1131=over 4
1132
1133=item * string
1134
1135For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1136
1137=item * integer
1138
1139For storing integers, maybe limited to 32 bits.
1140
1141=item * boolean
1142
1143For storing boolean values (numeric values of 1 or 0).
1144
1145=item * smallint
1146
1147For storing small integers, typically limited to 16 bits.
1148
1149=item * datetime
1150
1151For storing a full date and time value.
1152
1153=item * timestamp
1154
1155For storing a date and time that automatically updates upon save.
1156
1157=item * blob
1158
1159For storing binary data.
1160
1161=item * text
1162
1163For storing text data.
1164
1165=item * float
1166
1167For storing floating point values.
1168
1169=back
1170
1171Note: The physical data storage capacity of these types will vary depending on
1172the driver's implementation. Please refer to the documentation of the
1173MT::ObjectDriver you're using to determine the actual capacity for these
1174types.
1175
1176The '(size)' element of the declaration is only valid for the 'string' type.
1177
1178The 'options' element of the declaration is not required, but is used to
1179specify additional attributes of the column. Such as:
1180
1181=over 4
1182
1183=item * not null
1184
1185Specify this option when you wish to constrain the column so that it must contain a defined value. This is only enforced by the database itself, not by the MT::ObjectDriver.
1186
1187=item * auto_increment
1188
1189Specify for integer columns (typically the primary key) to automatically assign a value.
1190
1191=item * primary key
1192
1193Specify for identifying the column as the primary key (only valid for a single column).
1194
1195=back
1196
1197=item * indexes
1198
1199Specifies the column indexes on your objects; this only has consequence for
1200some object drivers (DBM, for example), where indexes are not automatically
1201maintained by the datastore (as they are in a relational database).
1202
1203The value for the I<indexes> key should be a reference to a hash containing
1204column names as keys, and the value C<1> for each key--each key represents
1205a column that should be indexed.
1206
1207B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1208will not be able to select objects with values matching that column using the
1209I<load> and I<load_iter> interfaces (see below).
1210
1211=item * audit
1212
1213Automatically adds bookkeeping capabilities to your class--each object will
1214take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1215I<modified_by>. The created_on, created_by columns will be populated
1216automatically (if they have not already been assigned at the time of saving
1217the object). Your application is responsible for updating the modified_on,
1218modified_by columns as these may require explicit application-specific
1219assignments (ie, your application may only want them updated during explicit
1220user interaction with the object, as opposed to cases where the object is
1221being changed and saved for mechanical purposes like upgrading a table).
1222
1223=item * datasource
1224
1225The name of the datasource for your class. The datasource is a name uniquely
1226identifying your class--it is used by the object drivers to construct table
1227names, file names, etc. So it should not be specific to any one driver.
1228
1229=item * meta
1230
1231Specify this property if you wish to add an additional 'meta' column to
1232the object properties. This is a special type of column that is used to
1233store complex data structures for the object. The data is serialized into
1234a blob for storage using the L<MT::Serialize> package.
1235
1236=item * meta_column
1237
1238If you wish to specify the name of the column to be used for storing
1239the object metadata, you may declare this property to name the column.
1240The default column name is 'meta'.
1241
1242=item * class_type
1243
1244If class_type is declared, an additional 'class' column is added to the
1245object properties. This column is then used to differentiate between
1246multiple object types that share the same physical table.
1247
1248Note that if this is used, all searches will be constrained to match
1249the class type of the package.
1250
1251=item * class_column
1252
1253Defines the name of the class column (default is 'class') for storing
1254classed objects (see 'class_type' above).
1255
1256=back
1257
1258=head1 USAGE
1259
1260=head2 System Initialization
1261
1262Before using (loading, saving, removing) an I<MT::Object> class and its
1263objects, you must always initialize the Movable Type system. This is done
1264with the following lines of code:
1265
1266    use MT;
1267    my $mt = MT->new;
1268
1269Constructing a new I<MT> objects loads the system configuration from the
1270F<mt.cfg> configuration file, then initializes the object driver that will
1271be used to manage serialized objects.
1272
1273=head2 Creating a new object
1274
1275To create a new object of an I<MT::Object> class, use the I<new> method:
1276
1277    my $foo = MT::Foo->new;
1278
1279I<new> takes no arguments, and simply initializes a new in-memory object.
1280In fact, you need not ever save this object to disk; it can be used as a
1281purely in-memory object.
1282
1283=head2 Setting and retrieving column values
1284
1285To set the column value of an object, use the name of the column as a method
1286name, and pass in the value for the column:
1287
1288    $foo->foo('bar');
1289
1290The return value of the above call will be C<bar>, the value to which you have
1291set the column.
1292
1293To retrieve the existing value of a column, call the same method, but without
1294an argument:
1295
1296    $foo->foo
1297
1298This returns the value of the I<foo> column from the I<$foo> object.
1299
1300=over 4
1301
1302=item * $obj->init()
1303
1304=back
1305
1306This method is used to initialize the object upon construction.
1307
1308=over 4
1309
1310=item * $obj->set_defaults()
1311
1312=back
1313
1314This method is used by the I<init> method to set the object defaults.
1315
1316=head2 Saving an object
1317
1318To save an object using the object driver, call the I<save> method:
1319
1320=over 4
1321
1322=item * $foo->save();
1323
1324=back
1325
1326On success, I<save> will return some true value; on failure, it will return
1327C<undef>, and you can retrieve the error message by calling the I<errstr>
1328method on the object:
1329
1330    $foo->save
1331        or die "Saving foo failed: ", $foo->errstr;
1332
1333If you are saving objects in a loop, take a look at the
1334L</"Note on object locking">.
1335
1336=head2 Loading an existing object or objects
1337
1338=over 4
1339
1340=item * $obj->load()
1341
1342=item * $obj->load_iter()
1343
1344=back
1345
1346You can load an object from the datastore using the I<load> method. I<load>
1347is by far the most complicated method, because there are many different ways
1348to load an object: by ID, by column value, by using a join with another type
1349of object, etc.
1350
1351In addition, you can load objects either into an array (I<load>), or by using
1352an iterator to step through the objects (I<load_iter>).
1353
1354I<load> has the following general form:
1355
1356    my @objects = MT::Foo->load(\%terms, \%arguments);
1357
1358I<load_iter> has the following general form:
1359
1360    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1361
1362Both methods share the same parameters; the only difference is the manner in
1363which they return the matching objects.
1364
1365If you call I<load> in scalar context, only the first row of the array
1366I<@objects> will be returned; this works well when you know that your I<load>
1367call can only ever result in one object returned--for example, when you load
1368an object by ID.
1369
1370I<\%terms> should be either:
1371
1372=over 4
1373
1374=item * The numeric ID of an object in the datastore.
1375
1376=item * A reference to a hash.
1377
1378The hash should have keys matching column names and the values are the
1379values for that column.
1380
1381For example, to load an I<MT::Foo> object where the I<foo> column is
1382equal to C<bar>, you could do this:
1383
1384    my @foo = MT::Foo->load({ foo => 'bar' });
1385
1386In addition to a simple scalar, the hash value can be a reference to an array;
1387combined with the I<range> setting in the I<\%arguments> list, you can use
1388this to perform range searches. If the value is a reference, the first element
1389in the array specifies the low end of the range, and the second element the
1390high end.
1391
1392=back
1393
1394I<\%arguments> should be a reference to a hash containing parameters for the
1395search. The following parameters are allowed:
1396
1397=over 4
1398
1399=item * sort => "column"
1400
1401Sort the resulting objects by the column C<column>; C<column> must be an
1402indexed column (see L</"indexes">, above).
1403
1404=item * direction => "ascend|descend"
1405
1406To be used together with I<sort>; specifies the sort order (ascending or
1407descending). The default is C<ascend>.
1408
1409=item * limit => "N"
1410
1411Rather than loading all of the matching objects (the default), load only
1412C<N> objects.
1413
1414=item * offset => "M"
1415
1416To be used together with I<limit>; rather than returning the first C<N>
1417matches (the default), return matches C<M> through C<N + M>.
1418
1419=item * start_val => "value"
1420
1421To be used together with I<limit> and I<sort>; rather than returning the
1422first C<N> matches, return the first C<N> matches where C<column> (the sort
1423column) is greater than C<value>.
1424
1425=item * range
1426
1427To be used together with an array reference as the value for a column in
1428I<\%terms>; specifies that the specific column should be searched for a range
1429of values, rather than one specific value.
1430
1431The value of I<range> should be a hash reference, where the keys are column
1432names, and the values are all C<1>; each key specifies a column that should
1433be interpreted as a range.
1434
1435    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1436        { range => { created_on => 1 } } );
1437
1438This selects C<MT::Foo> objects whose created_on date is greater than
14392001-10-08 00:00:00.
1440
1441=item * range_incl
1442
1443Like the 'range' attribute, but defines an inclusive range.
1444
1445=item * join
1446
1447Can be used to select a set of objects based on criteria, or sorted by
1448criteria, from another set of objects. An example is selecting the C<N>
1449entries most recently commented-upon; the sorting is based on I<MT::Comment>
1450objects, but the objects returned are actually I<MT::Entry> objects. Using
1451I<join> in this situation is faster than loading the most recent
1452I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1453individually.
1454
1455Note that I<join> is not a normal SQL join, in that the objects returned are
1456always of only one type--in the above example, the objects returned are only
1457I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1458
1459I<join> has the following general syntax:
1460
1461    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1462
1463Use the actual MT::Object-descended package name and the join_on static method
1464providing these parameters: I<JOIN_COLUMN> is the column joining the two
1465object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1466in the outer I<load> or I<load_iter> argument lists: they are used to select
1467the objects with which the join is performed.
1468
1469For example, to select the last 10 most recently commmented-upon entries, you
1470could use the following statement:
1471
1472    my @entries = MT::Entry->load(undef, {
1473        'join' => MT::Comment->join_on( 'entry_id',
1474                    { blog_id => $blog_id },
1475                    { 'sort' => 'created_on',
1476                      direction => 'descend',
1477                      unique => 1,
1478                      limit => 10 } )
1479    });
1480
1481In this statement, the I<unique> setting ensures that the I<MT::Entry>
1482objects returned are unique; if this flag were not given, two copies of the
1483same I<MT::Entry> could be returned, if two comments were made on the same
1484entry.
1485
1486=item * unique
1487
1488Ensures that the objects being returned are unique.
1489
1490This is really only useful when used within a I<join>, because when loading
1491data out of a single object datastore, the objects are always going to be
1492unique.
1493
1494=back
1495
1496=head2 Removing an object
1497
1498=over 4
1499
1500=item * $foo->remove()
1501
1502=back
1503
1504To remove an object from the datastore, call the I<remove> method on an
1505object that you have already loaded using I<load>:
1506
1507    $foo->remove();
1508
1509On success, I<remove> will return some true value; on failure, it will return
1510C<undef>, and you can retrieve the error message by calling the I<errstr>
1511method on the object:
1512
1513    $foo->remove
1514        or die "Removing foo failed: ", $foo->errstr;
1515
1516If you are removing objects in a loop, take a look at the
1517L</"Note on object locking">.
1518
1519=head2 Removing select objects of a particular class
1520
1521Combining the syntax of the load and remove methods, you can use the
1522static version of the remove method to remove particular objects:
1523
1524    MT::Foo->remove({ bar => 'baz' });
1525
1526The terms you specify to remove by should be indexed columns. This
1527method will load the object and remove it, firing the callback operations
1528associated with those operations.
1529
1530=head2 Removing all of the objects of a particular class
1531
1532To quickly remove all of the objects of a particular class, call the
1533I<remove_all> method on the class name in question:
1534
1535=over 4
1536
1537=item * MT::Foo->remove_all();
1538
1539=back
1540
1541On success, I<remove_all> will return some true value; on failure, it will
1542return C<undef>, and you can retrieve the error message by calling the
1543I<errstr> method on the class name:
1544
1545    MT::Foo->remove_all
1546        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1547
1548=head2 Removing all the children of an object
1549
1550=over 4
1551
1552=item * $obj->remove_children([ \%param ])
1553
1554=back
1555
1556If your class has registered 'child_classes' as part of it's properties,
1557then this method may be used to remove objects that are associated with
1558the active object.
1559
1560This method is typically used in an overridden 'remove' method.
1561
1562    sub remove {
1563        my $obj = shift;
1564        $obj->remove_children({ key => 'object_id' });
1565        $obj->SUPER::remove(@_);
1566    }
1567
1568The 'key' parameter specified here lets you identify the field name used by
1569the children classes to relate back to the parent class. If unspecified,
1570C<remove_children> will assume the key to be the datasource name of the
1571current class with an '_id' suffix.
1572
1573=head2 Getting the count of a number of objects
1574
1575To determine how many objects meeting a particular set of conditions exist,
1576use the I<count> method:
1577
1578    my $count = MT::Foo->count({ foo => 'bar' });
1579
1580I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1581and I<load_iter>, above.
1582
1583=head2 Determining if an object exists in the datastore
1584
1585To check an object for existence in the datastore, use the I<exists> method:
1586
1587    if ($foo->exists) {
1588        print "Foo $foo already exists!";
1589    }
1590
1591=head2 Counting groups of objects
1592
1593=over 4
1594
1595=item * $obj->count_group_by()
1596
1597=back
1598
1599The count_group_by method can be used to retrieve a list of all the
1600distinct values that appear in a given column along with a count of
1601how many objects carry that value. The routine can also be used with
1602more than one column, in which case it retrieves the distinct pairs
1603(or n-tuples) of values in those columns, along with the counts.
1604Yet more powerful, any SQL expression can be used in place of
1605the column names to count how many object produce any given result
1606values when run through those expressions.
1607
1608  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1609
1610C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1611usual way. C<$group_expressions> is an array reference containing the
1612SQL expressions for the values you want to group by. A single row will
1613be returned for each distinct tuple of values resulting from the
1614$group_expressions. For example, if $group_expressions were just a
1615single column (e.g. group => ['created_on']) then a single row would
1616be returned for each distinct value of the 'created_on' column. If
1617$group_expressions were multiple columns, a row would be returned for
1618each distinct pair (or n-tuple) of values found in those columns.
1619
1620Each application of the iterator C<$iter> returns a list in the form:
1621
1622  ($count, $group_val1, $group_val2, ...)
1623
1624Where C<$count> is the number of MT::Foo objects for which the group
1625expressions are the values ($group_val1, $group_val2, ...). These
1626values are in the same order as the corresponding group expressions in
1627the $group_exprs argument.
1628
1629In this example, we load up groups of MT::Pip objects, grouped by the
1630pair (cat_id, invoice_id), and print how many pips have that pair of
1631values.
1632
1633    $iter = MT::Pip->count_group_by(undef,
1634                                    {group => ['cat_id',
1635                                               'invoice_id']});
1636    while (($count, $cat, $inv) = $iter->()) {
1637        print "There are $count Pips with " .
1638            "category $cat and invoice $inv\n";
1639    }
1640
1641=head2 Inspecting and Manipulating Object State
1642
1643=over 4
1644
1645=item * $obj->column_values()
1646
1647=back
1648
1649Use C<column_values> and C<set_values> to get and set the fields of an
1650object I<en masse>. The former returns a hash reference mapping column
1651names to their values in this object. For example:
1652
1653    $values = $obj->column_values()
1654
1655=over 4
1656
1657=item * $obj->set_values()
1658
1659=back
1660
1661C<set_values> accepts a similar hash ref, which need not give a value
1662for every field. For example:
1663
1664    $obj->set_values({col1 => $val1, col2 => $val2});
1665
1666is equivalent to
1667
1668    $obj->col1($val1);
1669    $obj->col2($val2);
1670
1671=head2 Other Methods
1672
1673=over 4
1674
1675=item * $obj->clone([\%param])
1676
1677Returns a clone of C<$obj>. That is, a distinct object which has all
1678the same data stored within it. Changing values within one object does
1679not modify the other.
1680
1681An optional C<except> parameter may be provided to exclude particular
1682columns from the cloning operation. For example, the following would
1683clone the elements of the blog except the name attribute.
1684
1685   $blog->clone({ except => { name => 1 } });
1686
1687=item * $obj->column_names()
1688
1689Returns a list of the names of columns in C<$obj>; includes all those
1690specified to the install_properties method as well as the audit
1691properties (C<created_on>, C<modified_on>, C<created_by>,
1692C<modified_by>), if those were enabled in install_properties.
1693
1694=item * $obj->set_driver()
1695
1696This method sets the object driver to use to link with a database.
1697
1698=item * MT::Foo->driver()
1699
1700=item * $obj->driver()
1701
1702Returns the ObjectDriver object that links this object with a database.
1703
1704=item * $obj->created_on_obj()
1705
1706Returns an MT::DateTime object representing the moment when the
1707object was first saved to the database.
1708
1709=item * MT::Foo->set_by_key($key_terms, $value_terms)
1710
1711A convenience method that loads whatever object matches the C<$key_terms>
1712argument and sets some or all of its fields according to the
1713C<$value_terms>. For example:
1714
1715   MT::Foo->set_by_key({name => 'Thor'},
1716                       {region => 'Norway', gender => 'Male'});
1717
1718This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1719and sets the 'region' and 'gender' fields appropriately.
1720
1721More than one term is acceptable in the C<$key_terms> argument. The
1722matching object is the one that matches all of the C<$key_terms>.
1723
1724This method only useful if you know that there is a unique object
1725matching the given key. There need not be a unique constraint on the
1726columns named in the C<$key_hash>; but if not, you should be confident
1727that only one object will match the key.
1728
1729=item * MT::Foo->get_by_key($key_terms)
1730
1731A convenience method that loads whatever object matches the C<$key_terms>
1732argument. If no matching object is found, a new object will be constructed
1733and the C<$key_terms> provided will be assigned to it. So regardless of
1734whether the key exists already, this method will return an object with the
1735key requested. Note, however: if a new object is instantiated it is
1736not automatically saved.
1737
1738    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1739    $thor->region('Norway');
1740    $thor->gender('Male');
1741    $thor->save;
1742
1743The fact that it returns a new object if one isn't found is to help
1744optimize this pattern:
1745
1746    my $obj = MT::Foo->load({key => $value});
1747    if (!$obj) {
1748        $obj = new MT::Foo;
1749        $obj->key($value);
1750    }
1751
1752This is equivalent to:
1753
1754    my $obj = MT::Foo->get_by_key({key => $value});
1755
1756If you don't appreciate the autoinstantiation behavior of this method,
1757just use the C<load> method instead.
1758
1759More than one term is acceptable in the C<$key_terms> argument. The
1760matching object is the one that matches all of the C<$key_terms>.
1761
1762This method only useful if you know that there is a unique object
1763matching the given key. There need not be a unique constraint on the
1764columns named in the C<$key_hash>; but if not, you should be confident
1765that only one object will match the key.
1766
1767=item * $obj->cache_property($key, $code)
1768
1769Caches the provided key (e.g. entry, trackback) with the return value
1770of the given code reference (which is often an object load call) so
1771that the value does not have to be recomputed each time.
1772
1773=item * $obj->column_def($name)
1774
1775This method returns the value of the given I<$name> C<column_defs>
1776propery.
1777
1778=item * $obj->column_defs()
1779
1780This method returns all the C<column_defs> of the property of the
1781object.
1782
1783=item * $obj->to_hash()
1784
1785TODO - So far I have not divined what this method actually does. Hints?
1786
1787=item * Class->join_on()
1788
1789This method returns the list of used by the join arguments parameter
1790used by the L<MT::App::CMS/listing> method.
1791
1792=item * $obj->properties()
1793
1794TODO - Return the return properties of the object.
1795
1796=item * $obj->to_xml()
1797
1798TODO - Returns the XML representation of the object.
1799This method is defined in MT/BackupRestore.pm - you must first
1800use MT::BackupRestore to use this method.
1801
1802=item * $obj->restore_parent_ids()
1803
1804TODO - Backup file contains parent objects' ids (foreign keys).  However,
1805when parent objcects are restored, their ids will be changed.  This method
1806is to match the old and new ids of parent objects for children objects to be
1807correctly associated.
1808This method is defined in MT/BackupRestore.pm - you must first
1809use MT::BackupRestore to use this method.
1810
1811=item * $obj->parent_names()
1812
1813TODO - Should be overridden by subclasses to return correct hash
1814whose keys are xml element names of the object's parent objects
1815and values are class names of them.
1816This method is defined in MT/BackupRestore.pm - you must first
1817use MT::BackupRestore to use this method.
1818
1819=back
1820
1821=head1 NOTES
1822
1823=head2 Note on object locking
1824
1825When you read objects from the datastore, the object table is locked with a
1826shared lock; when you write to the datastore, the table is locked with an
1827exclusive lock.
1828
1829Thus, note that saving or removing objects in the same loop where you are
1830loading them from an iterator will not work--the reason is that the datastore
1831maintains a shared lock on the object table while objects are being loaded
1832from the iterator, and thus the attempt to gain an exclusive lock when saving
1833or removing an object will cause deadlock.
1834
1835For example, you cannot do the following:
1836
1837    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1838    while (my $foo = $iter->()) {
1839        $foo->remove;
1840    }
1841
1842Instead you should do either this:
1843
1844    my @foo = MT::Foo->load({ foo => 'bar' });
1845    for my $foo (@foo) {
1846        $foo->remove;
1847    }
1848
1849or this:
1850
1851    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1852    my @to_remove;
1853    while (my $foo = $iter->()) {
1854        push @to_remove, $foo
1855            if SOME CONDITION;
1856    }
1857    for my $foo (@to_remove) {
1858        $foo->remove;
1859    }
1860
1861This last example is useful if you will not be removing every I<MT::Foo>
1862object where I<foo> equals C<bar>, because it saves memory--only the
1863I<MT::Foo> objects that you will be deleting are kept in memory at the same
1864time.
1865
1866=head1 CALLBACKS
1867
1868=over 4
1869
1870=item * $obj->add_callback()
1871
1872=back
1873
1874Most MT::Object operations can trigger callbacks to plugin code. Some
1875notable uses of this feature are: to be notified when a database record is
1876modified, or to pre- or post-process the data being flowing to the
1877database.
1878
1879To add a callback, invoke the C<add_callback> method of the I<MT::Object>
1880subclass, as follows:
1881
1882   MT::Foo->add_callback( "pre_save", <priority>,
1883                          <plugin object>, \&callback_function);
1884
1885The first argument is the name of the hook point. Any I<MT::Object>
1886subclass has a pre_ and a post_ hook point for each of the following
1887operations:
1888
1889    load
1890    save
1891    remove
1892    remove_all
1893    (load_iter operations will call the load callbacks)
1894
1895The second argument, E<lt>priorityE<gt>, is the relative order in
1896which the callback should be called. The value should be between 1 and
189710, inclusive. Callbacks with priority 1 will be called before those
1898with 2, 2 before 3, and so on.
1899
1900Plugins which know they need to run first or last can use the priority
1901values 0 and 11. A callback with priority 0 will run before all
1902others, and if two callbacks try to use that value, an error will
1903result. Likewise priority 11 is exclusive, and runs last.
1904
1905How to remember which callback priorities are special? As you know,
1906most guitar amps have a volume knob that goes from 1 to 10. But, like
1907that of certain rock stars, our amp goes up to 11. A callback with
1908priority 11 is the "loudest" or most powerful callback, as it will be
1909called just before the object is saved to the database (in the case of
1910a 'pre' callback), or just before the object is returned (in the case
1911of a 'post' callback). A callback with priority 0 is the "quietest"
1912callback, as following callbacks can completely overwhelm it. This may
1913be a good choice for your plugin, as you may want your plugin to work
1914well with other plugins. Determining the correct priority is a matter
1915of thinking about your plugin in relation to others, and adjusting the
1916priority based on experience so that users get the best use out of the
1917plugin.
1918
1919The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
1920gives some information about the plugin. This is used to include
1921the plugin's name in any error messages.
1922
1923E<lt>callback functionE<gt> is a code referense for a subroutine that
1924will be called. The arguments to this
1925function vary by operation (see I<MT::Callback> for details),
1926but in each case the first parameter is the I<MT::Callback> object
1927itself:
1928
1929  sub my_callback {
1930      my ($cb, ...) = @_;
1931
1932      if ( <error condition> ) {
1933          return $cb->error("Error message");
1934      }
1935  }
1936
1937Strictly speaking, the return value of a callback is ignored. Calling
1938the error() method of the MT::Callback object (C<$cb> in this case)
1939propagates the error message up to the MT activity log.
1940
1941Another way to handle errors is to call C<die>. If a callback dies,
1942I<MT> will warn the error to the activity log, but will continue
1943processing the MT::Object operation: so other callbacks will still
1944run, and the database operation should still occur.
1945
1946=head2 Any-class Object Callbacks
1947
1948If you add a callback to the MT class with a hook point that begins
1949with C<*::>, such as:
1950
1951    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
1952
1953then it will be called whenever post_save callbacks are called.
1954"Any-class" callbacks are called I<after> all class-specific
1955callbacks. Note that C<add_callback> must be called on the C<MT> class,
1956not on a subclass of C<MT::Object>.
1957
1958=over 4
1959
1960=item * $obj->set_callback_routine()
1961
1962This method just calls the set_callback_routine as defined by the
1963MT::ObjectDriver set with the I<set_driver> method.
1964
1965
1966=back
1967
1968=head2 Caveat
1969
1970Be careful how you handle errors. If you transform data as it goes
1971into and out of the database, and it is possible for one of your
1972callbacks to fail, the data may get saved in an undefined state. It
1973may then be difficult or impossible for the user to recover that data.
1974
1975=head1 AUTHOR & COPYRIGHTS
1976
1977Please see the I<MT> manpage for author, copyright, and license information.
1978
1979=cut
Note: See TracBrowser for help on using the browser.