view Lib/IMPL/Web/Application/OperationContract.pm @ 272:47db27ed5b43

sync
author sergey
date Mon, 28 Jan 2013 17:24:37 +0400
parents 4abda21186cd
children
line wrap: on
line source

package IMPL::Web::Application::OperationContract;
use strict;

use IMPL::lang qw(:declare);
use IMPL::declare {
	require => {
		Exception         => 'IMPL::Exception',
		ArgumentException => '-IMPL::InvalidArgumentException',
		ResourceInterface => 'IMPL::Web::Application::ResourceInterface'
	  },
	  base => [
		'IMPL::Object'           => undef,
		'IMPL::Object::Autofill' => '@_'
	  ],
	  props => [
		binding    => PROP_ALL,
		success    => PROP_ALL,
		error      => PROP_ALL
	  ]
};

sub Invoke {
	my ( $this, $resource, $request ) = @_;

	die ArgumentException->new( resource => 'A valid resource is required' )
	  unless eval { $resource->isa(ResourceInterface) };
	
	my $result = eval {
		_InvokeDelegate($this->binding, $resource, $request)
	};
	
	if (my $e = $@) {
	    if ($this->error) {
		  $result = _InvokeDelegate($this->error, $resource, $request, $e) ;
	    } else {
	        die $e;
	    }
	    
	} else {
		$result = _InvokeDelegate($this->success, $resource, $request, $result)
		  if ($this->success);
	}

	return $result;
}

sub _InvokeDelegate {
	my $delegate = shift;
	
	return $delegate->(@_) if ref $delegate eq 'CODE';
	return $delegate->Invoke(@_) if eval { $delegate->can('Invoke')};
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::Application::OperationContract> - Описание операции над
веб-ресурсом.

=head1 SYNOPSIS

=begin code

use IMPL::require {
	'OperationContract' => 'IMPL::Web::Application::OperationContract',
	'HttpReponse' => 'IMPL::Web::Application::HttpReponse'
};

my $operation = OperationContract->new(
    binding => sub {
    	my ($resource,$request) = @_;
    	
    	my $itemName = $request->param('itemName', qr/^(\w+)$/);
    	
    	return $model->FindItem($itemName);
    },
    success => sub {
        my ($resource,$request,$result) = @_;
        return HttpReponse->Redirect(location => $resource->location->Child($result->id));
    }
);

my $response = $operation->InvokeOperation($resource);

=end code

=head1 DESCRIPTION

Для описания контракта операции используется понятие делегата, тоесть объекта,
представляющего собой функцию, либо объект, имеющий метод C<Invoke>.

Поскольку предметная область должна быть отделена от
контроллеров веб-сервиса, она ничего не знает про существование ресурсов и их
организацию и тем более о протоколе C<HTTP>, поэтому все вещи, связанные с
формированием ответов сервера, представлениями данных и т.п. должны выполняться
самими контроллерами. Поведение контроллеров описывается контрактами, в которых
указываются делегаты для реализации необходимого функционала, для корректного
отображения ресурсов в объекты предметной области и обратно.

Контракт операции состоит из нескольких свойств, осуществляющих привязку к 
предметной области:

=over

=item * C<binding>

делегат для привязки операции над ресурсом к предметной области.

=item * C<success>

делегат для обработки результат операции, например для формирования ответа с
перенаправлением.

=item * C<error>

делегат для обработки исключительной ситуации, может быть использован для
формирования представления для повторного ввода данных на форме.  

=back    

=head1 MEMBERS

=head2 C<[get,set] binding>

Привязка операции к ресурсу, например

=begin code

$operationContract->binding(sub {
	my ($resource,$action) = @_;
	$resource->model
})

=end code

Может быть как ссылка на процедуру, так и ссылкой на объект, имеющий метод
C<Invoke>.

=head2 C<[get,set] success>

Обрабатывает результат привязки к предметной области.

=begin code

# redirect (for example after POST)
$operationContract->success(sub {
	my ($resource,$action,$result) = @_;
	
	return IMPL::Web::HttpResponse
	   ->Redirect($resource->location->Child($result->id));
})

=end code

Может быть как ссылка на процедуру, так и ссылкой на объект, имеющий метод
C<Invoke>.

=head2 C<[get,set] error>

Обрабатывает ошибку возникшую при выполнении привязки к предметной области.

=begin

$operationContract->error(sub {
	my ($resource,$action,$error) = @_;
	
	$action->form->errors->{''} = $error;
	
	return $resource->model;
});

=end 

=cut