1

We have a sample code like below. Is it possible to to capture all missing attributes invoked in package FooBar and create it dynamically? This is something like PHP's __call.

test.pl

package Person;
use feature qw(say);
use Moo;

has name => (is => "ro");

my $p = Person->new(name => "John");

say $p->name;

# The missing attribute method will be dynamically created when 
# invoked even it's not declared in Person.
say $p->lalala;

$ perl test.pl
John
Can't locate object method "lalala" via package "Test" at test.pl line 13.
  • [Autoload](https://perldoc.perl.org/perlsub.html#Autoloading)? – choroba Jun 04 '18 at 15:08
  • 1
    There's no `$fb` variable in your example. Please [edit] your question and provide working code, or in your case code that produces the error you are talking about, not something else. – simbabque Jun 04 '18 at 15:11
  • 2
    @choroba you'd be able to install a method via autoload, but not really a full-blown attribute. If the attribute is already defined in the class, we could circumvent the predicate, but I'm not sure what good that would do. We could of course at runtime modify the class via the meta layer and add an attribute if this is done, without type or anything, as a read-write attribute, but that would then leak into every object of that class. Doesn't sound like a good idea. – simbabque Jun 04 '18 at 15:13
  • @simbabque Good catch. Sample code updated. – KianMeng Ang Jun 05 '18 at 13:19

1 Answers1

2

It's possible using AUTOLOAD and metaprogramming, the question remains Why.

There might be nicer ways using parameterized roles, but I just wanted to quickly show how to do it. I would reject such code in a review (I'd expect at least a comment explaining why autoloading is needed).

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

{   package MyObj;
    use Moose;

    sub AUTOLOAD {
        my ($self) = @_;
        ( my $method = our $AUTOLOAD ) =~ s/.*:://;
        (ref $self)->meta->add_attribute($method, is => 'rw');
        goto &$method
    }
}

say 'MyObj'->can('lalala');  # No, it can't.

my $o = 'MyObj'->new;
$o->lalala(12);              # Attribute created.
say $o->lalala;              # 12.

Update: Previously, my code was more complex, as it replied to @simbabque's comment to the question: it showed how to add the attribute to an instance, not the whole class.

choroba
  • 231,213
  • 25
  • 204
  • 289
  • Why do you create a new class? Also, `goto` should be used to hide AUTOLOAD from the call stack. – ikegami Jun 05 '18 at 01:06
  • @ikegami: Because I don't want to change the behaviour of the original class. – choroba Jun 05 '18 at 07:33
  • Also, I don't get the output you claim, but an endless chain of `Use of uninitialized value $new_class_name:: in concatenation (.) or string at a.pl line 19.` – ikegami Jun 05 '18 at 10:10
  • @ikegami: What Perl and Moose version? I'm on 5.18.2 with Moose 2.2009. – choroba Jun 05 '18 at 11:10
  • @ikegami: Also, I previously haven't noticed the OP wanted to change the behaviour of the whole class. I simplified the reply. – choroba Jun 05 '18 at 11:17
  • @choroba Thanks for the sample code. Appreciate it! The *WHY* part is that I want to create a generic and dynamic Moo (not sure if possible) or Moose class for JSON data or JSON schema (if exists) for different web service endpoints. I've searched CPAN and googled around but can't find any Perl module that do so. – KianMeng Ang Jun 05 '18 at 13:27