root/branches/release-35/lib/MT/Object.pm @ 1927

Revision 1927, 64.5 kB (checked in by mpaschal, 20 months ago)

Land the new implementation of metadata based on narrow tables
BugzID: 68749

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