Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re: Extending MIME::Lite

by tachyon (Chancellor)
on Sep 27, 2003 at 12:40 UTC ( [id://294638]=note: print w/replies, xml ) Need Help??


in reply to Extending MIME::Lite

The best way to extend a given function is generally to add args to the end of the arg list. This should not break old code but there is always the possibility that it will. So if you have lots of args you want to pass then passing an arg hash is a sound approach....BUT it looks like the new() function in Net::SMTP might cause you grief.

If you trace through what occurs when you call send('smtp',@args) in Mime::Lite you effectively end up calling send_by_smtp(@args) which ends up with this:

my $smtp = MIME::Lite::SMTP->new(@args)

Given that this just calls the Mime::Lite::SMTP package which is a subclass of Net::SMTP.....this is where that call FINALLY ends up in Net::SMTP:

sub new { my $self = shift; my $type = ref($self) || $self; my $host = shift if @_ % 2; my %arg = @_; my $hosts = defined $host ? [ $host ] : $NetConfig{smtp_hosts}; my $obj; my $h; foreach $h (@{$hosts}) { $obj = $type->SUPER::new(PeerAddr => ($host = $h), PeerPort => $arg{Port} || 'smtp(25)', LocalAddr => $arg{LocalAddr}, LocalPort => $arg{LocalPort}, Proto => 'tcp', Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120 ) and last; }

It seems to me that there is an assumption there that you are going to break. First the first arg to new() for Net::SMTP needs to be ($server/$host) and worse the %2 means it only recognises the $host arg if an ODD number of args is passed to new..... It then goes on to assume that all following args are a hash presented as a flat list. Adding your single scalar (hash ref) will cause it to explode with an odd number of elements in hash assignment error. Also passing {Debug=>1} as suggested will cause Net::SMTP to try to connect to a HASH REF which just aint gonna work. Presumably you plan to override this function in Mime::Lite - looks like you will have to to me.

Personally a standard coding practice around here is NEVER to write a function like func($scalar, @ary) - we always do func($scalar, \@ary) so we can easily add $forgot_this, $forgot_that to the end of the arg list func( $scalar, \@ary, $forgot_this, $forgot_that ). As soon as you code a trailing array into a function call you are screwed if you need to extend it without causing major dramas (ie breaking just about everything that calls that func)......

cheers

tachyon

s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Replies are listed 'Best First'.
Re: Re: Extending MIME::Lite
by demerphq (Chancellor) on Sep 27, 2003 at 13:23 UTC

    I'm thinking of intercepting the hashref before calling new. So that

    require Net::SMTP; my $smtp = MIME::Lite::SMTP->new(@args) or Carp::croak("Failed to connect to mail server: $!\n");

    becomes something like...

    require Net::SMTP; my $smtp; if (ref $args[0]) { $opts = shift @args; # handle special cases, and finally resulting in a usable valu +e # in $smtp. } else { $smtp = MIME::Lite::SMTP->new(@args) or Carp::croak("Failed to connect to mail server: $!\n"); }

    So before MIME::Lite::SMTP even gets called @args has been precleaned as appropriate. This would allow special cases like having a list of SMTP hosts that are tried until one lets us in, or handling authentication, or turning Debug on in Net::SMTP without having to do the ugly Net::SMTP->new(undef,Debug=>1) in the user code (inside MIME::Lite its ok to do this, as in there its unlikely to cause trouble to a newbie).

    Also I agree quite a bit with your points about positional arguments. The only quibble I have is from Perls flexibility with arguments and weakish typing. You can often extend positional arguments by getting creative with types. For instance the trick above works out because its extremely unlikely that somebody is using a blessed reference as a hostname. In fact I could get a lot more picky and check that the ref is a HASH and that if it is a hash that stringification is not overloaded. This would result in total backwards compatibility while still allowing extension.

    Cheers,


    ---
    demerphq

      First they ignore you, then they laugh at you, then they fight you, then you win.
      -- Gandhi


      The else doesn't need to be there. The $smtp is still going to need to be created in the same way (or at least it seems so to me). The authentication action occurs after the Net::SMTP object has been created, which was part of my initial concern when adding support to MIME::Lite. I am still working through why Net::SMTP doesn't allow for the authentication arguments in the object creation. It would seem that Net::SMTP should perhaps be the one that bends and allows for the authenication to be passed into it on object creation.

      If we moved this logic into the Net::SMTP module, what would happen if we did this right before we return the Net::STMP object:
      if (defined $arg{User}) { $obj->auth($arg{User},$arg{Password}); }
      inside of the Net::SMTP new? Then when we create a new Net::SMTP call we do it as:
      MIME::Lite->send('smtp', $out_smtp , Port =>$smtp_port , Timeout=>60 , Debug => $debug , Hello => 'domain.com', User => 'trs80', Password => 'blah' );
      My simple test indicates that it works, but I need to carefully review the side effects of performing the auth in the object creation.

      Placing the change inside of Net::SMTP makes more sense in that it keeps the logic in the right problem space and thereby making it benefical to a larger user base.

        The else doesn't need to be there. The $smtp is still going to need to be created in the same way (or at least it seems so to me).

        Key term here is need. Yes you are correct it doesnt need to be there, but the idea I had was that this extension is not only going to cover the case of authentication. One thing I would like to do is implement smart hosts. The user should be a able to supply a list of SMTP hosts and have MIME::Lite cycle through them until it manages to send its mail. So for what I have in mind keeping the simple case seperate from the more complex case makes sense.

        Placing the change inside of Net::SMTP makes more sense in that it keeps the logic in the right problem space and thereby making it benefical to a larger user base.

        Agreed. However I think you may be waiting a while before such a patch is implemented. Probably longer than it will take to go into MIME::Lite. If MIME::Lite does its own ting, and then sometime down the path Net::SMTP is altered as you say then MIME::Lite can be modified accordingly. Until that day Id rather think about what is directly under my control than what would be possible when a patch is applied to a module I have no control over.


        ---
        demerphq

          First they ignore you, then they laugh at you, then they fight you, then you win.
          -- Gandhi


      This reply is to address the change if the modification takes place in MIME::Lite vs. Net::SMTP as my previous post suggests. I think the change in behavior should occur at the send method rather then the send_by_smtp. This would allow future modifications of send methods to accounted for in the same manner. So here is my proposed revised sub send:
      # add new global that the top my $SenderOpts = ''; sub send { my $self = shift; if (ref($self)) { ### instance method: my ($method, @args); if (@_) { ### args; use them just t +his once $method = 'send_by_' . shift; @args = @_; } else { ### no args; use defaults $method = "send_by_$Sender"; @args = @{$SenderArgs{$Sender} || []}; } $self->verify_data if $AUTO_VERIFY; ### prevents missing part +s! return $self->$method(@args); } else { ### class method: if (@_) { my @old = ($Sender, @{$SenderArgs{$Sender}}); $Sender = shift; if ( ref($_[0]) eq 'HASH') { $SenderOpts = shift; } $SenderArgs{$Sender} = [@_]; ### remaining args return @old; } else { Carp::croak "class method send must have HOW... arguments\n" +; } } }
      Then below in our send_by_smtp method we add:
      if ($SenderOpts) { $smtp->auth( $SenderOpts->{auth_username}, $SenderOpts->{auth +_password} ); }
      directly after our $smtp object is created.

        I think the change in behavior should occur at the send method rather then the send_by_smtp.

        I can see what you are getting at here, but it doesnt take into account the full interface of MIME::Lite. We need to handle two distinct cases. The first is that someone uses MIME::Lite->send() to configure the class defaults. The second is that someone calls $mime_obj->send_by_smtp() to override the class defaults or simply because they are doing a quick and dirty, or more likely because they are a beginner and havent groked send() properly.

        Thus whatever processing happens in send() will also have to happen in send_by_smtp (and most likely send_by_sendmail and send_by_sub too). This isn't to say that send() wont be changed, just to say that it doesnt remove the requirement to alter send_by_smtp() as well.


        ---
        demerphq

          First they ignore you, then they laugh at you, then they fight you, then you win.
          -- Gandhi


Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://294638]
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: (2)
As of 2024-04-26 03:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found