From: Jesse Luehrs Date: Mon, 20 Feb 2012 15:29:33 +0000 (-0600) Subject: calling Moose->init_meta yourself really shouldn't be recommended X-Git-Tag: 2.0500~45 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=8bb032920c195cead606b337bda7aa3b63dbbd78;p=gitmo%2FMoose.git calling Moose->init_meta yourself really shouldn't be recommended --- diff --git a/lib/Moose/Cookbook/Extending/ExtensionOverview.pod b/lib/Moose/Cookbook/Extending/ExtensionOverview.pod index 20e2a77..e81d824 100644 --- a/lib/Moose/Cookbook/Extending/ExtensionOverview.pod +++ b/lib/Moose/Cookbook/Extending/ExtensionOverview.pod @@ -230,59 +230,93 @@ And then the consumer of your extension can use your C sub: This can be combined with metaclass and base class roles quite easily. -=head2 Extensions as Metaclass (and Base Object) Subclasses +=head2 More advanced extensions -B. - -Moose does not provide any simple APIs for consumers to use a subclass -extension, except for class and attribute metaclasses. The attribute -declaration options include a C option a consumer of your extension -can use to specify your subclass, and class metaclasses can be passed via the -C<-metaclass> import option when you C. - -This is one reason why implementing an extension as a subclass can be -a poor choice. However, you can force the use of certain subclasses at -import time by calling C<< Moose->init_meta >> for the caller, and -providing an alternate metaclass or base object class. - -If you do want to do this, you should look at using L -to re-export the L sugar function. With -L, if your exporting class has an C -method, L makes sure that this C method -gets called when your class is imported. - -Then in your C you can arrange for the caller to use your -subclasses: +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 C method in your exporter. For instance: package MooseX::Embiggen; - use Moose (); use Moose::Exporter; - use MooseX::Embiggen::Meta::Class; - use MooseX::Embiggen::Object; + my ($import, $unimport, $init_meta) = Moose::Exporter->build_import_methods( + install => ['import', 'unimport'], + with_meta => ['embiggen'], + class_metaroles => { + class => ['MooseX::Embiggen::Role::Meta::Class'], + }, + ); - Moose::Exporter->setup_import_methods( also => 'Moose' ); + sub embiggen { + my $meta = shift; + $meta->embiggen(@_); + } sub init_meta { - shift; # just your package name + my $package = shift; my %options = @_; - - return Moose->init_meta( - for_class => $options{for_class}, - metaclass => 'MooseX::Embiggen::Meta::Class', - base_class => 'MooseX::Embiggen::Object', - ); + 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); } -Note that there are several issues with this sort of extension mechanism, most -notably that Moose only ever initializes the metaclass for a class once. This -means that this extension must be loaded B, before anything -Moose-related touches the class. If you say C, for instance, the extension will not be applied properly. -There are very few reasons to implement an extension in this way; using -metaclass traits is the right answer in nearly every case. +In the previous examples, C was generated for you, but here you must +override it in order to add additional functionality. Some differences to note: + +=over 4 + +=item C instead of C + +C simply returns the C, C, and +C methods, rather than installing them under the appropriate names. +This way, you can write your own which wrap the existing functionality. +C also takes an additional C parameter, which +tells it to just go ahead and install these methods (since we don't need to +modify them). + +=item C + +Next, we must write our C wrapper. The important things to remember +are that it is called as a method, and that C<%options> needs to be passed +through to the existing implementation. Calling the base implementation just +uses the C<$init_meta> subroutine reference that was returned by +C earlier. + +=item Additional implementation + +This extension sets a different default base object class. To do so, it first +checks to see if it's being applied to a class, and then checks to see if +L is that class's only superclass, and if so, replaces that with +the superclass that this extension requires. + +Note that two extensions that do this same thing will not work together +properly (the second extension to be loaded won't see L 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 C 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 C +is not affected regardless. + +=back + +This is just one example of what can be done with a custom C 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 C method. =head1 LEGACY EXTENSION MECHANISMS