NAME

Venus::Hook - Venus Lifecycle Hooks

ABSTRACT

Lifecycle Hooks for Perl 5

SYNOPSIS

package User;

use base 'Venus::Core';

# Define attributes
User->ATTR('name');
User->ATTR('email');

# Post-construction initialization
sub BUILD {
  my ($self) = @_;
  $self->{created} = time;
  return $self;
}

# Pre-destruction cleanup
sub DECONSTRUCT {
  my ($self) = @_;
  $self->log("User destroyed");
  return $self;
}

package main;

my $user = User->BLESS(name => 'Elliot', email => 'e@example.com');

# bless({name => 'Elliot', email => 'e@example.com', created => ...}, 'User')

DESCRIPTION

This document provides a comprehensive reference for all lifecycle hooks available in the Venus OOP framework. Venus provides a rich set of hooks that allow you to customize class building, object construction, and object destruction behavior.

Venus lifecycle hooks are special methods that are automatically invoked at specific points during class building, object construction, and object destruction. These hooks provide extension points for customizing behavior without modifying core Venus code. Hooks are conventionally named in UPPERCASE to distinguish them from regular methods.

SUPER

When overriding lifecycle hooks in a subclass, you should almost always call the parent implementation using SUPER, or risk breaking the framework in unexpected ways.

The Venus framework relies on proper hook execution throughout the inheritance chain. Failing to call the parent implementation can break functionality in subtle ways, including:

  • Breaking role/mixin composition and their lifecycle hooks

  • Skipping critical initialization or cleanup code from parent classes

  • Bypassing framework-level validations and setup

  • Causing memory leaks by not cleaning up instance data properly

Required

When overriding any of the following hooks, you should call SUPER unless you have a very specific reason not to:

ARGS, ATTR, AUDIT, BASE, BLESS, BUILD, BUILDARGS, CONSTRUCT, CLONE,
DECONSTRUCT, DESTROY, DOES, EXPORT, FROM, GET, IMPORT, ITEM, META,
MIXIN, MASK, NAME, ROLE, SET, SUBS, TEST, UNIMPORT, USE

Correct Pattern

Always call the parent implementation first, then add your custom logic:

package Child;

use base 'Parent';

sub BUILD {
  my ($self, $data) = @_;

  # CRITICAL: Call parent implementation first
  $self->SUPER::BUILD($data);

  # Then add your custom logic
  $self->{child_field} = 'value';

  return $self;
}

Incorrect Pattern

DO NOT skip the SUPER call:

# WRONG - This breaks the framework!
sub BUILD {
  my ($self, $data) = @_;

  # Missing: $self->SUPER::BUILD($data);

  $self->{child_field} = 'value';  # Parent's BUILD never runs!

  return $self;
}

When You Might Skip SUPER

There are very rare cases where you might intentionally skip calling SUPER:

  • You are replacing the parent's behavior entirely (use with extreme caution)

  • You are implementing a base hook that has no parent (e.g., in Venus::Core itself)

  • You are implementing certain introspection hooks like DATA that are meant to be completely overridden

Rule of thumb: If you're unsure whether to call SUPER, call it. It's almost always the right choice.

HOOKS

This package provides the following hooks:

Object Construction Hooks

These hooks control how objects are constructed and initialized.

args
ARGS(any @args) (hashref)

The ARGS hook accepts constructor arguments and returns a blessable data structure. It is called during object construction, before blessing.

Since 1.00

args example 1
package List;

use base 'Venus::Core';

sub ARGS {
  my ($self, @args) = @_;

  return @args
    ? ((@args == 1 && ref $args[0] eq 'ARRAY') ? @args : [@args])
    : $self->DATA;
}

package main;

my $list = List->BLESS(1..4);

# bless([1..4], 'List')
bless
BLESS(any @args) (object)

The BLESS hook is the main object construction hook that creates and returns an instance. It is called when you call the constructor (or BLESS directly). The default behavior is equivalent to calling BUILD(bless(ARGS(BUILDARGS(@args) || DATA), $class)).

Since 1.00

bless example 1
package User;

use base 'Venus::Core';

package main;

my $user = User->BLESS(name => 'Elliot');

# bless({name => 'Elliot'}, 'User')
buildargs
BUILDARGS(any @args) (any @args | hashref $data)

The BUILDARGS hook pre-processes constructor arguments before they are converted to a data structure. It is called before ARGS, and receives raw constructor arguments.

Since 1.00

buildargs example 1
package User;

use base 'Venus::Core';

sub BUILDARGS {
  my ($self, @args) = @_;

  # Convert single string arg to hashref
  my $data = @args == 1 && !ref $args[0] ? {name => $args[0]} : {};

  return $data;
}

package main;

my $user = User->BLESS('Elliot');

# bless({name => 'Elliot'}, 'User')
build
BUILD(hashref $data) (object)

The BUILD hook is a post-construction initialization hook. It receives the blessed object and can modify it. It is called after the object is blessed, before it's returned to the caller. While the return value is ignored by BLESS, it should return $self for chaining.

Note: When inheriting, you should call the parent's BUILD using $self->SUPER::BUILD($data).

Since 1.00

build example 1
package User;

use base 'Venus::Core';

sub BUILD {
  my ($self) = @_;

  $self->{name} = 'Mr. Robot';

  return $self;
}

package main;

my $user = User->BLESS(name => 'Elliot');

# bless({name => 'Mr. Robot'}, 'User')
build example 2
package Elliot;

use base 'User';

sub BUILD {
  my ($self, $data) = @_;

  $self->SUPER::BUILD($data);

  $self->{name} = 'Elliot';

  return $self;
}

package main;

my $user = Elliot->BLESS;

# bless({name => 'Elliot'}, 'Elliot')
construct
CONSTRUCT() (any)

The CONSTRUCT hook is an additional post-construction hook for instance preparation, separate from the build process. It is called automatically after BLESS, without arguments. The return value is not used in subsequent processing.

Since 4.15

construct example 1
package User;

use base 'Venus::Core';

sub CONSTRUCT {
  my ($self) = @_;

  $self->{ready} = 1;

  return $self;
}

package main;

my $user = User->BLESS(name => 'Elliot');

# bless({name => 'Elliot', ready => 1}, 'User')
data
DATA() (Ref)

The DATA hook returns the default data structure to bless when no arguments are provided. It is called during object construction when no arguments are given. The default implementation returns an empty hashref {}.

Since 1.00

data example 1
package Example;

use base 'Venus::Core';

sub DATA {
  return [];  # Use arrayref instead of hashref
}

package main;

my $example = Example->BLESS;

# bless([], 'Example')
clone
CLONE() (object)

The CLONE hook creates a deep clone of the invocant. It is called when you call clone or CLONE. It must be called on an object instance (not a class). Private data (from MASK) is not included in the clone.

Since 4.15

clone example 1
package User;

use base 'Venus::Core';

User->MASK('password');

sub get_password {
  my ($self) = @_;
  $self->password;
}

sub set_password {
  my ($self) = @_;
  $self->password('secret');
}

package main;

my $user = User->BLESS(name => 'Elliot');
$user->set_password;

my $clone = $user->CLONE;

# bless({name => 'Elliot'}, 'User')
# Note: Private data (password) is not cloned

Object Destruction Hooks

These hooks are invoked when objects are being destroyed.

deconstruct
DECONSTRUCT() (any)

The DECONSTRUCT hook is a pre-destruction cleanup hook for releasing resources before the object is destroyed. It is called just before DESTROY. The return value is not used in subsequent processing.

Since 4.15

deconstruct example 1
package User;

use base 'Venus::Core';

our $CALLS = 0;
our $USERS = 0;

sub CONSTRUCT {
  return $CALLS = $USERS += 1;
}

sub DECONSTRUCT {
  return $USERS--;
}

package main;

my $user = User->BLESS('Elliot');
# $User::USERS is now 1

undef $user;
# $User::USERS is now 0
destroy
DESTROY() (any)

The DESTROY hook is the object destruction lifecycle hook. It is called when the last reference to the object goes away.

Since 1.00

destroy example 1
package User;

use base 'Venus::Core';

our $TRIES = 0;

sub BUILD {
  return $TRIES++;
}

sub DESTROY {
  return $TRIES--;
}

package main;

my $user = User->BLESS(name => 'Elliot');
undef $user;
# $User::TRIES is now 0

Class Building Hooks

These hooks are used during class definition to set up the class structure.

attr
ATTR(string $name, any @args) (string | object)

The ATTR hook installs attribute accessors in the calling package. It is called during class building when you call attr or ATTR. It creates getter/setter methods for the named attribute.

Since 1.00

attr example 1
package User;

use base 'Venus::Core';

User->ATTR('name');
User->ATTR('role');

package main;

my $user = User->BLESS(role => 'Engineer');

$user->name;           # ""
$user->name('Elliot'); # "Elliot"
$user->role;           # "Engineer"
$user->role('Hacker'); # "Hacker"
base
BASE(string $name) (string | object)

The BASE hook registers one or more base classes for the calling package. It is called during class building. Note: Unlike FROM, this does NOT invoke the AUDIT hook.

Since 1.00

base example 1
package Entity;

sub work {
  return;
}

package User;

use base 'Venus::Core';

User->BASE('Entity');

package main;

my $user = User->BLESS;
# $user->can('work') is true
mask
MASK(string $name, any @args) (string | object)

The MASK hook installs private instance data accessors that can only be accessed within the class or its subclasses. It is called during class building. It creates accessors that raise an exception if accessed outside the class hierarchy.

Since 4.15

mask example 1
package User;

use base 'Venus::Core';

User->MASK('password');

sub get_password {
  my ($self) = @_;
  $self->password;  # OK: called from within class
}

sub set_password {
  my ($self, $value) = @_;
  $self->password($value);  # OK: called from within class
}

package main;

my $user = User->BLESS(name => 'Elliot');
$user->set_password('secret');  # Works

# $user->password;  # Exception! Can't access private variable

Composition Hooks

These hooks handle role and mixin composition.

role
ROLE(string $name) (string | object)

The ROLE hook consumes a role into the calling package. It is called during class building via the role function.

Behavior:

  • Methods are copied from the role to the consumer

  • Methods are NOT copied if they already exist in the consumer

  • First method wins in naming collisions

  • Automatically invokes the role's IMPORT hook

  • Does NOT invoke the role's AUDIT hook

Since 1.00

role example 1
package Admin;

use base 'Venus::Core';

package User;

use base 'Venus::Core';

User->ROLE('Admin');

package main;

User->DOES('Admin');  # 1
mixin
MIXIN(string $name) (string | object)

The MIXIN hook consumes a mixin into the calling package. It is called during class building via the mixin function.

Behavior:

  • Methods are ALWAYS copied, even if they already exist (override)

  • Last mixin wins in naming collisions

  • Automatically invokes the mixin's IMPORT hook

  • More aggressive than roles

Since 1.02

mixin example 1
package Action;

use base 'Venus::Core';

package User;

use base 'Venus::Core';

User->MIXIN('Action');

package main;

User->DOES('Action');  # 0 (mixins don't register as roles)
test
TEST(string $name) (string | object)

The TEST hook consumes a role and invokes its AUDIT hook for interface validation. It is called during class building via the test or with function.

Behavior:

  • Same as ROLE but also invokes AUDIT hook

  • Allows roles to act as interfaces

Since 1.00

test example 1
package Admin;

use base 'Venus::Core';

package IsAdmin;

use base 'Venus::Core';

sub shutdown {
  return;
}

sub AUDIT {
  my ($self, $from) = @_;
  die "${from} is not a super-user" if !$from->DOES('Admin');
}

sub EXPORT {
  ['shutdown']
}

package User;

use base 'Venus::Core';

User->ROLE('Admin');
User->TEST('IsAdmin');  # Will validate User->DOES('Admin')

package main;

my $user = User->BLESS;  # OK
from
FROM(string $name) (string | object)

The FROM hook registers a base class and invokes its AUDIT and IMPORT hooks. It is called during class building via the from function.

Behavior:

  • Registers inheritance

  • Automatically invokes AUDIT hook for validation

  • Automatically invokes IMPORT hook

Since 1.00

from example 1
package Entity;

use base 'Venus::Core';

sub AUDIT {
  my ($self, $from) = @_;
  die "Missing startup" if !$from->can('startup');
  die "Missing shutdown" if !$from->can('shutdown');
}

package User;

use base 'Venus::Core';

User->ATTR('startup');
User->ATTR('shutdown');

User->FROM('Entity');  # Will validate interface

package main;

my $user = User->BLESS;  # OK

Interface and Validation Hooks

audit
AUDIT(string $role) (string | object)

The AUDIT hook is an interface validation callback executed when a role is consumed via TEST or FROM. It is called when the consumer invokes TEST or FROM hooks. It receives the role itself and the consuming class name, and should die/throw if the interface contract is not satisfied.

Since 1.00

audit example 1
package HasType;

use base 'Venus::Core';

sub AUDIT {
  my ($self, $from) = @_;
  die 'Consumer missing "type" attribute' if !$from->can('type');
}

package User;

use base 'Venus::Core';

User->ATTR('type');
User->TEST('HasType');  # OK: User has 'type'

package Admin;

use base 'Venus::Core';

# Admin->TEST('HasType');  # Would die: Admin missing 'type'

Import/Export Hooks

These hooks control what gets exported when roles/mixins are composed.

export
EXPORT(any @args) (arrayref)

The EXPORT hook returns an arrayref of routine names to be automatically imported by consumers. It is called when a class is used via use, or whenever import is called, or during role/mixin composition via ROLE, TEST, or MIXIN hooks. Only methods listed in EXPORT are copied to the consumer. Note: By default, if no EXPORT routine is declared, and if a package variable exists named `@EXPORT`, the package variable is used as the list of routines to be automatically exported.

Since 1.00

export example 1
package Admin;

use base 'Venus::Core';

sub shutdown {
  return;
}

sub restart {
  return;
}

sub EXPORT {
  ['shutdown']  # Only shutdown will be exported
}

package User;

use base 'Venus::Core';

User->ROLE('Admin');

package main;

my $user = User->BLESS;
# $user->can('shutdown') is true
# $user->can('restart') is false
import
IMPORT(string $into, any @args) (string | object)

The IMPORT hook dispatches the EXPORT hook when roles/mixins are consumed. It is called when a class is used via use, or whenever import is called, or during role/mixin composition via ROLE, TEST, or MIXIN hooks. Override this hook to track or customize the import process.

Since 1.00

import example 1
package Admin;

use base 'Venus::Core';

our $USES = 0;

sub shutdown {
  return;
}

sub EXPORT {
  ['shutdown']
}

sub IMPORT {
  my ($self, $into) = @_;

  $self->SUPER::IMPORT($into);

  $USES++;

  return $self;
}

package User;

use base 'Venus::Core';

User->ROLE('Admin');

package main;

# $Admin::USES is now 1
use
USE(string $into, any @args) (any)

The USE hook is invoked when the Perl use declaration is used. It is called during compilation when you use a package.

Since 2.91

use example 1
package User;

use base 'Venus::Core';

package main;

User->USE;  # 'User'
unimport
UNIMPORT(string $into, any @args) (any)

The UNIMPORT hook is invoked when the Perl no declaration is used. It is called when you no a package.

Since 2.91

unimport example 1
package User;

use base 'Venus::Core';

package main;

User->UNIMPORT;  # 'User'

Instance Data Hooks

These hooks control how instance data (attributes) are accessed and modified.

get
GET(string $name) (any)

The GET hook is responsible for getting instance attribute values. By default, all attribute getters dispatch to this method. Override this hook to implement custom getter logic for all attributes.

Since 2.91

get example 1
package User;

use base 'Venus::Core';

User->ATTR('name');

package main;

my $user = User->BLESS(title => 'Engineer');

my $value = $user->GET('title');  # "Engineer"
set
SET(string $name, any @args) (any)

The SET hook is responsible for setting instance attribute values. By default, all attribute setters dispatch to this method. Override this hook to implement custom setter logic for all attributes.

Since 2.91

set example 1
package User;

use base 'Venus::Core';

User->ATTR('name');

package main;

my $user = User->BLESS(title => 'Engineer');

$user->SET('title', 'Manager');  # "Manager"
item
ITEM(string $name, any @args) (string | object)

The ITEM hook is responsible for both getting and setting instance attributes. By default, all attribute accessors dispatch to this method. Without extra args, it acts as a getter; with args, it acts as a setter.

Since 1.11

item example 1
package User;

use base 'Venus::Core';

User->ATTR('name');

package main;

my $user = User->BLESS;

$user->ITEM('name', 'unknown');  # "unknown" (set)
$user->ITEM('name');             # "unknown" (get)

Introspection Hooks

These hooks provide information about the package/object structure.

meta
META() (Venus::Meta)

The META hook returns a Venus::Meta object describing the package configuration. It is called when you call meta or META on a class or instance.

Since 1.00

meta example 1
package User;

use base 'Venus::Core';

package main;

my $meta = User->META;
# bless({name => 'User'}, 'Venus::Meta')

# Query the meta object
$meta->attrs;   # List attributes
$meta->bases;   # List base classes
$meta->roles;   # List consumed roles
$meta->subs;    # List methods
name
NAME() (string)

The NAME hook returns the name of the package. It is called whenever the package name is accessed via NAME.

Since 1.00

name example 1
package User;

use base 'Venus::Core';

package main;

my $name = User->NAME;           # "User"
my $user = User->BLESS;
my $instance_name = $user->NAME; # "User"
does
DOES(string $name) (boolean)

The DOES hook returns true if the invocant consumed the specified role or mixin. It is called on-demand, typically to check role composition.

Since 1.00

does example 1
package Admin;

use base 'Venus::Core';

package User;

use base 'Venus::Core';

User->ROLE('Admin');

package main;

User->DOES('Admin');  # 1
User->DOES('Owner');  # 0
subs
SUBS() (arrayref)

The SUBS hook returns the routines defined on the package and consumed from roles (excluding inherited methods). It is called on-demand, typically for introspection of available methods, whenever SUBS is accessed.

Since 1.00

subs example 1
package Example;

use base 'Venus::Core';

sub custom_method {
  return;
}

package main;

my $subs = Example->SUBS;
# [...list of method names...]

Hook Execution Order

Understanding the order in which hooks are executed is crucial for proper initialization.

Class Building Phase

1. BASE / FROM - Register base classes

FROM also triggers: AUDIT (from parent), IMPORT (from parent)

2. ATTR / MASK - Define attributes
3. ROLE / TEST / MIXIN - Compose roles/mixins

ROLE triggers: IMPORT (from role)

TEST triggers: AUDIT (from role), IMPORT (from role)

MIXIN triggers: IMPORT (from mixin)

Object Construction Phase

1. BUILDARGS - Pre-process constructor arguments
2. ARGS - Convert arguments to blessable data structure
3. DATA - Provide default data structure (if no args)
4. bless - Perl's built-in blessing
5. BUILD - Post-construction initialization
6. CONSTRUCT - Additional post-construction setup
7. Return object to caller

Object Destruction Phase

1. DECONSTRUCT - Pre-destruction cleanup
2. DESTROY - Final destruction

Role/Mixin Composition Phase

When a role/mixin is consumed:

1. Consumer calls ROLE / TEST / MIXIN
2. If TEST or FROM: AUDIT is called on the role/parent
3. IMPORT is called on the role/mixin
4. EXPORT is called to determine what to copy
5. Methods are copied according to composition rules

Best Practices

1. Always Return $self in BUILD

sub BUILD {
  my ($self) = @_;
  # ... initialization ...
  return $self;  # Always return $self
}

2. Call SUPER in Inherited BUILD

sub BUILD {
  my ($self, $data) = @_;
  $self->SUPER::BUILD($data);  # Call parent's BUILD
  # ... your initialization ...
  return $self;
}

3. Explicitly Declare EXPORT

sub EXPORT {
  # Be explicit about what you export
  ['method1', 'method2']
}

4. Use AUDIT for Interface Enforcement

sub AUDIT {
  my ($self, $from) = @_;
  die "Missing required method 'foo'" if !$from->can('foo');
  die "Missing required method 'bar'" if !$from->can('bar');
}

5. Use MASK for Encapsulation

# Instead of documenting "don't use this attribute"
# Use MASK to enforce it programmatically
User->MASK('internal_cache');

6. Keep BUILDARGS Simple

sub BUILDARGS {
  my ($self, @args) = @_;
  # Simple transformations only
  # Complex logic goes in BUILD
  return @args == 1 && !ref $args[0] ? {id => $args[0]} : {@args};
}

SUMMARY

Venus provides a comprehensive set of lifecycle hooks organized into several categories:

  • Construction: ARGS, BLESS, BUILDARGS, BUILD, CONSTRUCT, DATA, CLONE

  • Destruction: DECONSTRUCT, DESTROY

  • Class Building: ATTR, BASE, MASK

  • Composition: ROLE, MIXIN, TEST, FROM

  • Validation: AUDIT

  • Import/Export: EXPORT, IMPORT, USE, UNIMPORT

  • Instance Data: GET, SET, ITEM, STORE

  • Introspection: META, METACACHE, NAME, DOES, SUBS

These hooks provide fine-grained control over every aspect of object-oriented programming in Venus, from class construction to object lifecycle management.

AUTHORS

Awncorp, awncorp@cpan.org

LICENSE

Copyright (C) 2022, Awncorp, awncorp@cpan.org.

This program is free software, you can redistribute it and/or modify it under the terms of the Apache license version 2.0.