Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Generating accessors and mutators for complex structures inside the object

by KSURi (Monk)
on Jan 11, 2008 at 18:22 UTC ( [id://661941]=perlquestion: print w/replies, xml ) Need Help??

KSURi has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks! There is the object of such a structure:
$self = { Hashref => { A => 1, B => 2, # etc. } }
How can I automagically generate get/set methods for embedded member data? I mean that I need a method B generated, which would change $self->{Hashref}->{B} for the object described above. Any ideas?
  • Comment on Generating accessors and mutators for complex structures inside the object
  • Download Code

Replies are listed 'Best First'.
Re: Generating accessors for complex structures inside the object
by kyle (Abbot) on Jan 11, 2008 at 18:36 UTC
      There are some useful information for me in that discussion, tnx
Re: Generating accessors for complex structures inside the object
by lima1 (Curate) on Jan 11, 2008 at 19:11 UTC
      Thanks for the link, but I have a more complex situation. The above Hashref is not static name. Here's a good example of what I need:
      $self = { A => { B => 'C' }, D => { E => { F => 'G' } } }
      So there are 2 hasrefs in example (in real code there will be more) inside the object. I need this methods to be generated:
      • A - would get/set the whole 'A' hashref
      • B - would get/set the 'B' key of the 'A' hashref
      • D - would get/set the whole 'D' hashref
      • E - would get/set the whole 'E' hashref
      • F - would get/set the 'F' key of the 'E' hashref

        What do you want to do if somewhere in your jungle of hashes you have two keys with the same name?

        $self = { A => { A => 'C' }, };

        Does the structure have cycles in it?

Re: Generating accessors for complex structures inside the object
by snoopy (Curate) on Jan 12, 2008 at 00:38 UTC
    Here's a base class for generating flattened accessors for nested hash structs:
    package Class::StructAccessors; use warnings; use strict; use Scalar::Util qw/reftype/; sub mk_struct_accessors { my ($class, $struct, $path, $key_seen, $ref_seen) = @_; $path ||= []; $key_seen ||= {}; $ref_seen ||= {}; while (my ($k,$v) = each %$struct) { my $reftype = reftype($v)||''; if ($reftype) { die "Bad reference type: $reftype" unless ($reftype eq 'HASH'); die "Cycle detected at $v" if ($ref_seen->{$v}); $ref_seen->{$v} = 1; $class->mk_struct_accessors($v, [@$path, $k], $key_seen, $ +ref_seen); } else { die "null key" unless (defined $v && $v ne ''); die "Duplicate key detected $v" if ($key_seen->{$v}); $key_seen->{$v} = 1; # # add this accessor to the class # no strict 'refs'; warn "adding: $v"; *$v = sub { my $self = shift; return $self->_dereference(@$path, $k, $v); } } } } sub _dereference { my $self = shift; my @hash_keys = @_; my $ref = $self; while (@hash_keys && $ref) { $ref = $ref->{shift(@hash_keys)}; } return $ref; } 1;
    It dies if the struct contains any duplicate keys or cycles.

    A test script follows:

    package Foo; use base qw{Class::StructAccessors}; __PACKAGE__->mk_struct_accessors({ A => { B => 'C' }, D => { E => { F => 'G' } }, ## Z => 'G', # Duplicate test ## X => [1,2,3], # Bad type test ## N => do {my $cycle = {}; $cycle->{M} = $cycle; $cycle;}, # Cyclic + test }); package main; use Foo; my $obj = bless { A => { B => {'C' => "It's hello from C"}, }, D => { E => { F => {'G' => "G'day from G"} } } }, 'Foo'; print "C:".$obj->C."\n"; print "G:".$obj->G."\n";

    Update: Added test for cycles.

      I've read your code attentively, now I want to ask a few questions please. Here:
      ... D => { E => { F => 'G' } }, ## Z => 'G', # Duplicate test ...
      If I'll uncomment 'Z' key there will be a warning thrown about duplicate 'G' key. But 'G' is not a key here, it's keys value.

      And here:
      ## X => [1,2,3], # Bad type test
      Why arrayref is a bad type?

      And the last one: I tried to add mutators to your code through assignment the lvalue attribute to _derefernce sub but it has no effect =( Any ideas of how can I do this?
        Hi KSURi,

        Thanks for your questions. Before getting into the details; I did make some assumptions that need to be tested:

        The class level structure (defined in Foo) is a effectively a struct definition:

        So, if in Foo, you define

        package Foo; use base qw/Class::StructAccessors/; __PACKAGE__->mk_struct_accessors({A => {B => C}});
        then the actual method created is C.

        my $obj = {}; bless $obj, "Foo"; $obj->{A}{B}{C} = "this is a C"; print $obj->C; # outputs "this is a C"
        If you want another object with another fixed structure, then you need another class
        package Bar; use base qw/Class::StructAccessors/; __PACKAGE__->mk_struct_accessors({X => {Y => Z}});
        This will generate accessor method for Z.

        In an nutshell, the above code works when you have finite number of structures that are know at run time.

        I'm getting the feeling that you want a more dynamic solution that'll work at the instance on any structure; something like:

        my $obj = bless {a => {b => {c => 'data for c'}}}, "Class::StructAc +cessorMk2";
        Always happy to assist (and code) :-) Please advise.
      Thanks! I'll try your code asap. Unfortunately I can't do this now =(
Re: Generating accessors for complex structures inside the object
by fenLisesi (Priest) on Jan 12, 2008 at 13:21 UTC
    KSURi,

    Having the key Hashref in there may be an indication of a design issue. You may be trying to solve implementation problems stemming from a design problem. Would you care to tell us more about your objects? What other keys are there in your object than Hashref? How about some sample actual data?

      Sure, here it is. This is my class constructor which sets primary attributes:
      sub new { my $class = shift || __PACKAGE__; my $self; if ( scalar @_ == 1 ) { ( $self->{RemoteAddr}, $self->{RemotePort} ) = split( ':', shi +ft ); } else { $self = {@_} } foreach (qw(RemoteAddr RemotePort)) { confess $_ . " was not cofigured" unless $self->{$_}; } $self->{FuzzLevel} ||= 1; $self->{Debug} ||= 0; $self->{Path} ||= 'report.html'; $self->{Report} ||= 0; $self->{Ready} = 0; $self->{Socket} = undef; $self->{LastSentFinger} = undef; bless( $self, ref $class || $class ); }
      And these are some additional attributes which will be added later by another sub:
      $self->{FuzzData} = { _Letters => { __data => [ # a lot of data here ], __example => qr/(\w{1})/ }, _Formats => { __data => [ # again data ], __example => qr/(%\w{1})/ }, _Numbers => { __data => [ # and again ], __example => qr/(\d{1)}/ } }; $self->{ProtoData} = { _ActionData => { __ActionCmd => [], __ActionExit => undef, __ActionTemplate => undef }, _LoginData => { __LoginUser => undef, __LoginPass => undef, __LoginSuccess => undef, __LoginTemplate => undef }, _ProtoPresets => { ftp => \&{ $self->_LoadPresetFTP }, http => \&{ $self->_LoadPresetHTTP }, pop3 => \&{ $self->_LoadPresetPOP3 }, smtp => \&{ $self->_LoadPresetSMTP } } };
      So, you can notice that attributes are grouped topically
      Ok, seems you were right) I've redesigned the object structure and removed all the nested structures. After that it works fine, thank you!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://661941]
Approved by kyle
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (3)
As of 2024-04-25 17:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found