line
stmt
bran
cond
sub
pod
time
code
1
package Oryx::Class;
2
3
18
18
95
use Carp qw(carp croak);
18
29
18
1061
4
18
18
89
use UNIVERSAL qw(isa can);
18
32
18
85
5
18
18
5925
use Scalar::Util qw(weaken);
18
40
18
2014
6
18
18
89
use warnings;
18
34
18
526
7
18
18
88
use strict;
18
28
18
773
8
18
18
100
no strict 'refs';
18
36
18
594
9
10
18
18
86
use base qw(Class::Data::Inheritable Class::Observable);
18
45
18
17716
11
12
=head1 NAME
13
14
Oryx::Class - abstract base class for Oryx classes
15
16
=head1 SYNOPSIS
17
18
# define a persistent class
19
package CMS::Page;
20
use base qw(Oryx::Class);
21
22
# ... class meta-data here (see DEFINING CLASS META-DATA below) ...
23
24
1;
25
26
#===========================================================================
27
# use a persistent class
28
use CMS::Page;
29
30
$page = CMS::Page->create({title => 'Life in the Metaverse'});
31
$page = CMS::Page->retrieve($id);
32
33
$page->update;
34
$page->delete;
35
36
@pages = CMS::Page->search({author => 'Richard Hun%'}, \@order, $limit, $offset);
37
38
#===========================================================================
39
# commit your changes
40
$page->dbh->commit; # or simply ...
41
$page->commit;
42
43
#===========================================================================
44
# attribute mutator
45
$page->title('The Metamanic Mechanic');
46
$tite = $page->title;
47
48
#===========================================================================
49
# reference association mutator
50
$template_obj = $page->template;
51
$page->template( $template_obj );
52
53
#===========================================================================
54
# array association accessor
55
$page->paragraphs->[0] = $intro_para;
56
$paragraph = $page->paragraphs->[42];
57
58
#===========================================================================
59
# array association operators
60
$concl = pop @{$page->paragraphs};
61
$intro = shift @{$page->paragraphs};
62
push @{$page->paragraphs}, $concl;
63
unshift @{$page->paragraphs}, $new_intro;
64
splice @{$page->paragraphs}, 1, 4, ($summary);
65
66
#===========================================================================
67
# hash association accessor
68
$image_obj = $page->images->{logo};
69
$page->images->{mug_shot} = $my_ugly_mug;
70
@keys = keys %{$page->images};
71
@values = values %{$page->images};
72
73
#===========================================================================
74
# support for Class::Observable
75
Page->add_observer(sub {
76
my ($item, $action) = @_;
77
#...
78
});
79
$page->add_observer(...); # instance
80
81
=head1 DESCRIPTION
82
83
Abstract base class for Oryx persistent classes.
84
85
=head1 ABSTRACT METHODS
86
87
These methods are overridden by the implementing Class class, i.e.
88
L or L, for example, but the
89
interfaces stay the same, so they are documented here.
90
91
=over
92
93
=item B
94
95
creates a persistent object using C<\%proto> to set up the initial state.
96
97
=item B
98
99
retrieves an object from storage by its object id
100
101
=item B
102
103
updates storage to persist and reflect changes in the object
104
105
=item B
106
107
deletes the object from storage
108
109
=item B
110
111
searches for objects with fields matching C<\%param>. SQL style
112
C<%> wildcards are supported. C<\@order>, C<$limit> and C<$offset> are
113
optional. C<\@order> is a list of columns which are used to sort the
114
results, C<$limit> is an integer which is used to limit the number of
115
results, and C<$offset> is used to exclude the first results up to
116
that number. These last two arguments are useful for paging through
117
search results.
118
119
=item B
120
121
commits the transaction if your database supports it and AutoCommit
122
is disabled, then you must do this.
123
124
=back
125
126
=head2 Observers
127
128
Oryx::Class objects now unherit from L thereby
129
implementing a publish/subscribe system similar to triggers.
130
131
The signals are named according to the 6 interface methods prefixed with
132
I and I, so the following signals are sent:
133
134
=over 4
135
136
=item before_create
137
138
Handler is passed a hashref as argument with fields: C , the search parameters, and C, the L where clause
139
140
=item after_create
141
142
Handler is passed a hashref as argument with fields: C , the search parameters, and C, the hashref which will be blessed into an instance of this class (during 'construct')
143
144
=item before_retrieve
145
146
Handler is passed a hashref as argument with fields: C, the id of the object to fetch, and C, the L where clause
147
148
=item after_retrieve
149
150
Handler is passed a hashref as argument with fields: C, the hashref which will be blessed into an instance of this class (during 'construct')
151
152
=item before_update
153
154
Handler is passed a hashref as argument with fields: C, the L where clause.
155
156
=item after_update
157
158
Handler takes no arguments.
159
160
=item before_delete
161
162
Handler is passed a hashref as argument with fields: C, the L where clause.
163
164
=item after_delete
165
166
Handler takes no arguments.
167
168
=item before_search
169
170
Handler is passed a hashref as argument with fields: C, the L where clause, C , the search parameters, the C and C parameters.
171
172
=item after_search
173
174
Handler is passed a hashref as argument with fields: C, the L where clause, C , the search parameters, the C and C parameters, and C, an arrayref of objects returned by the search.
175
176
=item before_construct
177
178
Handler is passed a hashref as argument with fields: C, the hashref which will be blessed into an instance of this class.
179
180
=item after_construct
181
182
Handler is passed a hashref as argument with fields: C, the persistent object.
183
184
=back
185
186
187
=cut
188
189
18
18
121294
use vars qw($XML_DOM_Lite_Is_Available);
18
39
18
1519
190
191
BEGIN {
192
18
18
284
__PACKAGE__->mk_classdata("auto_deploy");
193
18
551
__PACKAGE__->mk_classdata("dont_cache");
194
195
18
274
$XML_DOM_Lite_Is_Available = 1;
196
18
18
1166
eval "use XML::DOM::Lite qw(Parser Node :constants);";
18
7398
0
0
0
0
197
18
50
37964
$XML_DOM_Lite_Is_Available = 0 if $@;
198
}
199
200
our $DEBUG = 0;
201
our $PARSER;
202
203
sub parser {
204
0
0
0
0
0
$PARSER = Parser->new( whitespace => 'strip' ) unless defined $PARSER;
205
0
0
$PARSER;
206
}
207
208
our %Orphans;
209
210
# Object Cache
211
our %Live_Objects;
212
213
=head1 METHODS
214
215
These methods are concrete.
216
217
=over
218
219
=item init
220
221
Initialises the class data (see L)
222
223
=cut
224
225
sub init {
226
0
0
1
0
my $class = shift;
227
0
0
0
$DEBUG && $class->_carp('initializing class data');
228
229
# set up class data accessors :
230
0
0
$class->mk_classdata("_meta");
231
0
0
$class->mk_classdata("attributes");
232
0
0
$class->mk_classdata("associations");
233
0
0
$class->mk_classdata("methods");
234
0
0
$class->mk_classdata("parents");
235
236
# DATA section cache
237
0
0
$class->mk_classdata('dataNode');
238
239
0
0
$class->meta({});
240
0
0
$class->attributes({});
241
0
0
$class->associations({});
242
0
0
$class->methods({});
243
0
0
$class->parents([]);
244
245
0
0
${ $class.'::__initialized' } = 1;
0
0
246
}
247
248
=item import
249
250
Does the work of constructing the class' meta-instances
251
(L, L and L
252
instances) from the C<$shema> class variable or defined
253
in the DATA section of the module if you have L
254
installed.
255
256
=cut
257
258
sub import {
259
50
50
377768
my $class = shift;
260
50
146
my %param = @_;
261
262
50
50
281
$DEBUG>1 && $class->_carp("importing...");
263
264
50
100
100
319
if ($class eq __PACKAGE__ and defined $param{auto_deploy}) {
265
6
45
$class->auto_deploy($param{auto_deploy});
266
6
187
return; # not interested in doing anything further
267
}
268
44
50
66
13083
return if $class eq __PACKAGE__
66
269
or $class eq 'Oryx::MetaClass'
270
or $class =~ /Oryx::[^:]+::Class/;
271
272
26
50
45
if (${ $class.'::__initialized' }) {
26
169
273
0
0
0
$DEBUG>1 && $class->_carp('already initialized, returning');
274
0
0
return;
275
}
276
277
26
50
33
414
if (can($class, 'storage') and $class->storage) {
278
0
0
0
if (%Orphans) {
279
0
0
foreach (keys %Orphans) {
280
0
0
0
$DEBUG>1 && $_->_carp('YAY! I am no longer an Orphan');
281
0
0
$class->storage->schema->addClass($_)
282
}
283
0
0
%Orphans = ();
284
}
285
0
0
$class->storage->schema->addClass($class);
286
} else {
287
26
50
85
$DEBUG && $class->_carp("no storage available Orphaning");
288
26
74
$Orphans{$class}++;
289
26
100
227
$class->auto_deploy($param{auto_deploy})
290
if defined $param{auto_deploy};
291
26
32190
return;
292
}
293
294
# initialise class data
295
0
$class->init;
296
0
0
$DEBUG && $class->_carp("setting up...");
297
298
# first set up parent relationships (this doesn't *have* to be
299
# done first, but I believe that the chicken came before the
300
# egg... the are, as always, good semantic and performance reasons
301
# behind this belief... if not behind this particular fragment of
302
# code being here instead of at the bottom of this function).
303
0
foreach (@{$class.'::ISA'}) {
0
304
# only if the superclass is a subclass of Oryx::DBx::Class
305
0
0
0
if (isa($_, __PACKAGE__)
0
306
and $_ ne __PACKAGE__
307
and $_ !~ /Oryx::[^:]+::Class/) {
308
0
$class->addParent($_);
309
}
310
}
311
312
0
my $schema;
313
0
0
unless ($schema = ${$class.'::schema'}) {
0
314
0
my $xmldata = $class->parseDataIO;
315
0
eval(q{use Oryx::Schema::Generator});
316
0
0
die $@ if $@;
317
0
$schema = Oryx::Schema::Generator->generate($class, $xmldata);
318
}
319
0
0
if ($schema) {
320
0
0
$class->name($schema->{name}) if defined $schema->{name};
321
0
foreach (@{$schema->{attributes}}) {
0
322
0
$class->addAttribute($_);
323
}
324
0
foreach (@{$schema->{associations}}) {
0
325
0
$class->addAssociation($_);
326
}
327
0
foreach (@{$schema->{methods}}) {
0
328
0
$class->addMethod($_);
329
}
330
}
331
0
0
0
if ($class->auto_deploy or $param{auto_deploy}) {
332
0
0
unless ($class->storage->util->table_exists(
333
$class->dbh, $class->table)) {
334
0
$class->storage->deploy_class($class);
335
}
336
}
337
0
0
if ($param{dont_cache}) { $class->dont_cache(1) }
0
338
}
339
340
=item meta
341
342
Simple accessor to the class meta data.
343
344
=cut
345
346
sub meta {
347
0
0
1
my $class = shift;
348
0
0
$class->_meta(shift) if @_;
349
0
$class->_meta;
350
}
351
352
=item construct( $class, $proto )
353
354
This is typically called from within the C and C
355
methods of the implementation class (L or
356
357
into C<$class> and then allows each class meta-instance to frobnicate
358
it in turn if they have any need to, before handing the instance to
359
you.
360
361
=cut
362
363
sub construct {
364
0
0
1
my ($class, $proto) = @_;
365
366
0
my $object;
367
0
my $key = $class->_mk_cache_key($proto->{id});
368
0
0
return $object if ($object = $Live_Objects{$key});
369
370
0
$class->notify_observers('before_construct', { proto => $proto });
371
0
$object = bless $proto, $class;
372
373
0
$_->construct($object) foreach $class->members;
374
0
$_->construct($object) foreach @{$class->parents};
0
375
376
0
$class->notify_observers('after_construct', { object => $object });
377
0
0
$DEBUG && $class->_carp("constructing $object id => ".$object->id);
378
379
0
0
weaken($Live_Objects{$key} = $object) unless $object->dont_cache;
380
0
return $object;
381
}
382
383
=item addAttribute( $meta )
384
385
Creates an Attribute meta-instance and associates it with the class.
386
387
=cut
388
389
sub addAttribute {
390
0
0
1
my ($class, $meta) = @_;
391
0
my $attrib =
392
(ref($class->storage).'::Attribute')->new($meta, $class);
393
0
$class->attributes->{$attrib->name} = $attrib;
394
}
395
396
=item addAssociation( $meta )
397
398
Creates an Association meta-instance and associates it with the class.
399
400
=cut
401
402
sub addAssociation {
403
0
0
1
my ($class, $meta) = @_;
404
0
my $assoc =
405
(ref($class->storage).'::Association')->new($meta, $class);
406
0
$class->associations->{$assoc->role} = $assoc;
407
}
408
409
=item addMethod( $meta )
410
411
Does nothing at the moment as I cannot decide what such a method
412
would be used for exactly.
413
414
=cut
415
416
sub addMethod {
417
0
0
1
my ($class, $meta) = @_;
418
0
my $methd =
419
(ref($class->storage).'::Method')->new($meta, $class);
420
0
$class->methods->{$methd->name} = $methd;
421
}
422
423
=item addParent( $super )
424
425
Creates a Parent meta-instance and associates it with the class.
426
427
=cut
428
429
sub addParent {
430
0
0
1
my ($class, $super) = @_;
431
0
push @{$class->parents},
0
432
(ref($class->storage).'::Parent')->new($super, $class);
433
}
434
435
=item id
436
437
Returns the object id.
438
439
=cut
440
441
0
0
1
sub id { $_[0]->{id} }
442
443
=item is_abstract
444
445
True if the class does not define any attributes. This is used
446
for creating a special table for sharing sequences accross subclasses
447
and for instantiating the correct subclass instance if C
448
is called on an abstract class.
449
450
=cut
451
452
sub is_abstract {
453
0
0
1
my $class = shift;
454
0
return not %{$class->attributes};
0
455
}
456
457
=item table
458
459
Returns the table name for this class.
460
461
=cut
462
463
sub table {
464
0
0
1
my $class = shift;
465
0
0
unless (defined $class->meta->{table}) {
466
0
$class->meta->{table} = $class->schema->prefix.$class->name;
467
}
468
0
$class->meta->{table};
469
}
470
471
=item name([ $name ])
472
473
Get or set the C meta-attribute for the class.
474
475
=cut
476
477
sub name {
478
0
0
1
my $class = shift;
479
0
my $name = shift;
480
0
0
$class->setMetaAttribute("name", $name) if $name;
481
0
0
unless (defined $class->getMetaAttribute("name")) {
482
0
$class =~ /([^:]+)$/;
483
0
$class->setMetaAttribute("name", lc("$1"));
484
}
485
0
$class->getMetaAttribute("name");
486
}
487
488
=item members
489
490
Return a list of all meta-instances (Attribute, Association, Method
491
and Parent instances).
492
493
=cut
494
495
sub members {
496
0
0
1
my $class = shift;
497
return (
498
0
values %{$class->attributes},
0
499
0
values %{$class->associations},
500
0
values %{$class->methods},
501
# not really members, but we'll treat the same
502
#@{$class->parents},
503
);
504
}
505
506
=item commit
507
508
calls $self->dbh->commit to commit the trasaction
509
510
=cut
511
512
0
0
1
sub commit { $_[0]->dbh->commit }
513
514
=item schema
515
516
shortcut for $self->storage->schema. Read only.
517
518
=cut
519
520
0
0
1
sub schema { $_[0]->storage->schema }
521
522
sub parseDataIO {
523
0
0
0
my ($class) = @_;
524
0
0
unless ($XML_DOM_Lite_Is_Available) {
525
0
$class->_carp('XML DATA schemas are not supported unless'
526
.' you have XML::DOM::Lite installed');
527
0
return undef;
528
}
529
0
my $stream = $class->loadDataIO;
530
0
0
if ($stream) {
531
0
return $class->parser->parse($stream)->documentElement;
532
} else {
533
0
return undef;
534
}
535
}
536
537
sub loadDataIO {
538
0
0
0
my $class = shift;
539
0
my $fh = *{"$class\::DATA"}{IO};
0
540
0
0
return undef unless $fh;
541
0
local $/ = undef;
542
0
my $stream = <$fh>;
543
0
return $stream;
544
}
545
546
=item remove_from_cache
547
548
Object method to remove it from the memory cache.
549
550
=cut
551
552
sub remove_from_cache {
553
0
0
1
my $self = shift;
554
0
my $key = $self->_mk_cache_key($self->id);
555
0
CORE::delete( $Live_Objects{$key} );
556
}
557
558
sub _mk_cache_key {
559
0
0
0
my $class = ref($_[0]) || $_[0];
560
0
my $id = $_[1];
561
0
return join('|', ( $class, $id ));
562
}
563
564
sub _carp {
565
0
0
0
my $thing = ref($_[0]) ? ref($_[0]) : $_[0];
566
0
carp("[$thing] $_[1]");
567
}
568
569
sub _croak {
570
0
0
0
my $thing = ref($_[0]) ? ref($_[0]) : $_[0];
571
0
croak("[$thing] $_[1]");
572
}
573
574
0
0
sub DESTROY { $_[0]->remove_from_cache }
575
576
1;
577
__END__