No description
This repository has been archived on 2026-08-22. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
  • Perl 89.3%
  • Raku 10.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jan Henning Thorsen a8a62f38a6 Released version 0.02
- Extended EXPERIMENTAL warning
 - Improved can() resolving
 - Avoid generating extra packages
2018-10-19 11:13:41 +09:00
lib/Mojolicious/Plugin Released version 0.02 2018-10-19 11:13:41 +09:00
t Speed is back and error message is improved 2018-10-19 11:11:43 +09:00
.gitignore git ship start 2018-10-17 21:19:07 +09:00
.perltidyrc Add perltidyrc 2018-10-17 21:49:56 +09:00
.ship.conf git ship start 2018-10-17 21:19:07 +09:00
.travis.yml Testing with travis 2018-10-18 13:56:22 +09:00
Changes Released version 0.02 2018-10-19 11:13:41 +09:00
cpanfile Testing with travis 2018-10-18 13:56:22 +09:00
Makefile.PL Testing with travis 2018-10-18 13:56:22 +09:00
MANIFEST.SKIP git ship start 2018-10-17 21:19:07 +09:00
README.pod Add documentation 2018-10-17 22:14:20 +09:00

package Mojolicious::Plugin::FastHelpers;
use Mojo::Base 'Mojolicious::Plugin';

use Mojo::Util 'monkey_patch';

use constant DEBUG => $ENV{MOJO_FASTHELPERS_DEBUG} || 0;

our $VERSION = '0.02';

sub register {
  my ($self, $app, $config) = @_;

  $self->_add_helper_classes;
  $self->_monkey_patch_add_helper($app);
}

sub _monkey_patch_add_helper {
  my ($self, $app) = @_;
  my $renderer = $app->renderer;

  # Add any helper that has been added already
  _add_helper_method($_) for sort map { (split /\./, $_)[0] } keys %{$renderer->helpers};

  state $patched = {};
  return if $patched->{ref($renderer)}++;

  # Add new helper methods when calling $app->helper(...)
  my $orig = $renderer->can('add_helper');
  monkey_patch $renderer => add_helper => sub {
    my ($renderer, $name) = (shift, shift);
    _add_helper_method($name);
    $orig->($renderer, $name, @_);
  };
}

sub _add_helper_classes {
  my $self = shift;

  for my $class (qw(Mojolicious Mojolicious::Controller)) {
    my $helper_class = "${class}::_FastHelpers";
    next if UNIVERSAL::isa($class, $helper_class);
    eval "package $helper_class;1" or die $@;

    monkey_patch $class => can => sub {
      my ($self, $name, @rest) = @_;
      return undef unless my $can = $self->SUPER::can($name, @rest);
      return undef if $can eq ($helper_class->can($name) // '');    # Hiding helper methods from can()
      return $can;
    };

    no strict 'refs';
    unshift @{"${class}::ISA"}, $helper_class;
  }
}

sub _add_helper_method {
  my $name = shift;
  return if Mojolicious::_FastHelpers->can($name);                  # No need to add it again

  monkey_patch 'Mojolicious::_FastHelpers' => $name => sub {
    my $app = shift;
    Carp::croak qq/Can't locate object method "$name" via package "@{[ref $app]}"/
      unless my $helper = $app->renderer->get_helper($name);
    return $app->build_controller->$helper(@_);
  };

  monkey_patch 'Mojolicious::Controller::_FastHelpers' => $name => sub {
    my $c = shift;
    my $p = $c->{_FastHelpers} ||= $c->app->renderer->get_helper('')->($c);
    Carp::croak qq/Can't locate object method "$name" via package "@{[ref $c]}"/ unless $p->can($name);
    return $p->$name(@_);
  };
}

1;

=encoding utf8

=head1 NAME

Mojolicious::Plugin::FastHelpers - Faster helpers for your Mojolicious application

=head1 SYNOPSIS

=head2 Lite app

  use Mojolicious::Lite;
  plugin "FastHelpers";
  app->start;

=head1 DESCRIPTION

L<Mojolicious::Plugin::FastHelpers> is a L<Mojolicious> plugin which can speed
up your helpers, by avoiding C<AUTOLOAD>.

It does this by injecting some new classes into the inheritance tree of
L<Mojolicious> and L<Mojolicious::Controller>.

=head2 Warning

This module must be considered EXPERIMENTAL. There might even be some security
isseus, so use it with care.

It is not currently used in production anywhere I know of, and I'm not sure if
I can endorce such usage.

This is strictly a (unproven) proof of concept.

=head2 Benchmarks

There is a benchmark test bundled with this distribution, if you want to run it
yourself, but here is a quick overview:

  $ TEST_BENCHMARK=200000 prove -vl t/benchmark.t
  ok 1 - App::Normal 2.08688 wallclock secs ( 2.08 usr +  0.00 sys =  2.08 CPU) @ 96153.85/s (n=200000)
  ok 2 - Ctrl::Normal 0.654221 wallclock secs ( 0.65 usr +  0.00 sys =  0.65 CPU) @ 307692.31/s (n=200000)
  ok 3 - App::FastHelpers 1.62765 wallclock secs ( 1.62 usr + -0.01 sys =  1.61 CPU) @ 124223.60/s (n=200000)
  ok 4 - Ctrl::FastHelpers 0.131942 wallclock secs ( 0.13 usr +  0.00 sys =  0.13 CPU) @ 1538461.54/s (n=200000)
  ok 5 - App::FastHelpers (1.61s) is not slower than App::Normal (2.08s)
  ok 6 - Ctrl::FastHelpers (0.13s) is not slower than Ctrl::Normal (0.65s)

                         Rate App::Normal App::FastHelpers Ctrl::Normal Ctrl::FastHelpers
  App::Normal         96154/s          --             -23%         -69%              -94%
  App::FastHelpers   124224/s         29%               --         -60%              -92%
  Ctrl::Normal       307692/s        220%             148%           --              -80%
  Ctrl::FastHelpers 1538462/s       1500%            1138%         400%                --

=head1 METHODS

=head2 register

Will create new classes for your application and
L<Mojolicious/controller_class>, and monkey patch in all the helpers.

=head1 AUTHOR

Jan Henning Thorsen

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2018, Jan Henning Thorsen

This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.

=head1 SEE ALSO

L<Mojolicious>

=cut