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.
Moose comes with several modules that exist to help your write cooperative extensions. These are Moose::Exporter and Moose::Util::MetaRole. 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.
Many of the Moose extensions on CPAN work by providing an attribute metaclass role. For example, the MooseX::Aliases 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 Moose::Util::MetaRole module.
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 Moose::Meta::Instance, Moose::Meta::Method::Constructor, and Moose::Meta::Method::Destructor objects.
The Moose::Util::MetaRole module lets you apply roles to the base object class, as well as the meta classes just mentioned.
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.
See Moose::Cookbook::Meta::Labeled_AttributeTrait and Moose::Cookbook::Meta::Table_MetaclassTrait 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.
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.
Just as with a subclass, you will probably want to package your extensions for consumption with a single module that uses Moose::Exporter. However, in this case, you will use Moose::Util::MetaRole 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 Moose::Util::MetaRole can be handled by Moose::Exporter directly; see the Moose::Exporter 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 Moose::Util::MetaRole 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.
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.
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:
Note that two extensions that do this same thing will not work together properly (the second extension to be loaded won't see Moose::Object 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.
These methods include metaclass.pm, Moose::Policy (which uses metaclass.pm under the hood), and various hacks to do what Moose::Exporter 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.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.