The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MojoX::Plugin::PHP - use PHP as a templating system in Mojolicious

VERSION

0.05

WTF

Keep reading.

SYNOPSIS

    # MyApp.pl, using Mojolicious
    app->plugin('MojoX::Plugin::PHP');
    app->plugin('MojoX::Plugin::PHP', {
        php_var_preprocessor => sub { my $params = shift; ... },
        php_stderr_preprocessor => sub { my $msg = shift; ... },
        php_header_processor => sub { my ($field,$value,$repl) = @_; ... },
        php_output_processor => sub { my ($outref, $headers, $c) = @_; ... }
    } );

    # using Mojolicious::Lite
    plugin 'MojoX::Plugin::PHP';
    plugin 'MojoX::Plugin::PHP', {
        php_var_preprocessor => sub { my $params = shift; ... },
        php_stderr_preprocessor => sub { my $msg = shift; ... },
        php_header_processor => sub { my ($field,$value,$repl) = @_; ... },
        php_output_processor => sub { my ($outref, $headers, $c) = @_; ... }
    };

DESCRIPTION

MojoX::Plugin::PHP establishes a PHP engine as the default handler for php files and templates in a Mojolicious application. This allows you to put a PHP template (say, called foo/bar.php under your Mojolicious application's /templates or /public directory, make a request to

    /foo/bar.php

and have a PHP interpreter process your file, and Mojolicious return a response as if it the request were processed in Apache with mod_php.

Why would anyone want to do this? Here are a couple of reasons I can think of:

  • to put a Mojolicious wrapper around some decent PHP application (WordPress?). Then you could use Perl and any other state of your Mojolicious application to post process output and response headers.

  • allow PHP developers on your project to keep prototyping in PHP, postponing the religious war about which appserver your project should use

CONFIG

use_index_php
    use_index_php => boolean | undef

Describes how the before_dispatch hook should handle requests for a path that contains a file called index.php.

If use_index_php is set to a defined value, then a request like /foo/ (with a trailing slash) will be routed to /foo/index.php if /foo/index.php would resolve to a valid PHP template.

If use_index_php is set to a true value, then a request like /foo (with or without a trailing slash) will be routed to /foo/index.php if /foo/index.php would resolve to a valid PHP template.

If use_index_php is not defined or set to undef, then this module will not look for an index.php file related to any request.

Callbacks during PHP processing

There are four hooks in the PHP template processing engine (MojoX::Template::PHP) where you can customize or extend the behavior of the PHP engine. In the plugin configuration, you can specify the code that should be run off each of these hooks. All of these configuration are optional.

php_var_preprocessor
    php_var_preprocessor => sub { my $params = shift; ... }

MojoX::Template::PHP gathers several variables from Perl and sets them as global variables in the PHP environment. These include the standard $_GET, $_POST, $_REQUEST, $_SERVER, $_ENV, $_COOKIE, and $_FILES variables, but also includes most of the stash variables. All of these variable values are gathered into a single hash reference. Right before all of the variables are assigned in PHP, the PHP engine will look for a php_var_preprocessor setting, and will invoke its code, passing that hash reference as an argument. In this callback, you can add, remove, or edit the set of variables that will be initialized in PHP.

php_stderr_processor
    php_stderr_processor => sub { my $msg = shift; ... }

When the PHP interpreter writes a message to its standard error stream, a callback specified by the php_stderr_processor config setting can be called with the text that PHP was trying to write to that stream. You can use this callback to log warnings and errors from PHP.

php_header_processor
    php_header_processor => sub { 
        my ($field,$value,$replace) = @_; 
        ... 
        return $keep_header;
    }

When the PHP header() function is invoked in the PHP interpreter, a callback specified by the php_header_processor config setting can be called with the name and value of the header. If this callback returns a true value (or if there is no callback), the header from PHP will be included in the Mojolicious response headers. If this callback returns a false value, the header will not be returned with the Mojolicious response.

One powerful use of the header callback is as a communication channel between PHP and Perl. For example, the header processor can look for a specific header field. When it sees this header, the value can be a JSON-encoded payload which can be processed in Perl. Perl can return the results of the processing through a global PHP variable (again, possibly JSON encoded). The t/10-headers.t test case in this distribution has a proof-of-concept of this kind of use of the header callback.

php_output_postprocessor
    php_output_postprocessor => sub {
        my ($output_ref, $headers, $c) = @_;
        ...
    }

When the PHP engine has finished processing a PHP template, and a callback has been specified with the php_output_postprocessor config setting, then that callback will be invoked with a reference to the PHP output, the set of headers returned by PHP (probably in a Mojo::Headers object), and the current controller/context object. You can use this callback for postprocessing the output or the set of headers that will be included in the Mojolicious response.

One thing that you might want to do in the output post-processing is to look for a Location: ... header, and determine if you want the application to follow it.

METHODS

register

    $plugin->register(Mojolicious->new);

Register the php renderer in Mojolicious application.

COMMUNICATION BETWEEN PERL AND PHP

As mentioned in the "php_header_processor" documentation in the CONFIG section above, it is possible to use the header callback mechanism to execute arbitrary Perl code from PHP and to establish a communication channel between your PHP scripts and your Mojolicious application.

Let's demonstrate with a simple example:

The Collatz conjecture states that the following algorithm:

    Take any natural number  n . If  n  is even, divide it by 2.
    If  n  is odd, multiply it by 3 and add 1 so the result is  3n + 1 .
    Repeat the process until you reach the number 1.

will always terminate in a finite number of steps.

Suppose we are interested in finding out, for a given numner n, how many steps of this algorithm are required to reach the number 1. We'll make a request to a path like:

collatz.php?n=n

and return the number of steps in the response. Our collatz.php template looks like:

    <?php
      $nsteps = 0;
      $n = $_GET['n'];
      while ($n > 1) {
        if ($n % 2 == 0) {
          $n = divide_by_two($n);
        } else {
          $n = triple_plus_one($n);
        }
        $nsteps++;
      }

      function divide_by_two($x) {
        return $x / 2;
      }

      function triple_plus_one($x) {
        ...
      }
    ?>
    number of Collatz steps is <?php echo $nsteps; ?>

and we will implement the triple_plus_one function in Perl.

Components of the communication channel

The configuration for MojoX::Plugin::PHP can specify a callback function that will be invoked when PHP sends a response header. To use this channel to perform work in PHP, we need

1. a MojoX::Plugin::PHP header callback function that listens for a specific header
2. PHP code to produce that header
3. an agreed upon global PHP variable, that Perl code can set (with the PHP::assign_global function) with the result of its operation, and that PHP can read

Perl code

In the Mojolicious application, we intercept a header of the form X-collatz:payload where payload is the JSON-encoding of a hash that defines n, the number to operate on, and result, the name of the PHP variable to publish the results to.

JSON-encoding the header value is a convenient way to pass complicated, arbitrary data from PHP to Perl, including binary data or strings with newlines. For complex results, it is also convenient to assign a JSON-encoded value to a single PHP global variable.

    ...
    use Mojo::JSON;
    ...
    app->plugin('MojoX::Plugin::PHP',
        { php_header_processor => \&my_header_processor };

    sub my_header_processor {
        my ($field,$value,$replace) = @_;
        if ($field eq 'X-collatz') {
            my $payload = Mojo::JSON::decode_json($value);
            my $n = $payload->{n};
            my $result_var = $payload->{result};
            $n = 3 * $n + 1;
            PHP::assign_global( $result_var, $n );
            return 0;   # don't include this header in response
        }
        return 1;       # do include this header in response
    }
    ...

PHP code

The PHP code merely has to set a response header that looks like X-collatz:payload where payload is a JSON-encoded associative array with the number to operate on the variable to receive the results in. Then it must read the result out of that variable.

    ...
    function triple_plus_one($x) {
        global $collatz_result;
        $payload = encode_json(   // requires php >=v5.2.0
            array( "n" => $x, "result" => "collatz_result")
        );
        header("X-collatz: $payload");
        return $collatz_result;
    }

Now we can not only run PHP scripts in Mojolicious, our PHP templates can execute code in Perl.

    $ perl our_app.pl get /collatz.php?n=5
    number of Collatz steps is 5
    $ perl our_app.pl get /collatz.php?n=42
    number of Collatz steps is 8

Other possible uses

Other ways you might use this feature include:

  • have PHP execute functions or use modules that are hard to implement in Perl or only available in Perl

  • have PHP manipulate data in your app's Perl model

  • perform authentication or other function in PHP that changes the state on the Perl side of your application

SEE ALSO

MojoX::Template::PHP, Mojolicious::Plugin::EPRenderer, Mojolicious::Plugin::EPLRenderer, Catalyst::View::Template::PHP, PHP, PHP::Interpreter.

AUTHOR

Marty O'Brien <mob@cpan.org>

COPYRIGHT

Copyright 2013-2015, Marty O'Brien. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Sortware Foundation; or the Artistic License.

See http://dev.perl.org/licenses for more information.