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

changing object's methods at runtime

by ViceRaid (Chaplain)
on May 21, 2003 at 11:21 UTC ( [id://259686]=perlquestion: print w/replies, xml ) Need Help??

ViceRaid has asked for the wisdom of the Perl Monks concerning the following question:

Morning

I'm using the Apache::FakeRequest module to exercise some mod_perl handlers. In my test script, the FakeRequest is set up with some parameters:

my $req = Apache::FakeRequest->('uri' => '/foo/bar', 'is_main' => 1, ... );

This is all good. However, how the Apache::FakeRequest works is to return the set values when the method's called without arguments, and to set the named value to the passed value when the method is called with arguments. All good, but there's one method, $req->lookup_uri where I don't want this behaviour. I'd like to supply my own anonymous sub to handle calls to this method.

However, I'm a bit stuck. How can I change the definition of this method on this object only, at runtime, after the instance's creation? Perl keeps giving me an error I don't understand: "Can't modify non-lvalue subroutine call at..."

With thanks
Alex

Replies are listed 'Best First'.
Re: changing object's methods at runtime
by robartes (Priest) on May 21, 2003 at 11:46 UTC
    Overriding one instance's methods at runtime is not directly possible, unless I'm mistaken (which is entirely possible and often even likely). What you could do is provide your own subclass of Apache::FakeRequest that overrides that method, and use that as the class of your object:
    package Apache::FakeRequest::ViceRaid; use base qw(Apache::FakeRequest); sub lookup_uri { # do your stuff. }
    In your actual script, you'd use something like my $req=Apache::FakeRequest::ViceRaid->new() to instantiate your derived object.

    The non-lvalue error means that you're assigning something to something else that cannot be assigned to. Things that you can assign to are lvalues, thus things that you can't assign to are non-lvalues. E.g.:

    $ perl -e 'sub nonlvalue { return }; nonlvalue()=1;' Can't modify non-lvalue subroutine call in scalar assignment at -e lin +e 1, near "1;"
    The nonlvalue subroutine is not an lvalue, so you get the error when you want to assign to it. perlsub has some information on how to make a subroutine return an lvalue.

    CU
    Robartes-

      Overriding one instance's methods at runtime is not directly possible, unless I'm mistaken (which is entirely possible and often even likely).

      While not the best solution in this instance (I'd subclass or go with Test::MockObject) it is possible to override methods at runtime - and it can occasionally be useful. See Micro Mocking: using local to help test subs for some examples.

      Just create a subclass, that's what I'd do too. As for the custom method: provide an extra field in the original object (that is indeed the most dangerous aspect of it), and assign the sub ref to it. The method itself, as per robartes, needn't do anything other than fetching that sub ref and calling it.

      Now, looking at the source of Apache::FakeRequest, it looks like it is a blessed hash ref, with your named parameters as hash items. So you can just call new() with this additional named parameter, to have it inserted.

      $req=Apache::FakeRequest::ViceRaid->new( method::lookup_uri => sub { # ... insert what you want it to do });
      That was easy.
      n.b. "method::lookup_uri" is still a bareword as far as Perl is concerned. I just used an outstanding value to reduce the risk of field name clashes.

      The source for the subclass can be pretty much as in robartes note. You just need to fill in the method call, which could look something like this:

      sub lookup_uri { $_[0]->{method::lookup_uri}->(@_); }
      and now you have quite a generic subclass, all objects having their own custom method. You could generalize it, adding more methods, and calling SUPER::method if the necessary sub ref is missing. For example:
      sub lookup_uri { ($_[0]->{method::lookup_uri} || $_[0]->can(SUPER::lookup_uri))->(@ +_); }

      p.s. Caveat: I haven't tested this, as I don't have a mod_perl installation handy. Heh.

      Update: I have. It seems to work well. Who needs mod_perl for this kind of test, anyway? :)

Re: changing object's methods at runtime
by kilinrax (Deacon) on May 21, 2003 at 11:52 UTC

    You'd have to create a new class which subclasses Apache::FakeRequest, and re-bless the object into that class.
    Attempting to overwrite the method (e.g. which '*Apache::Request::lookup_uri = sub { ... }') would affect all Apache::Request objects within the script.

Re: changing object's methods at runtime
by broquaint (Abbot) on May 21, 2003 at 12:57 UTC
    How can I change the definition of this method on this object only, at runtime, after the instance's creation?
    Since calling a method is really just calling a subroutine with a bit of extra magic this isn't easily doable, as if you change the method that a given object calls, it'll be changed for any other objects that access that method. Off the top of my head you could do something like this
    sub fudge_method { my($obj, $method, $sub) = @_; ## you'll probably want to extend this for other data types $_[0] = bless {%$obj}, init_class(ref $obj, $method, $sub); } { my $cnt = 0; sub init_class { my($class,$method,$sub) = @_; my $name = $class."::_tmp".$cnt++; no strict 'refs'; @{$name."::ISA"} = $class; *{$name."::$method"} = $sub; return $name; } } { package foo; sub new { bless {@_[1 .. $#_]}, shift } sub dostuff { print "I'm doing stuff [", %{$_[0]}, "]\n" } } my $o = foo->new(field => 'var'); print "pre fudge - "; $o->dostuff(); fudge_method($o, 'dostuff', sub { print "I've done stuff [", %{$_[0]}, "]\n"; }); print "post fudge - "; $o->dostuff(); my $o2 = foo->new(this => 'that'); print "new object - "; $o2->dostuff(); __output__ pre fudge - I'm doing stuff [fieldvar] post fudge - I've done stuff [fieldvar] new object - I'm doing stuff [thisthat]
    That code is rather hackish (only deals with blessed hashes for exampple) but hopefully it'll give you a start on what you want achieved.
    HTH

    _________
    broquaint

Re: changing object's methods at runtime
by Anonymous Monk on May 21, 2003 at 12:05 UTC
    my $req = Apache::FakeRequest( uri='/foo/bar', is_main=>1...); *Apache::FakeRequest::request_uri = sub{ #gen the uri here };

    This should generate a warning

    Subroutine Apache::FakeRequest::request_uri redefined at...

    Which is your indication that it worked and only affects the FakeRequests not the real ones.

Re: changing object's methods at runtime
by ViceRaid (Chaplain) on May 21, 2003 at 13:39 UTC

    Many thanks for the thoughtful replies to my question. Successively, they pointed out that

    1. I was, as so often, missing the obvious by not sub-classing
    2. but that the creation of a new class implies, to me at least, a more stable setup than I wanted;
    3. but then that there's quite an easy way to change a class-wide method definition, which was good enough for me
    4. and that finally, there's a way of doing it the original way I had conceived anyway.

    In the end, subclassing was easiest, and mostly fitted what I wanted to do. After all, it's a test script, which for me only needs to test elegant code, not be elegant code itself.

    As an aside, the reason that I'd originally thought of approaching it by runtime method redefinition is probably a habit from Ruby (please don't curse me for mentioning it in this holy place). Ruby has a cute of way of doing this (and so, maybe will Perl6?):

    class Dog def bark puts "woof" end end fido = Dog.new() rover = Dog.new() class << rover def bark puts "meow" end end fido.bark() # woof! rover.bark() # meow!

    Once again, thanks for your replies

      That Ruby code is awfully sweet. I'm not sure how to replicate it, since I bet both objects are still technically Dog objects, and yet they have different methods. It's cool, though. Perhaps Perl 6 will allow that.

      _____________________________________________________
      Jeff[japhy]Pinyan: Perl, regex, and perl hacker, who'd like a job (NYC-area)
      s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Re: changing object's methods at runtime
by IlyaM (Parson) on May 21, 2003 at 13:51 UTC

      Thanks Ilya

      Actually, I started off with Test::MockObject to fake the request, but there was a lot of methods I needed to mock and that's a bit laborious. The other reason is that Apache::Request conveniently fills out all the Apache::Constants, eg, Apache::Contants::FORBIDDEN which otherwise need to be set manually as they're unavailable when running outside mod_perl.

      But having tried it both ways now ;), there's really not much in it.

Re: changing object's methods at runtime
by Zaxo (Archbishop) on May 22, 2003 at 01:32 UTC

    You can change a method at runtime with a scoped application of { local *Apache::FakeRequest::lookup_uri = sub { '127.0.0.1' } ...}

    Here's an example using CGI.pm:

    use CGI; my $q = CGI->new({foo=>'bar'}); print $q->param('foo'),$/; { local *CGI::param = sub {}; print "Empty", $/ unless defined $q->param('foo'); } print $q->param('foo'),$/;
    That does not change the behavior for just a single object, but for all objects of the class within the local dynamic scope. As othermonks said, subclass for specializing a single instance.

    After Compline,
    Zaxo

•Re: changing object's methods at runtime
by merlyn (Sage) on May 21, 2003 at 19:28 UTC
Re: changing object's methods at runtime
by gmpassos (Priest) on May 21, 2003 at 17:37 UTC
    Well, use eval(). For example, for my HTTP-Daemon I need to redefine a socket method that make impossible to share the same socket port with multiple childs on Win32, so I use this:
    # Fix a bug on sockname of IO::Socket, that block the app for multi ch +ilds with # the same server opened. Or for a GLOB blessed in a package different + than # IO::Socket, since the socket of the server is blessed to HTTP::Daemo +n! (Bug saw only on Win32). if ( $^O =~ /(?:^ms|win|dos)/i ) { eval(q` package HTTP::Daemon ; sub sockname { #getsockname($_[0]); getsockname( fileno($_[0]) ); } `); }
    But I think that the best option is to extend the class (like robartes said), since this doesn't change the method for all the mod_perl scripts (let's say that you run different scripts inside mod_perl):
    package Apache::FakeRequest::ViceRaid; use base qw(Apache::FakeRequest); sub lookup_uri { ... }

    Graciliano M. P.
    "The creativity is the expression of the liberty".

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://259686]
Approved by broquaint
Front-paged by kilinrax
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (5)
As of 2024-03-19 02:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found