Class:...vanced(3pm)

πŸ“¦
version 1.14 Β· 2 versions
  • 1.14 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 1.14 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– Class::InsideOut::Manual::Advanced πŸ’–

okay bestie, so this is Class::InsideOut::Manual::Advanced (3pm, perl, the whole vibe) β€” it's the advanced guide, the sequel, the part where we stop holding your hand and start showing you the cool stuff. User-contributed perl docs, no cap. πŸ¦‹


✨ VERSION ✨

version 1.14 β€” that's the one we're on, periodt.


🌸 DESCRIPTION 🌸

This manual is literally just further documentation for advanced usage of Class::InsideOut. Like, you already know the basics? Cute. Now we level up. πŸ’…


πŸŽ€ Customizing accessors πŸŽ€

Class::InsideOut supports custom subroutine hooks to modify the behavior of accessors. You pass them in as property options: set_hook and get_hook. Two little keys, so much power. πŸ”₯

The set_hook gets called when the accessor is called with an argument. The hook subroutine receives the entire argument list. And here's the slay part: just before the hook is called, $_ is locally aliased to the first argument for convenience. When the set_hook returns, the property gets set equal to $_. This is useful for on-the-fly modification of the value that will be stored. Obsessed.

public initials => my %initials, {
   set_hook => sub { $_ = uc $_ }
};

public tags => my %tags, {
   set_hook => sub { $_ = [ @_ ] } # stores arguments in a reference
};

If the set_hook dies, the error is caught and rethrown with a preamble that includes the name of the accessor. The error should end with a newline to prevent die from adding 'at ... filename line N'. The correct location will be added when the error is rethrown with croak:

public height  => my %height, {
   set_hook => sub { /^\d+$/ or die "must be a positive integer" }
};

# dies with "height() must be a positive integer at ..."
$person->height(3.5);
Not me being dramatic but β€” the return value of the set_hook function is ignored. It literally does not matter. This simplifies syntax in the case where die is used to validate input. πŸ™ˆ

The get_hook is called when the accessor is called without an argument. Just before the hook is called, $_ is set equal to the property value of the object for convenience. The hook is called in the same context (i.e. list versus scalar) as the accessor. The return value of the hook is passed through as the return value of the accessor.

public tags => my %tags, {
   set_hook => sub { $_ = [ @_ ] }, # stores arguments in a reference
   get_hook => sub { @$_ }          # return property as a list
};

Because $_ is a copy, not an alias, of the property value, it can be modified directly, if necessary, without affecting the underlying property. Main character energy tbh. πŸ’œ

As with set_hook, the get_hook can die to indicate an error condition and errors are handled similarly. This could be used as a way to implement a protected property:

sub _protected {
   die "is protected\n" unless caller(2)->isa(__PACKAGE__)
}

public hidden => my %hidden, {
   get_hook => \&_protected,
   set_hook => \&_protected,
}

Accessor hooks can be set as a global default with the options function, though they may still be overridden with options passed to specific properties. So like, set the vibe globally, then customize per-property. Iconic. ⭐


πŸ¦‹ Black-box inheritance πŸ¦‹

Because inside-out objects built with Class::InsideOut can use any type of reference for the object, inside-out objects can be built from other objects. This is useful to extend a superclass without needing to know whether it is based on hashes, array, or other types of blessed references. It's giving "no questions asked." 🌈

use base 'IO::File';

sub new {
  my ($class, $filename) = @_;

  my $self = IO::File->new( $filename );

  register( $self, $class );
}

In the example above, IO::File is a superclass. The object is an IO::File object, re-blessed into the inside-out class. The resulting object can be used directly anywhere an IO::File object would be, without interfering with any of its own inside-out functionality. Bestie it just works. ✨

Classes using black-box inheritance should consider providing a DEMOLISH function that calls the black-box class destructor explicitly.


🌸 Serialization 🌸

Class::InsideOut automatically imports STORABLE_freeze and STORABLE_thaw methods to provide serialization support with Storable. Due to limitations of Storable, this serialization will only work for objects based on scalars, arrays or hashes. (Read that again, it's a real limit, not a vibe.) πŸ’€

References to objects within the object being frozen will result in clones upon thawing unless the other references are included in the same freeze operation. (See "Storable" for details.)

# assume $alice and $bob are objects
$alice->friends( $bob );
$bob->friends( $alice );

$alice2 = Storable::dclone( $alice );

# $bob was cloned, too, thanks to the reference
die if $alice2->has_friend( $bob ); # doesn't die

# get alice2's friend
($bob2) = $alice2->friends();

# preserved relationship between bob2 and alice2
die unless $bob2->has_friend( $alice2 ); # doesn't die

Class::InsideOut also allows customizing freeze and thaw hooks. When an object is frozen, if its class or any superclass provides a FREEZE method, they are each called with the object as an argument prior to the rest of the freezing process. This allows for custom preparation for freezing, such as writing a cache to disk, closing network connections, or disconnecting database handles. The way that this is so thorough? Obsessed. πŸ”₯

Likewise, when a serialized object is thawed, if its class or any superclass provides a THAW method, they are each called after the object has been thawed with the thawed object as an argument.

Class::InsideOut also supports serialization of singleton objects for recent versions of Storable (2.14 or later) that support STORABLE_attach. Users must signal that STORABLE_attach should be used instead of STORABLE_thaw by adding :singleton to their import line as follows:

use Class::InsideOut qw( :std :singleton );

When attaching, the singleton object will be recreated in one of two ways:

  1. If the singleton class contains an ATTACH method, it will be called with three arguments: the class name, a flag for whether this is part of a dclone, and a data structure representing the object:
$data = {
    class => ref $obj,              # class name
    type => $type,                  # object reference type
    contents => $contents,          # object reference contents
    properties => \%property_vals,  # HoH of classes and properties
}

contents is a reference of the same type as type. properties is a multi-level hash, with the names of the class and any superclasses as top-level keys and property labels as second-level keys. This data may be used to reconstruct or reattach to the singleton. The ATTACH method should return the singleton.

  1. If no ATTACH routine is found, but the class has or inherits a new method, then new will be called with no arguments and the result will be returned as the singleton.

πŸ’… Thread-safety πŸ’…

Because Class::InsideOut uses memory addresses as indices to object properties, special handling is necessary for use with threads. When a new thread is created, the Perl interpreter is cloned, and all objects in the new thread will have new memory addresses. Starting with Perl 5.8, if a CLONE function exists in a package, it will be called when a thread is created to provide custom responses to thread cloning. (See perlmod for details.) To avoid bugs in the implementation of threading, Perl 5.8.5 or later is strongly recommended. 🫢

Class::InsideOut itself has a CLONE function that automatically fixes up properties in a new thread to reflect the new memory addresses for all classes created with Class::InsideOut. register must be called on all newly constructed inside-out objects to register them for use in Class::InsideOut::CLONE.

Users are strongly encouraged not to define their own CLONE functions as they may interfere with the operation of Class::InsideOut::CLONE and leave objects in an undefined state. Future versions may support a user-defined CLONE hook, depending on demand. (So like, hold off, okay? πŸ™ˆ)

Limitations:

fork on Perl for Win32 is emulated using threads since Perl 5.6. (See perlfork.) As Perl 5.6 did not support CLONE, inside-out objects that use memory addresses (e.g. Class::InsideOut) are not fork-safe for Win32 on Perl 5.6. Win32 Perl 5.8 fork is supported.

The technique for thread-safety requires creating weak references using Scalar::Util::weaken(), which is implemented in XS. If the XS-version of Scalar::Util is not installed or if run on an older version of Perl without support for weak references, Class::InsideOut will issue a warning and continue without thread-safety. Also, objects will leak memory unless manually deregistered with a private function:

# destroying an object when weaken() isn't availalbe
Class::InsideOut::_deregister( $obj );
undef $obj;
Note: availalbe is spelled exactly like that in the original. Not my typo, not your typo, we just vibe with it. 😭

🌈 SEE ALSO 🌈

  • Class::InsideOut
  • Class::InsideOut::Manual::About

πŸ‘‘ AUTHOR πŸ‘‘

David Golden <dagolden@cpan.org>


This software is Copyright (c) 2006 by David A. Golden.

This is free software, licensed under:

The Apache License, Version 2.0, January 2004

and that's a wrap bestie πŸ’– this whole page was perl v5.36.0, dated 2022-12-12, and yes it really is Class:...vanced(3pm) β€” the manpage that ate and left no crumbs. go forth and customize those accessors, love u, bye πŸ¦‹βœ¨