A Class within a Subroutine

Ovid on 2004-10-18T17:08:01

Sometimes refactoring will take you down some strange paths. I have a subroutine that needs to be exported to other test classes. This subroutine mocks up certain portions of the SOAP::Lite interface so I can pass in the return XML and also see what the request XML looks like. I've implemented an entire class within a subroutine. I don't think this is really a good piece of code, but it was interesting to write.

sub _mock_soap_interface {
    package MOCK::SOAP::INTERFACE;
    require Sub::Override;
    require SOAP::Lite;
                                                                                                       
    my $request = '';
                                                                                                       
    my $xml  = shift;
    my $token = Sub::Override->new(
        'SOAP::Lite::call',
        sub {
            my $self = shift;
            my $serializer = $self->serializer;
            $request = $serializer->envelope(method => shift(@_), @_);
            return $xml;
        }
    );
    *MOCK::SOAP::INTERFACE::xml = sub { ${shift->{_request}} };
    bless {
        _request => \$request,
        _token   => $token,
    } => __PACKAGE__;
}

And using it:

my $request = _mock_soap_interface($response_xml);
my $soap = My::SOAP::Lite::Subclass->new;
my $results = $soap->get_some_service;
# test results
my $request_xml = $request->xml;
# test that request XML is properly formatted


Huh?

jplindstrom on 2004-10-18T18:09:44

I'm confused.

What does it mean to change package within a sub?

Re:Huh?

Ovid on 2004-10-18T18:12:49

Well, package declarations are lexically scoped. It would have been simpler for me to have simply declared another package in that file and have the subroutine return a new object created from that package. Instead, just because of the way the refactoring worked, I had simply wrapped the package in the sub and now that I look at it, I can't see why this would be a bad thing.

There's no real benefit that I can tell, but there's no real harm I can identify, either.

Re:Huh?

drhyde on 2004-10-19T08:55:55

Quite simply, it means that when you enter the sub you're in one package, and then at some point during the sub the current package changes. I recently did something like that, and it was also because of some refactoring.

I have a bunch of classes, all subclasses of a single class, and all pretty similar. I broke a ton of code out into a "helper" module which they all use. However, part of that is to call a method in the class's superclass, like this:

package Foo;
@ISA(qw(Bar));

use HelperMethods;

sub blah {
# massage data
return HelperMethods::blah(...);
}

########

package HelperMethods;

sub blah {
...
$class->SUPER::blah(...) # call calling class's superclass's blah()
}

Trouble is, although in HelperMethods::blah() the $class variable is correctly set to Foo (indicating that this was called from Foo::blah), but it doesn't work. This is because SUPER is evaluated in the context of the current package (HelperMethods) and not really in the context of $class.

The solution was:

eval "
package $class;
\$class->SUPER::blah(...);
";

The extra step of the eval is needed because packge $class is illegal - package names must be constant.