Moose::Cookbook::Extending::ExtensionOverview - Moose extension overview |
Moose::Cookbook::Extending::ExtensionOverview - Moose extension overview
version 2.1605
Moose provides several ways in which extensions can hook into Moose and change its behavior. Moose also has a lot of behavior that can be changed. This recipe will provide an overview of each extension method and give you some recommendations on what tools to use.
If you haven't yet read the recipes on metaclasses, go read those first. You can't write Moose extensions without understanding the metaclasses, and those recipes also demonstrate some basic extension mechanisms, such as metaclass subclasses and traits.
One of the goals of this overview is to help you build extensions that cooperate well with other extensions. This is especially important if you plan to release your extension to CPAN.
Moose comes with several modules that exist to help your write cooperative extensions. These are the Moose::Exporter manpage and the Moose::Util::MetaRole manpage. By using these two modules, you will ensure that your extension works with both the Moose core features and any other CPAN extension using those modules.
The types of things you might want to do in Moose extensions fall into a few broad categories.
One way of extending Moose is by extending one or more Moose
metaclasses. For example, in the Moose::Cookbook::Meta::Table_MetaclassTrait manpage we saw
a metaclass role that added a table
attribute to the
metaclass. If you were writing an ORM, this would be a logical
extension.
Many of the Moose extensions on CPAN work by providing an attribute metaclass role. For example, the the MooseX::Aliases manpage module provides an attribute metaclass trait that lets you specify aliases to install for methods and attribute accessors.
A metaclass extension can be packaged as a role/trait or a subclass. If you can, we recommend using traits instead of subclasses, since it's much easier to combine disparate traits than it is to combine a bunch of subclasses.
When your extensions are implemented as roles, you can apply them with the the Moose::Util::MetaRole manpage module.
As part of a metaclass extension, you may also want to provide some sugar functions, just like Moose.pm does. Moose provides a helper module called the Moose::Exporter manpage that makes this much simpler. We will be use the Moose::Exporter manpage in several of the extension recipes.
Another common Moose extension technique is to change the default object class's behavior. As with metaclass extensions, this can be done with a role/trait or with a subclass. For example, the MooseX::StrictConstructor manpage extension applies a trait that makes the constructor reject arguments which don't match its attributes.
Object class extensions often include metaclass extensions as well. In particular, if you want your object extension to work when a class is made immutable, you may need to modify the behavior of some or all of the the Moose::Meta::Instance manpage, the Moose::Meta::Method::Constructor manpage, and the Moose::Meta::Method::Destructor manpage objects.
The the Moose::Util::MetaRole manpage module lets you apply roles to the base object class, as well as the meta classes just mentioned.
Some extensions come in the form of a role for you to consume. The
the MooseX::Object::Pluggable manpage extension is a great example of this. In
fact, despite the MooseX
name, it does not actually change anything
about Moose's behavior. Instead, it is just a role that an object
which wants to be pluggable can consume.
If you are implementing this sort of extension, you don't need to do
anything special. You simply create a role and document that it should
be used via the normal with
sugar:
package MyApp::User;
use Moose;
with 'My::Role';
Don't use ``MooseX'' in the name for such packages.
Another common Moose extension is a new type for the Moose type system. In this case, you simply create a type in your module. When people load your module, the type is created, and they can refer to it by name after that. The the MooseX::Types::URI manpage and the MooseX::Types::DateTime manpage distributions are two good examples of how this works. These both build on top of the the MooseX::Types manpage extension.
It is important to understand that roles and traits are the same thing. A
trait is simply a role applied to a instance. The only thing that may
distinguish the two is that a trait can be packaged in a way that lets Moose
resolve a short name to a class name. In other words, with a trait, the caller
can refer to it by a short name like ``Big'', and Moose will resolve it to a
class like MooseX::Embiggen::Meta::Attribute::Role::Big
.
See the Moose::Cookbook::Meta::Labeled_AttributeTrait manpage and the Moose::Cookbook::Meta::Table_MetaclassTrait manpage for examples of traits in action. In particular, both of these recipes demonstrate the trait resolution mechanism.
Implementing an extension as a (set of) metaclass or base object
role(s)
will make your extension more cooperative. It is hard for an
end-user to effectively combine together multiple metaclass
subclasses, but it is very easy to combine roles.
There are a number of ways in which an extension can be applied. In some cases you can provide multiple ways of consuming your extension.
If your extension is available as a trait, you can ask end users to simply specify it in a list of traits. Currently, this only works for (class) metaclass and attribute metaclass traits:
use Moose -traits => [ 'Big', 'Blue' ];
has 'animal' => ( traits => [ 'Big', 'Blue' ], ... );
If your extension applies to any other metaclass, or the object base class, you cannot use the trait mechanism.
The benefit of the trait mechanism is that is very easy to see where a trait is applied in the code, and consumers have fine-grained control over what the trait applies to. This is especially true for attribute traits, where you can apply the trait to just one attribute in a class.
Implementing your extensions as metaclass roles makes your extensions easy to apply, and cooperative with other role-based extensions for metaclasses.
Just as with a subclass, you will probably want to package your extensions for consumption with a single module that uses the Moose::Exporter manpage. However, in this case, you will use the Moose::Util::MetaRole manpage to apply all of your roles. The advantage of using this module is that it preserves any subclassing or roles already applied to the user's metaclasses. This means that your extension is cooperative by default, and consumers of your extension can easily use it with other role-based extensions. Most uses of the Moose::Util::MetaRole manpage can be handled by the Moose::Exporter manpage directly; see the the Moose::Exporter manpage docs.
package MooseX::Embiggen;
use Moose::Exporter;
use MooseX::Embiggen::Role::Meta::Class; use MooseX::Embiggen::Role::Meta::Attribute; use MooseX::Embiggen::Role::Meta::Method::Constructor; use MooseX::Embiggen::Role::Object;
Moose::Exporter->setup_import_methods( class_metaroles => { class => ['MooseX::Embiggen::Role::Meta::Class'], attribute => ['MooseX::Embiggen::Role::Meta::Attribute'], constructor => ['MooseX::Embiggen::Role::Meta::Method::Constructor'], }, base_class_roles => ['MooseX::Embiggen::Role::Object'], );
As you can see from this example, you can use the Moose::Util::MetaRole manpage to apply roles to any metaclass, as well as the base object class. If some other extension has already applied its own roles, they will be preserved when your extension applies its roles, and vice versa.
With the Moose::Exporter manpage, you can also export your own sugar functions:
package MooseX::Embiggen;
use Moose::Exporter;
Moose::Exporter->setup_import_methods( with_meta => ['embiggen'], class_metaroles => { class => ['MooseX::Embiggen::Role::Meta::Class'], }, );
sub embiggen { my $meta = shift; $meta->embiggen(@_); }
And then the consumer of your extension can use your embiggen
sub:
package Consumer;
use Moose; use MooseX::Embiggen;
extends 'Thing';
embiggen ...;
This can be combined with metaclass and base class roles quite easily.
Providing your extension simply as a set of traits that gets applied to the
appropriate metaobjects is easy, but sometimes not sufficient. For instance,
sometimes you need to supply not just a base object role, but an actual base
object class (due to needing to interact with existing systems that only
provide a base class). To write extensions like this, you will need to provide
a custom init_meta
method in your exporter. For instance:
package MooseX::Embiggen;
use Moose::Exporter;
my ($import, $unimport, $init_meta) = Moose::Exporter->build_import_methods( install => ['import', 'unimport'], with_meta => ['embiggen'], class_metaroles => { class => ['MooseX::Embiggen::Role::Meta::Class'], }, );
sub embiggen { my $meta = shift; $meta->embiggen(@_); }
sub init_meta { my $package = shift; my %options = @_; if (my $meta = Class::MOP::class_of($options{for_class})) { if ($meta->isa('Class::MOP::Class')) { my @supers = $meta->superclasses; $meta->superclasses('MooseX::Embiggen::Base::Class') if @supers == 1 && $supers[0] eq 'Moose::Object'; } } $package->$init_meta(%options); }
In the previous examples, init_meta
was generated for you, but here you must
override it in order to add additional functionality. Some differences to note:
build_import_methods
instead of setup_import_methods
build_import_methods
simply returns the import
, unimport
, and
init_meta
methods, rather than installing them under the appropriate names.
This way, you can write your own methods which wrap the functionality provided
by the Moose::Exporter manpage. The build_import_methods
sub also takes an
additional install
parameter, which tells it to just go ahead and install
these methods (since we don't need to modify them).
sub init_meta
init_meta
wrapper. The important things to remember
are that it is called as a method, and that %options
needs to be passed
through to the existing implementation. We call the base implementation by
using the $init_meta
subroutine reference that was returned by
build_import_methods
earlier.
Note that two extensions that do this same thing will not work together properly (the second extension to be loaded won't see the Moose::Object manpage as the base object, since it has already been overridden). This is why using a base object role is recommended for the general case.
This init_meta
also works defensively, by only applying its functionality if
a metaclass already exists. This makes sure it doesn't break with legacy
extensions which override the metaclass directly (and so must be the first
extension to initialize the metaclass). This is likely not necessary, since
almost no extensions work this way anymore, but just provides an additional
level of protection. The common case of use Moose; use MooseX::Embiggen;
is not affected regardless.
This is just one example of what can be done with a custom init_meta
method.
It can also be used for preventing an extension from being applied to a role,
doing other kinds of validation on the class being applied to, or pretty much
anything that would otherwise be done in an import
method.
Before the existence of the Moose::Exporter manpage and the Moose::Util::MetaRole manpage, there were a number of other ways to extend Moose. In general, these methods were less cooperative, and only worked well with a single extension.
These methods include metaclass.pm, the Moose::Policy manpage (which uses metaclass.pm under the hood), and various hacks to do what the Moose::Exporter manpage does. Please do not use these for your own extensions.
Note that if you write a cooperative extension, it should cooperate with older extensions, though older extensions generally do not cooperate with each other.
If you can write your extension as one or more metaclass and base object roles, please consider doing so. Make sure to read the docs for the Moose::Exporter manpage and the Moose::Util::MetaRole manpage as well.
This software is copyright (c) 2006 by Infinity Interactive, Inc.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
Moose::Cookbook::Extending::ExtensionOverview - Moose extension overview |