Zend Framework InlineScript helper hack

Pepe -_blacky_- Jiménez has been kind enough to act as special guest in this blog and write about a hack he found, when including Javascript chunks into PHP files, while using Zend Framework’s InlineScript view helper.

If you are looking for a talented web frontend programmer I can only speak good words about this bloke.

Why this hack?

Syntax highlighting is essential for developers. If you are familiar with Zend Framework you will know that InlineScript view helper doesn’t need the <script> tag. The reason is simple, that tag is automatically included at the view rendering stage. There is one big disadvantage however when you are developing in your favorite IDE… No syntax highlighting for your JavaScript piece of code.

When I noticed that, I searched for a solution at Google. No luck this time… So I started thinking how to solve this issue. As far as I know, IDEs detect JavaScript language in source files thanks to the <script> tag, so that should be a good start. If we could find a way to keep the tag in the source file and remove it before rendering it the problem would disappear.

How it works?

The InlineScript inherits from HeadScript who is responsible for introducing JavaScript content (literal source code or file). Taking a look at the HeadScript __call method, the createData function is the best candidate to solve this tag issue. If we look at Zend Framework’s source code, the 3rd parameter is the one containing our JavaScript code (potentially including the <script> tag).

Once we have this function located is very easy to remove the <script> tag. We just need to create a custom view helper extending Zend Framework’s one in order to override the createData method. At this point take a look at the source code below to see how it works.

I hope this post will prove useful to other developers who have been in the same situation as me.

Note: Don’t forget that custom view helpers prefix and paths have to be properly configured in your application to be automatically loaded and used.

Code

    <?php
    /**
     * Remove <script> tags
     *
     * @author      Jose Luis Jiménez <jljc.work@gmail.com>
     */
    class Pepe_Zend_View_Helper_InlineScript extends Zend_View_Helper_InlineScript
    {
      /**
       * Create data item containing all necessary components of script
       *
       * @param  string $type
       * @param  array $attributes
       * @param  string $content
       * @return stdClass
       */
      public function createData($type, array $attributes, $content = null)
      {
        // Replace <script> tag
        if (!empty($content)) {
          $content = preg_replace('@^\s*<script[^>]*>@i', '', $content);
          $content = preg_replace('@</script>\s*$@i', '', $content);
        }

        // Call parent
        return parent::createData($type, $attributes, $content);
      }
    }

PHP post_max_size issue

PHP’s ability to manage uploaded files has always left mixing feelings in me, on one hand it’s really trivial to implement file upload functionality on PHP scripts but in the other, managing big files uploads (just anything above a couple megabytes) is far from reliable.

Recently a couple solutions became popular to implement progressive uploads in PHP, PECL’s uploadprogress extension and recent APC versions. But anyway, the default PHP behaviour when uploading a file is to wait until it’s been fully transferred to the server before executing the php script.

This design decision from the PHP folks greatly simplifies the most common case, since you always get the full file when your PHP code runs, but also means that big files can cause some serious trouble. To defend itself, PHP has a few ini options that tune its ability to work with big files.

upload_max_filesize tells PHP the maximum size an uploaded file can have, files bigger will be reported as an error in the $_FILES array. max_input_time control the maximum time PHP can spend receiving data from the client, this is important because if you have many visitors with slow connections (or a malevolent attacker) a lot of PHP processes will be idling in memory collecting data slowly, consuming your server resources and potentially provoking your server to become unresponsive. Finally, we have post_max_size, which is a bit tricky, it limits the maximum amount of data a request from the client can hold. The tricky part is that if that limit is reached PHP won’t error out or signal the problem anyhow to your script, instead it will continue normal script execution but won’t populate $_POST or $_FILES superglobals, leaving them empty.

So if your post_max_size is set to 2Mb and a client uploads a 3Mb file, nor $_POST neither $_FILES will have any content. Even if you set your upload_max_filesize to 2Mb or below, if size of the file (or files) is bigger you will find yourself with those superglobals empty.

There is however a way to detect this condition, like shown in the following code snippet.

<?php

if (in_array($_SERVER['REQUEST_METHOD'], array('POST', 'PUT'))) {
  if (empty($_POST) && empty($_FILES)) {

    // Get maximum size and meassurement unit
    $max = ini_get('post_max_size');
    $unit = substr($max, -1);
    if (!is_numeric($unit)) {
      $max = substr($max, 0, -1);
    }

    // Convert to bytes
    switch (strtoupper($unit)) {
      case 'G':
        $max *= 1024;
      case 'M':
        $max *= 1024;
      case 'K':
        $max *= 1024;
    }

    // Assert the content length is within limits
    $length = $_SERVER['CONTENT_LENGTH'];
    if ($max < $length) {
      throw new Exception('Maximum content length size (' . $max . ') exceeded');
    }
  }
}

With that code in place you can gracely detect the issue and act accordingly by showing the user an error page explaining what just happened for example.

PHP command runner class

PHP offers multiple methods to execute external applications, for simple things they are more than enough but sometimes we need more flexibility. The following class acts as a wrapper around proc_open.

It implements a fluent interface to ease its configuration and non-blocking pipes for stdout and stderr. The output can be obtained at the end of the program execution or incrementally by using a callback function.

<?php
$cmd = Command::factory('/usr/bin/svn');
$cmd->option('--username', 'drslump')
    ->option('-r', 'HEAD')
    ->option('log')
    ->argument('http://code.google.com/drslump/trunk');
    ->run();
if ($cmd->getExitCode() === 0) {
    echo $cmd->getStdOut();
} else {
    echo $cmd->getStdErr();
}

Incremental updates can be accomplished with a callback function, like in the following example (PHP 5.3+):

<?php
$cmd = Command::factory('ls');
$cmd->setCallback(function($pipe, $data){
        if ($pipe === Command::STDOUT) echo 'STDOUT: ';
        if ($pipe === Command::STDERR) echo 'STDERR: ';
        echo $data === NULL ? "EOF\n" : "$data\n";
        // If we return "false" all pipes will be closed
        // return false;
    })
    ->setDirectory('/tmp')
    ->option('-l')
    ->run();
if ($cmd->getExitCode() === 0) {
    echo $cmd->getStdOut();
} else {
    echo $cmd->getStdErr();
}

Some more features:

  • StdIn data can be provided to the process as a parameter to run()
  • Set environment variables for the process with setEnv()
  • Second argument to option() and argument to argument() are automatically escaped.
  • Options separator is white space by default, it can be changed by manually setting it as third argument to option() or setting a new default with setOptionSeparator().
  • The proc_open wrapper is exposed as a static method for your convenience Command::exec()

And finally the class which makes all that possible :)

Subversion svnsync post-commit hook work around

Just a quickie for future reference. Svnsync is a tool that comes with Subversion that allows to create read-only mirrors of Subversion repositories.

Creating a mirror is quite easy and usually involves the following steps:

  • First we create a standard subversion repository.

     mkdir mirror
     svnadmin create mirror
  • Now we have to create a pre-revprop-change hook that only allows the svnsync write access to it. Remember that the mirror is read-only. Put the following contents in mirror/hooks/pre-revprop-change.

     #!/bin/sh
    
     if [ "$3" = "svnsync" ]; then exit 0; fi
     echo "Only svnsync user may edit revision properties through svnsync" >&2
     exit 1
  • To activate the hook we need to give it executable permissions.

     chmod +x mirror/hooks/pre-revprop-change
  • Configure the synchronization

     svnsync init --username svnsync \
         file:///path/to/mirror \
         https://url.to/repository
  • Finally perform the synchronization! You can put this command in the cron system for example to ensure the mirror is kept up-to-date.

     svnsync sync file:///path/to/mirror

There is a problem though with this setup if we want to use a post-commit hook. svnsync works by first committing a synced revision with its own user and then, in a second step, modifies the revision properties to change the author and date to those of the original repository. This means that post-commit hooks which need to obtain the revision author or date cannot be property run, since the reported author and date will be wrong.

The solution is to use a different hook, post-revprop-change, which is triggered each time a revision property is changed. We can have something as the following script to emulate a normal post-commit hook.

  #!/bin/sh

  REPOS="$1"
  REV="$2"
  USER="$3"
  PROPNAME="$4"
  ACTION="$5"

  # Only do actual work when the "svn:date" property is modified, which seems
  # to be the last revision property modified by the svnsync process.
  if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:date" ]; then
    AUTHOR=`svnlook author $REPOS -r $REV`
    DATE=`svnlook date $REPOS -r $REV`
    LOG=`svnlook log $REPOS -r $REV`

    echo "$REPOS@$REV: $LOG by $AUTHOR at $DATE"
  fi