Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

OO: Leaving a constructor midway?

by jest (Pilgrim)
on Oct 22, 2003 at 02:09 UTC ( #301133=perlquestion: print w/replies, xml ) Need Help??

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

I'm curious about a technique I've been using for a few months (successfully), which nonetheless seems as if it might not be quite kosher. Basically, in the middle of a constructor, I leave it to run a different program, never to return. This is regular and expected behavior, not an exception.

A simplified version of what I'm trying to do looks more or less like this:

sub new { my ($class, %arg) = @_; my $self = { table => $arg{table} || 'test', message => $arg{message}, dbh => MyDatabase::DBManager::connect(), cookie_name => $cookie_name, # other similar stuff here }; _login_user($self); return bless $self, $class; } sub _login_user { my $self = shift; # do something to log the user in based on $self->{cookie_name}, # returning $sess_ref as a reference to a db-stored session; then: if (!defined($sess_ref)) { print CGI::redirect(-uri=>'login.cgi'); } # i.e. if there's no session, bounce the user straight to a # login page, thus leaving in the middle of the constructor, # before hitting the "bless" else { # $sess_ref is defined, store some session info into # $self and get on with life, returning to "new" and to the # original program that called this module } }

It's been working fine, but I wonder if there's something wrong with doing this as a regular procedure; yet I didn't find anything specifically about it in Camel, Conway, the perltoot & related docs I checked, etc.

So is this safe, or not? If not, what's a good solution, given that the whole point of modularizing this login process is to keep identical code all together?

Replies are listed 'Best First'.
Re: OO: Leaving a constructor midway?
by antirice (Priest) on Oct 22, 2003 at 02:25 UTC

    It's fine as long as you're exiting right after the redirect. Otherwise the rest of your program will execute. Observe:

    #!/usr/bin/perl -w use strict; use CGI "redirect"; print redirect("login.cgi"); print "Here's some stuff that you shouldn't be able to see";

    And then we open up telnet and look at what it gives us.

    # telnet localhost 80 Trying Connected to localhost. Escape character is '^]'. GET / HTTP/1.1 Host: localhost HTTP/1.1 302 Moved Date: Wed, 22 Oct 2003 02:23:34 GMT Server: Apache/1.3.26 (Unix) Location: login.cgi Transfer-Encoding: chunked Content-Type: text/plain Here's some stuff that you shouldn't be able to see

    It's just something to watch out for.

    Note on a matter of style: Since this is a class that is quite possibly being designed for reuse later down the line, I'd highly suggest having the object return undef to signify that authentication failed. Sometimes you may wish for the script to redirect to another page. Other times you may just want it to serve a different page. If you aren't going for reuse and are just looking to personalize the coding style of your scripts to something you like, then this is fine. Just be aware that if someone else later needs to maintain your code, they may attempt to track you down and upon finding you threaten you with a rubber mallet.

    Hope this helps.

    The first rule of Perl club is - use Perl
    ith rule of Perl club is - follow rule i - 1 for i > 1

Re: OO: Leaving a constructor midway?
by chromatic (Archbishop) on Oct 22, 2003 at 03:16 UTC

    I'd throw an exception from the constructor and catch and redirect where it's called instead. I don't expect any constructor to exit the program for me.

      Ahh, a moment of sanity. I was hoping someone would have seen it this way.
Re: OO: Leaving a constructor midway?
by pg (Canon) on Oct 22, 2003 at 02:31 UTC

    This is all about the way you think.

    To be frank, I see this particular practice as non-OO, although it is inside an OO prorgam. (The rest of your code could be quite OO, I haven't seen it, and I have no right to comment it.)

    Constructor is used to create the object, make it EXIST. Before you exit the constructor, this object does not exist in the OO world. Now how can something start to ACT before it even EXIST?

    On the other hand, if you make it act inside the constructor, then what is the point to do it in a OO-way, becaue it is actually really procedural.

      Well, in Perl the constructor is called bless. Now, it's popular to call a subroutine that is called as a class method, and that returns a blessed reference a 'constructor', but on the language level, it's just a subroutine that's called as a class method. Nothing special there.

      And, as far as I know, it's not possible to leave a bless midway. From a language perspective, it's an atomic operation, with one op in the optree.

      Of course, in the given code fragment, neither bless, nor the subroutine returning the blessed reference is left halfway. Printing out a redirection header won't make your program exit (unless there are some bugs somewhere).


        (I'm going to regret doing this, but what the hey!)

        The magicking action in Perl may be bless. But, OOD/OOP are language-independent concepts. The fact that Perl has certain quirks in how it implements OO (as opposed to the C++ or Java quirks) should be irrelevant to a discussion of OO technique.

        The "popular way" of using a subroutine called as a class method that returns a blessed reference also happens to be a rather decent implementation of an OO design for constructing an object. Your statement about "on the language level" is irrelevant to the discussion. On the language level, C++ reuses a ton of C features that are not OO. So does Perl. So what?!?

        OP is calling some class method sub (presumably named new) as a constructor. His program calls it, probably with some set of values, and gets back something that looks, talks, and walks like an object. Sounds like a constructor to me? The fact that, under the hood, it calls bless is completely irrelevant to the client code. In fact, a number of programmers use OO Perl without ever knowing about bless.

        We are the carpenters and bricklayers of the Information Age.

        The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

        ... strings and arrays will suffice. As they are easily available as native data types in any sane language, ... - blokhead, speaking on evolutionary algorithms

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: OO: Leaving a constructor midway?
by Anonymous Monk on Oct 22, 2003 at 07:25 UTC
    It's not a question of safety, it's a question of purpose. The purpose of a constructor is to deliver an object, or if unable, to throw an exception or return null.
Re: OO: Leaving a constructor midway?
by tbone1 (Monsignor) on Oct 22, 2003 at 12:58 UTC

    This is probably better left for a philosophy section, but the world is not always object-oriented, just as it is not always procedural. Granted, there are many cases where one approach is preferable to the other, and obviously so, but sometimes we have to work in that grey area that quantum physicists so love, where you look for an approach that is the least bad. Worse, sometimes the specs change on us, often after a project is long in production.

    FWIW, I have had the same argument with our Oracle people that normalizing the data when the data does not lend itself to normalization may not be a good idea. Their training is in a rote method and to look at data as a series of tables; I learned to program by being a researcher in a rather complex area and having to always account for exceptional data or odd initial conditions.

    Just my $.01. (The IRS took the other cent.)

    Ain't enough 'O's in 'stoopid' to describe that guy.
    - Dave "the King" Wilson

Re: OO: Leaving a constructor midway?
by Art_XIV (Hermit) on Oct 22, 2003 at 15:43 UTC

    There's nothing WRONG or distinctly non-Perlish with the way that you did it - if it works, then that's fine.

    It's probably tweaking you at some level because it is rather OO-unorthodox in spite of the fact that it works.

    Your OO daemon may be happier if you don't do a redirect right in the midst of _login_user, but instead have a class field/method ($valid_user?) that gets set during _login_user, which can be checked by the consumer(s) of this class after instantiation. The consumer(s) can then decide on a redirect.

Re: OO: Leaving a constructor midway?
by jest (Pilgrim) on Oct 22, 2003 at 14:54 UTC

    Thanks for the various thoughts. I appreciate, if not fully understand, the technicalities about exactly when this is an "object" and whether or not it's really a "constructor" if I leave midway.

    I don't have any other OO background, so my approach to this was a DWIW one. The functionality of every program that will use this module involves having a user logged in with some info in a session; if the user isn't logged in there's no point going further. When, in my original calling program, I say

    my $book = MyPackage->new( # stuff );
    , my purpose is not "to create an object", it's "get started, with whatever that entails", and if that entails logging the user in, so be it. Throwing an exception seems to force me to add an extra step to every single program that uses this module; instead of the above line, I now have to say something like
    my $book = MyPackage->new( # stuff ); print CGI::redirect(-uri=>"login.cgi?msg=$book->{error_msg}") unless $book->{login_successful};
    or whatever, i.e. adding an extra line of identical code to every program.

    Indeed, in practice it might even be longer, since in some circumstances I need to check whether the user has the right access level. Then I'll have yet another similar or identical line to every program:

    print CGI::redirect(-uri=>"login.cgi?msg=$book->{no_access}") if $book->{user_access_level} < $book->{required_access_level};

    I had wanted to put the code in the module, instead of the programs, specifically to avoid this. But if this is bad style, then it's bad style; the reason I asked is because I thought it might be, and I'll do it by throwing exceptions instead.

      Why not do this:
      my $book = MyPackage->startup( $stuff ); package MyPackage; sub startup { # do stuff here, like creating the object # do your check for authority here and exit, if appropriate # Do more stuff here return $book; }

      This is the method used by classes like DBI. Calling it startup() makes it more clear to your maintainer (which may be you!) what exactly you're doing. (Which is why I suspect Tim Bunce chose connect() instead of new() for DBI.)

      We are the carpenters and bricklayers of the Information Age.

      The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

      ... strings and arrays will suffice. As they are easily available as native data types in any sane language, ... - blokhead, speaking on evolutionary algorithms

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

        Silly question, but: what's the difference? I mean, I see that there's a difference behind the scenes, in that this suggestion involves calling a separate constructor that really does bless the object, and then does the login business etc. But from the point of view of the user, it's the same: you call some method (whether called "new" or "startup"), and either bounce to a different program, or get back an object blessed into MyPackage. After all, you are the one who so recently said to Abigail-II that if I call a method and get back something that quacks like an object, it's a constructor, so....

        If this version makes the OO gods smile upon me, I'm happy to go with it, but I'm just curious. Several people seemed to react with horror to the idea of a constructor exiting the program, so IDG why it's not a horror to having a constructor-like thing exiting the program.

      What you describe in that code is not catching an exception, it's handling a return value, which means that it's doing the work too near to the failure. Have your login_user method use a real exception class, so if login fails you'd do:
      ... die Exception::NoUser->new(-requested_uri => $request->uri); ...
      Then, way up at the top level, in your basic handler method that everything uses, you would have:
      my $result = eval { $app->process($request) }; if ($@) { $result = ref($@) ? $@ : Exception::Unknown->new(-requested_uri => $request->uri, -message => $@); } $self->emit($result);
      There are big problems with your code though, in that it makes assumptions about how it's being used, which makes it very hard to write tests for it. For instance, what is it that's inherent in your Book object that means it needs to know it's being used via a webpage? Slightly more controversially, why does it need to know it's being fetched from a database? As someone else suggested, maybe your application's process method needs to do something like:
      sub process { my($self, $request) = @_; local Exception::current_uri = $request->uri; $self->get_resources($request); $self->authenticate_user($request); $self->authorize_user($request); $self->generate_response($request); } sub get_resources { my($self,$request) = @_; # Fetch your book from the database. $self->set_book($book); } sub authenticate_user { my($self, $request) = @_; my $user = $self->make_user_from_request($request) or die Exception::NoUser->new; $self->set_user($user); } sub authorize_user { my $self = shift; $self->user_has_required_access($user) or die Exception::InsufficientAccess->new(user => $user); }
      If you set up your inheritance tree (or compose your objects) correctly, you'll only have to override one or two methods for each distinct page. And you'll have a collection of methods that you can test without having to pretend to be a webserver all the time, and that's a big win.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (7)
As of 2022-05-24 14:46 GMT
Find Nodes?
    Voting Booth?
    Do you prefer to work remotely?

    Results (83 votes). Check out past polls.