slightly more complicated resolver
[scpubgit/Object-Builder.git] / lib / Object / Builder.pm
CommitLineData
ff402c4d 1package Object::Builder;
2
3use Module::Runtime qw(use_module);
322a6ab3 4use List::Util qw(reduce);
5use Scalar::Util qw(weaken);
ff402c4d 6use Moo;
7
8our $VERSION = '0.000001'; # 0.0.1
9
10$VERSION = eval $VERSION;
11
12has class => (
21dba240 13 is => 'rw', lazy => 1, builder => 1,
ff402c4d 14 trigger => sub { shift->_clear_final_class },
15);
16
21dba240 17sub _build_class { die "No default class set" }
18
ff402c4d 19has roles => (
322a6ab3 20 is => 'rw', lazy => 1, builder => 1, clearer => 1,
ff402c4d 21 trigger => sub { shift->_clear_final_class },
322a6ab3 22 coerce => sub { ref($_[0]) eq 'ARRAY' ? { '' => $_[0] } : $_[0] },
ff402c4d 23);
24
322a6ab3 25sub set_roles_for {
26 my ($self, $for, @roles) = @_;
27 $self->roles({ %{$self->roles}, $for => \@roles });
28 return;
29}
30
31after clear_roles => sub { shift->_clear_final_class };
21dba240 32
322a6ab3 33sub _build_roles { {} }
34
35sub _role_list {
36 my ($self) = @_;
37 my $roles = $self->roles;
38 map @{$roles->{$_}}, sort keys %$roles;
39}
ff402c4d 40
41has _final_class => (is => 'lazy', clearer => 1);
42
43sub _build__final_class {
44 my ($self) = @_;
c789b081 45
46 # This path will only be taken if class => undef was intentionally passed
47 # to our constructor; this allows for a subref constructor which of course
48 # doesn't necessarily need a class at all.
49
50 return unless defined($self->class);
51
ff402c4d 52 my $class = use_module($self->class);
322a6ab3 53 if (my @roles = $self->_role_list) {
ff402c4d 54 require Moo::Role;
55 return Moo::Role->create_class_with_roles($class, @roles);
56 } else {
57 return $class;
58 }
59}
60
61after _clear_final_class => sub { shift->clear_object };
62
63has constructor => (is => 'ro', default => sub { 'new' });
64
65has arguments => (
322a6ab3 66 is => 'rw', builder => 1, clearer => 1,
ff402c4d 67 trigger => sub { shift->_clear_final_arguments },
ff402c4d 68);
69
322a6ab3 70sub set_argument {
71 my ($self, $name, @arg) = @_;
72 $self->arguments({ %{$self->arguments}, $name => \@arg });
73}
21dba240 74
322a6ab3 75sub set_argument_weaken {
76 my ($self, $name, @arg) = @_;
77 weaken($arg[0]);
78 $self->arguments({ %{$self->arguments}, $name => \@arg });
79}
ff402c4d 80
322a6ab3 81sub _build_arguments { {} }
ff402c4d 82
322a6ab3 83after clear_arguments => sub { shift->_clear_final_arguments };
ff402c4d 84
85has _final_arguments => (is => 'lazy', clearer => 1);
86
322a6ab3 87after _clear_final_arguments => sub { shift->clear_object };
ff402c4d 88
89sub _build__final_arguments {
90 my ($self) = @_;
322a6ab3 91 my %arguments = %{$self->arguments};
92 map +($_ => $self->_resolve($arguments{$_})), keys %arguments;
93}
94
95sub _resolve {
96 my ($self, $to_resolve) = @_;
97
98 # [ $x ] -> $x
99 # [ $inv, 'x', 'y' ] -> $inv->x->y
c789b081 100 # [ $inv, [ 'x', [ 'y' ] ], 'z' ] -> $inv->x('y')->z
101 # [ $inv, [ 'x', [ $other, 'y' ] ], 'z' ] -> $inv->x($other->y)->z
102
103 no warnings 'once'; # $a
322a6ab3 104
105 return reduce {
106 my ($meth, @arg) = (ref($b) eq 'ARRAY' ? @$b : $b);
c789b081 107 $a->$meth(map $self->_resolve($_), @arg);
322a6ab3 108 } @$to_resolve;
ff402c4d 109}
110
111has object => (is => 'lazy', clearer => 1);
112
113sub _build_object {
114 my ($self) = @_;
115 $self->_final_class->${\$self->constructor}($self->_final_arguments);
116}
117
322a6ab3 118sub fresh_object {
119 my ($self) = @_;
120 $self->_build_object;
121}
122
21dba240 123sub BUILD {
124 my ($self, $args) = @_;
125 unless (
c789b081 126 $args->{object} or exists $args->{class}
127 or ($self->can('_build_class') ne __PACKAGE__->can('_build_class'))
21dba240 128 ) {
129 die "No static object passed, and no class supplied or defaulted";
130 }
131}
132
ff402c4d 1331;
134
135=head1 NAME
136
137Object::Builder - An object for building other objects.
138
139=head1 SYNOPSIS
140
141=head1 DESCRIPTION
142
143=head1 AUTHOR
144
145 mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
146
147=head1 CONTRIBUTORS
148
149None yet - maybe this software is perfect! (ahahahahahahahahaha)
150
151=head1 COPYRIGHT
152
153Copyright (c) 2012 the Object::Builder L</AUTHOR> and L</CONTRIBUTORS>
154as listed above.
155
156=head1 LICENSE
157
158This library is free software and may be distributed under the same terms
159as perl itself.