It seems, there's a bug in TT which causes that an array of single object item could be handled differently than an array of more object items. This already was discussed many times, but I didn't found any satisfying solution, yet.
Problem description: This could happen when a method returns an array of objects rather than a reference to it. If such returned array has just one item then TT assumes it meant a single item (rather than an array of 1 item). This is standard (and documented) behavior. Problem could occur if you try to call some list VMethod (sort, first, ...) on such array of one item. It seems it has been partially solved in some previous TT version (2.20?) in such way that if a list VMethod is called on a scalar value then the value is promoted to a list in advance of the list VMethod call. But, if the only item of returned array is an object it seems that it is not promoted to a single item array as it would be expected. Instead, TT tries to find the VMethod among the object's methods. If it finds there a method with such name, then TT calls it (this is still ok and understandable). In opposite, if it doesn't find it there, then TT decides to treat the object as a hash (and that's the problem). Thus, the called list VMethod becomes hash VMethod and its expected function is done on the object's attributes. (Note: As my object is a blessed hash). Instead, I would expect TT to promote the object (as well as other scalars) to a list and then call a VMethod on it (of course, in case that such method couldn't be found among the object's methods). Questions: 1) Did I understand TT behavior correctly? 2) What is the reason for such behavior? Isn't there any way in Perl to determine whether item an object or a hash ref is? 3) Is this already solved in some TT version or is it planned to be solved in some further version (3.0, ...)? 4) Is there any way how this could be worked around? Hints: 1) I couldn't change methods to return array refs instead of arrays as I already use them in lot of other modules. 2) One possible workaround I found is to determine whether the method is called from perl or from TT and return array or array ref respectively. This solution is based on http://www.mail-archive.com/[email protected]/msg08524.html. But this solution, considering e.g. object inheritance, is vulnerable. 3) I would prefer a solution on TT side, not to modify object methods as they are "third party" objects/modules. 4) Objects inside the returned array are blessed hashes. 5) I have TT version 2.20. 6) Sample code to reproduce the problem follows: ------------ CODE --------------------- use strict; use warnings; use Template; package MyObj; sub new { my $type = shift; my %params = @_; my $self = {}; $self->{name} = $params{name}; $self->{foo} = undef; bless $self, $type; } sub get_name { my $self = shift; return $self->{name}; } #sub sort { # just to demonstrate that TT goes here, if the object has a method # named like some VMethod # if you uncomment this stupid work around TT will work fine # # my $self = shift; # return $self; #} package main; sub get_MyObj_list1 { my @array = ( MyObj->new( name => 'a' ) ); # demonstration of another vulnerable workaround from Hint 2) # return caller =~ m/Template::/ ? [...@array] : @array; } sub get_MyObj_list2 { my @array = ( MyObj->new( name => 'b' ), MyObj->new( name => 'a' ) ); # demonstration of another vulnerable workaround from Hint 2) # return caller =~ m/Template::/ ? [...@array] : @array; } my $template = Template->new(); $template->process(\*DATA, { get_MyObj_list1 => \&get_MyObj_list1, get_MyObj_list2 => \&get_MyObj_list2 } ) || print $template->error(); __DATA__ 1 ARRAY DESCRIPTION TT ASSUMES IT MEANT ARRAY CONTENT 2 ----------------- ------------------- ------------- 3 Unsorted array of 1 MyObj item : [% get_MyObj_list1 %] -> [% FOREACH item IN get_MyObj_list1 %] [% item %] = [% item.name %] [% END %] 4 Unsorted array of 2 MyObj items : [% get_MyObj_list2 %] -> [% FOREACH item IN get_MyObj_list2 %] [% item %] = [% item.name %] [% END %] 5 6 Sorted array of 1 MyObj item : [% get_MyObj_list1 %] -> [% FOREACH item IN get_MyObj_list1.sort('name') %] [% item %] = [% item.name %] [% END %] 7 Sorted array of 2 MyObj items : [% get_MyObj_list2 %] -> [% FOREACH item IN get_MyObj_list2.sort('get_name') %] [% item %] = [% item.name %] [% END %] ------------ END OF CODE --------------------- ------------ OUTPUT ------------------- 1 ARRAY DESCRIPTION TT ASSUMES IT MEANT ARRAY CONTENT 2 ----------------- ------------------- ------------- 3 Unsorted array of 1 MyObj item : MyObj=HASH(0x1c5fee4) -> MyObj=HASH(0x1c5bcfc) = a 4 Unsorted array of 2 MyObj items : ARRAY(0x1c5bacc) -> MyObj=HASH(0x1c5bc6c) = b MyObj=HASH(0x1c5bd0c) = a 5 6 Sorted array of 1 MyObj item : MyObj=HASH(0x1c57bf4) -> foo = name = 7 Sorted array of 2 MyObj items : ARRAY(0x1c5bcdc) -> MyObj=HASH(0x1c57b84) = a MyObj=HASH(0x1c5bacc) = b ------------ END OF OUTPUT --------------------- Note: As this could be dangerous and not easily debugged, I would suppose to emphasize strongly that TT is happy with array refs but not with arrays and discuss problem with single object lists more visible than as a last chapter of VMethod documentation. (see http://template-toolkit.org/docs/manual/VMethods.html#section_Automagic_Promotion_of_Scalar_to_List_for_Virtual_Methods). Btw, the chapter name doesn't make me think of it as a danger. Any suggestions very appreciated. Thanks a lot in advance! Kind regards, Petr DanihlĂk
_______________________________________________ templates mailing list [email protected] http://mail.template-toolkit.org/mailman/listinfo/templates
