[![Actions Status](https://github.com/darviarush/perl-aion/actions/workflows/test.yml/badge.svg)](https://github.com/darviarush/perl-aion/actions)
# NAME

Aion - a postmodern object system for Perl 5, as `Moose` and `Moo`, but with improvements

# VERSION

0.1

# SYNOPSIS

```perl
package Calc {

    use Aion;

    has a => (is => 'ro+', isa => Num);
    has b => (is => 'ro+', isa => Num);
    has op => (is => 'ro', isa => Enum[qw/+ - * \/ **/], default => '+');

    sub result {
        my ($self) = @_;
        eval "${\ $self->a} ${\ $self->op} ${\ $self->b}"
    }

}

Calc->new(a => 1.1, b => 2)->result   # => 3.1
```

# DESCRIPTION

Aion — OOP framework for create classes with **features**, has **aspects**, **roles** and so on.

Properties declared via `has` are called **features**.

And `is`, `isa`, `default` and so on in `has` are called **aspects**.

In addition to standard aspects, roles can add their own aspects using subroutine `aspect`.

# SUBROUTINES IN CLASSES AND ROLES

`use Aion` include in module types from `Aion::Types` and next subroutines:

## has ($name, %aspects)

Make method for get/set feature (property) of the class.

File lib/Animal.pm:
```perl
package Animal;
use Aion;

has type => (is => 'ro+', isa => Str);
has name => (is => 'rw-', isa => Str, default => 'murka');

1;
```

```perl
use lib "lib";
use Animal;

my $cat = Animal->new(type => 'cat');

$cat->type   # => cat
$cat->name   # => murka

$cat->name("murzik");
$cat->name   # => murzik
```

## with

Add to module roles. It call on each the role method `import_with`.

File lib/Role/Keys/Stringify.pm:
```perl
package Role::Keys::Stringify;

use Aion -role;

sub keysify {
    my ($self) = @_;
    join ", ", sort keys %$self;
}

1;
```

File lib/Role/Values/Stringify.pm:
```perl
package Role::Values::Stringify;

use Aion -role;

sub valsify {
    my ($self) = @_;
    join ", ", map $self->{$_}, sort keys %$self;
}

1;
```

File lib/Class/All/Stringify.pm:
```perl
package Class::All::Stringify;

use Aion;

with qw/Role::Keys::Stringify Role::Values::Stringify/;

has [qw/key1 key2/] => (is => 'rw', isa => Str);

1;
```

```perl
use lib "lib";
use Class::All::Stringify;

my $s = Class::All::Stringify->new(key1=>"a", key2=>"b");

$s->keysify     # => key1, key2
$s->valsify     # => a, b
```

## isa ($package)

Check `$package` is the class what extended this class.

```perl
package Ex::X { use Aion; }
package Ex::A { use Aion; extends qw/Ex::X/; }
package Ex::B { use Aion; }
package Ex::C { use Aion; extends qw/Ex::A Ex::B/ }

Ex::C->isa("Ex::A") # -> 1
Ex::C->isa("Ex::B") # -> 1
Ex::C->isa("Ex::X") # -> 1
Ex::C->isa("Ex::X1") # -> ""
Ex::A->isa("Ex::X") # -> 1
Ex::A->isa("Ex::A") # -> 1
Ex::X->isa("Ex::X") # -> 1
```

## does ($package)

Check `$package` is the role what extended this class.

```perl
package Role::X { use Aion -role; }
package Role::A { use Aion; with qw/Role::X/; }
package Role::B { use Aion; }
package Ex::Z { use Aion; with qw/Role::A Role::B/ }

Ex::Z->does("Role::A") # -> 1
Ex::Z->does("Role::B") # -> 1
Ex::Z->does("Role::X") # -> 1
Role::A->does("Role::X") # -> 1
Role::A->does("Role::X1") # -> ""
Ex::Z->does("Ex::Z") # -> ""
```

## aspect ($aspect => sub { ... })

It add aspect to `has` in this class or role, and to the classes, who use this role, if it role.

```perl
package Example::Earth {
    use Aion;

    aspect lvalue => sub {
        my ($cls, $name, $value, $construct, $feature) = @_;

        $construct->{attr} .= ":lvalue";
    };

    has moon => (is => "rw", lvalue => 1);
}

my $earth = Example::Earth->new;

$earth->moon = "Mars";

$earth->moon # => Mars
```

Aspect is called every time it is specified in `has`.

Aspect handler has parameters:

* `$cls` — the package with the `has`.
* `$name` — the feature name.
* `$value` — the aspect value.
* `$construct` — the hash with code fragments for join to the feature method.
* `$feature` — the hash present feature.

```perl
package Example::Mars {
    use Aion;

    aspect lvalue => sub {
        my ($cls, $name, $value, $construct, $feature) = @_;

        $construct->{attr} .= ":lvalue";

        $cls # => Example::Mars
        $name # => moon
        $value # -> 1
        [sort keys %$construct] # --> [qw/attr eval get name pkg ret set sub/]
        [sort keys %$feature] # --> [qw/construct has name opt/]

        my $_construct = {
            pkg => $cls,
            name => $name,
			attr => ':lvalue',
			eval => 'package %(pkg)s {
	%(sub)s
}',
            sub => 'sub %(name)s%(attr)s {
		if(@_>1) {
			my ($self, $val) = @_;
			%(set)s%(ret)s
		} else {
			my ($self) = @_;
			%(get)s
		}
	}',
            get => '$self->{%(name)s}',
            set => '$self->{%(name)s} = $val',
            ret => '; $self',
        };

        $construct # --> $_construct

        my $_feature = {
            has => [is => "rw", lvalue => 1],
            opt => {
                is => "rw",
                lvalue => 1,
            },
            name => $name,
            construct => $_construct,
        };

        $feature # --> $_feature
    };

    has moon => (is => "rw", lvalue => 1);
}
```

# SUBROUTINES IN CLASSES

## extends (@superclasses)

Extends package other package. It call on each the package method `import_extends` if it exists.

```perl
package World { use Aion;

    our $extended_by_this = 0;

    sub import_extends {
        my ($class, $extends) = @_;
        $extended_by_this ++;

        $class      # => World
        $extends    # => Hello
    }
}

package Hello { use Aion;
    extends qw/World/;

    $World::extended_by_this # -> 1
}

Hello->isa("World")     # -> 1
```

## new (%param)

Constructor. 

* Set `%param` to features.
* Check if param not mapped to feature.
* Set default values.

```perl
package NewExample { use Aion;
    has x => (is => 'ro', isa => Num);
    has y => (is => 'ro+', isa => Num);
    has z => (is => 'ro-', isa => Num);
}

eval { NewExample->new(f => 5) }; $@            # ~> f is not feature!
eval { NewExample->new(n => 5, r => 6) }; $@    # ~> n, r is not features!
eval { NewExample->new }; $@                    # ~> Feature y is required!
eval { NewExample->new(z => 10) }; $@           # ~> Feature z cannot set in new!

my $ex = NewExample->new(y => 8);

eval { $ex->x }; $@  # ~> Get feature `x` must have the type Num. The it is undef

$ex = NewExample->new(x => 10.1, y => 8);

$ex->x # -> 10.1
```

# SUBROUTINES IN ROLES

## requires (@subroutine_names)

Check who in classes who use the role present the subroutines.

```perl
package Role::Alpha { use Aion -role;

    sub in {
        my ($self, $s) = @_;
        $s =~ /[${\ $self->abc }]/
    }

    requires qw/abc/;
}

eval { package Omega1 { use Aion; with Role::Alpha; } }; $@ # ~> abc requires!

package Omega { use Aion;
    with Role::Alpha;

    sub abc { "abc" }
}

Omega->new->in("a")  # -> 1
```

# METHODS

## has ($feature)

It check what property is set.

```perl
package ExHas { use Aion;
    has x => (is => 'rw');
}

my $ex = ExHas->new;

$ex->has("x")   # -> ""

$ex->x(10);

$ex->has("x")   # -> 1
```

## clear (@features)

Cleared the features.

```perl
package ExClear { use Aion;
    has x => (is => 'rw');
    has y => (is => 'rw');
}

my $c = ExClear->new(x => 10, y => 12);

$c->has("x")   # -> 1
$c->has("y")   # -> 1

$c->clear(qw/x y/);

$c->has("x")   # -> ""
$c->has("y")   # -> ""
```


# METHODS IN CLASSES

`use Aion` include in module next methods:

## new (%parameters)

The constructor.

# ASPECTS

`use Aion` include in module next aspects for use in `has`:

## is => $permissions

* `ro` — make getter only.
* `wo` — make setter only.
* `rw` — make getter and setter.

Default is `rw`.

Additional permissions:

* `+` — the feature is required. It is not used with `-`.
* `-` — the feature cannot be set in the constructor. It is not used with `+`.
* `*` — the value is reference and it maked weaken can be set.

```perl
package ExIs { use Aion;
    has rw => (is => 'rw');
    has ro => (is => 'ro+');
    has wo => (is => 'wo-');
}

eval { ExIs->new }; $@ # ~> \* Feature ro is required!
eval { ExIs->new(ro => 10, wo => -10) }; $@ # ~> \* Feature wo cannot set in new!
ExIs->new(ro => 10);
ExIs->new(ro => 10, rw => 20);

ExIs->new(ro => 10)->ro  # -> 10

ExIs->new(ro => 10)->wo(30)->has("wo")  # -> 1
eval { ExIs->new(ro => 10)->wo }; $@ # ~> has: wo is wo- \(not get\)
ExIs->new(ro => 10)->rw(30)->rw  # -> 30
```

Feature with `*` don't hold value:

```perl
package Node { use Aion;
    has parent => (is => "rw*", isa => Maybe[Object["Node"]]);
}

my $root = Node->new;
my $node = Node->new(parent => $root);

$node->parent->parent   # -> undef
undef $root;
$node->parent   # -> undef

# And by setter:
$node->parent($root = Node->new);

$node->parent->parent   # -> undef
undef $root;
$node->parent   # -> undef
```

## isa => $type

Set feature type. It validate feature value 

## default => $value

Default value set in constructor, if feature falue not present.

```perl
package ExDefault { use Aion;
    has x => (is => 'ro', default => 10);
}

ExDefault->new->x  # -> 10
ExDefault->new(x => 20)->x  # -> 20
```

If `$value` is subroutine, then the subroutine is considered a constructor for feature value. This subroutine lazy called where the value get.

```perl
my $count = 10;

package ExLazy { use Aion;
    has x => (default => sub {
        my ($self) = @_;
        ++$count
    });
}

my $ex = ExLazy->new;
$count   # -> 10
$ex->x   # -> 11
$count   # -> 11
$ex->x   # -> 11
$count   # -> 11
```

## trigger => $sub

`$sub` called after the value of the feature is set (in `new` or in setter).

```perl
package ExTrigger { use Aion;
    has x => (trigger => sub {
        my ($self, $old_value) = @_;
        $self->y($old_value + $self->x);
    });

    has y => ();
}

my $ex = ExTrigger->new(x => 10);
$ex->y      # -> 10
$ex->x(20);
$ex->y      # -> 30
```

# ATTRIBUTES

Aion add universal attributes.

## Isa (@signature)

Attribute `Isa` check the signature the function where it called.

**WARNING**: use atribute `Isa` slows down the program.

**TIP**: use aspect `isa` on features is more than enough to check the correctness of the object data.

```perl
package Anim { use Aion;

    sub is_cat : Isa(Object => Str => Bool) {
        my ($self, $anim) = @_;
        $anim =~ /(cat)/
    }
}

my $anim = Anim->new;

$anim->is_cat('cat')    # -> 1
$anim->is_cat('dog')    # -> ""


eval { Anim->is_cat("cat") }; $@ # ~> Arguments of method `is_cat` must have the type Tuple\[Object, Str\].
eval { my @items = $anim->is_cat("cat") }; $@ # ~> Returns of method `is_cat` must have the type Tuple\[Bool\].
```

If use name of type in `@signature`, then call subroutine with this name from current package.

# AUTHOR

Yaroslav O. Kosmina [dart@cpan.org](dart@cpan.org)

# LICENSE

âš– **GPLv3**

# COPYRIGHT

The Aion module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All rights reserved.