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

NAME

Venus::Container - Container Class

ABSTRACT

Container Class for Perl 5

SYNOPSIS

  package main;

  use Venus::Container;

  my $container = Venus::Container->new;

  # my $object = $container->resolve('...');

  # "..."

DESCRIPTION

This package provides methods for building objects with dependency injection.

INHERITS

This package inherits behaviors from:

Venus::Kind::Utility

INTEGRATES

This package integrates behaviors from:

Venus::Role::Buildable

Venus::Role::Valuable

METHODS

This package provides the following methods:

metadata

  metadata(string $name) (any)

The metadata method returns the $metadata section of the configuration data if no name is provided, otherwise returning the specific metadata keyed on the name provided.

Since 3.20

metadata example 1
  # given: synopsis

  package main;

  my $metadata = $container->metadata;

  # {}
metadata example 2
  # given: synopsis

  package main;

  $container->value({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => { package => "Venus/Path", argument => { '$metadata' => "tmplog" } }
    }
  });

  my $metadata = $container->metadata;

  # {
  #   tmplog => "/tmp/log"
  # }
metadata example 3
  # given: synopsis

  package main;

  $container->value({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => { package => "Venus/Path", argument => { '$metadata' => "tmplog" } }
    }
  });

  my $metadata = $container->metadata("tmplog");

  # "/tmp/log"

reify

  reify(string $name) (any)

The reify method resolves and returns an object or value based on the service name provided.

Since 3.20

reify example 1
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => {
          '$metadata' => "tmplog"
        }
      }
    }
  });

  my $reify = $container->reify('tmp');

  # undef
reify example 2
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => {
          '$metadata' => "tmplog"
        }
      }
    }
  });

  my $reify = $container->reify('log');

  # bless({value => '/tmp/log'}, 'Venus::Path')
reify example 3
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => {
          '$metadata' => "tmplog"
        }
      }
    }
  });

  my $reify = $container->reify('log', '.');

  # bless({value => '.'}, 'Venus::Path')
reify example 4
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => {
          '$metadata' => "tmplog"
        }
      }
    }
  });

  my $reify = $container->reify('log', {value => '.'});

  # bless({value => '.'}, 'Venus::Path')

resolve

  resolve(string $name) (any)

The resolve method resolves and returns an object or value based on the configuration key or service name provided.

Since 3.20

resolve example 1
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    name => 'app',
    log => '/tmp/log/app.log',
    '$metadata' => {
      tmplog => "/tmp/log",
      varlog => "/var/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => '.'
      },
      tmp_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "tmplog"
        }
      },
      var_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "varlog"
        }
      }
    }
  });

  my $result = $container->resolve;

  # undef
resolve example 2
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    name => 'app',
    log => '/tmp/log/app.log',
    '$metadata' => {
      tmplog => "/tmp/log",
      varlog => "/var/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => '.'
      },
      tmp_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "tmplog"
        }
      },
      var_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "varlog"
        }
      }
    }
  });

  my $result = $container->resolve('log');

  # "/tmp/log/app.log"
resolve example 3
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    name => 'app',
    log => '/tmp/log/app.log',
    '$metadata' => {
      tmplog => "/tmp/log",
      varlog => "/var/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => '.'
      },
      tmp_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "tmplog"
        }
      },
      var_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "varlog"
        }
      }
    }
  });

  my $result = $container->resolve('tmp_log');

  # bless({value => '/tmp/log'}, 'Venus::Path')
resolve example 4
  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    name => 'app',
    log => '/tmp/log/app.log',
    '$metadata' => {
      tmplog => "/tmp/log",
      varlog => "/var/log"
    },
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => '.'
      },
      tmp_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "tmplog"
        }
      },
      var_log => {
        package => "Venus/Path",
        extends => 'log',
        argument => {
          '$metadata' => "varlog"
        }
      }
    }
  });

  my $result = $container->resolve('var_log');

  # bless({value => '/var/log'}, 'Venus::Path')

services

  services(string $name) (any)

The services method returns the $services section of the configuration data if no name is provided, otherwise returning the specific service keyed on the name provided.

Since 3.20

services example 1
  # given: synopsis

  package main;

  my $services = $container->services;

  # {}
services example 2
  # given: synopsis

  package main;

  $container->value({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => { package => "Venus/Path", argument => { '$metadata' => "tmplog" } }
    }
  });

  my $services = $container->services;

  # {
  #   log => {
  #     package => "Venus/Path",
  #     argument => {'$metadata' => "tmplog"}
  #   }
  # }
services example 3
  # given: synopsis

  package main;

  $container->value({
    '$metadata' => {
      tmplog => "/tmp/log"
    },
    '$services' => {
      log => { package => "Venus/Path", argument => { '$metadata' => "tmplog" } }
    }
  });

  my $services = $container->services('log');

  # {
  #   package => "Venus/Path",
  #   argument => {'$metadata' => "tmplog"}
  # }

FEATURES

This package provides the following features:

#argument

This package supports providing static and/or dynamic arguments during object construction from metadata or other services.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      'date' => {
        'package' => 'Venus/Date',
        'argument' => 570672000,
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')

example 2

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      'date' => {
        'package' => 'Venus/Date',
        'argument' => {
          year => 1988,
          month => 2,
          day => 1,
        },
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')
#argument_as

This package supports transforming the way static and/or dynamic arguments are passed to the operation during object construction. Acceptable options are array or arrayref (which provides an arrayref), hash or hashref (which provides a hashref), or list (which provides a flattened list of arguments).

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      'date' => {
        'package' => 'Venus/Date',
        'argument' => {
          year => 1988,
          month => 2,
          day => 1,
        },
        argument_as => 'list',
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')

example 2

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      'date' => {
        'package' => 'Venus/Date',
        'argument' => {
          year => 1988,
          month => 2,
          day => 1,
        },
        argument_as => 'hash',
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')
#builder

This package supports specifying multiple build steps as function, method, and routine calls and chaining them together. Each build step supports any directive that can be used outside of a build step. Each build step can be configured, with the return directive, to use a particular value to chain the next subroutine call. Acceptable return values are class (package name string), result (scalar return value from the current build step), and self (instantiated package). Additionally, you can use the inject directive (with any value accepted by argument_as) to override the default arguments using the arguments provided to the "reify" or "resolve" method.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      datetime => {
        package => "Venus/Date",
        builder => [
          {
            method => 'new',
            argument => 570672000,
            return => 'self',
          },
          {
            method => 'string',
            return => 'result',
          }
        ],
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('datetime');

  # "1988-02-01T00:00:00Z"

example 2

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      datetime => {
        package => "Venus/Date",
        builder => [
          {
            method => 'new',
            argument => 570672000,
            return => 'self',
            inject => 'list',
          },
          {
            method => 'string',
            return => 'result',
          }
        ],
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('datetime', 604945074);

  # "1989-03-03T16:17:54Z"
#config

This package supports configuring services and metadata in the service of building objects and values.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    'name' => 'app',
    'secret' => '...',
    '$metadata' => {
      home => {
        '$envvar' => 'home',
      }
    },
    '$services' => {
      date => {
        package => "Venus/Date",
      },
      path => {
        package => "Venus/Path",
        argument => {
          '$metadata' => 'home',
        },
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $path = $container->resolve('path');

  # bless(..., 'Venus::Path')

  # my $name = $container->resolve('name');

  # "app"
#constructor

This package supports specifying constructors other than the traditional new routine. A constructor is always called with the package name as the invocant.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      path => {
        package => "Venus/Path",
        constructor => "new",
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('path');

  # bless(..., 'Venus::Path')
#extends

This package supports extending services in the definition of other services, recursively compiling service configurations and eventually executing the requested compiled service.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      log => {
        package => "Venus/Log",
        argument => {
          level => "trace",
        },
      },
      development_log => {
        package => "Venus/Log",
        extends => "log",
        builder => [
          {
            method => "new",
            return => "self",
            inject => "hash",
          }
        ],
      },
      production_log => {
        package => "Venus/Log",
        extends => "log",
        argument => {
          level => "error",
        },
        builder => [
          {
            method => "new",
            return => "self",
            inject => "hash",
          }
        ],
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('development_log');

  # bless(..., 'Venus::Log')

  # my $level = $result->level;

  # "trace"

  # $result = $container->resolve('production_log');

  # bless(..., 'Venus::Log')

  # $level = $result->level;

  # "error"
#function

This package supports specifying construction as a function call, which when called does not provide an invocant.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      foo_hex => {
        package => "Digest/MD5",
        function => "md5_hex",
        argument => "foo",
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('foo_hex');

  # "acbd18db4cc2f85cedef654fccc4a4d8"
#lifecycle

This package supports different lifecycle options which determine when services are built and whether they're persisted. Acceptable lifecycle values are singleton (which caches the result once encountered) and eager (which caches the service upon the first execution of any service).

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      match => {
        package => "Venus/Match",
        argument => {
          'a'..'h'
        },
        builder => [
          {
            method => "new",
            return => "result",
          },
          {
            method => "data",
            return => "result",
            inject => "hash",
          }
        ],
        lifecycle => 'eager',
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('thing');

  # undef

  # my $result = $container->resolve('match');

  # bless(..., 'Venus::Match')

example 2

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      match => {
        package => "Venus/Match",
        argument => {
          'a'..'h'
        },
        builder => [
          {
            method => "new",
            return => "result",
          },
          {
            method => "data",
            return => "result",
            inject => "hash",
          }
        ],
        lifecycle => 'singleton',
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('match');

  # bless(..., 'Venus::Match')
#metadata

This package supports specifying data and structures which can be used in the construction of multiple services.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      'homedir' => '/home',
      'tempdir' => '/tmp',
    },
    '$services' => {
      home => {
        package => "Venus/Path",
        argument => {
          '$metadata' => 'homedir',
        },
      },
      temp => {
        package => "Venus/Path",
        argument => {
          '$metadata' => 'tempdir',
        },
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('home');

  # bless(..., 'Venus::Path')

  # my $result = $container->resolve('temp');

  # bless(..., 'Venus::Path')
#method

This package supports specifying construction as a method call, which when called provides the package or object instance as the invocant.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      date => {
        package => "Venus/Date",
        argument => 570672000,
        method => "new",
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')
#routine

This package supports specifying construction as a function call, which when called provides the package as the invocant.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      date => {
        package => "Venus/Date",
        argument => 570672000,
        routine => "new",
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('date');

  # bless(..., 'Venus::Date')
#service

This package supports defining services to be constructed on-demand or automatically on instantiation.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      path => {
        package => "Venus/Path",
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('path');

  # bless(..., 'Venus::Path')
$callback

This package supports resolving services as callbacks to be passed around and/or resolved by other services. The $callback directive is used to specify the name of a service to be resolved and passed as an argument.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      log => {
        package => "Venus/Path",
        argument => '.',
      },
      lazy_log => {
        package => "Venus/Code",
        argument => {
          '$callback' => 'log',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('lazy_log');

  # bless(..., 'Venus::Code')

  # my $return = $result->call;

  # bless(..., 'Venus::Path')
$envvar

This package supports inlining environment variables as arguments to services. The $envvar directive is used to specify the name of an environment variable, and can also be used in metadata for reusability.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      home => {
        package => "Venus/Path",
        argument => {
          '$envvar' => 'home',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('home');

  # bless(..., 'Venus::Path')

example 2

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      home => {
        '$envvar' => 'home',
      }
    },
    '$services' => {
      home => {
        package => "Venus/Path",
        argument => {
          '$metadata' => 'home',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('home');

  # bless(..., 'Venus::Path')
$function

This package supports inlining the result of a service resolution and function call as arguments to services. The # delimited $function directive is used to specify the name of an existing service on the right-hand side, and an arbitrary function to be call on the result on the left-hand side.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      filespec => {
        package => 'File/Spec/Functions',
      },
      tempdir => {
        package => "Venus/Path",
        argument => {
          '$function' => 'filespec#tmpdir',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('tempdir');

  # bless(..., 'Venus::Path')
$metadata

This package supports inlining configuration data as arguments to services. The $metadata directive is used to specify the name of a stashed configuration value or data structure.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$metadata' => {
      home => '/home/ubuntu',
    },
    '$services' => {
      home => {
        package => "Venus/Path",
        argument => {
          '$metadata' => 'home',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('home');

  # bless(..., 'Venus::Path')
$method

This package supports inlining the result of a service resolution and method call as arguments to services. The # delimited $method directive is used to specify the name of an existing service on the right-hand side, and an arbitrary method to be call on the result on the left-hand side.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      filespec => {
        package => 'File/Spec',
      },
      tempdir => {
        package => "Venus/Path",
        argument => {
          '$method' => 'filespec#tmpdir',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('tempdir');

  # bless(..., 'Venus::Path')
$routine

This package supports inlining the result of a service resolution and routine call as arguments to services. The # delimited $routine directive is used to specify the name of an existing service on the right-hand side, and an arbitrary routine to be call on the result on the left-hand side.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      filespec => {
        package => 'File/Spec',
      },
      tempdir => {
        package => "Venus/Path",
        argument => {
          '$routine' => 'filespec#tmpdir',
        }
      }
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('tempdir');

  # bless(..., 'Venus::Path')
$service

This package supports inlining resolved services as arguments to other services. The $service directive is used to specify the name of a service to be resolved and passed as an argument.

example 1

  package main;

  use Venus::Container;

  my $container = Venus::Container->new({
    '$services' => {
      'path' => {
        'package' => 'Venus/Path',
      },
    }
  });

  # bless(..., 'Venus::Container')

  # my $result = $container->resolve('path');

  # bless(..., 'Venus::Path')

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.