Search code examples
perlmoose

Perl/Moose do not create the object but don't die if an attribute is wrong


Have the next package

package MyTest;
use warnings;
use Moose;
use Types::Path::Tiny qw(AbsPath AbsFile);

has 'file' => (
    is => 'ro',
    isa => AbsPath,
    required => 1,
    coerce => 1,
);
no Moose;
__PACKAGE__->meta->make_immutable;
1;

works (nearly) ok, so when use it

use strict;
use warnings;
use feature 'say';
use Mytest;
use DDP;

my $t1 = MyTest->new(file => './t');    # the ./t is existing file in the filesystem
say $t1 ? "ok" : "no";

my $t2 = MyTest->new(file => './nonexistent_file');
say $t2 ? "ok" : "no";
p $t2;

says "ok" for both. and the $t2->file is isa "Path::Tiny

But, I don't want create the object if the file isn't really exists in the filesystem. So, the second ($t2) invocation should return undef.

Changing the

        isa => AbsPath,

to

        isa => AbsFile,

will check the existence of the file, but if it isn't exists - the script will die with the

Attribute (file) does not pass the type constraint because:  File '/tmp/nonexistent_file' does not exist

I don't want die, only want not create the MyTest instance and return undef if the file isn't exists or it isn't a plain file. If the file exists the file should be a Path::Tiny instance. (coerced from Str).

Can somebody help me?


Solution

  • The simplest way would be to catch and discard expected errors:

    use Try::Tiny;
    
    my $instance = try {
        MyTest->new(file => './nonexistent_file');
    } catch {
        # only mute the constraint errors
        return undef if /\AAttribute [(]\w+[)] does not pass the type constraint/;
        die $_;   # rethrow other errors
    };
    

    Mucking around with the constructor so that it returns undef on failure is not a good idea, as there is an implied contract that ->new will always return a valid instance. In older Perl code, returning a special value on failure is considered OK, but this forces additional checks on the caller – and checks can be forgotten. Moose has taken a more robust route by using exeptions instead (thus forcing that they be handled), although in this specific case this does add a bit boilerplate.

    If you want to hide this boilerplate, consider writing a factory method.