root/branches/release-38/lib/MT/Meta.pm @ 2281

Revision 2281, 10.9 kB (checked in by bchoate, 19 months ago)

Applying patch from Jay to reduce warnings under FastCGI for dynamic requires. BugID:79441

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    no strict 'refs'; ## no critic
216    return if defined ${"${subclass}::VERSION"};
217
218    ## Try to use this subclass first to see if it exists
219    my $subclass_file = $subclass . '.pm';
220    $subclass_file =~ s{::}{/}g;
221    eval "# line " . __LINE__ . " " . __FILE__ . "\nno warnings 'all';require '$subclass_file';$subclass->import();";
222    if ($@) {
223        ## Die if we get an unexpected error
224        die $@ unless $@ =~ /Can't locate /;
225    } else {
226        ## This class exists.  We don't need to do anything.
227        return 1;
228    }
229
230    my $base_class = 'MT::Object::Meta';
231
232    my $subclass_src = "
233        # line " . __LINE__ . " " . __FILE__ . "
234        package $subclass;
235        our \$VERSION = 1.0;
236        use base qw($base_class);
237        1;
238    ";
239
240    ## no critic ProhibitStringyEval
241    eval $subclass_src or print STDERR "Could not create package $subclass!\n";
242
243    $subclass->install_properties($meta);
244}
245
2461;
247
248__END__
249
250=head1 NAME
251
252MT::Meta - Get/Set metadata on a variety of objects
253
254=head1 SYNOPSIS
255
256    package Foo;
257    use base qw( MT::Object );
258
259    __PACKAGE__->install_properties({ ... });
260
261    __PACKAGE__->install_meta({
262        datasource => 'foo_meta',
263        fields     => [
264            { name => 'selfaware', type => 'vchar', key => 1 },
265        ],
266    });
267
268    sub meta_args {
269        +{ key => 'foo' };
270    }
271
272
273    package main;
274
275    my $foo = Foo->new;
276    $foo->selfaware(1);
277    $foo->save if you dare;
278
279
280=head1 DESCRIPTION
281
282The I<MT::Meta> class manages the configuration and of metadata for
283I<MT::Object>s. As metadata is fully integrated into I<MT::Object>,
284you should not need to access I<MT::Meta> directly.
285
286
287=head1 USAGE
288
289These class methods allow you to retrieve information about the metadata
290defined for specific classes and metadata fields.
291
292=head2 MT::Meta->install($class, $params)
293
294Defines the set of metadata for the class I<$class> as described in the hash
295reference I<$params>, and configures I<$class> for use as a metadata host.
296
297Members of I<$params> can include:
298
299=over 4
300
301=item * fields
302
303An array reference describing the metadata fields to define. Each member of the
304array should be a hash reference containing:
305
306=over 4
307
308=item * name
309
310The name of the metadata field. This corresponds to the object method you'd use
311to get/set the metadata on a particular object in the package I<$class>.
312
313=item * id
314
315The numeric ID of the metadata field. This is used as the key for this metadata
316field in the database.
317
318=item * type
319
320The data type of the metadata field. One of: C<vchar>, C<vchar_indexed>, or
321C<vblob>.
322
323=back
324
325The I<fields> member is required.
326
327=item * key
328
329A string to uniquely describe a hierarchy of classes that should share a set of
330metadata fields. For example, for ArcheType::M::Asset I<and its subclasses>,
331I<key> is C<asset>.
332
333Note that, as this should be the same key as returned in the original class's
334I<meta_args> method, you should probably not bother sending it here.
335
336=back
337
338The I<$params> hash may also contain arguments to be set as properties of the
339metadata package (the class of I<MT::Object> actually containing the meta
340data). Useful properties to set include:
341
342=over 4
343
344=item * datasource
345
346=item * primary_key
347
348=item * get_driver
349
350=back
351
352As I<install> does not mark metadata as installed in the properties of
353I<$class>, you should not use it directly, but prefer instead to use
354I<MT::Object::install_meta>. (Its single argument is the same as
355I<$params> here.)
356
357=head2 MT::Meta->register($class, $key, $fields)
358
359Defines the metadata fields I<$fields> for the class I<$class> under the key
360I<$key>. The fields and key arguments are the same as those given to the
361I<install> method in I<$params>.
362
363As I<register> does not configure I<$class> for use as a metadata host
364(defining the meta data class, enabling automatic initialization of metadata on
365loaded instances of I<$class>, etc), you should not use it directly, but prefer
366instead to use I<install>.
367
368=head2 MT::Meta->metadata_by_class($class)
369
370Returns a list of hash references describing all the metadata defined for the
371class I<$class>. Each item in the array is a reference to a hash containing the
372following keys:
373
374=over 4
375
376=item * name
377
378The name of the metadata field. This corresponds to the object method you'd use
379to get/set the metadata on a particular object in the package I<$class>.
380
381=item * id
382
383The numeric ID of the metadata field. This is used as the key for this metadata
384field in the database.
385
386=item * type
387
388The data type of the metadata field. One of: C<vchar>, C<vchar_indexed>, or
389C<vblob>.
390
391=item * type_id
392
393The numeric ID corresponding to I<type>.
394
395=item * pkg
396
397The name of the original I<MT::Object> subclass to which this metadata
398field belongs.
399
400=back
401
402=head2 MT::Meta->metadata_by_name($class, $name)
403
404Looks up a metadata field in the class I<$class> with the name I<$name>. If
405I<$name> is a valid metadata field for I<$class>, returns a reference to a hash
406containing the same keys as is returned above from I<metadata_by_class>.
407Otherwise, returns false.
408
409=head2 MT::Meta->metadata_by_id($class, $id)
410
411Looks up a metadata field in the class I<$class> with the numeric ID I<$id>.
412If I<$id> identifies a valid metadata field for I<$class>, returns a reference
413to a hash containing the same keys as is returned above from
414I<metadata_by_class>. Otherwise, returns false.
415
416=head2 MT::Meta->has_own_metadata_of($class)
417
418Returns true if the given class has any metadata fields defined. Otherwise,
419returns false.
420
421
422=head1 SEE ALSO
423
424L<MT::Object>, L<MT::Meta::Proxy>
425
426=cut
Note: See TracBrowser for help on using the browser.