NAME

Catalyst::Seal::Modifiers - install the composed body of a wrapped method directly

DESCRIPTION

A method carrying a before, after or around modifier is a Class::MOP::Method::Wrapped, and what gets installed in the symbol table is not the composed body but a trampoline:

sub {
    my $wrapped
        = set_subname( "${pkg_name}::_wrapped_${method_name}" =>
            $modifier_table->{cache} );
    return $wrapped->(@_) ;
}

cache is the composed body, rebuilt by Class::MOP whenever a modifier is added. The trampoline exists so that a call always reaches the current one, and it pays for that with one extra subroutine call and one set_subname on every invocation of every wrapped method, for the life of the process, to attach a name that has been constant since wrap returned.

A bare Catalyst application has twenty-three of them, and the ten that are on the request path, among them Catalyst::Response::status, Catalyst::Response::headers, Catalyst::Request::parameters and three BUILD methods, make 36 set_subname calls per request between them.

After setup_finalize the modifier lists are final, so this step takes cache, names it once, and installs it in the glob. The trampoline is gone and nothing else changes: the sub that runs is the same sub that the trampoline would have called.

When a modifier does arrive late

A plugin that applies a role at first request adds to a modifier table this step has already read, and Class::MOP rebuilds cache without knowing that the old one is now installed under the method's own name. So every entry point that rebuilds it, add_before_modifier, add_after_modifier and add_around_modifier, is wrapped: the method un-flattens back to the stock trampoline before the modifier is added, and stays that way.

flattened

my @names = Catalyst::Seal::Modifiers::flattened();

The Class::Name::method of every method currently flattened, in no particular order.

flatten_class

my $count = Catalyst::Seal::Modifiers::flatten_class('Catalyst::Response');

Flattens every wrapped method the class declares itself, and returns how many. A class that is still mutable is left alone: mutable is the state a class is in while it is still being built, and a method installed here would be replaced by the next thing that touched it.

unflatten_method

Catalyst::Seal::Modifiers::unflatten_method($wrapped);

Puts the stock trampoline back for one Class::MOP::Method::Wrapped, so that modifiers added from here on are seen. Returns true if it did anything. Called for you when a modifier is added to a method this step flattened.

The response header guard

Catalyst::Response carries a before modifier on status, headers, content_encoding, content_length and content_type that warns when one of them is used as a setter after the headers have been finalised, and a second one on header that does the same. Both conditions end in && @_, so on a read the warning cannot fire, but the terms before it are evaluated first and each one is an accessor call. That is 40 calls per request asking whether a response that has not been finalised has been finalised.

This step replaces the modifier with one that returns immediately unless it was called as a setter, and otherwise calls the stock guard with the arguments it was given. Reordering a condition whose last term already decides it is not a behaviour change, and the warning itself is the original.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)