Most of the time calling $object->can($method); just works. Unfortunately, when dealing with dynamic languages, sometimes we find weird corner cases where our stretching things actually breaks them. can() is one thing which breaks ... a lot. One type of breakage which seriosly bit me was having a module say "yes, I can do that imported function."
{ package Naughty; use base 'Exporter'; our @EXPORT = 'some_func'; sub some_func { 'naughty function. no biscuit' } } { package Unsuspecting; Naughty->import; sub new { bless {}, shift } sub _private { 'Unsuspecting _private' } } if ( Unsuspecting->can('some_func') ) { die "a horrible death"; } elsif ( Unsuspecting->can('_private') ) { die "Don't rely on private methods!"; } else { print "home free"; }
Frequently when writing classes we might import (knowingly or otherwise) functions to help our class, but we don't want people to rely on them or, for that matter, to call them. This can also apply to private "underscore" methods. So I wrote a little module which makes can() report false for those.
package strict::can; use warnings; use strict; use vars qw( $VERSION ); use B qw/svref_2object/; use base 'Exporter'; our @EXPORT = qw(can); my $package_for = sub { my $package; eval { my $stash = svref_2object(shift)->STASH; if ( $stash && $stash->can('NAME') ) { $package = $stash->NAME; } else { $package = ''; } }; if ($@) { warn "Could not determine calling package: $@"; } return $package; }; sub can { my ( $proto, $method ) = @_; return if '_' eq substr $method, 0, 1; my $class = ref $proto || $proto; if ( my $code = $proto->SUPER::can($method) ) { my $code_package = $package_for->($code); if ( $code_package eq $class || $class->isa($code_package) ) { return $code; } else { return; } } return; } 1;
With this, your class merely needs to add the line use strict::can; and it gets a can() method which doesn't report what you don't want it to. First, the weird strict:: namespace was started (I believe) by Sean Burke (TorgoX) with strict::ModuleName. I feel odd with the name strict::can, but I can't think of a better one.
I'm still torn about the "private methods" bit. It seems to me that perhaps can() should succeed if you're checking for a private method within a package that subclasses the package you're checking. This assumes that the outside world shouldn't know about "private" methods but if you're subclassing we'll trust you a little bit more.
In any event, if you want to play around with this idea, here are a few tests:
#!perl #use Test::More tests => 1; use Test::More qw/no_plan/; BEGIN { chdir 't' if -d 't'; unshift @INC, '../lib'; } { package Naughty; use base 'Exporter'; our @EXPORT = 'some_func'; sub some_func { 'naughty function. no biscuit' } } { package Unsuspecting; use strict::can; Naughty->import; sub new { bless {}, shift } sub _private { 'Unsuspecting _private' } } { package Unsuspecting::Subclass; our @ISA = 'Unsuspecting'; } ok my $new = Unsuspecting->can('new'), 'can() should return something if a package->can do something'; is ref $new, 'CODE', '... and that something should be a code referenc +e'; ok my $object = Unsuspecting->$new, '... which we should be able to ca +ll'; is ref $object, 'Unsuspecting', '... and it should behave as we expect +'; ok !Unsuspecting->can('some_func'), 'strict::can() should not report imported functions'; ok Unsuspecting::some_func(), '... even though the package can still c +all it'; ok !Unsuspecting->can('_private'), 'strict::can should ignore functions which begin with an underscore' +; ok $new = Unsuspecting::Subclass->can('new'), 'can() should return something if a subclass->can do something'; is ref $new, 'CODE', '... and that something should be a code referenc +e'; ok $object = Unsuspecting::Subclass->$new, '... which we should be able to call'; is ref $object, 'Unsuspecting::Subclass', '... and it should behave as we expect'; ok !Unsuspecting::Subclass->can('some_func'), 'strict::can() should not report imported functions'; ok !Unsuspecting::Subclass->can('_private'), 'strict::can should ignore functions which begin with an underscore' +;
Note that the tests are not using some of the built-in "can_ok", "isa_ok" and other test shortcuts because of the nature of the code I'm testing.
Update: a newer version of the can() method.
sub can { my ( $proto, $method ) = @_; my ($callpack) = caller(0); my $class = ref $proto || $proto; if ( my $coderef = $proto->SUPER::can($method) ) { return $coderef if 'can' eq $method; my $code_package = $package_for->($coderef); if ( $class->isa($code_package) && ('_' ne substr($method, 0, 1) || $callpack->isa($code_pack +age)) ) { return $coderef; } } return; }
Cheers,
Ovid
New address of my CGI Course.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Fixing nits with $object->can($method); (packages)
by tye (Sage) on Dec 04, 2005 at 21:59 UTC | |
|
Re: Fixing nits with $object->can($method);
by friedo (Prior) on Dec 05, 2005 at 00:26 UTC | |
|
Re: Fixing nits with $object->can($method);
by xdg (Monsignor) on Dec 04, 2005 at 21:45 UTC | |
by chromatic (Archbishop) on Dec 04, 2005 at 22:18 UTC | |
|
Re: Fixing nits with $object->can($method);
by Roy Johnson (Monsignor) on Dec 05, 2005 at 18:40 UTC | |
|
Re: Fixing nits with $object->can($method);
by tilly (Archbishop) on Dec 06, 2005 at 04:48 UTC | |
|
Re: Fixing nits with $object->can($method);
by adrianh (Chancellor) on Dec 06, 2005 at 12:53 UTC | |
by xdg (Monsignor) on Dec 06, 2005 at 14:34 UTC | |
by adrianh (Chancellor) on Dec 07, 2005 at 12:56 UTC |