Class::Throwable - A minimal lightweight exception class |
Class::Throwable - A minimal lightweight exception class
use Class::Throwable;
# simple usage eval { # code code code, if ($something_goes_wrong) { throw Class::Throwable "Something has gone wrong"; } }; if ($@) { # we just print out the exception message here print "There has been an exception: " $@->getMessage(); # but if we are debugging we get the whole # stack trace as well if (DEBUG) { print $@->getStackTraceAsString(); } }
# it can be used to catch perl exceptions # and wrap them in a Class::Throwable exception eval { # generate a perl exception eval "2 / 0"; # then throw our own with the # perl exception as a sub-exception throw Class::Throwable "Throwing an exception" => $@ if $@; }; if ($@) { # setting the verbosity to # 2 gives a full stack trace # including any sub-exceptions # (see below for examples of # this output format) $@->toString(2); }
# you can also declare inline exceptions use Class::Throwable qw(My::App::Exception::IllegalOperation);
# set their global verbosity as well # with the class method My::App::Exception::IllegalOperation->setVerbosity(2);
eval { throw My::App::Exception::IllegalOperation "Bad, real bad"; };
# can also declare subclasses of Class::Throwable # in other files, then when you import them, you # can set their verbosity use My::Sub::Class::In::A::Seperate::File (VERBOSE => 1);
throw My::Sub::Class::In::A::Seperate::File "This excepton will use a verbosity of 1";
# you can even create exceptions, then throw them later my $e = Class::Throwable->new("Things have gone bad, but I need to do something first", $@);
# do something else ...
# then throw the exception we created earlier throw $e
This module implements a minimal lightweight exception object. It is meant to be a compromise between more basic solutions like the Carp manpage which can only print information and cannot handle exception objects, and more more complex solutions like the Exception::Class manpage which can be used to define complex inline exceptions and has a number of module dependencies.
You can easily create new exception classes inline by passing them with the use
statment like this:
use Class::Throwable ('My::InlineException', 'My::Other::InlineException');
This is a quick and easy way to define arbitrary exception classes without the need to manually create seperate files or packages for them. However, it should be noted that subclasses of Class::Throwable cannot be used to define inline exceptions. If you attempt to do this, an exception will be thrown.
Class::Throwable offeres a number of different types of diagnostic outputs to suit your needs. Most of this is controlled through the verbosity levels. If the verbosity level is set to 0 or below, an empty string is returned. If the value is set to 1, then the exception's message is returned. If the value is set to 2 or above, a full stack trace along with full stack traces for all sub-exceptions are returned in the format shown in stackTraceToString
. The default verbosity setting is 1.
There are a number of ways in which you can set the verbosity of the exceptions produced by Class::Throwable. The simplest way is as the argument to the toString
method. Using this method will override any other settings you may have, and insure that the output of this method is as you ask it to be.
$@->toString(2);
However, to use this style properly, this requires that you test the value of $@
to be sure it is a Class::Throwable object. In some cases, this may not be an issue, while in others, it makes more sense to set verbosity on a wider scale.
For instance, if you define inline exceptions, then the simplest way to set a verbostity level for a particular inline exception is through the class method setVerbosity
.
use Class::Throwable qw(My::InlineException);
My::InlineException->setVerbosity(2);
This means that unless the toString
verbosity argument overrides it, all My::InlineException exceptions will use a verbosity setting of 2. This method means that you can easily print
the value of $@
and then any My::InlineException exceptions will be automatically stringified with a verbosity level of 2. This can simplify exception catching by reducing the need to inspect the value of $@
.
If you defined your exceptions as subclasses of Class::Throwable and stored them in seperate files, then another means of setting the verbosity level is to assign it in the use
statement.
use My::SeperateFileSubClass::Exception (VERBOSE => 2);
This has the same effect as the setVerbosity
class method, in fact, there is nothing to stop you from using the setVerbosity
class method in this case if you like. This method can also be used on Class::Throwable itself, however, this does not set the verbosity level for all subclasses, only for Class::Throwable exceptions.
There is one last method which can be used. This method has the widest scope of all the methods. The variable $Class::Throwable::DEFAULT_VERBOSITY
can be set. Setting this value will take effect if, 1) there is no value passed to the toString
method and 2) no verbosity level has been set for the particular class, either through setVerbosity
or the use
statement.
It is possible to retrofit a module to use Class::Throwable exceptions if you want to. Basially this will allow modules which die
with either strings or some other value, to throw Class::Throwable based exceptions. This feature is relatively new and should be considered to be experimental, any feedback on it is greatly appreciated.
NOTE: It is important to do module retrofitting at the earliest possible moment (peferrably before the module you are retrofitting is compiled), as it will override die
within a specified package.
Other than all this, retrofitting is quite simple. Here is a basic example:
use Class::Throwable retrofit => 'My::Class';
Now anytime C<die> is called within I<My::Class> the calls will get converted to a Class::Throwable instance. You can also control how exceptions are converted like so:
use Class::Throwable retrofit => 'My::Class' => sub { My::Exception->throw(@_) };
Now anytime die
is called within My::Class the calls will get converted to a My::Exception instance instead. Or a slightly more complex examples like this:
use Class::Throwable retrofit => ( 'My::Class' => sub { My::IllegalOperation->throw(@_) if $_[0] =~ /^Illegal Operation/; My::Exception->throw(@_); });
Now anytime C<die> is called within I<My::Class> the calls will get converted to a My::Exception instance unless the exception matches the reg-exp, in which case an My::IllegalOperation exception is thrown.
There are a couple of points to be made regarding this functionality. First, it will add another stack frame to your exceptions (the retrofit routine basically). This is probably avoidable, but as this is still experimental I wanted to keep things somewhat simple. And second, if you supply a custom die
handler, you should be sure that it will die
somewhere within that routine. If you do not, you may have many un-intended consequences.
throw
it. This method will construct the exception object, collect all the information from the call stack and then die
.
The optional $message
argument can be used to pass custom information along with the exception object. Commonly this will be a string, but this module makes no attempt to enforce that it be anything other than a scalar, so more complex references or objects can be used. If no $message
is passed in, a default one will be constructed for you.
The second optional argument, $sub_exception
, can be used to retain information about an exception which has been caught but might not be appropriate to be re-thrown and is better wrapped within a new exception object. While this argument will commonly be another Class::Throwable object, that fact is not enforced so you can pass in normal string based perl exceptions as well.
If this method is called as an instance method on an exception object pre-built with new
, only then is the stack trace information populated and the exception is then passed to die
.
throw
, except that it does not collect stack trace information or die
. It stores the $message
and $sub_exception
values, and then returns the exception instance, to be possibly thrown later on.
caller
($package
, $filename
, $line
, $subroutine
, $hasargs
, $wantarray
, $evaltext
, $is_require
, $hints
, $bitmask
) we do not bother to capture the last two as they are subject to change and meant for internal use, all others are retained in the order returned by caller
.
1
) if this exception has a sub-exception, and false (0
) otherwise.
This object overloads the stringification operator, and will call the toString
method to perform that stringification.
$verbosity
. See the section Exception Verbosity above for more details.
toString
method.
|--[ main::foo called in my_script.pl line 12 ] |--[ main::bar called in my_script.pl line 14 ] |--[ main::baz called in my_script.pl line 16 ]
Given the following code:
{ package Foo; sub foo { eval { Bar::bar() }; throw Class::Throwable "Foo!!", $@ }
package Bar; sub bar { eval { Baz::baz() }; throw Class::Throwable "Bar!!", $@ }
package Baz; sub baz { throw Class::Throwable "Baz!!" } }
eval { Foo::foo() }; print $@->toString($verbosity) if $@;
If you were to print the exception with verbosity of 0, you would get no output at all. This mode can be used to supress exception output if needed. If you were to print the exception with verbosity of 1, you would get this output.
Class::Throwable : Foo!!
If you were to print the exception with verbosity of 2, you would get this output.
Class::Throwable : Foo!! |--[ Foo::foo called in test.pl line 26 ] |--[ main::(eval) called in test.pl line 26 ] + Class::Throwable : Bar!! |--[ Bar::bar called in test.pl line 19 ] |--[ Foo::(eval) called in test.pl line 19 ] |--[ Foo::foo called in test.pl line 26 ] |--[ main::(eval) called in test.pl line 26 ] + Class::Throwable : Baz!! |--[ Baz::baz called in test.pl line 21 ] |--[ Bar::(eval) called in test.pl line 21 ] |--[ Bar::bar called in test.pl line 19 ] |--[ Foo::(eval) called in test.pl line 19 ] |--[ Foo::foo called in test.pl line 26 ] |--[ main::(eval) called in test.pl line 26 ]
None that I am aware of. Of course, if you find a bug, let me know, and I will be sure to fix it. This is based on code which has been heavily used in production sites for over 2 years now without incident.
I use Devel::Cover to test the code coverage of my tests, below is the Devel::Cover report on this module test suite.
---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt branch cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ Class/Throwable.pm 100.0 98.0 63.6 100.0 100.0 100.0 95.7 ---------------------------- ------ ------ ------ ------ ------ ------ ------ Total 100.0 98.0 63.6 100.0 100.0 100.0 95.7 ---------------------------- ------ ------ ------ ------ ------ ------ ------
There are a number of ways to do exceptions with perl, I was not really satisifed with the way anyone else did them, so I created this module. However, if you find this module unsatisfactory, you may want to check these out.
%SIG
handlers and other such things. This can be dangerous territory sometimes, and for me, far more than my needs.
stevan little, <stevan@iinteractive.com>
Copyright 2004 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Class::Throwable - A minimal lightweight exception class |