[![Actions Status](https://github.com/darviarush/perl-aion/actions/workflows/test.yml/badge.svg)](https://github.com/darviarush/perl-aion/actions) [![MetaCPAN Release](https://badge.fury.io/pl/Aion.svg)](https://metacpan.org/release/Aion) [![Coverage Status](https://img.shields.io/coveralls/darviarush/perl-aion/master.svg?style=flat)](https://coveralls.io/r/darviarush/perl-aion?branch=master)
# NAME

Aion - постмодернистская объектная система для Perl 5, такая как «Mouse», «Moose», «Moo», «Mo» и «M», но с улучшениями

# VERSION

0.3

# 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 : Isa(Object => Num) {
        my ($self) = @_;
        eval "${\ $self->a} ${\ $self->op} ${\ $self->b}"
    }

}

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

# DESCRIPTION

Aion — ООП-фреймворк для создания классов с **фичами**, имеет **аспекты**, **роли** и так далее.

Свойства, объявленные через has, называются **фичами**.

А `is`, `isa`, `default` и так далее в `has` называются **аспектами**.

Помимо стандартных аспектов, роли могут добавлять свои собственные аспекты с помощью подпрограммы **aspect**.

Сигнатура методов может проверяться с помощью атрибута `:Isa(...)`.

# SUBROUTINES IN CLASSES AND ROLES

`use Aion` импортирует типы из модуля `Aion::Types` и следующие подпрограммы:

## has ($name, %aspects)

Создаёт метод для получения/установки функции (свойства) класса.

Файл 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

Добавляет в модуль роли. Для каждой роли вызывается метод `import_with`.

Файл lib/Role/Keys/Stringify.pm:
```perl
package Role::Keys::Stringify;

use Aion -role;

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

1;
```

Файл lib/Role/Values/Stringify.pm:
```perl
package Role::Values::Stringify;

use Aion -role;

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

1;
```

Файл lib/Class/All/Stringify.pm:
```perl
package Class::All::Stringify;

use Aion;

with q/Role::Keys::Stringify/;
with q/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)

Проверяет, что `$package` — это суперкласс для данного или сам этот класс.

```perl
package Ex::X { use Aion; }
package Ex::A { use Aion; extends q/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)

Проверяет, что `$package` — это роль, которая используется в классе или другой роли.

```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 { ... })

Добавляет аспект к `has` в текущем классе и его классам-наследникам или текущей роли и применяющим её классам.

```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
```

Аспект вызывается каждый раз, когда он указан в `has`.

Создатель аспекта имеет параметры:

* `$cls` — пакет с `has`.
* `$name` — имя фичи.
* `$value` — значение аспекта.
* `$construct` — хэш с фрагментами кода для присоединения к методу объекта.
* `$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 order/]

        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,
            order => 0,
        };

        $feature # --> $_feature
    };

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

# SUBROUTINES IN CLASSES

## extends (@superclasses)

Расширяет класс другим классом/классами. Он вызывает из каждого наследуемого класса метод `import_extends`, если он в нём есть.

```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 q/World/;

    $World::extended_by_this # -> 1
}

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

## new (%param)

Конструктор.

* Устанавливает `%param` для фич.
* Проверяет, что параметры соответствуют фичам.
* Устанавливает значения по умолчанию.

```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)

Проверяет, что в классах использующих эту роль есть указанные подпрограммы или фичи.

```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)

Проверяет, что свойство установлено.

Фичи имеющие `default => sub { ... }` выполняют `sub` при первом вызове геттера, то есть: являются отложенными.

`$object->has('фича')` позволяет проверить, что `default` ещё не вызывался.

```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)

Удаляет ключи фич из объекта предварительно вызвав на них `clearer` (если есть).

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

my $c = ExClearer->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` включает в модуль следующие методы:

## new (%parameters)

Конструктор.

# ASPECTS

`use Aion` включает в модуль следующие аспекты для использования в `has`:

## is => $permissions

* `ro` — создать только геттер.
* `wo` — создать только сеттер.
* `rw` — создать геттер и сеттер.

По умолчанию — `rw`.

Дополнительные разрешения:

* `+` — фича обязательна в параметрах конструктора. `+` не используется с `-`.
* `-` — фича не может быть установлена через конструктор. '-' не используется с `+`.
* `*` — не инкрементировать счётчик ссылок на значение (применить `weaken` к значению после установки его в фичу).

```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
```

Функция с `*` не удерживает значение:

```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

Указывает тип, а точнее – валидатор, фичи.

```perl
package ExIsa { use Aion;
    has x => (is => 'ro', isa => Int);
}

eval { ExIsa->new(x => 'str') }; $@ # ~> \* Feature x must have the type Int. The it is 'str'
eval { ExIsa->new->x          }; $@ # ~> Get feature `x` must have the type Int. The it is undef
ExIsa->new(x => 10)->x              # -> 10
```

Список валидаторов см. в [Aion::Type](https://metacpan.org/pod/Aion::Type).

## default => $value

Значение по умолчанию устанавливается в конструкторе, если параметр с именем фичи отсутствует.

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

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

Если `$value` является подпрограммой, то подпрограмма считается конструктором значения фичи. Используется ленивое вычисление.

```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` вызывается после установки свойства в конструкторе (`new`) или через сеттер.
Этимология – впустить.

```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
```

## release => $sub

`$sub` вызывается перед возвратом свойства из объекта через геттер.
Этимология – выпустить.

```perl
package ExRelease { use Aion;
    has x => (release => sub {
        my ($self, $value) = @_;
        $_[1] = $value + 1;
    });
}

my $ex = ExRelease->new(x => 10);
$ex->x      # -> 11
```

## clearer => $sub

`$sub` вызывается при вызове декструктора или `$object->clear("feature")`, но только если свойство имеется (см. `$object->has("feature")`).

```perl
package ExClearer { use Aion;
	
	our $x;

    has x => (clearer => sub {
        my ($self) = @_;
        $x = $self->x
    });
}

$ExClearer::x      	# -> undef
ExClearer->new(x => 10);
$ExClearer::x      	# -> 10

my $ex = ExClearer->new(x => 12);

$ExClearer::x      # -> 10
$ex->clear('x');
$ExClearer::x      # -> 12

undef $ex;

$ExClearer::x      # -> 12
```

# ATTRIBUTES

`Aion` добавляет в пакет универсальные атрибуты.

## Isa (@signature)

Атрибут `Isa` проверяет сигнатуру функции.

**ВНИМАНИЕ**: использование атрибута «Isa» замедляет работу программы.

**СОВЕТ**: использования аспекта `isa` для объектов более чем достаточно, чтобы проверить правильность данных объекта.

```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\].
```

# AUTHOR

Yaroslav O. Kosmina <dart@cpan.org>

# LICENSE

вљ– **GPLv3**

# COPYRIGHT

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