NAME

Exception::Base - Lightweight exceptions

SYNOPSIS

# Use module and create needed exceptions
use Exception::Base
   'Exception::Runtime',              # create new module
   'Exception::System',               # load existing module
   'Exception::IO',          => {
       isa => 'Exception::System' },  # create new based on existing
   'Exception::FileNotFound' => {
       isa => 'Exception::IO',        # create new based on previous
       message => 'File not found',   # override default message
       has => [ 'filename' ],         # define new rw attribute
       string_attributes => [ 'message', 'filename' ],
   };                                 # output message and filename

# eval is used as "try" block
eval {
  open my $file, '/etc/passwd'
    or Exception::FileNotFound->throw(
          message=>'Something wrong',
          filename=>'/etc/passwd');
};
# syntax for Perl >= 5.10
use feature 'switch';
if ($@) {
  given (my $e = Exception::Base->catch) {
    when ($e->isa('Exception::IO')) { warn "IO problem"; }
    when ($e->isa('Exception::Eval')) { warn "eval died"; }
    when ($e->isa('Exception::Runtime')) { warn "some runtime was caught"; }
    when ($e->matches({value=>9})) { warn "something happened"; }
    when ($e->matches(qr/^Error/)) { warn "some error based on regex"; }
    default { $e->throw; } # rethrow the exception
  }
}
# standard syntax for older Perl
if ($@) {
  my $e = Exception::Base->catch;   # convert $@ into exception
  if ($e->isa('Exception::IO')) { warn "IO problem"; }
  elsif ($e->isa('Exception::Eval')) { warn "eval died"; }
  elsif ($e->isa('Exception::Runtime')) { warn "some runtime was caught"; }
  elsif ($e->matches({value=>9})) { warn "something happened"; }
  elsif ($e->matches(qr/^Error/)) { warn "some error based on regex"; }
  else { $e->throw; } # rethrow the exception
}

# $@ has to be recovered ASAP!
eval { die "this die will be caught" };
my $e = Exception::Base->catch;
eval { die "this die will be ignored" };
if ($e) {
   (...)
}

# the exception can be thrown later
my $e = Exception::Base->new;
# (...)
$e->throw;

# ignore our package in stack trace
package My::Package;
use Exception::Base '+ignore_package' => __PACKAGE__;

# define new exception in separate module
package Exception::My;
use Exception::Base (__PACKAGE__) => {
    has => ['myattr'],
};

# run Perl with changed verbosity for debugging purposes
$ perl -MException::Base=verbosity,4 script.pl

DESCRIPTION

This class implements a fully OO exception mechanism similar to Exception::Class or Class::Throwable. It provides a simple interface allowing programmers to declare exception classes. These classes can be thrown and caught. Each uncaught exception prints full stack trace if the default verbosity is increased for debugging purposes.

The features of Exception::Base:

OVERLOADS

CONSTANTS

ATTRIBUTES

Class attributes are implemented as values of blessed hash. The attributes are also available as accessors methods.

IMPORTS

CONSTRUCTORS

The throw can be also used as a method.

METHODS

= Class Diagram =

[ <> Exception::Base

+ignore_class : ArrayRef {new} +ignore_level : Int = 0 {new} +ignore_package : ArrayRef {new} +max_arg_len : Int = 64 {new} +max_arg_nums : Int = 8 {new} +max_eval_len : Int = 0 {new} +message : Str|ArrayRef[Str] = "Unknown exception" {new} +value : Int = 0 {new} +verbosity : Int = 2 {new} +caller_stack : ArrayRef +egid : Int +euid : Int +gid : Int +pid : Int +propagated_stack : ArrayRef +tid : Int +time : Int +uid : Int #defaults : HashRef #default_attribute : Str = "message" #numeric_attribute : Str = "value" #eval_attribute : Str = "message" #string_attributes : ArrayRef[Str] = ["message"]

<> +new( args : Hash ) <> +throw( args : Hash = undef ) <> +throw( message : Str, args : Hash = undef ) +catch() : Exception::Base +catch( variable : Any ) : Exception::Base +matches( that : Any ) : Bool {overload="~~"} +to_string() : Str {overload='""'} +to_number() : Num {overload="0+"} +to_bool() : Bool {overload="bool"} +get_caller_stacktrace() : Array[Str]|Str +PROPAGATE() #_collect_system_data() #_make_accessors() {init} #_make_caller_info_accessors() {init} <> +ATTRS() : HashRef ]

SEE ALSO

Repository: http://github.com/dex4er/perl-Exception-Base

There are more implementation of exception objects available on CPAN. Please note that Perl has built-in implementation of pseudo-exceptions:

eval { die { message => "Pseudo-exception", package => __PACKAGE__,
             file => __FILE__, line => __LINE__ };
};
if ($@) {
  print $@->{message}, " at ", $@->{file}, " in line ", $@->{line}, ".\n";
}

The more complex implementation of exception mechanism provides more features.

The Exception::Base does not depend on other modules like Exception::Class and it is more powerful than Class::Throwable. Also it does not use closures as Error and does not pollute namespace as Exception::Class::TryCatch. It is also much faster than Exception::Class::TryCatch and Error for success scenario.

The Exception::Base is compatible with syntax sugar modules like TryCatch and Try::Tiny.

The Exception::Base is also a base class for enhanced classes:

EXAMPLES

New exception classes

The Exception::Base module allows to create new exception classes easily. You can use "import" in perlfunc interface or base module to do it.

The "import" in perlfunc interface allows to create new class with new read-write attributes.

package Exception::Simple;
use Exception::Base (__PACKAGE__) => {
  has => qw{ reason method },
  string_attributes => qw{ message reason method },
};

For more complex exceptions you can redefine ATTRS constant.

package Exception::Complex;
use base 'Exception::Base';
use constant ATTRS => {
  %{ Exception::Base->ATTRS },     # SUPER::ATTRS
  hostname => { is => 'ro' },
  string_attributes => qw{ hostname message },
};
sub _collect_system_data {
  my $self = shift;
  my $hostname = `hostname`;
  chomp $hostname;
  $self->{hostname} = $hostname;
  return $self->SUPER::_collect_system_data(@_);
}

PERFORMANCE

There are two scenarios for "eval" in perlfunc block: success or failure. Success scenario should have no penalty on speed. Failure scenario is usually more complex to handle and can be significantly slower.

Any other code than simple if ($@) is really slow and shouldn't be used if speed is important. It means that any module which provides try/catch syntax sugar should be avoided: Error, Exception::Class::TryCatch, TryCatch, Try::Tiny. Be careful because simple if ($@) has many gotchas which are described in Try::Tiny's documentation.

The Exception::Base module was benchmarked with other implementations for simple try/catch scenario. The results (Perl 5.10.1 x86_64-linux-thread-multi) are following:

-----------------------------------------------------------------------
| Module                              | Success sub/s | Failure sub/s |
-----------------------------------------------------------------------
| eval/die string                     |       3715708 |        408951 |
-----------------------------------------------------------------------
| eval/die object                     |       4563524 |        191664 |
-----------------------------------------------------------------------
| Exception::Base eval/if             |       4903857 |         11291 |
-----------------------------------------------------------------------
| Exception::Base eval/if verbosity=1 |       4790762 |         18833 |
-----------------------------------------------------------------------
| Error                               |        117475 |         26694 |
-----------------------------------------------------------------------
| Class::Throwable                    |       4618545 |         12678 |
-----------------------------------------------------------------------
| Exception::Class                    |        643901 |          3493 |
-----------------------------------------------------------------------
| Exception::Class::TryCatch          |        307825 |          3439 |
-----------------------------------------------------------------------
| TryCatch                            |        690784 |        294802 |
-----------------------------------------------------------------------
| Try::Tiny                           |        268780 |        158383 |
-----------------------------------------------------------------------

The Exception::Base module was written to be as fast as it is possible. It does not use internally i.e. accessor functions which are slower about 6 times than standard variables. It is slower than pure die/eval for success scenario because it is uses OO mechanisms which are slow in Perl. It can be a little faster if some features are disables, i.e. the stack trace and higher verbosity.

You can find the benchmark script in this package distribution.

BUGS

If you find the bug or want to implement new features, please report it at https://github.com/dex4er/perl-Exception-Base/issues

The code repository is available at http://github.com/dex4er/perl-Exception-Base

AUTHOR

Piotr Roszatycki dexter@cpan.org

LICENSE

Copyright (c) 2007-2010, 2012-2013 Piotr Roszatycki dexter@cpan.org.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://dev.perl.org/licenses/artistic.html