view Lib/IMPL/Web/Application/ControllerUnit.pm @ 171:59e5fcb59d86

Исправления, изменена концепция веб-форм
author sourcer
date Mon, 06 Jun 2011 03:30:36 +0400
parents b88b7fe60aa3
children 068acfe903c3
line wrap: on
line source

use strict;
package IMPL::Web::Application::ControllerUnit;
use parent qw(IMPL::Object);

use IMPL::Class::Property;
use IMPL::DOM::Transform::PostToDOM;
use IMPL::DOM::Schema;
use Class::Inspector;
use File::Spec;
use Sub::Name;

use constant {
	CONTROLLER_METHODS => 'controller_methods',
	STATE_CORRECT => 'correct',
	STATE_NEW => 'new',
	STATE_INVALID => 'invalid'
};

BEGIN {
	public property action => prop_get | owner_set;
	public property application => prop_get | owner_set;
	public property query => prop_get | owner_set;
	public property response => prop_get | owner_set;
	public property formData => prop_get | owner_set;
	public property formSchema => prop_get | owner_set;
	public property formErrors => prop_get | owner_set;
}

my %publicProps = map {$_->Name , 1} __PACKAGE__->get_meta(typeof IMPL::Class::PropertyInfo);

__PACKAGE__->class_data(CONTROLLER_METHODS,{});

our @schemaInc;

sub CTOR {
	my ($this,$action,$args) = @_;
	
	$this->action($action);
	$this->application($action->application);
	$this->query($action->query);
	$this->response($action->response);
	
	$this->$_($args->{$_}) foreach qw(formData formSchema formErrors);
}

sub unitNamespace() {
	""
}

sub transactions {
	my ($self,%methods) = @_;
	
	while (my ($method,$info) = each %methods) {
		if ($info and ref $info ne 'HASH') {
			warn "Bad transaction $method description";
			$info = {};
		}
		
		$info->{wrapper} = 'TransactionWrapper';
		$self->class_data(CONTROLLER_METHODS)->{$method} = $info;
	}
}

sub forms {
	my ($self,%forms) = @_;
	
	while ( my ($method,$info) = each %forms ) {
		die new IMPL::Exception("A method doesn't exists in the controller",$self,$method) unless $self->can($method);
		if ( not ref $info ) {
			$self->class_data(CONTROLLER_METHODS)->{$method} = {
				wrapper => 'FormWrapper',
				schema => $info
			};
		} elsif (ref $info eq 'HASH') {
			$info->{wrapper} = 'FormWrapper';
			
			$self->class_data(CONTROLLER_METHODS)->{$method} = $info;
		} else {
			die new IMPL::Exception("Unsupported method information",$self,$method);
		}
	}
}

sub InvokeAction {
	my ($self,$method,$action) = @_;
	
	if (my $methodInfo = $self->class_data(CONTROLLER_METHODS)->{$method}) {
		if (my $wrapper = $methodInfo->{wrapper}) {
			return $self->$wrapper($method,$action,$methodInfo);
		} else {
			return $self->TransactionWrapper($method,$action,$methodInfo);			
		}
	} else {
		die new IMPL::InvalidOperationException("Invalid method call",$self,$method);
	}
}

sub MakeParams {
	my ($this,$methodInfo) = @_;
	
	my $params;
	if ($params = $methodInfo->{parameters} and ref $params eq 'ARRAY') {
		return map $this->ResolveParam($_,$methodInfo->{inflate}{$_}), @$params; 
	}
	return();
}

sub ResolveParam {
	my ($this,$param,$inflate) = @_;
	
	if ( $param =~ /^::(\w+)$/ and $publicProps{$1}) {
		return $this->$1();
	} else {
		my $value;
		if ( my $rx = $inflate->{rx} ) {
			$value = $this->action->param($param,$rx);
		} else {
			$value = $this->query->param($param);
		}
		
		if (my $method = $inflate->{method}) {
			$value = $this->$method($value); 
		}
		return $value;
	}
}

sub TransactionWrapper {
	my ($self,$method,$action,$methodInfo) = @_;
	
	my $unit = $self->new($action);
	return $unit->$method($unit->MakeParams($methodInfo));
}

sub FormWrapper {
	my ($self,$method,$action,$methodInfo) = @_;
	
	my $schema = $methodInfo->{schema} ? $self->loadSchema($methodInfo->{schema}) : $self->unitSchema;
	
	my $process = $action->query->param('process') || 0;
	my $form = $methodInfo->{form}
		|| $action->query->param('form')
		|| $method;
	
	my %result;
	
	my $transform = IMPL::DOM::Transform::PostToDOM->new(
		undef,
		$schema,
		$form
	);
	
	$result{formName} = $form;
	$result{formSchema} = $schema;
	
	if ($process) {
		$result{formData} = $transform->Transform($action->query);
		$result{formErrors} = $transform->Errors->as_list;
		if ($transform->Errors->Count) {
			$result{state} = STATE_INVALID;
		} else {
			$result{state} = STATE_CORRECT;
			my $unit = $self->new($action,\%result);
			
			eval {
				$result{result} = $unit->$method($unit->MakeParams($methodInfo));
			};
			if (my $err = $@) {
				$result{state} = STATE_INVALID;
				if (eval { $err->isa(typeof IMPL::WrongDataException) } ) {
					$result{formErrors} = $err->Args;
				} else {
					die $err;
				}
			} 
		}
	} else {
		if (my $initMethod = $methodInfo->{init}) {
			my $unit = $self->new($action,\%result);
			$result{formData} = $transform->Transform( $unit->$initMethod($unit->MakeParams($methodInfo)) );
		} else {
			$result{formData} = $transform->Transform($action->query);
		}
		
		$result{formErrors} = $transform->Errors->as_list;
		$result{state} = STATE_NEW;
	}
	
	return \%result;
}

sub loadSchema {
	my ($self,$name) = @_;
	
	foreach my $path (map File::Spec->catfile($_,$name) ,@schemaInc) {
		return IMPL::DOM::Schema->LoadSchema($path) if -f $path;
	}

	die new IMPL::Exception("A schema isn't found", $name);
}

sub unitSchema {
	my ($self) = @_;
	
	my $class = ref $self || $self;
	
	my @parts = split(/:+/, $class);
	
	my $file = pop @parts;
	$file = "${file}.schema.xml";
	
	foreach my $inc ( @schemaInc ) {
		my $path = File::Spec->catfile($inc,@parts,$file);
		
		return IMPL::DOM::Schema->LoadSchema($path) if -f $path;
	}
	
	return undef;
}

sub discover {
	my ($this) = @_;
	
	my $methods = $this->class_data(CONTROLLER_METHODS);
	
	my $namespace = $this->unitNamespace;
	(my $module = typeof $this) =~ s/^$namespace//;
	
	my %smd = (
		module => [grep $_, split /::/, $module ],
	);
	
	while (my ($method,$info) = each %$methods) {
		my %methodInfo = (
			name => $method
		);
		$methodInfo{parameters} = [ grep /^[^\:]/, @{ $info->{parameters} } ] if ref $info->{parameters} eq 'ARRAY';
		push @{$smd{methods}},\%methodInfo; 
	}
	return \%smd;
}

__PACKAGE__->transactions(
	discover => undef
);

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::Application::ControllerUnit> -        .

=head1 DESCRIPTION

,       .     
      C<transaction>, C<form>.

     ,      .
    C<InvokeAction($method,$action)>,  / 
.

        .      
  (C<TransactionWrapper>  C<FormWrapper>).     
       ,      .  

=head2  

    ,   ,    
 .

=head2 

     ,   DOM    .
  DOM   .      
C<formData>, C<formSchema>, C<formErrors>.

      ,    , 
  

=begin code

{
	state => '{ new | correct | invalid }',
	result => $transactionResult,
	formData => $formDOM,
	formSchema => $formSchema,
	formErrors => @errors
}

=end code

=over

=item C<state>

  .

=over

=item C<new>

  ,    ,   .
      .

=item C<correct>

  ,  ,       C<result>

=item C<invalid>

    ,     C<formErrors>. 
 .

=back

=item C<result>

  ,    .

=item C<formData>

    .   ,     ,
     ,   .

=item C<formSchema>

  ,      .

=item C<formErrors>

       .

=back

=head1 MEMBERS

=over

=item C<[get] application>

 ,   .

=item C<[get] query>

 .

=item C<[get] response>

 .

=item C<[get] formData>

C<IMPL::DOM::Document>   ,     .

=item C<[get] formSchema>

C<IMPL::DOM::Schema>      .

=item C<[get] formErrors>

  ,   .      , 
 ,      .

=item C<InvokeAction($method,$action)>

   ,       
 .

=item C<TransactionWrapper($method,$action,$methodInfo)>

    ,       
 .

=item C<FormWrapper($method,$action,$methodInfo)>

   ,       
 .

=item C<discover()>

,    ,      C<Simple Module Definition>.

=begin code

# SMD structure
{
	module => ['Foo','Bar'],
	methods => [
		{
			name => 'search',
			parameters => ['text','limit'] #optional
		}
	]
}

=end code

=back

=head1 EXAMPLE

=begin code

package MyBooksUnit;
use strict;
use parent qw(IMPL::Web::Application::ControllerUnit);

__PACKAGE__->PassThroughArgs;

sub unitDataClass { 'My::Books' }

__PACKAGE__->transactions(
	find => {
		parameters => [qw(author)]
	},
	info => {
		parameters => [qw(id)]
	}
);
__PACKAGE__->forms(
	create => 'books.create.xml'
);

sub find {
	my ($this,$author) = @_;
	
	return $this->ds->find({author => $author});
}

sub info {
	my ($this,$id) = @_;
	
	return $this->ds->find({id => $id});
}

sub create {
	my ($this) = @_;
	
	my %book = map {
		$_->nodeName, $_->nodeValue
	} $this->formData->selectNodes([qw(author_id title year ISBN)]);
	
	return $this->ds->create(\%book);
}

=end code

=cut