Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot

SVN::Client subclassing issue

by tj_thompson (Monk)
on Sep 27, 2011 at 17:32 UTC ( #928123=perlquestion: print w/replies, xml ) Need Help??
tj_thompson has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,

I'm trying to subclass SVN::Client but I'm running into an issue that I haven't been able to figure out. First off, here's some code. Note that I've obviously removed the correct URL/user/pass information so it will be hard to check this code unless you have SVN::Client and a repo to play with.

#! /usr/intel/pkgs/perl/5.12.2/bin/perl use strict; use warnings; package Sub; use SVN::Client; our @ISA = ('SVN::Client'); sub ls { my $self = shift; print "Sub ls function called with arguments:\n".join("\n", @_)."\ +n"; $self->SUPER::ls(@_); } package main; use Data::Dumper; my $obj = Sub->new( auth => [ SVN::Client::get_simple_provider(), SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_simple_prompt_provider( sub {&_set_credential +s(@_)}, 2 ) ] ); my @args = ( 'https://svn.repo.url', 'HEAD', 1 ); print "Calling main ls with arguments:\n".join("\n", @args)."\n\n"; my $ref = $obj->ls(@args); print STDERR Dumper($ref); ###################################################################### +####### sub _set_credentials { my $cred = shift; $cred->username( 'username' ); $cred->password( 'password' ); }

The problem I'm having is with the ls wrapper function in sub. Run as is here (with valid url/user/pass, of course) I get the following output:

plxc16479> $h2/scripts/ Calling main ls with arguments: https://svn.repo.url HEAD 1 Sub ls function called with arguments: https://svn.repo.url HEAD 1 TypeError in method 'svn_client_ls', argument 2 of type 'char const *'

However, if instead of instantiating my subclass I change the Sub->new call to SVN::Client->new, I get this:

plxc16479> $h2/scripts/ Calling main ls with arguments: https://svn.repo.url HEAD 1 $VAR1 = { 'revision' => bless( do{\(my $o = 10904232)}, '_p_svn_dirent +_t' ), 'all.plist' => bless( do{\(my $o = 10904112)}, '_p_svn_diren +t_t' ) };

Now it works. This tells me that the arguments to the function are good since they're identical in both the direct SVN::Client::ls call and the Sub::ls wrapper function. The question is what's happening in the subclass wrapper function. I've verified the arguments are indeed being passed as expected, identical to the arguments to the direct SVN::Client->ls call

I have put a lot of time into trying to solve this, but no luck as of yet. Am I somehow subclassing this wrong? Am I making the call via SUPER incorrectly somehow? My simple subclass and SUPER tests seem to work, indicating my syntax and methodology here seems to be correct. In any case, I genuinely appreciate any help you kind folks might give.

EDIT: Here's something interesting. If I change the Sub::ls function like so:

sub ls { my $self = shift; print "Sub ls function called with arguments:\n".join("\n", @_)."\ +n"; bless $self, 'SVN::Client'; $self->SUPER::ls(@_); }

It now works as expected. Exact same arguments and call, but it now works with the object being of the SVN::Client class instead of the Sub class. Calling $self->SUPER::ls from the Sub package should call the SVN::Client::ls function regardless of the class of the invocant (right?). Why is the class of the invocant affecting the results of the call here?

Replies are listed 'Best First'.
Re: SVN::Client subclassing issue
by tj_thompson (Monk) on Sep 27, 2011 at 19:33 UTC

    Ok, after some digging I believe I understand the issue. In the file as it creates functions there is a section of code that adjusts the call parameters. Specifically, it checks if the first function is blessed into 'SVN::Client'. If it is, the arguments are handled in one manner. If it is not, they are handled a different way.

    # import methods into our name space and wrap them in a closure # to support method calling style $ctx->log() foreach my $function (@_all_fns) { no strict 'refs'; my $real_function = \&{"SVN::_Client::svn_client_$function"}; *{"SVN::Client::$function"} = sub { my ($self, $ctx); my @args; # Don't shift the first param if it isn't a SVN::Client # object. This lets the old style interface still work. # And is useful for functions like url_from_path which # don't take a ctx param, but might be called in method # invocation style or as a normal function. for (my $index = $[; $index <= $#_; $index++) { if (ref($_[$index]) eq 'SVN::Client') { ($self) = splice(@_,$index,1); $ctx = $self->{'ctx'}; last; } elsif (ref($_[$index]) eq '_p_svn_client_ctx_t') { $self = undef; ($ctx) = splice(@_,$index,1); last; } }

    This seems to mean I can't directly subclass SVN::Client as at least some of the functions break. However, I have found a couple obscure mentions of subclassing SVN::Client in my I find that strange.

    Guess I look for another way unless I want to modify I don't :)

      Guess I look for another way unless I want to modify I don't :)

      Make your class has-a-SVN::Client not is-a-SVN::Client?

      It seems to me that portion of SVN::Client is broken, in that it needs to use

      UNIVERSAL::isa($_[$index], 'SVN::Client')
      instead of ref/eq

      Apparently you can report bugs at

        Make your class has-a-SVN::Client not is-a-SVN::Client?

        This is what I had in mind. I was going to subclass initially because I wanted some functions to 'fall through' to the SVN::Client class without having to wrap each one. This should be easy enough to do from another object, but it's not something I've done before.

        So assuming my new Foo class is called with a function that is not part of the package, let's say Foo->cat. Foo does not have a cat function, but Foo does have a SVN::Client object. I'd like to pass a call to a function that does not exist in the current package to be handled by the SVN::Client object. What is the proper/elegant way to do this?

Re: SVN::Client subclassing issue
by tj_thompson (Monk) on Sep 27, 2011 at 20:38 UTC
    I'll read up on autoload and figure out how to set up what I want to do. I'm also going to see about submitting a bug report for the binding issue.

    As always, thanks for the help. You people are awesome and I appreciate it :)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://928123]
Front-paged by SparkeyG
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (6)
As of 2018-06-22 17:32 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (124 votes). Check out past polls.