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

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

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

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: Meta.pm 71460 2008-01-18 18:01:06Z ykerherve $
6
7package MT::Meta;
8
9#--------------------------------------#
10# Dependencies
11
12use strict;
13use warnings;
14
15#--------------------------------------#
16# Constants
17
18sub TYPE_VCHAR ()             { 1 }
19sub TYPE_VCHAR_INDEXED ()     { 2 }
20sub TYPE_VBLOB ()             { 3 }
21sub TYPE_VINTEGER ()          { 4 }
22sub TYPE_VINTEGER_INDEXED ()  { 5 }
23sub TYPE_VDATETIME ()         { 6 }
24sub TYPE_VDATETIME_INDEXED () { 7 }
25sub TYPE_VFLOAT ()            { 8 }
26sub TYPE_VFLOAT_INDEXED ()    { 9 }
27sub TYPE_VCLOB ()             { 10 }
28
29sub DEBUG () { 0 }
30
31## Specify if the faster REPLACE INTO can be used instead of INSERT/UPDATE
32our $REPLACE_ENABLED = 0;
33
34our (%Types, %TypesByName);
35BEGIN {
36    %Types = (
37        TYPE_VCHAR()             => "vchar",
38        TYPE_VCHAR_INDEXED()     => "vchar_indexed",
39        TYPE_VINTEGER()          => "vinteger",
40        TYPE_VINTEGER_INDEXED()  => "vinteger_indexed",
41        TYPE_VDATETIME()         => "vdatetime",
42        TYPE_VDATETIME_INDEXED() => "vdatetime_indexed",
43        TYPE_VFLOAT()            => "vfloat",
44        TYPE_VFLOAT_INDEXED()    => "vfloat_indexed",
45        TYPE_VBLOB()             => "vblob",
46        TYPE_VCLOB()             => "vclob",
47    );
48
49    %TypesByName = reverse %Types;
50
51    # some other aliases
52    $TypesByName{string} = TYPE_VCHAR;
53    $TypesByName{integer} = TYPE_VINTEGER;
54    $TypesByName{datetime} = TYPE_VDATETIME;
55    $TypesByName{float} = TYPE_VFLOAT;
56    $TypesByName{string_indexed} = TYPE_VCHAR_INDEXED;
57    $TypesByName{integer_indexed} = TYPE_VINTEGER_INDEXED;
58    $TypesByName{datetime_indexed} = TYPE_VDATETIME_INDEXED;
59    $TypesByName{float_indexed} = TYPE_VFLOAT_INDEXED;
60    $TypesByName{text} = TYPE_VCLOB;
61    $TypesByName{hash} = TYPE_VBLOB;
62    $TypesByName{array} = TYPE_VBLOB;
63}
64
65## $Registry = {
66##   'foo' => { # key
67##     'MT::Foo' => {
68##       'column' => {
69##         name    => 'column',
70##         id      => 123,
71##         type_id => 1,
72##         type    => 'vchar_indexed',
73##         pkg     => 'MT::Foo',
74##         zip     => $cfg,   ## optional
75##       },
76##     }
77##   },
78## }
79
80## $RegistryById = {
81##   'foo' => { # key
82##     123 => {
83##       name    => 'column',
84##       id      => 123,
85##       type_id => 1,
86##       zip     => $cfg,   ## optional
87##     },
88##   },
89## }
90our($Registry, $RegistryById);
91
92#--------------------------------------#
93# Public Class Methods
94
95sub install {
96    my $class = shift;
97    my ($pkg, $params) = @_;
98
99    ## add base class defs, if they exist
100    my $base_args = $pkg->meta_args;
101    if ($base_args) {
102        while ( my ($k, $v) = each (%{ $base_args }) ) {
103            $params->{$k} = $v;
104        }
105    }
106
107    ## add inherited metadata fields...
108    my $key = $params->{key};
109    my $inherited = $class->_load_inheritance($pkg, $key);
110
111    my $fields = delete $params->{fields}; # we'll reduce this big value
112    push @$fields, @$inherited;
113
114    ## ... and add metadata fields to registry after
115    $class->register($pkg, $key, $fields);
116
117    ## ... and save reduced fields in params (will be installed in properties)
118    ##     while saving extra properties
119    for (@$fields) {
120        $params->{fields}{ $_->{name} } = 1;
121        $params->{blob_zip_cfg}{ $_->{name} } = $_->{zip} if $_->{zip};
122    }
123
124    ## build subclass
125    $class->_build_subclass($pkg, $params);
126
127    return $params->{fields};
128}
129
130sub register {
131    my $class = shift;
132    my ($pkg, $key, $fields) = @_;
133
134    foreach my $field ( @{ $fields } ) {
135        my $name = $field->{name};
136        my $type = $field->{type};
137        my $zip  = $field->{zip};
138
139        ## check for potential deep recursion
140#        warn("$pkg has $name subroutine! Deep recursion imminent!")
141#            if $pkg->can($name);
142
143        my $type_id = $TypesByName{$type}
144            or Carp::croak("Invalid metadata type '$type' for field $pkg $field->{name}");
145
146        ## load registry
147        print STDERR "$pkg is registering metadata $key\t$name\n" if DEBUG;
148
149        ## clone it
150        my $value = {
151            name    => $name,
152            type_id => $type_id,
153            type    => $Types{$type_id},
154            pkg     => $pkg,
155        };
156        $value->{zip} = $zip if defined $zip;
157
158        $Registry->{$key}{$pkg}{$name} = $value;
159    }
160}
161
162sub metadata_by_class {
163    my $class = shift;
164    my($pkg) = @_;
165    values %{ $Registry->{ $pkg->meta_args->{key} }{$pkg} };
166}
167
168sub metadata_by_name {
169    my $class = shift;
170    my($pkg, $name) = @_;
171    $Registry->{ $pkg->meta_args->{key} }{$pkg}{$name};
172}
173
174*metadata_by_id = \&metadata_by_name;
175
176sub has_own_metadata_of {
177    my $class = shift;
178    my($pkg)  = @_;
179    my $key   = $pkg->meta_args->{key}; # xxx is it really safe to call meta_args?
180    exists $Registry->{$key}{$pkg};
181}
182
183sub normalize_type {
184    my $pkg = shift;
185    my ($type) = @_;
186    return $Types{ $TypesByName{ $type } } || TYPE_VBLOB;
187}
188
189#--------------------------------------#
190# Private Class Methods
191
192sub _load_inheritance {
193    my $class = shift;
194    my ($pkg, $key) = @_;
195
196    no strict 'refs'; ## no critic
197    my $base = ${"$pkg\::ISA"}[0];
198    return [] if $base eq $pkg;
199    my @inherited;
200    if (exists $Registry->{$key}{$base}) {
201        for my $field ( values %{ $Registry->{$key}->{$base} } ) {
202            push @inherited, $field;
203        }
204    }
205    return \@inherited;
206}
207
208sub _build_subclass {
209    my $class = shift;
210    my ($pkg, $meta) = @_;
211
212    my $subclass = $pkg->meta_pkg;
213    return unless $subclass;
214
215    return if defined ${"${subclass}::VERSION"};
216
217    ## Try to use this subclass first to see if it exists
218    my $subclass_file = $subclass . '.pm';
219    $subclass_file =~ s{::}{/}g;
220    eval {
221        require $subclass_file;
222        $subclass->import();
223    };
224    if ($@) {
225        ## Die if we get an unexpected error
226        die $@ unless $@ =~ /^Can't locate /;
227    } else {
228        ## This class exists.  We don't need to do anything.
229        return 1;
230    }
231
232    my $base_class = 'MT::Object::Meta';
233
234    my $subclass_src = "
235        # line " . __LINE__ . " " . __FILE__ . "
236        package $subclass;
237        our \$VERSION = 1.0;
238        use base qw($base_class);
239        1;
240    ";
241
242    ## no critic ProhibitStringyEval
243    eval $subclass_src or print STDERR "Could not create package $subclass!\n";
244
245    $subclass->install_properties($meta);
246}
247
2481;
249
250__END__
251
252=head1 NAME
253
254MT::Meta - Get/Set metadata on a variety of objects
255
256=head1 SYNOPSIS
257
258    package Foo;
259    use base qw( MT::Object );
260
261    __PACKAGE__->install_properties({ ... });
262
263    __PACKAGE__->install_meta({
264        datasource => 'foo_meta',
265        fields     => [
266            { name => 'selfaware', type => 'vchar', key => 1 },
267        ],
268    });
269
270    sub meta_args {
271        +{ key => 'foo' };
272    }
273
274
275    package main;
276
277    my $foo = Foo->new;
278    $foo->selfaware(1);
279    $foo->save if you dare;
280
281
282=head1 DESCRIPTION
283
284The I<MT::Meta> class manages the configuration and of metadata for
285I<MT::Object>s. As metadata is fully integrated into I<MT::Object>,
286you should not need to access I<MT::Meta> directly.
287
288
289=head1 USAGE
290
291These class methods allow you to retrieve information about the metadata
292defined for specific classes and metadata fields.
293
294=head2 MT::Meta->install($class, $params)
295
296Defines the set of metadata for the class I<$class> as described in the hash
297reference I<$params>, and configures I<$class> for use as a metadata host.
298
299Members of I<$params> can include:
300
301=over 4
302
303=item * fields
304
305An array reference describing the metadata fields to define. Each member of the
306array should be a hash reference containing:
307
308=over 4
309
310=item * name
311
312The name of the metadata field. This corresponds to the object method you'd use
313to get/set the metadata on a particular object in the package I<$class>.
314
315=item * id
316
317The numeric ID of the metadata field. This is used as the key for this metadata
318field in the database.
319
320=item * type
321
322The data type of the metadata field. One of: C<vchar>, C<vchar_indexed>, or
323C<vblob>.
324
325=back
326
327The I<fields> member is required.
328
329=item * key
330
331A string to uniquely describe a hierarchy of classes that should share a set of
332metadata fields. For example, for ArcheType::M::Asset I<and its subclasses>,
333I<key> is C<asset>.
334
335Note that, as this should be the same key as returned in the original class's
336I<meta_args> method, you should probably not bother sending it here.
337
338=back
339
340The I<$params> hash may also contain arguments to be set as properties of the
341metadata package (the class of I<MT::Object> actually containing the meta
342data). Useful properties to set include:
343
344=over 4
345
346=item * datasource
347
348=item * primary_key
349
350=item * get_driver
351
352=back
353
354As I<install> does not mark metadata as installed in the properties of
355I<$class>, you should not use it directly, but prefer instead to use
356I<MT::Object::install_meta>. (Its single argument is the same as
357I<$params> here.)
358
359=head2 MT::Meta->register($class, $key, $fields)
360
361Defines the metadata fields I<$fields> for the class I<$class> under the key
362I<$key>. The fields and key arguments are the same as those given to the
363I<install> method in I<$params>.
364
365As I<register> does not configure I<$class> for use as a metadata host
366(defining the meta data class, enabling automatic initialization of metadata on
367loaded instances of I<$class>, etc), you should not use it directly, but prefer
368instead to use I<install>.
369
370=head2 MT::Meta->metadata_by_class($class)
371
372Returns a list of hash references describing all the metadata defined for the
373class I<$class>. Each item in the array is a reference to a hash containing the
374following keys:
375
376=over 4
377
378=item * name
379
380The name of the metadata field. This corresponds to the object method you'd use
381to get/set the metadata on a particular object in the package I<$class>.
382
383=item * id
384
385The numeric ID of the metadata field. This is used as the key for this metadata
386field in the database.
387
388=item * type
389
390The data type of the metadata field. One of: C<vchar>, C<vchar_indexed>, or
391C<vblob>.
392
393=item * type_id
394
395The numeric ID corresponding to I<type>.
396
397=item * pkg
398
399The name of the original I<MT::Object> subclass to which this metadata
400field belongs.
401
402=back
403
404=head2 MT::Meta->metadata_by_name($class, $name)
405
406Looks up a metadata field in the class I<$class> with the name I<$name>. If
407I<$name> is a valid metadata field for I<$class>, returns a reference to a hash
408containing the same keys as is returned above from I<metadata_by_class>.
409Otherwise, returns false.
410
411=head2 MT::Meta->metadata_by_id($class, $id)
412
413Looks up a metadata field in the class I<$class> with the numeric ID I<$id>.
414If I<$id> identifies a valid metadata field for I<$class>, returns a reference
415to a hash containing the same keys as is returned above from
416I<metadata_by_class>. Otherwise, returns false.
417
418=head2 MT::Meta->has_own_metadata_of($class)
419
420Returns true if the given class has any metadata fields defined. Otherwise,
421returns false.
422
423
424=head1 SEE ALSO
425
426L<MT::Object>, L<MT::Meta::Proxy>
427
428=cut
Note: See TracBrowser for help on using the browser.