The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Class::StateMachine - define classes for state machines

SYNOPSIS

  package MySM;
  no warnings 'redefine';

  use parent 'Class::StateMachine';

  sub foo : OnState(one) { print "on state one\n" }
  sub foo : OnState(two) { print "on state two\n" }

  sub bar : OnState(__any__) { print "default action\n" }
  sub bar : OnState(three, five, seven) { print "on several states\n" }
  sub bar : OnState(one) { print "on state one\n" }

  sub new {
      my $class = shift;
      my $self = {};
      Class::StateMachine::bless $self, $class;
      $self;
  }

  sub leave_state : OnState(one) { print "leaving state $_[1] from $_[2]" }
  sub enter_state : OnState(two) { print "entering state $_[1] from $_[2]" }

  package main;

  my $sm = MySM->new;

  $sm->state('one');
  $sm->foo; # prints "on state one"

  $sm->state('two');
  $sm->foo; # prints "on state two"

DESCRIPTION

This module allows to build classes whose instance behavior (methods) depends not only on inheritance but also on some internal state.

For example, suppose we want to develop a Dog class implementing the following behavior:

  my $dog = Dog->new;
  $dog->state("happy");
  $dog->on_touched_head; # the dog moves his tail
  $dog->state("angry");
  $dog->on_touched_head; # the dog bites you

With the help of Class::StateMachine, that state dependant behaviour can be easily programmed using the OnState subroutine attribute as follows:

  package Dog;

  use parent 'Class::StateMachine';

  sub on_touched_head : OnState(happy) { shift->move_tail }
  sub on_touched_head : OnState(angry) { shift->bite }

Object construction

Class::StateMachine does not imposse any particular type of data structure for the instance objects. Any Perl reference type (HASH, ARRAY, SCALAR, GLOB, etc.) can be used.

The unique condition that must be fulfilled is to use the bless subroutine provided by Class::StateMachine to create the object instead of the Perl builtin of the same name.

For instance:

  package Dog;

  sub new {
    my $class = shift;
    my $dog = { name => 'Oscar' };
    Class::StateMachine::bless($dog, $class, 'happy');
  }

A default state new gets assigned to the object when the third parameter to Class::StateMachine::bless is ommited.

Instance state

The instance state is maintained internally by Class::StateMachine and can be accessed though the "state" method:

  my $state = Dog->state;

State changes must be performed explicitly calling the state method with the new state as an argument:

  Dog->state('tired');

Class::StateMachine will not change the state of your objects in any other way.

If you want to limit the possible set of states that the objects of some class can take, define a "state_check" method for that class:

  package Dog;
  ...
  sub state_check {
    my ($self, $state) = @_;
    $state =~ /^(?:happy|angry|tired)$/
  }

That will make die any call to state requesting a change to an invalid state.

New objects get assigned the state 'new' when they are created.

Method definition

Inside a class derived from Class::StateMachine, methods (submethods) can be assigned to some particular states using the OnState attribute with a list of the states where it applies.

  sub bark :OnState(happy, tired) { play "happy_bark.wav" }
  sub bark :OnState(injured) { play "pitiful_bark.wav" }

The text inside the OnState parents is evaluated in list context on the current package and with strictures turned off in order to allow usage of barewords.

For instance:

  sub foo : OnState(map "foo$_", a..z) { ... }

Though note that lexicals variables will not be reachable from the text inside the parents. Note also that Perl does not allow attribute declarations to spawn over several lines.

A special state __any__ can be used to indicate a default submethod that is called in case a specific submethod has not been declared for the current object state.

For instance:

  sub happy :OnState(happy  ) { say "I am happy" }
  sub happy :OnState(__any__) { say "I am not happy" }

Method resolution order

What happens when you declare submethods spreaded among a class inheritance hierarchy?

Class::StateMachine will search for the method as follows:

  1. Search in the inheritance tree for a specific submethod declared for the current object state.

  2. Search in the inheritance tree for a submethod declared for the pseudo state __any__.

  3. Search for a regular method defined without the OnState attribute.

  4. Use the AUTOLOAD mechanism.

mro can be used to set the search order inside the inheritance trees (for instance, the default deep-first or C3).

State transitions

When an object changes between two different states, the methods "leave_state" and "enter_state" are called if they are defined.

Note that they can be defined using the OnState attribute:

  package Dog;
  ...
  sub enter_state :OnState(angry) { shift->bark }
  sub enter_state :OnState(tired) { shift->lie_down }

API

These are the methods available from Class::StateMachine:

Class::StateMachine::bless($obj, $class, $state)
$obj->bless($class)

Sets or changes the object class in a manner compatible with Class::StateMachine.

This function must be used as the way to create new objetcs of classes derived from Class::StateMachine.

If the third argument $state is not given, new is used as the default.

$obj->state

Gets the object state.

$obj->state($new_state)

Changes the object state.

This method calls back the methods check_state, leave_state and enter_state if they are defined in the class or any of its subclasses for the corresponding state.

If $new_state equals the current object state, this method does nothing (including not invoking the callback methods).

$self->check_state($new_state)

This callback can be used to limit the set of states acceptable for the object. If the method returns a false value the state call will die.

If this method is not defined any state will be valid.

$self->leave_state($old_state, $new_state)

This method is called just before changing the state.

It the state is changed from its inside to something different than $old_state, the requested state change is canceled.

$self->enter_state($new_state, $old_state)

This method is called just after changing the state to the new value.

Class::StateMachine::ref($obj)
$obj->ref

Returns the class of the object without the parts related to Class::StateMachine magic.

Class::StateMachine::install_method($class, $sub, @states)

Sets a submethod for a given class/state combination.

Debugging

Class::StateMachine supports a debugging mode that prints traces of state changes and callback invocation. It can be enabled as follows:

  $Class::StateMachine::debug = 1;

Internals

This module internally plays with the inheritance chain creating new classes and reblessing objects on the fly and (ab)using the mro mechanism in funny ways.

The objects state is maintained inside a Hash::Util::FieldHash.

BUGS

Backward compatibility has been broken in version 0.13 in order to actualize the class to use modern Perl features as mro and provide sanner semantics.

Passing several states in the same submethod definition can break the next::method machinery from the mro package.

For instace:

  sub foo :OnState(one, two, three) { shift->next::method(@_) }

may not work as expected.

SEE ALSO

attributes, perlsub, perlmod, Attribute::Handlers, mro, MRO::Define.

The dog.pl example included within the package.

COPYRIGHT AND LICENSE

Copyright (C) 2003-2006, 2011 by Salvador Fandiño (sfandino@yahoo.com).

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.