root/branches/release-33/lib/MT/Object.pm @ 1771

Revision 1771, 60.0 kB (checked in by fumiakiy, 20 months ago)

Use database driver directory for non-cacheable classes. BugId:72003

  • 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        if ( my $fb_driver = $driver->fallback ) {
741            $driver = $fb_driver;
742        }
743        $DBI_DRIVER = $driver;
744    }
745    return $DBI_DRIVER;
746}
747
748sub table_name {
749    my $obj = shift;
750    return $obj->driver->table_for($obj);
751}
752
753sub clone {
754    my $obj = shift;
755    my($param) = @_;
756    my $clone = $obj->SUPER::clone_all;
757
758    ## If the caller has listed a set of columns not to copy to the clone,
759    ## delete them from the clone.
760    if ($param && ($param->{Except} || $param->{except})) {
761        for my $col (keys %{ $param->{Except} || $param->{except} }) {
762            $clone->$col(undef);
763        }
764    }
765    return $clone;
766}
767
768sub columns_of_type {
769    my $obj = shift;
770    my(@types) = @_;
771    my $props = $obj->properties;
772    my $cols = $props->{columns};
773    my $col_defs = $obj->column_defs;
774    my @cols;
775    my %types = map { $_ => 1 } @types;
776    for my $col (@$cols) {
777        push @cols, $col
778            if $col_defs->{$col} && exists $types{$col_defs->{$col}{type}};
779    }
780    \@cols;
781}
782
783sub created_on_obj {
784    my $obj = shift;
785    return $obj->column_as_datetime('created_on');
786}
787
788sub column_as_datetime {
789    my $obj = shift;
790    my ($col) = @_;
791    if (my $ts = $obj->column($col)) {
792        my $blog;
793        if ($obj->isa('MT::Blog')) {
794            $blog = $obj;
795        } else {
796            if (my $blog_id = $obj->blog_id) {
797                require MT::Blog;
798                $blog = MT::Blog->lookup($blog_id);
799            }
800        }
801        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)/;
802        require MT::DateTime;
803        my $four_digit_offset;
804        if ($blog) {
805            $four_digit_offset = sprintf('%.02d%.02d', int($blog->server_offset),
806                                        60 * abs($blog->server_offset
807                                                 - int($blog->server_offset)));
808        }
809        return new MT::DateTime(year => $y, month => $mo, day => $d,
810                                hour => $h, minute => $m, second => $s,
811                                time_zone => $four_digit_offset);
812    }
813    undef;
814}
815
816sub join_on {
817    return [ @_ ];
818}
819
820sub remove_children {
821    my $obj = shift;
822    return 1 unless ref $obj;
823
824    my ($param) = @_;
825    my $child_classes = $obj->properties->{child_classes} || {};
826    my @classes = keys %$child_classes;
827    return 1 unless @classes;
828
829    $param ||= {};
830    my $key = $param->{key} || $obj->datasource . '_id';
831    my $obj_id = $obj->id;
832    for my $class (@classes) {
833        eval "use $class;";
834        $class->remove({ $key => $obj_id });
835    }
836    1;
837}
838
839sub get_by_key {
840    my $class = shift;
841    my ($key) = @_;
842    my($obj) = $class->search($key);
843    $obj ||= new $class;
844    $obj->set_values($key);
845    return $obj;
846}
847
848sub set_by_key {
849    my $class = shift;
850    my ($key, $value) = @_;
851    my ($obj) = $class->search($key);
852    unless ($obj) {
853        $obj = new $class;
854        $obj->set_values($key);
855    }
856    $obj->set_values($value) if $value;
857    $obj->save or return;
858    return $obj;
859}
860
861# This method is overridden since D::OD uses column_values to retrieve
862# the content to cache if caching is enabled. Thus, we must ensure any
863# metadata is serialized prior to caching.
864sub column_values {
865    my $props = $_[0]->properties;
866    if ($props->{meta_column}
867        && $_[0]->{changed_cols}{$props->{meta_column}}) {
868        $_[0]->pre_save_serialize_metadata;
869    }
870    return $_[0]->SUPER::column_values(@_);
871}
872
873# We override D::OD's set_values method here only allowing the
874# assignment of a column if the value given is defined. There are
875# some legacy reasons for doing this, mostly for backward
876# compatibility.
877sub set_values {
878    my $obj = shift;
879    my ($values) = @_;
880    for my $col (keys %$values) {
881        unless ( $obj->has_column($col) ) {
882            Carp::croak("You tried to set inexistent column $col to value $values->{$col} on " . ref($obj));
883        }
884        $obj->$col($values->{$col}) if defined $values->{$col};
885    }
886}
887
888sub column_def {
889    my $obj = shift;
890    my ($name) = @_;
891    my $defs = $obj->column_defs;
892    my $def = $defs->{$name};
893    if (!ref($def)) {
894        $defs->{$name} = $def = $obj->__parse_def($name, $def);
895    }
896    return $def;
897}
898
899sub index_defs {
900    my $obj = shift;
901    my $props = $obj->properties;
902    $props->{indexes};
903}
904
905sub column_defs {
906    my $obj = shift;
907    my $props = $obj->properties;
908    my $defs = $props->{column_defs};
909    return undef if !$defs;
910    my ($key) = keys %$defs;
911    if (!(ref $defs->{$key})) {
912        $obj->__parse_defs($props->{column_defs});
913    }
914    $props->{column_defs};
915}
916
917sub __parse_defs {
918    my $obj = shift;
919    my ($defs) = @_;
920    foreach my $col ( keys %$defs ) {
921        next if ref($defs->{$col});
922        $defs->{$col} = $obj->__parse_def($col, $defs->{$col});
923    }
924}
925
926sub __parse_def {
927    my $obj = shift;
928    my ($col, $def) = @_;
929    return undef if !defined $def;
930    my $props = $obj->properties;
931    my %def;
932    if ($def =~ s/^([^( ]+)\s*//) {
933        $def{type} = $1;
934    }
935    if ($def =~ s/^\((\d+)\)\s*//) {
936        $def{size} = $1;
937    }
938    $def{not_null} = 1 if $def =~ m/\bnot null\b/i;
939    $def{key} = 1 if $def =~ m/\bprimary key\b/i;
940    $def{key} = 1 if ($props->{primary_key}) && ($props->{primary_key} eq $col);
941    $def{auto} = 1 if $def =~ m/\bauto[_ ]increment\b/i;
942    $def{default} = $props->{defaults}{$col} if exists $props->{defaults}{$col};
943    \%def;
944}
945
946sub cache_property {
947    my $obj = shift;
948    my $key = shift;
949    my $code = shift;
950    if (ref $key eq 'CODE') {
951        ($key, $code) = ($code, $key);
952    }
953    $key ||= (caller(1))[3];
954
955    my $r = MT->request;
956    my $oc = $r->cache('object_cache');
957    unless ($oc) {
958        $oc = {};
959        $r->cache('object_cache', $oc);
960    }
961    $oc = $oc->{"$obj"} ||= {};
962    if (@_) {
963        $oc->{$key} = $_[0];
964    } else {
965        if ((!exists $oc->{$key}) && $code) {
966            $oc->{$key} = $code->($obj, @_);
967        }
968    }
969    return exists $oc->{$key} ? $oc->{$key} : undef;
970}
971
972sub clear_cache {
973    my $obj = shift;
974    my $oc = MT->request('object_cache') or return;
975    if (@_) {
976        $oc = $oc->{"$obj"};
977        delete $oc->{shift} if $oc;
978    } else {
979        delete $oc->{"$obj"};
980    }
981}
982
983sub to_hash {
984    my $obj = shift;
985    my $hash = {};
986    my $props = $obj->properties;
987    my $pfx = $obj->datasource;
988    my $values = $obj->column_values;
989    foreach (keys %$values) {
990        $hash->{"${pfx}.$_"} = $values->{$_};
991    }
992    if (my $meta = $props->{meta_columns}) {
993        foreach (keys %$meta) {
994            $hash->{"${pfx}.$_"} = $obj->meta($_);
995        }
996    }
997    if ($obj->has_column('blog_id')) {
998        my $blog_id = $obj->blog_id;
999        require MT::Blog;
1000        if (my $blog = MT::Blog->lookup($blog_id)) {
1001            my $blog_hash = $blog->to_hash;
1002            $hash->{"${pfx}.$_"} = $blog_hash->{$_} foreach keys %$blog_hash;
1003        }
1004    }
1005    $hash;
1006}
1007
10081;
1009__END__
1010
1011=head1 NAME
1012
1013MT::Object - Movable Type base class for database-backed objects
1014
1015=head1 SYNOPSIS
1016
1017Creating an I<MT::Object> subclass:
1018
1019    package MT::Foo;
1020    use strict;
1021
1022    use base 'MT::Object';
1023
1024    __PACKAGE__->install_properties({
1025        columns_defs => {
1026            'id'  => 'integer not null auto_increment',
1027            'foo' => 'string(255)',
1028        },
1029        indexes => {
1030            foo => 1,
1031        },
1032        primary_key => 'id',
1033        datasource => 'foo',
1034    });
1035
1036Using an I<MT::Object> subclass:
1037
1038    use MT;
1039    use MT::Foo;
1040
1041    ## Create an MT object to load the system configuration and
1042    ## initialize an object driver.
1043    my $mt = MT->new;
1044
1045    ## Create an MT::Foo object, fill it with data, and save it;
1046    ## the object is saved using the object driver initialized above.
1047    my $foo = MT::Foo->new;
1048    $foo->foo('bar');
1049    $foo->save
1050        or die $foo->errstr;
1051
1052=head1 DESCRIPTION
1053
1054I<MT::Object> is the base class for all Movable Type objects that will be
1055serialized/stored to some location for later retrieval; this location could
1056be a DBM file, a relational database, etc.
1057
1058Movable Type objects know nothing about how they are stored--they know only
1059of what types of data they consist, the names of those types of data (their
1060columns), etc. The actual storage mechanism is in the I<MT::ObjectDriver::Driver::DBI>
1061class and its driver subclasses; I<MT::Object> subclasses, on the other hand,
1062are essentially just standard in-memory Perl objects, but with a little extra
1063self-knowledge.
1064
1065This distinction between storage and in-memory representation allows objects
1066to be serialized to disk in many different ways--for example, an object could
1067be stored in a MySQL database, in a DBM file, etc. Adding a new storage method
1068is as simple as writing an object driver--a non-trivial task, to be sure, but
1069one that will not require touching any other Movable Type code.
1070
1071=head1 SUBCLASSING
1072
1073Creating a subclass of I<MT::Object> is very simple; you simply need to
1074define the properties and metadata about the object you are creating. Start
1075by declaring your class, and inheriting from I<MT::Object>:
1076
1077    package MT::Foo;
1078    use strict;
1079
1080    use base 'MT::Object';
1081
1082=item * __PACKAGE__->install_properties($args)
1083
1084Then call the I<install_properties> method on your class name; an easy way
1085to get your class name is to use the special I<__PACKAGE__> variable:
1086
1087    __PACKAGE__->install_properties({
1088        column_defs => {
1089            'id' => 'integer not null auto_increment',
1090            'foo' => 'string(255)',
1091        },
1092        indexes => {
1093            foo => 1,
1094        },
1095        primary_key => 'id',
1096        datasource => 'foo',
1097    });
1098
1099I<install_properties> performs the necessary magic to install the metadata
1100about your new class in the MT system. The method takes one argument, a hash
1101reference containing the metadata about your class. That hash reference can
1102have the following keys:
1103
1104=over 4
1105
1106=item * column_defs
1107
1108The definition of the columns (fields) in your object. Column names are also
1109used for method names for your object, so your column name should not
1110contain any strange characters. (It could also be used as part of the name of
1111the column in a relational database table, so that is another reason to keep
1112column names somewhat sane.)
1113
1114The value for the I<columns> key should be a reference to an hashref
1115containing the key/value pairs that are names of your columns matched with
1116their schema definition.
1117
1118The type declaration of a column is pseudo-SQL. The data types loosely match
1119SQL types, but are vendor-neutral, and each MT::ObjectDriver will map these
1120to appropriate types for the database it services. The format of a column
1121type is as follows:
1122
1123    'column_name' => 'type(size) options'
1124
1125The 'type' part of the declaration can be any one of:
1126
1127=over 4
1128
1129=item * string
1130
1131For storing string data, typically up to 255 characters, but assigned a length identified by '(size)'.
1132
1133=item * integer
1134
1135For storing integers, maybe limited to 32 bits.
1136
1137=item * boolean
1138
1139For storing boolean values (numeric values of 1 or 0).
1140
1141=item * smallint
1142
1143For storing small integers, typically limited to 16 bits.
1144
1145=item * datetime
1146
1147For storing a full date and time value.
1148
1149=item * timestamp
1150
1151For storing a date and time that automatically updates upon save.
1152
1153=item * blob
1154
1155For storing binary data.
1156
1157=item * text
1158
1159For storing text data.
1160
1161=item * float
1162
1163For storing floating point values.
1164
1165=back
1166
1167Note: The physical data storage capacity of these types will vary depending on
1168the driver's implementation. Please refer to the documentation of the
1169MT::ObjectDriver you're using to determine the actual capacity for these
1170types.
1171
1172The '(size)' element of the declaration is only valid for the 'string' type.
1173
1174The 'options' element of the declaration is not required, but is used to
1175specify additional attributes of the column. Such as:
1176
1177=over 4
1178
1179=item * not null
1180
1181Specify 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.
1182
1183=item * auto_increment
1184
1185Specify for integer columns (typically the primary key) to automatically assign a value.
1186
1187=item * primary key
1188
1189Specify for identifying the column as the primary key (only valid for a single column).
1190
1191=back
1192
1193=item * indexes
1194
1195Specifies the column indexes on your objects; this only has consequence for
1196some object drivers (DBM, for example), where indexes are not automatically
1197maintained by the datastore (as they are in a relational database).
1198
1199The value for the I<indexes> key should be a reference to a hash containing
1200column names as keys, and the value C<1> for each key--each key represents
1201a column that should be indexed.
1202
1203B<NOTE:> with the DBM driver, if you do not set up an index on a column you
1204will not be able to select objects with values matching that column using the
1205I<load> and I<load_iter> interfaces (see below).
1206
1207=item * audit
1208
1209Automatically adds bookkeeping capabilities to your class--each object will
1210take on four new columns: I<created_on>, I<created_by>, I<modified_on>, and
1211I<modified_by>. The created_on, created_by columns will be populated
1212automatically (if they have not already been assigned at the time of saving
1213the object). Your application is responsible for updating the modified_on,
1214modified_by columns as these may require explicit application-specific
1215assignments (ie, your application may only want them updated during explicit
1216user interaction with the object, as opposed to cases where the object is
1217being changed and saved for mechanical purposes like upgrading a table).
1218
1219=item * datasource
1220
1221The name of the datasource for your class. The datasource is a name uniquely
1222identifying your class--it is used by the object drivers to construct table
1223names, file names, etc. So it should not be specific to any one driver.
1224
1225=item * meta
1226
1227Specify this property if you wish to add an additional 'meta' column to
1228the object properties. This is a special type of column that is used to
1229store complex data structures for the object. The data is serialized into
1230a blob for storage using the L<MT::Serialize> package.
1231
1232=item * meta_column
1233
1234If you wish to specify the name of the column to be used for storing
1235the object metadata, you may declare this property to name the column.
1236The default column name is 'meta'.
1237
1238=item * class_type
1239
1240If class_type is declared, an additional 'class' column is added to the
1241object properties. This column is then used to differentiate between
1242multiple object types that share the same physical table.
1243
1244Note that if this is used, all searches will be constrained to match
1245the class type of the package.
1246
1247=item * class_column
1248
1249Defines the name of the class column (default is 'class') for storing
1250classed objects (see 'class_type' above).
1251
1252=back
1253
1254=head1 USAGE
1255
1256=head2 System Initialization
1257
1258Before using (loading, saving, removing) an I<MT::Object> class and its
1259objects, you must always initialize the Movable Type system. This is done
1260with the following lines of code:
1261
1262    use MT;
1263    my $mt = MT->new;
1264
1265Constructing a new I<MT> objects loads the system configuration from the
1266F<mt.cfg> configuration file, then initializes the object driver that will
1267be used to manage serialized objects.
1268
1269=head2 Creating a new object
1270
1271To create a new object of an I<MT::Object> class, use the I<new> method:
1272
1273    my $foo = MT::Foo->new;
1274
1275I<new> takes no arguments, and simply initializes a new in-memory object.
1276In fact, you need not ever save this object to disk; it can be used as a
1277purely in-memory object.
1278
1279=head2 Setting and retrieving column values
1280
1281To set the column value of an object, use the name of the column as a method
1282name, and pass in the value for the column:
1283
1284    $foo->foo('bar');
1285
1286The return value of the above call will be C<bar>, the value to which you have
1287set the column.
1288
1289To retrieve the existing value of a column, call the same method, but without
1290an argument:
1291
1292    $foo->foo
1293
1294This returns the value of the I<foo> column from the I<$foo> object.
1295
1296=over 4
1297
1298=item * $obj->init()
1299
1300=back
1301
1302This method is used to initialize the object upon construction.
1303
1304=over 4
1305
1306=item * $obj->set_defaults()
1307
1308=back
1309
1310This method is used by the I<init> method to set the object defaults.
1311
1312=head2 Saving an object
1313
1314To save an object using the object driver, call the I<save> method:
1315
1316=over 4
1317
1318=item * $foo->save();
1319
1320=back
1321
1322On success, I<save> will return some true value; on failure, it will return
1323C<undef>, and you can retrieve the error message by calling the I<errstr>
1324method on the object:
1325
1326    $foo->save
1327        or die "Saving foo failed: ", $foo->errstr;
1328
1329If you are saving objects in a loop, take a look at the
1330L</"Note on object locking">.
1331
1332=head2 Loading an existing object or objects
1333
1334=over 4
1335
1336=item * $obj->load()
1337
1338=item * $obj->load_iter()
1339
1340=back
1341
1342You can load an object from the datastore using the I<load> method. I<load>
1343is by far the most complicated method, because there are many different ways
1344to load an object: by ID, by column value, by using a join with another type
1345of object, etc.
1346
1347In addition, you can load objects either into an array (I<load>), or by using
1348an iterator to step through the objects (I<load_iter>).
1349
1350I<load> has the following general form:
1351
1352    my @objects = MT::Foo->load(\%terms, \%arguments);
1353
1354I<load_iter> has the following general form:
1355
1356    my $iter = MT::Foo->load_iter(\%terms, \%arguments);
1357
1358Both methods share the same parameters; the only difference is the manner in
1359which they return the matching objects.
1360
1361If you call I<load> in scalar context, only the first row of the array
1362I<@objects> will be returned; this works well when you know that your I<load>
1363call can only ever result in one object returned--for example, when you load
1364an object by ID.
1365
1366I<\%terms> should be either:
1367
1368=over 4
1369
1370=item * The numeric ID of an object in the datastore.
1371
1372=item * A reference to a hash.
1373
1374The hash should have keys matching column names and the values are the
1375values for that column.
1376
1377For example, to load an I<MT::Foo> object where the I<foo> column is
1378equal to C<bar>, you could do this:
1379
1380    my @foo = MT::Foo->load({ foo => 'bar' });
1381
1382In addition to a simple scalar, the hash value can be a reference to an array;
1383combined with the I<range> setting in the I<\%arguments> list, you can use
1384this to perform range searches. If the value is a reference, the first element
1385in the array specifies the low end of the range, and the second element the
1386high end.
1387
1388=back
1389
1390I<\%arguments> should be a reference to a hash containing parameters for the
1391search. The following parameters are allowed:
1392
1393=over 4
1394
1395=item * sort => "column"
1396
1397Sort the resulting objects by the column C<column>; C<column> must be an
1398indexed column (see L</"indexes">, above).
1399
1400=item * direction => "ascend|descend"
1401
1402To be used together with I<sort>; specifies the sort order (ascending or
1403descending). The default is C<ascend>.
1404
1405=item * limit => "N"
1406
1407Rather than loading all of the matching objects (the default), load only
1408C<N> objects.
1409
1410=item * offset => "M"
1411
1412To be used together with I<limit>; rather than returning the first C<N>
1413matches (the default), return matches C<M> through C<N + M>.
1414
1415=item * start_val => "value"
1416
1417To be used together with I<limit> and I<sort>; rather than returning the
1418first C<N> matches, return the first C<N> matches where C<column> (the sort
1419column) is greater than C<value>.
1420
1421=item * range
1422
1423To be used together with an array reference as the value for a column in
1424I<\%terms>; specifies that the specific column should be searched for a range
1425of values, rather than one specific value.
1426
1427The value of I<range> should be a hash reference, where the keys are column
1428names, and the values are all C<1>; each key specifies a column that should
1429be interpreted as a range.
1430
1431    MT::Foo->load( { created_on => [ '20011008000000', undef ] },
1432        { range => { created_on => 1 } } );
1433
1434This selects C<MT::Foo> objects whose created_on date is greater than
14352001-10-08 00:00:00.
1436
1437=item * range_incl
1438
1439Like the 'range' attribute, but defines an inclusive range.
1440
1441=item * join
1442
1443Can be used to select a set of objects based on criteria, or sorted by
1444criteria, from another set of objects. An example is selecting the C<N>
1445entries most recently commented-upon; the sorting is based on I<MT::Comment>
1446objects, but the objects returned are actually I<MT::Entry> objects. Using
1447I<join> in this situation is faster than loading the most recent
1448I<MT::Comment> objects, then loading each of the I<MT::Entry> objects
1449individually.
1450
1451Note that I<join> is not a normal SQL join, in that the objects returned are
1452always of only one type--in the above example, the objects returned are only
1453I<MT::Entry> objects, and cannot include columns from I<MT::Comment> objects.
1454
1455I<join> has the following general syntax:
1456
1457    join => MT::Foo->join_on( JOIN_COLUMN, I<\%terms>, I<\%arguments> )
1458
1459Use the actual MT::Object-descended package name and the join_on static method
1460providing these parameters: I<JOIN_COLUMN> is the column joining the two
1461object tables, I<\%terms> and I<\%arguments> have the same meaning as they do
1462in the outer I<load> or I<load_iter> argument lists: they are used to select
1463the objects with which the join is performed.
1464
1465For example, to select the last 10 most recently commmented-upon entries, you
1466could use the following statement:
1467
1468    my @entries = MT::Entry->load(undef, {
1469        'join' => MT::Comment->join_on( 'entry_id',
1470                    { blog_id => $blog_id },
1471                    { 'sort' => 'created_on',
1472                      direction => 'descend',
1473                      unique => 1,
1474                      limit => 10 } )
1475    });
1476
1477In this statement, the I<unique> setting ensures that the I<MT::Entry>
1478objects returned are unique; if this flag were not given, two copies of the
1479same I<MT::Entry> could be returned, if two comments were made on the same
1480entry.
1481
1482=item * unique
1483
1484Ensures that the objects being returned are unique.
1485
1486This is really only useful when used within a I<join>, because when loading
1487data out of a single object datastore, the objects are always going to be
1488unique.
1489
1490=back
1491
1492=head2 Removing an object
1493
1494=over 4
1495
1496=item * $foo->remove()
1497
1498=back
1499
1500To remove an object from the datastore, call the I<remove> method on an
1501object that you have already loaded using I<load>:
1502
1503    $foo->remove();
1504
1505On success, I<remove> 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->remove
1510        or die "Removing foo failed: ", $foo->errstr;
1511
1512If you are removing objects in a loop, take a look at the
1513L</"Note on object locking">.
1514
1515=head2 Removing select objects of a particular class
1516
1517Combining the syntax of the load and remove methods, you can use the
1518static version of the remove method to remove particular objects:
1519
1520    MT::Foo->remove({ bar => 'baz' });
1521
1522The terms you specify to remove by should be indexed columns. This
1523method will load the object and remove it, firing the callback operations
1524associated with those operations.
1525
1526=head2 Removing all of the objects of a particular class
1527
1528To quickly remove all of the objects of a particular class, call the
1529I<remove_all> method on the class name in question:
1530
1531=over 4
1532
1533=item * MT::Foo->remove_all();
1534
1535=back
1536
1537On success, I<remove_all> will return some true value; on failure, it will
1538return C<undef>, and you can retrieve the error message by calling the
1539I<errstr> method on the class name:
1540
1541    MT::Foo->remove_all
1542        or die "Removing all foo objects failed: ", MT::Foo->errstr;
1543
1544=head2 Removing all the children of an object
1545
1546=over 4
1547
1548=item * $obj->remove_children([ \%param ])
1549
1550=back
1551
1552If your class has registered 'child_classes' as part of it's properties,
1553then this method may be used to remove objects that are associated with
1554the active object.
1555
1556This method is typically used in an overridden 'remove' method.
1557
1558    sub remove {
1559        my $obj = shift;
1560        $obj->remove_children({ key => 'object_id' });
1561        $obj->SUPER::remove(@_);
1562    }
1563
1564The 'key' parameter specified here lets you identify the field name used by
1565the children classes to relate back to the parent class. If unspecified,
1566C<remove_children> will assume the key to be the datasource name of the
1567current class with an '_id' suffix.
1568
1569=head2 Getting the count of a number of objects
1570
1571To determine how many objects meeting a particular set of conditions exist,
1572use the I<count> method:
1573
1574    my $count = MT::Foo->count({ foo => 'bar' });
1575
1576I<count> takes the same arguments (I<\%terms> and I<\%arguments>) as I<load>
1577and I<load_iter>, above.
1578
1579=head2 Determining if an object exists in the datastore
1580
1581To check an object for existence in the datastore, use the I<exists> method:
1582
1583    if ($foo->exists) {
1584        print "Foo $foo already exists!";
1585    }
1586
1587=head2 Counting groups of objects
1588
1589=over 4
1590
1591=item * $obj->count_group_by()
1592
1593=back
1594
1595The count_group_by method can be used to retrieve a list of all the
1596distinct values that appear in a given column along with a count of
1597how many objects carry that value. The routine can also be used with
1598more than one column, in which case it retrieves the distinct pairs
1599(or n-tuples) of values in those columns, along with the counts.
1600Yet more powerful, any SQL expression can be used in place of
1601the column names to count how many object produce any given result
1602values when run through those expressions.
1603
1604  $iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});
1605
1606C<$terms> and C<%args> pick out a subset of the MT::Foo objects in the
1607usual way. C<$group_expressions> is an array reference containing the
1608SQL expressions for the values you want to group by. A single row will
1609be returned for each distinct tuple of values resulting from the
1610$group_expressions. For example, if $group_expressions were just a
1611single column (e.g. group => ['created_on']) then a single row would
1612be returned for each distinct value of the 'created_on' column. If
1613$group_expressions were multiple columns, a row would be returned for
1614each distinct pair (or n-tuple) of values found in those columns.
1615
1616Each application of the iterator C<$iter> returns a list in the form:
1617
1618  ($count, $group_val1, $group_val2, ...)
1619
1620Where C<$count> is the number of MT::Foo objects for which the group
1621expressions are the values ($group_val1, $group_val2, ...). These
1622values are in the same order as the corresponding group expressions in
1623the $group_exprs argument.
1624
1625In this example, we load up groups of MT::Pip objects, grouped by the
1626pair (cat_id, invoice_id), and print how many pips have that pair of
1627values.
1628
1629    $iter = MT::Pip->count_group_by(undef,
1630                                    {group => ['cat_id',
1631                                               'invoice_id']});
1632    while (($count, $cat, $inv) = $iter->()) {
1633        print "There are $count Pips with " .
1634            "category $cat and invoice $inv\n";
1635    }
1636
1637=head2 Inspecting and Manipulating Object State
1638
1639=over 4
1640
1641=item * $obj->column_values()
1642
1643=back
1644
1645Use C<column_values> and C<set_values> to get and set the fields of an
1646object I<en masse>. The former returns a hash reference mapping column
1647names to their values in this object. For example:
1648
1649    $values = $obj->column_values()
1650
1651=over 4
1652
1653=item * $obj->set_values()
1654
1655=back
1656
1657C<set_values> accepts a similar hash ref, which need not give a value
1658for every field. For example:
1659
1660    $obj->set_values({col1 => $val1, col2 => $val2});
1661
1662is equivalent to
1663
1664    $obj->col1($val1);
1665    $obj->col2($val2);
1666
1667=head2 Other Methods
1668
1669=over 4
1670
1671=item * $obj->clone([\%param])
1672
1673Returns a clone of C<$obj>. That is, a distinct object which has all
1674the same data stored within it. Changing values within one object does
1675not modify the other.
1676
1677An optional C<except> parameter may be provided to exclude particular
1678columns from the cloning operation. For example, the following would
1679clone the elements of the blog except the name attribute.
1680
1681   $blog->clone({ except => { name => 1 } });
1682
1683=item * $obj->column_names()
1684
1685Returns a list of the names of columns in C<$obj>; includes all those
1686specified to the install_properties method as well as the audit
1687properties (C<created_on>, C<modified_on>, C<created_by>,
1688C<modified_by>), if those were enabled in install_properties.
1689
1690=item * $obj->set_driver()
1691
1692This method sets the object driver to use to link with a database.
1693
1694=item * MT::Foo->driver()
1695
1696=item * $obj->driver()
1697
1698Returns the ObjectDriver object that links this object with a database.
1699
1700=item * $obj->created_on_obj()
1701
1702Returns an MT::DateTime object representing the moment when the
1703object was first saved to the database.
1704
1705=item * MT::Foo->set_by_key($key_terms, $value_terms)
1706
1707A convenience method that loads whatever object matches the C<$key_terms>
1708argument and sets some or all of its fields according to the
1709C<$value_terms>. For example:
1710
1711   MT::Foo->set_by_key({name => 'Thor'},
1712                       {region => 'Norway', gender => 'Male'});
1713
1714This loads the C<MT::Foo> object having 'name' field equal to 'Thor'
1715and sets the 'region' and 'gender' fields appropriately.
1716
1717More than one term is acceptable in the C<$key_terms> argument. The
1718matching object is the one that matches all of the C<$key_terms>.
1719
1720This method only useful if you know that there is a unique object
1721matching the given key. There need not be a unique constraint on the
1722columns named in the C<$key_hash>; but if not, you should be confident
1723that only one object will match the key.
1724
1725=item * MT::Foo->get_by_key($key_terms)
1726
1727A convenience method that loads whatever object matches the C<$key_terms>
1728argument. If no matching object is found, a new object will be constructed
1729and the C<$key_terms> provided will be assigned to it. So regardless of
1730whether the key exists already, this method will return an object with the
1731key requested. Note, however: if a new object is instantiated it is
1732not automatically saved.
1733
1734    my $thor = MT::Foo->get_by_key({name => 'Thor'});
1735    $thor->region('Norway');
1736    $thor->gender('Male');
1737    $thor->save;
1738
1739The fact that it returns a new object if one isn't found is to help
1740optimize this pattern:
1741
1742    my $obj = MT::Foo->load({key => $value});
1743    if (!$obj) {
1744        $obj = new MT::Foo;
1745        $obj->key($value);
1746    }
1747
1748This is equivalent to:
1749
1750    my $obj = MT::Foo->get_by_key({key => $value});
1751
1752If you don't appreciate the autoinstantiation behavior of this method,
1753just use the C<load> method instead.
1754
1755More than one term is acceptable in the C<$key_terms> argument. The
1756matching object is the one that matches all of the C<$key_terms>.
1757
1758This method only useful if you know that there is a unique object
1759matching the given key. There need not be a unique constraint on the
1760columns named in the C<$key_hash>; but if not, you should be confident
1761that only one object will match the key.
1762
1763=item * $obj->cache_property($key, $code)
1764
1765Caches the provided key (e.g. entry, trackback) with the return value
1766of the given code reference (which is often an object load call) so
1767that the value does not have to be recomputed each time.
1768
1769=item * $obj->column_def($name)
1770
1771This method returns the value of the given I<$name> C<column_defs>
1772propery.
1773
1774=item * $obj->column_defs()
1775
1776This method returns all the C<column_defs> of the property of the
1777object.
1778
1779=item * $obj->to_hash()
1780
1781TODO - So far I have not divined what this method actually does. Hints?
1782
1783=item * Class->join_on()
1784
1785This method returns the list of used by the join arguments parameter
1786used by the L<MT::App::CMS/listing> method.
1787
1788=item * $obj->properties()
1789
1790TODO - Return the return properties of the object.
1791
1792=item * $obj->to_xml()
1793
1794TODO - Returns the XML representation of the object.
1795This method is defined in MT/BackupRestore.pm - you must first
1796use MT::BackupRestore to use this method.
1797
1798=item * $obj->restore_parent_ids()
1799
1800TODO - Backup file contains parent objects' ids (foreign keys).  However,
1801when parent objcects are restored, their ids will be changed.  This method
1802is to match the old and new ids of parent objects for children objects to be
1803correctly associated.
1804This method is defined in MT/BackupRestore.pm - you must first
1805use MT::BackupRestore to use this method.
1806
1807=item * $obj->parent_names()
1808
1809TODO - Should be overridden by subclasses to return correct hash
1810whose keys are xml element names of the object's parent objects
1811and values are class names of them.
1812This method is defined in MT/BackupRestore.pm - you must first
1813use MT::BackupRestore to use this method.
1814
1815=back
1816
1817=head1 NOTES
1818
1819=head2 Note on object locking
1820
1821When you read objects from the datastore, the object table is locked with a
1822shared lock; when you write to the datastore, the table is locked with an
1823exclusive lock.
1824
1825Thus, note that saving or removing objects in the same loop where you are
1826loading them from an iterator will not work--the reason is that the datastore
1827maintains a shared lock on the object table while objects are being loaded
1828from the iterator, and thus the attempt to gain an exclusive lock when saving
1829or removing an object will cause deadlock.
1830
1831For example, you cannot do the following:
1832
1833    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1834    while (my $foo = $iter->()) {
1835        $foo->remove;
1836    }
1837
1838Instead you should do either this:
1839
1840    my @foo = MT::Foo->load({ foo => 'bar' });
1841    for my $foo (@foo) {
1842        $foo->remove;
1843    }
1844
1845or this:
1846
1847    my $iter = MT::Foo->load_iter({ foo => 'bar' });
1848    my @to_remove;
1849    while (my $foo = $iter->()) {
1850        push @to_remove, $foo
1851            if SOME CONDITION;
1852    }
1853    for my $foo (@to_remove) {
1854        $foo->remove;
1855    }
1856
1857This last example is useful if you will not be removing every I<MT::Foo>
1858object where I<foo> equals C<bar>, because it saves memory--only the
1859I<MT::Foo> objects that you will be deleting are kept in memory at the same
1860time.
1861
1862=head1 CALLBACKS
1863
1864=over 4
1865
1866=item * $obj->add_callback()
1867
1868=back
1869
1870Most MT::Object operations can trigger callbacks to plugin code. Some
1871notable uses of this feature are: to be notified when a database record is
1872modified, or to pre- or post-process the data being flowing to the
1873database.
1874
1875To add a callback, invoke the C<add_callback> method of the I<MT::Object>
1876subclass, as follows:
1877
1878   MT::Foo->add_callback( "pre_save", <priority>,
1879                          <plugin object>, \&callback_function);
1880
1881The first argument is the name of the hook point. Any I<MT::Object>
1882subclass has a pre_ and a post_ hook point for each of the following
1883operations:
1884
1885    load
1886    save
1887    remove
1888    remove_all
1889    (load_iter operations will call the load callbacks)
1890
1891The second argument, E<lt>priorityE<gt>, is the relative order in
1892which the callback should be called. The value should be between 1 and
189310, inclusive. Callbacks with priority 1 will be called before those
1894with 2, 2 before 3, and so on.
1895
1896Plugins which know they need to run first or last can use the priority
1897values 0 and 11. A callback with priority 0 will run before all
1898others, and if two callbacks try to use that value, an error will
1899result. Likewise priority 11 is exclusive, and runs last.
1900
1901How to remember which callback priorities are special? As you know,
1902most guitar amps have a volume knob that goes from 1 to 10. But, like
1903that of certain rock stars, our amp goes up to 11. A callback with
1904priority 11 is the "loudest" or most powerful callback, as it will be
1905called just before the object is saved to the database (in the case of
1906a 'pre' callback), or just before the object is returned (in the case
1907of a 'post' callback). A callback with priority 0 is the "quietest"
1908callback, as following callbacks can completely overwhelm it. This may
1909be a good choice for your plugin, as you may want your plugin to work
1910well with other plugins. Determining the correct priority is a matter
1911of thinking about your plugin in relation to others, and adjusting the
1912priority based on experience so that users get the best use out of the
1913plugin.
1914
1915The E<lt>plugin objectE<gt> is an object of type MT::Plugin which
1916gives some information about the plugin. This is used to include
1917the plugin's name in any error messages.
1918
1919E<lt>callback functionE<gt> is a code referense for a subroutine that
1920will be called. The arguments to this
1921function vary by operation (see I<MT::Callback> for details),
1922but in each case the first parameter is the I<MT::Callback> object
1923itself:
1924
1925  sub my_callback {
1926      my ($cb, ...) = @_;
1927
1928      if ( <error condition> ) {
1929          return $cb->error("Error message");
1930      }
1931  }
1932
1933Strictly speaking, the return value of a callback is ignored. Calling
1934the error() method of the MT::Callback object (C<$cb> in this case)
1935propagates the error message up to the MT activity log.
1936
1937Another way to handle errors is to call C<die>. If a callback dies,
1938I<MT> will warn the error to the activity log, but will continue
1939processing the MT::Object operation: so other callbacks will still
1940run, and the database operation should still occur.
1941
1942=head2 Any-class Object Callbacks
1943
1944If you add a callback to the MT class with a hook point that begins
1945with C<*::>, such as:
1946
1947    MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);
1948
1949then it will be called whenever post_save callbacks are called.
1950"Any-class" callbacks are called I<after> all class-specific
1951callbacks. Note that C<add_callback> must be called on the C<MT> class,
1952not on a subclass of C<MT::Object>.
1953
1954=over 4
1955
1956=item * $obj->set_callback_routine()
1957
1958This method just calls the set_callback_routine as defined by the
1959MT::ObjectDriver set with the I<set_driver> method.
1960
1961
1962=back
1963
1964=head2 Caveat
1965
1966Be careful how you handle errors. If you transform data as it goes
1967into and out of the database, and it is possible for one of your
1968callbacks to fail, the data may get saved in an undefined state. It
1969may then be difficult or impossible for the user to recover that data.
1970
1971=head1 AUTHOR & COPYRIGHTS
1972
1973Please see the I<MT> manpage for author, copyright, and license information.
1974
1975=cut
Note: See TracBrowser for help on using the browser.