view Lib/IMPL/Web/View/TTLoader.pm @ 307:2da2564f115d

*TTView: fixed memory leak
author cin
date Thu, 18 Apr 2013 02:21:28 +0400
parents 85572f512abc
children 47a09a8dc23a
line wrap: on
line source

package IMPL::Web::View::TTLoader;
use strict;

use Template::Constants qw(:status);

use IMPL::Const qw(:prop);
use IMPL::declare {
    require => {
        Provider => 'Template::Provider',
        Context => 'Template::Context',
        TTDocument => 'IMPL::Web::View::TTDocument',
        Exception => 'IMPL::Exception',
        ArgumentException => '-IMPL::InvalidArgumentException',
        KeyNotFoundException => '-IMPL::KeyNotFoundException'
    },
    base => [
        'IMPL::Object' => undef,
        'IMPL::Object::Serializable' => undef
    ],
    props => [
        options => PROP_RO,
        provider => PROP_RO,
        context => PROP_RO,
        ext => PROP_RO,
        layoutBase => PROP_RO,
        isInitialized => PROP_RO,
        initializer => PROP_RO,
        _globals => PROP_RW
    ]
};

sub save {
	my ($this,$context) = @_;
	
	$context->AddVar($_, $this->$_()) for qw(options provider context ext layoutBase);
}

sub restore {
	my ($class,$data,$surrogate) = @_;
	
	my %params = @$data;
	
	my $refOpts = delete $params{options};
	
	if ($surrogate){
		$surrogate->callCTOR($refOpts,%params);
	} else {
		$surrogate = $class->new($refOpts,%params);
	}
	return $surrogate;
}

sub CTOR {
    my ($this,$refOpts,%args) = @_;
    
    $refOpts ||= {};
    
    $this->ext($args{ext}) if $args{ext};
    $this->initializer($args{initializer}) if $args{initializer};
    $this->_globals(ref $args{globals} eq 'HASH' ? $args{globals} : {});
    
    $this->options($refOpts);
    $this->layoutBase($args{layoutBase}) if $args{layoutBase};
    
    # to aviod cyclic references we need to do a copy of $refOpts
    $refOpts->{LOAD_TEMPLATES} = $this->provider(Provider->new( { %$refOpts } ));
    
    $this->context(Context->new($refOpts));
}

sub document {
    my ($this,$name,$vars) = @_;
    
    my $tt = $this->template($name);
        
    $this->_init();
    
    my $opts = { %{ $this->options } };
    
    $this->context->localise();
    my $ctx = _clone_context($this->context);
    $this->context->delocalise();
    
    return TTDocument->new( $tt, $ctx, $this, $vars );
}

sub template {
    my ($this,$name) = @_;
    
    $name =~ s/^\s+|\s+$//g;
    
    die ArgumentException->new("A valid template name is required") unless length $name;
    
    $name = $this->_appendExt($name);
    
    my ($tt,$error) = $this->provider->fetch($name);
    
    if (defined $error and $error == STATUS_DECLINED) {
        die KeyNotFoundException->new($name);
    } elsif (defined $error and $error == STATUS_ERROR) {
        die Exception->new("Failed to load a template", $name, $tt);
    }
    
    return $tt;
}

sub layout {
	my ($this,$name) = @_;
	
	my $layout;
	
	if ($this->layoutBase) {
		$layout = $this->layoutBase . "/";
	}
	
	$layout .= $name;
	return $this->template($layout);
}

sub _appendExt {
    my ($this,$name) = @_;
    
    return $name unless $this->ext;
    
    if (length $this->ext and substr( $name, -length($this->ext) ) eq $this->ext) {
        return $name;
    } else {
        return $name . $this->ext;
    }
}

sub _init {
    my ($this) = @_;
    
    if (!$this->isInitialized) {
        my $initializer = $this->initializer || sub {};
        
        eval {
            $this->context->process($initializer,$this->_globals);
        };
        if (my $e = $@) {
            die Exception->new("Failed to process an initializer", $this->initializer, $e);
        }
        
        $this->isInitialized(1);
    }
}

sub _clone_context {
    my $args = { %{shift || {}} };
    delete $args->{CONFIG};
    
    return Template::Context->new($args);
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::View::TTLoader> - предоставляет глобальный контекст для загрузки шаблонов

=head1 SYNOPSIS

=begin code

use IMPL::Web::View::TTLoader();

my $loader = new IMPL::Web::View::TTLoader(
    {
        INCLUDE_PATH => [
            '/my/app/tt',
            '/my/app/tt/lib'
        ]
    },
    ext => '.tt',
    initializer => 'shared/global'
        
);

my $doc = $loader->document('index');

my $html = $doc->Render();

=end code

=head1 DESCRIPTION

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

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

=head1 MEMBERS

=head2 C<CTOR($options,%args)>

=over

=item * C<$options>

Параметры контекста загрузчика, контексты документов и элементов управления
также унаследуют эти свойства. Напрямую передаются в конструктор
C<Template::Context>.

=item * C<%args>

Именованные параметы загрузчика.

=over

=item * C<ext>

Расширение, которое будет добавляться к именам шаблонов и документов (если оно
не будет указано явно).

=item * C<initializer>

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

=item * C<globals>

Глобальные переменнын, которые будут переданы в контекст.

=item * C<layoutBase>

Путь к шаблонам для оформления документов. Каждый документ может задавать свой
C<layout> указанный в его C<META> блоке или конструкторе.
См. C<IMPL::View::TTDocument>.

=back

=back

=head2 C<document($docName)>

Загружает документ с именем C<$docName>. При необходимости к нему будет
добавлено расширение, указанное в свойстве C<ext>. Это единственно полезный
метод провайдера.

=cut