Lua is a lightweight programming language that is easy to embed and extend. It is even possible to use it from within PHP using the Lua extension. Unfortunately the documentation is bare to say the least, but with this article I hope to clear some things up.

Fortunately I have some previous experience with Lua, so it wasn’t that difficult to get it up and running. The most trouble I had was compiling the extension as the path to the Lua lib was different within my Linux distribution. In this case it was easily fixed by using some linking (ln) magic.

Why

There are multiple use cases where you could benefit of having some simple programming language available that we could communicate with through PHP.

Take for example a gamification framework. You could write simple rules in Lua that can easily be changed without reprogramming the whole framework. As Lua is a really simple language it should be even possible for non developers to change the rules.

Passing variables

The first snippet we will see, is an example of passing PHP variables to our Lua script.

// init our lua engine
$lua = new Lua();


// create an simple_var variable within our lua environment
// and assign the number 999 to it
$lua->assign("simple_var", 999);

// run some lua code which will print 999
$lua->eval("print(simple_var)");

As Lua is a completely functional scripting language, we can do a lot more than only printing values. In the next example we will calculate the “Answer to the Ultimate Question of Life, the Universe, and Everything#The_Hitchhiker.27s_Guide_to_the_Galaxy)“.

// init our lua engine
$lua = new Lua();


// create an simple_var variable within our lua environment
// and assign the number 24 to it
$lua->assign("simple_var", 24);

// run some lua code which will print 42
$result = $lua->eval("
    local answer_life = simple_var * 2
	return answer_life
");

print $result; // 42

Next up are arrays which isn’t that different from assigning a single value. One thing that I need to mention is that it’s customary for Lua indexes to start at position 1, so it’s best to keep this in mind.

// init our lua engine
$lua = new Lua();

// create an array_var variable within our lua environment
// and assign an array to it
$lua->assign("array_var", array(
    1=>"hello",
    2 => "world"
));

// print array
$lua->eval("
    print(array_var);
");

// lua table index starts at 1
$lua->eval("print(array_var[1])"); // hello

// return array
$test = $lua->eval("return array_var");

var_dump($test); // array(2) { .... }

Classes are partially supported. Although Lua supports OOP, it seems that it can only access the public properties of classes we assign. Also notice how we are using Lua’s string library in our example!

// dummy vehicle class
class Vehicle{
	public $wheels;

 	function __construct($name, $wheels){
		$this->name = $name;
	}
}

$quad = new Vehicle("quad", 4);
$lua->assign("quad", $quad);

$vehicle = $lua->eval("
	return string.format(
		'You are driving a %q',
		quad.name
	)
");

print $vehicle; // you are driving a "quad"

Calling php functions

Next on the list is calling PHP functions from within our Lua script.

// init lua
$lua = new Lua();

/**
* Hello world method
*/
function helloWorld()
{
    return "hello world";
}

// register our hello world method
$lua->registerCallback("helloWorld", helloWorld);
$lua->eval("
    -- call php method
    local retVal = helloWorld()

    print(retVal)
");

It is even possible to use a different method name.

// register our hello world method but using an other name
$lua->registerCallback("worldHello", helloWorld);

// run our lua script
$lua->eval("
    -- call php method
    local retVal = worldHello()

    print(retVal)
");

Passing variables is no big deal and is quite easy.


/**
* Hello param method
*/
function hello($name)
{
    return "hello ". $name;
}

// register our hello method
$lua->registerCallback("hello", hello);

// run our lua script
$lua->eval("
    -- call php method
    local retVal = hello('glenn')

    print(retVal); --outputs hello glenn
");

Sandboxing

Don’t be fooled by it simplicity, the lua language can be very powerful. It comes out of the box with a lot of libraries you don’t always want to be available in a secure environment.

One problem of the PHP Lua extension is that it loads all Lua modules. The issue is the code at line number 857 of the lua.c file.

PHP_METHOD(lua, \_\_construct) {
	...
	luaL_openlibs(L);
	...
}

This ensures (see docs - lua_register is an alias) that all modules get loaded and that is not something you always want e.g. you want to build a secure environment. You can do 2 things: fork it and modify that line to include the modules you want or (depending on your lua version) use lua’s support for sand boxing.

I’m using Lua version 5.3 which was the latest version at the moment of writing. This is important to note because the way you can sandbox your code is different since version 5.2. If you are on a system which only have 5.1 available, I can point you to this stackoverflow question.

Next is some unsafe code where all libraries are available.

$lua = new Lua();

// not sandboxed (unsafe)
$val = $lua->eval("
  time = os.date(\"*t\")

  function foo()
    print(time.hour)
  end

  foo()
");

echo $val; // prints hour

Next is the same code but we are using sand boxing.

// sandboxed code (print and os allowed)
$val = $lua->eval("
    local foo;

    do
      -- user has access to print and os
      local _ENV = { print = print, os = os }

      time = os.date(\"*t\")

      function foo()
        return time.hour
      end

      return foo()
    end
");

echo  $val; //  will print hour

In the next code we are still using sand boxing, but only print is allowed. There will be an error thrown when we try to use the os.date method.

// sandboxed code (only print allowed)
$val = $lua->eval("
    local foo;

    do
      -- user has access to print
      local _ENV = { print = print }

      -- will throw error 'attempt to index a nil value (global os)'
      time = os.date(\"*t\")

      function foo()
        return time.hour
      end

      return foo()
    end
");

echo  $val; //  will output nothing

Real world example: gamification / user levels

I want to expand a bit on the gamification that I already mentioned. Next up is a simple example of how the leveling system in such a system could in theory work.

We start of with a simple player class that contains his/here name, points and level.

/**
 * Player class
 */
class Player
{
  public $name;
  public $points;
  public $level;

  function __construct($name, $points){
    $this->name = $name;
    $this->points = $points;
  }
}

Next is our Lua code that calculates the level of a user. This could be easily be exposed through an online editor that administrators can easily change.

local level;

do
  -- user has only access to player and print
  local _ENV = { player = player, print = print}

  -- determine player level
  if player.points <= 1000 then
    level = "noob"
  elseif player.points <= 5000 then
    level = "novice"
  else
    level = "master"
  end

  return level
end

We then create some players and evaluate their points against our level script to determine the level they achieve.

// create player mike
$playerMike = new Player('mike', 1000);

// determine mikes level
$lua->assign("player", $playerMike);
$level = $lua->eval($luaLevelScript);

// output: mike his level is noob
echo $playerMike->name . " his level is " . $level . "<br>";


// create player jack
$playerJack = new Player('jack', 5000);

// determine jacks level
$lua->assign("player", $playerJack);
$level = $lua->eval($luaLevelScript);

// output: jack his level is novice
echo $playerJack->name . " his level is " . $level . "<br>";

// create player kevin
$playerKevin = new Player('kevin', 10000);

// determine jacks level
$lua->assign("player", $playerKevin);
$level = $lua->eval($luaLevelScript);

// output: kevin his level is master
echo $playerKevin->name . " his level is " . $level;