Perl export to child modules - perl

I have this parent module MyApp.pm:
package MyApp;
use Moose;
use base 'Exporter';
our #EXPORT = qw(msg);
sub msg {
print "Hello msg\n";
}
1;
which is inherited by this child module MyApp2.pm:
package MyApp2;
use Moose;
extends qw(MyApp);
1;
and when used in the App.cgi script like this:
#!/usr/bin/perl
use MyApp2;
msg();
I get error message:
Undefined subroutine &main::msg called at App.cgi line 3.
So the exported function does not work in the child class MyApp2 but works only if I use "use MyApp" instead of "use MyApp2". I assume the exported function should be accessible to the child modules also which is extending the parent class. What I am doing wrong.

Inheritance only changes how method calls are handled; function calls or variable accesses (like our #EXPORT) are not affected.
Instead of exporting a function, you could use it as a method:
use MyApp2;
MyApp2->msg;
but in this case, it would be cleaner to explicitly load MyApp in order to import the msg function, and to additionally load MyApp2 in order to load this class.
use MyApp;
use MyApp2;
msg;
It is generally advisable for a module to either be object oriented or to offer an interface via exported functions, but not do both.

Here is the solution I found for my request:
package MyApp;
use Moose;
use base 'Exporter';
our #EXPORT = qw(msg);
sub import {
my ($class, #args) = #_;
my $caller = $class.'::';
{
no strict 'refs';
#{$caller.'EXPORT'} = #EXPORT;
foreach my $sub (#EXPORT) {
next if (*{"$caller$sub"}{CODE});
*{"$caller$sub"} = \*{$sub};
}
}
goto &Exporter::import;
}
sub msg {
print "Hello msg MyApp\n";
}
1;
The idea here is I export all the contents of the "#EXPORT" array into the child module, only add none existent subs so will not overwrite any methods in the child class.
In this example above, this exports from MyApp to the child MyApp2.
This works for my own needs.

Related

Perl exported functions does not work

Assuming I have this module:
package MyApp;
use base 'Exporter';
our #EXPORT = qw(msg);
sub import {
my ($class, #args) = #_;
my ($package, $script) = caller;
print "$package, $script\n";
}
sub msg {
print "Hello msg\n";
}
1;
and used by this script App.cgi:
#!/usr/bin/perl
use MyApp;
msg();
if I run this App.cgi I get this error:
undefined subroutine &main::msg in App.cgi at line 3
If I rename or remove the sub import in the package MyApp.pm it works fine.
So what is the problem with the import or how it should be used while exporting functions.
You're overriding Exporter's import method with one of your own that doesn't actually export anything, it just prints to stdout. Either don't do that (what's the point?) Or call $class->export_to_level(1, #_) to ensure that Exporter's stuff gets called. You need to use export_to_level and not SUPER::import, because your own import method adds a caller frame, and without being told otherwise, Exporter would export to the wrong place.

How to override a sub in a Moose::Role?

I'm trying to implement a Moose::Role class that behaves like an abstract class would in Java. I'd like to implement some methods in the Role, but then have the ability to override those methods in concrete classes. If I try this using the same style that works when I extend classes I get the error Cannot add an override method if a local method is already present. Here's an example:
My abstract class:
package AbstractClass;
use Moose::Role;
sub my_ac_sub {
my $self = shift;
print "In AbstractClass!\n";
return;
}
1;
My concrete class:
package Class;
use Moose;
with 'AbstractClass';
override 'my_ac_sub' => sub {
my $self = shift;
super;
print "In Class!\n";
return;
};
__PACKAGE__->meta->make_immutable;
1;
And then:
use Class;
my $class = Class->new;
$class->my_ac_sub;
Am I doing something wrong? Is what I'm trying to accomplish supposed to be done a different way? Is what I'm trying to do not supposed to be done at all?
Turns out I was using it incorrectly. I opened a ticket and was shown the correct way of doing this:
package Class;
use Moose;
with 'AbstractClass';
around 'my_ac_sub' => sub {
my $next = shift;
my $self = shift;
$self->$next();
print "In Class!\n";
return;
};
__PACKAGE__->meta->make_immutable;
1;
Making this change has the desired effect.
Some time ago, I did this by having a role that consists solely of requires statements. That forms the abstract base class. Then, you can put your default implementations in another class and inherit from that:
#!/usr/bin/env perl
use 5.014;
package AbstractClass;
use Moose::Role;
requires 'my_virtual_method_this';
requires 'my_virtual_method_that';
package DefaultImpl;
use Moose;
with 'AbstractClass';
sub my_virtual_method_this {
say 'this';
}
sub my_virtual_method_that {
say 'that'
}
package MyImpl;
use Moose;
extends 'DefaultImpl';
with 'AbstractClass';
override my_virtual_method_that => sub {
super;
say '... and the other';
};
package main;
my $x = MyImpl->new;
$x->my_virtual_method_this;
$x->my_virtual_method_that;
If you want to provide default implementations for only a few methods define in the role, remove the requires from DefaultImpl.
Output:
$ ./zpx.pl
this
that
... and the other

Catalyst: how declare a global subroutine

Hi i don't want to repeate the same code in the controllers, so i created a sub in the main MyApp package:
sub do_stuff {
my $input = shift;
do something
}
But then i want to use it in controller MyApp::Controller::Foo
sub test : Chained('base') Args(0) {
my ($self, $c) = #_;
my $test = do_stuff($c->request->params->{s});
do something more
}
i get following error:
Caught exception in MyApp::Controller::Foo->test "Undefined subroutine
&MyApp::Controller::Foo::do_stuff called at
/home/student/workspace/MyApp/script/../lib/MyApp/Controller/Foo.pm
line 24, line 1000."
How can i create a subroutine / function to use global in all Catalyst Controllers???
In principle it is already available in all the modules that were used by your main MyApp.
But if it is defined in the main package, you must either call it from within that namespace (either main or your MyApp namespace), or import it into your current package namespace.
Depending on where it was defined, use one of those ways.
my $test = main::do_stuff($c->request->params->{s});
my $test = MyApp::do_stuff($c->request->params->{s});
The alternative is to import it into your namespace in each package.
package MyApp::Controller::Foo;
if (defined &MyApp::do_stuff) {
*do_stuff = *MyApp::do_stuff;
}
With defined you can check whether a subroutine exists.
On another note, maybe this do_stuff sub is better placed inside another module that has Exporter. You can use it in all your controllers or other modules where you need it, and Exporter will take care of importing it into your namespace on its own.
The context object ($c) that you pass to most methods in Catalyst is already an object of type MyApp, so if you say
$c->do_stuff($c->request->params->{s})
it is the same as calling
MyApp::do_stuff($c, $c->request->params->{s});
If you expect your global subroutines to make use of this context object, then you'll want to consider writing them as methods (i.e., subroutines in a package where the first argument is always an instance of the package):
# to be called like $c->do_stuff("s") to do something with form input "s"
sub do_stuff {
my ($c, $param) = #_;
... do something with $c->request->param($param) ...
}

Is there a standard way to selectively inherit methods from a Perl superclass?

Or: Is there a standard way to create subclass but make certain methods from the superclass yield a "Can't locate object method" error when called?
For example, if My::Foo inherits from My::Bar, and My::Bar has a method called dostuff, calling Foo->new->dostuff would die with the "Can't locate object method" error in some non-contrived/hackish way.
If the superclass is a Moose class you could use remove_method.
package My::Foo;
use Moose;
extends 'My::Bar';
# some code here
my $method_name = 'method_to_remove';
__PACKAGE__->meta->remove_method($method_name);
1;
This is documented in Class::MOP::Class and should work with MooseX::NonMoose but i am not sure.
You can create dummy methods in your child class that intercept the method calls and die.
package My::Foo;
our #ISA = 'My::Bar';
use Carp ();
for my $method qw(dostuff ...) {
no strict 'refs';
*$method = sub {Carp::croak "no method '$method' on '$_[0]'"};
}
You could even write a module to do this:
package No::Method;
use Carp ();
sub import {
my $class = shift;
my $caller = caller;
for my $method (#_) {
no strict 'refs';
*{"$caller\::$method"} = sub {
Carp::croak "no method '$method' on '$_[0]'"
};
}
}
And then to use it:
package My::Foo;
our #ISA = 'My::Bar';
use No::Method qw(dostuff);
This depends entirely on the way My::Bar and My::Foo are constructed. If they are your modules you may want to look into Exporter.
You can also import select functions from a class like so:
use POSIX qw{setsid};

inspect the parameters to "use", and pass on the rest?

I have a Perl module and I'd like to be able to pick out the parameters that my my module's user passed in the "use" call. Whichever ones I don't recognize I'd like to pass on. I tried to do this by overriding the "import" method but I'm not having much luck.
EDIT:
To clarify, as it is, I can use my module like this:
use MyModule qw/foo bar/;
which will import the foo and bar methods of MyModule. But I want to be able to say:
use MyModule qw/foo doSpecialStuff bar/;
and look for doSpecialStuff to check if I need to do some special stuff at the beginning of the program, then pass qw/foo bar/ to the Exporter's import
Normally, you would do this to gain Exporter's import() functionality (this isn't the only way, but it's a common method that works):
package MyClass;
use strict;
use warnings;
use Exporter 'import'; # gives you Exporter's import() method directly
our #EXPORT_OK = qw(stuff more_stuff even_more_stuff);
...and then you will get an import() method automatically created for you. However, if you want to do something extra in import() before the normal method gets a hold of the parameters, then don't import Exporter's import(), and define your own, which calls Exporter's import() after making any alterations to the argument list that you need:
package MyClass;
use strict;
use warnings;
use parent 'Exporter';
sub import
{
my ($class, #symbols) = #_;
# do something with #symbols, as appropriate for your application
# ...code here left as an exercise for the reader :)
# now call Exporter's import, and import to the right level
local $Exporter::ExportLevel = 1;
$class->SUPER::import(#symbols);
}
However, I'm wondering why you need to do this... the standard behaviour of dying when being passed an unrecognized symbol is normally a good thing. Why would you want to ignore unrecognized symbols? (Edit: I see now, you want to specify additional behaviour on top of importing symbols, which is not uncommon in Perl. So defining your own import() method is definitely the way to go here, to grab those values.)
PS. if you only want to import symbols which are defined by #EXPORT_OK, it could be implemented like this:
#symbols = grep {
my $sym = $_;
grep { $_ eq $sym } #EXPORT_OK
} #symbols;
The typical use of Exporter is to declare your module to inherit from Exporter, and to have Exporter's import method called implicitly when your module is used. But this keeps you from creating your own import method for your module.
The workaround is to use Exporter's export_to_level method, which performs Exporter's functions without explicitly going through the Exporter::import method. Here's a typical way to use it:
package My::Module;
use base 'Exporter'; # or use Exporter; our #ISA=qw(Exporter);
our #EXPORT = qw(...);
our #EXPORT_OK = qw(...);
our %EXPORT_TAGS = (...);
sub import {
my ($class,#import_args) = #_;
my #import_args_to_pass_on = ();
foreach my $arg (#import_args) {
if (... want to process this arg here ...) {
...
} else {
push #import_args_to_pass_on, $arg;
}
}
My::Module->export_to_level(1, "My::Module", #import_args_to_pass_on, #EXPORT);
#or: $class->export_to_level(1, $class, #import_args_to_pass_on, #EXPORT);
}
I have done it this way in my modules:
sub import {
return if not #_;
require Exporter;
my $pkg = shift;
# process #_ however you want
unshift #_, $pkg;
goto &Exporter::import;
}
you can also inherit from Exporter if you want unimport and the like.