A secure Lua script execution engine for Laravel applications using the PHP LuaSandbox extension.
This package provides a clean, Laravel-friendly wrapper around LuaSandbox, making it easy to execute Lua scripts safely within your Laravel application with proper error handling, logging, and resource management.
- 🔒 Safe Sandbox: Automatic removal of dangerous Lua functions (os, io, require, etc.)
- ⚡ Easy Integration: Built specifically for Laravel
- 🎯 Flexible Configuration: Control which Lua modules are allowed
- 📊 Data Binding: Pass PHP variables to Lua scripts
- 🧪 Resource Limits: Timeout and memory limit controls
- 📝 Error Logging: Configurable logging levels
- ✅ Validation: Check script syntax before execution
- PHP 8.1 or higher (compatible with PHP 8.4)
- Laravel 10.0 or higher
- LuaSandbox PHP extension (
pecl install luasandbox)
- Install the LuaSandbox PHP extension:
sudo pecl install luasandbox- Add this package to your Laravel application:
composer require diogo-graciano/laravel-lua-engine- Publish the configuration file:
php artisan vendor:publish --provider="DiogoGraciano\LuaEngine\LuaEngineServiceProvider"The configuration file is published to config/lua-engine.php:
return [
'options' => [
// CPU limit in seconds (0 = no limit)
// Sets the maximum CPU time allowed for Lua execution
// See: https://www.php.net/manual/en/luasandbox.setcpulimit.php
'cpu_limit' => 30,
// Maximum memory limit
// Can be specified as:
// - Integer bytes: 67108864
// - String with unit: '64M', '128K', '2G'
// See: https://www.php.net/manual/en/luasandbox.setmemorylimit.php
'memory_limit' => '64M', // or 67108864 for 64MB
// Log errors to Laravel log
'log_errors' => true,
// Log level for Lua errors
// Available: 'error', 'warning', 'info', 'debug'
'log_level' => 'error',
],
];Note: LuaSandbox automatically provides a secure sandboxed environment. All dangerous Lua functions are disabled by default. See the Security & Sandbox section for details.
use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Execute a simple Lua script
$result = LuaEngine::execute('return 42');
echo $result; // 42
// Pass data to Lua using registerLibrary
// Note: Functions must return arrays (LuaSandbox requirement)
$data = ['name' => 'John', 'age' => 30];
LuaEngine::registerLibrary([
'getName' => function() use ($data) {
return [$data['name']]; // Return as array
},
'getAge' => function() use ($data) {
return [$data['age']];
},
], 'data');
$result = LuaEngine::execute('return data.getName()');
echo $result; // Johnuse DiogoGraciano\LuaEngine\LuaEngine;
class MyController
{
public function __construct(private LuaEngine $luaEngine) {}
public function example()
{
$result = $this->luaEngine->execute('return math.floor(3.7)');
return $result; // 3
}
}use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Basic math operations
$result = LuaEngine::execute('return 10 + 5 * 2');
// Result: 20
// Using Lua math library
$result = LuaEngine::execute('return math.sqrt(16)');
// Result: 4.0use DiogoGraciano\LuaEngine\Facades\LuaEngine;
$data = [
'user' => [
'name' => 'Alice',
'age' => 28,
'scores' => [85, 90, 78]
]
];
// Access nested data using registerLibrary
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'getUserName' => function() use ($data) {
return [$data['user']['name']]; // Return as array
},
'getScore' => function($index) use ($data) {
return [$data['user']['scores'][$index - 1]]; // Lua is 1-indexed
},
], 'data');
$name = LuaEngine::execute('return data.getUserName()');
// Result: "Alice"
// Calculate average score
$avg = LuaEngine::execute(
'return (data.getScore(1) + data.getScore(2) + data.getScore(3)) / 3'
);
// Result: 84.33333333333333use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Use evaluate() for boolean conditions
$data = ['age' => 25, 'minimumAge' => 18];
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'getAge' => function() use ($data) {
return [$data['age']]; // Return as array
},
'getMinimumAge' => function() use ($data) {
return [$data['minimumAge']];
},
], 'data');
$canVote = LuaEngine::evaluate('data.getAge() >= data.getMinimumAge()');
// Result: true
$isAdult = LuaEngine::evaluate('data.getAge() >= 21');
// Result: trueuse DiogoGraciano\LuaEngine\Facades\LuaEngine;
$data = ['message' => 'hello world'];
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'getMessage' => function() use ($data) {
return [$data['message']]; // Return as array
},
], 'data');
// Transform string
$upper = LuaEngine::execute('return string.upper(data.getMessage())');
// Result: "HELLO WORLD"
// Get string length
$length = LuaEngine::execute('return #data.getMessage()');
// Result: 11use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Register a library of PHP functions
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'double' => function($x) {
return [$x * 2]; // Return as array
},
'square' => function($x) {
return [$x * $x];
},
'cube' => function($x) {
return [$x * $x * $x];
},
], 'custom');
// Use in Lua - functions are accessible as custom.functionName()
$result = LuaEngine::execute('return custom.double(21)');
// Result: 42
$square = LuaEngine::execute('return custom.square(5)'); // 25
$cube = LuaEngine::execute('return custom.cube(3)'); // 27
// Extend the library - add more functions to existing table
LuaEngine::registerLibrary([
'add' => function($a, $b) {
return [$a + $b];
},
], 'custom');
// Now custom library has: double, square, cube, and adduse DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Check if script is valid before execution
$isValid = LuaEngine::validate('return true and false');
// Result: true
$isValid = LuaEngine::validate('invalid syntax [[[[');
// Result: falseuse DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Execute from a file
// Note: If your Lua script needs data, use registerLibrary first
// Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'getValue' => function() {
return [100]; // Return as array
},
], 'data');
$result = LuaEngine::executeFile('/path/to/script.lua');use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Define a function in Lua and store it globally
LuaEngine::execute('
_G["multiply"] = function(a, b)
return a * b
end
');
// Call the function using callFunction
$result = LuaEngine::callFunction('multiply', 6, 7);
// Result: [42] (returns array from LuaSandbox)
// Access first element if single result
$value = is_array($result) ? $result[0] : $result;
// Result: 42use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Load Lua code into a function
$func = LuaEngine::loadString('return a + b');
if ($func !== false) {
// Execute the loaded function (note: you need to set variables first)
// For simplicity, use execute() method instead
$result = LuaEngine::execute('return 10 + 5');
// Result: 15
}use DiogoGraciano\LuaEngine\Facades\LuaEngine;
$order = [
'items' => [
['price' => 10, 'quantity' => 2],
['price' => 15, 'quantity' => 1],
],
'discount' => 0.1, // 10%
'tax' => 0.08 // 8%
];
// Register order data as a library
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary([
'getItemPrice' => function($index) use ($order) {
return [$order['items'][$index - 1]['price']]; // Return as array
},
'getItemQuantity' => function($index) use ($order) {
return [$order['items'][$index - 1]['quantity']];
},
'getItemsCount' => function() use ($order) {
return [count($order['items'])];
},
'getDiscount' => function() use ($order) {
return [$order['discount']];
},
'getTax' => function() use ($order) {
return [$order['tax']];
},
], 'data');
$total = LuaEngine::execute('
local subtotal = 0
local itemsCount = data.getItemsCount()
for i = 1, itemsCount do
subtotal = subtotal + data.getItemPrice(i) * data.getItemQuantity(i)
end
local discount = subtotal * data.getDiscount()
local afterDiscount = subtotal - discount
local tax = afterDiscount * data.getTax()
return afterDiscount + tax
');
echo "Total: $" . number_format($total, 2);
// Total: $35.64use DiogoGraciano\LuaEngine\Facades\LuaEngine;
// Register only one function inside a library (creates the lib if needed)
LuaEngine::registerFunction('triple', function($x) {
return [$x * 3];
}, 'mathx');
$value = LuaEngine::execute('return mathx.triple(7)');
// Result: 21
// You can keep adding more functions to the same library
LuaEngine::registerFunction('inc', fn($x) => [$x + 1], 'mathx');
$next = LuaEngine::execute('return mathx.inc(21)');
// Result: 22Execute a Lua script and return the result.
Parameters:
$script- Lua code to execute
Returns: The execution result (first element if array, otherwise the result as-is)
Note: To pass data to Lua scripts, use registerLibrary() to register PHP functions that provide access to your data.
Example:
$result = LuaEngine::execute('return 42');Evaluate a Lua expression and return a boolean result. Used for conditional checks.
Parameters:
$script- Lua expression (will be wrapped withreturn (...))
Returns: true or false
Note: To pass data to Lua expressions, use registerLibrary() to register PHP functions that provide access to your data.
Example:
// Note: Functions must return arrays (LuaSandbox requirement)
LuaEngine::registerLibrary('data', [
'getAge' => function() { return [20]; }, // Return as array
]);
$valid = LuaEngine::evaluate('data.getAge() >= 18');
// Result: trueCheck if a Lua script has valid syntax without executing it.
Parameters:
$script- Lua code to validate
Returns: true if valid, false otherwise
Example:
if (LuaEngine::validate($userScript)) {
$result = LuaEngine::execute($userScript);
}Register a set of PHP functions as a Lua library.
This method wraps LuaSandbox::registerLibrary().
Parameters:
$functions- An associative array where each key is a function name and each value is a corresponding PHP callable (function name string or callable)$libname- The name of the library. In the Lua state, the global variable of this name will be set to a table of functions
Important Notes:
- If a table with the same
$libnamealready exists in Lua, the new functions will be added to the existing table (not replaced) - Functions can be specified as:
- Callable closures:
function($x) { return [$x * 2]; } - Function name strings:
'myFunction'(references a PHP function by name)
- Callable closures:
- In Lua, functions are accessed as
libname.functionName() - Functions that return values must return arrays - the first element is used as the Lua return value
- Functions that don't need to return values can return nothing (void)
- Functions can throw
LuaSandboxRuntimeErroror other LuaSandbox exceptions
Example:
// Define a PHP function
function frobnosticate($v) {
return [$v + 42]; // Return array for values
}
// Register library with both callables and function name strings
LuaEngine::registerLibrary([
'frobnosticate' => 'frobnosticate', // Function name as string
'output' => function($string) { // Void function - no return needed
echo "$string\n";
},
'add' => function($a, $b) { // Returns value - must return array
return [$a + $b];
},
'error' => function() { // Can throw exceptions
throw new \LuaSandboxRuntimeError("Something is wrong");
},
'multiply' => fn($a, $b) => [$a * $b], // Arrow function
], 'php');
// Use in Lua - functions are accessible as php.functionName()
$result = LuaEngine::execute('return php.frobnosticate(100)'); // 142
LuaEngine::execute('php.output("Hello from Lua")'); // Prints: Hello from Lua
$sum = LuaEngine::execute('return php.add(5, 3)'); // 8
// Add more functions to the same library (extends existing)
LuaEngine::registerLibrary('php', [
'subtract' => function($a, $b) {
return [$a - $b];
},
]);
// Now php library has: frobnosticate, output, add, error, multiply, and subtractLoad Lua code into a function without executing it.
This method wraps LuaSandbox::loadString().
Parameters:
$code- Lua code string
Returns: LuaSandboxFunction on success, false on failure
Example:
$func = LuaEngine::loadString('return 42');
if ($func !== false) {
$result = $func->call();
// Result: [42]
}Call a function in a Lua global variable.
This method wraps LuaSandbox::callFunction().
Parameters:
$name- Lua function name (can contain "." for nested access like "string.match")...$args- Variable arguments to pass to the function
Returns: Array of return values or false on failure
Example:
// Define function in Lua
LuaEngine::execute('_G["add"] = function(a, b) return a + b end');
// Call the function
$result = LuaEngine::callFunction('add', 5, 3);
// Result: [8]Wrap a PHP callable for use in Lua.
This method wraps LuaSandbox::wrapPhpFunction().
Parameters:
$function- PHP callable to wrap
Returns: LuaSandboxFunction or false on failure
Example:
$phpFunc = LuaEngine::wrapPhpFunction(function($x) {
return [$x * 2];
});
if ($phpFunc !== false) {
// Call directly from PHP
$ret = $phpFunc->call(21); // returns array [42]
}
// Tip: For exposing PHP functions to Lua, prefer registerFunction/registerLibrary
LuaEngine::registerFunction('double', function($x) { return [$x * 2]; }, 'custom');
$result = LuaEngine::execute('return custom.double(21)'); // 42Register a single PHP function inside a Lua library (creates the library if it doesn't exist yet).
Parameters:
$functionName- Function name as exposed to Lua$function- PHP callable that returns an array when returning a value$libName- Library name (table in Lua)
Example:
LuaEngine::registerFunction('triple', fn($x) => [$x * 3], 'mathx');
$res = LuaEngine::execute('return mathx.triple(7)'); // 21Get all registered functions for a specific library.
Get all libraries and their registered functions.
Check whether a function is registered within a given library.
Execute a Lua script from a file.
Parameters:
$filePath- Path to Lua file
Returns: Execution result
Note: To pass data to Lua scripts, use registerLibrary() before calling this method to register PHP functions that provide access to your data.
Throws: Exception if file not found
Example:
$result = LuaEngine::executeFile('/path/to/script.lua');Set the memory limit for the Lua environment.
This method wraps LuaSandbox::setMemoryLimit().
Parameters:
$bytes- Memory limit in bytes
Example:
LuaEngine::setMemoryLimit(128 * 1024 * 1024); // 128MBSet the CPU time limit for the Lua environment.
This method wraps LuaSandbox::setCPULimit().
Parameters:
$seconds- CPU limit in seconds (0 or false = no limit)
Example:
LuaEngine::setCPULimit(60.0); // 60 secondsGet the underlying LuaSandbox instance for advanced usage.
Returns: LuaSandbox instance or null if not available
Example:
$sandbox = LuaEngine::getSandbox();
if ($sandbox) {
// Access advanced LuaSandbox methods directly
// See: https://www.php.net/manual/en/class.luasandbox.php
$memoryUsage = $sandbox->getMemoryUsage();
$cpuUsage = $sandbox->getCPUUsage();
}Get the last error message that occurred.
Returns: Last error message or null
Example:
try {
LuaEngine::execute('invalid syntax');
} catch (Exception $e) {
$error = LuaEngine::getLastError();
}Check if the LuaSandbox extension is available.
Returns: true if available, false otherwise
Example:
if (LuaEngine::isAvailable()) {
// Use Lua engine
}This package uses the PHP LuaSandbox extension, which provides a secure sandboxed Lua environment by default.
LuaSandbox automatically disables dangerous Lua functions and modules:
- ❌
os- Most operating system functions are disabled⚠️ Exceptions:os.clock(),os.date(),os.difftime(),os.time()are available
- ❌
io- All file I/O operations are disabled - ❌
require,package,module- Module loading and package management disabled - ❌
dofile,loadfile- File operations disabled - ❌
load,loadstring- Dynamic code loading disabled - ❌
debug- Most debugging functions disabled⚠️ Exception:debug.traceback()is available
- ❌
print- Standard output disabled
These standard Lua modules are available and safe to use:
- ✅
math- Mathematical functions (math.sin,math.cos,math.floor, etc.) - ✅
string- String manipulation (string.upper,string.lower,string.match, etc.) - ✅
table- Table operations (table.insert,table.remove,table.concat, etc.)
LuaSandbox provides built-in resource limits to prevent resource exhaustion:
- CPU Time Limits: Controlled via
setCPULimit()or configuration optioncpu_limit - Memory Limits: Controlled via
setMemoryLimit()or configuration optionmemory_limit - Automatic Error Handling: LuaSandbox throws specific exceptions:
LuaSandboxTimeoutError- When CPU limit is exceededLuaSandboxMemoryError- When memory limit is exceededLuaSandboxSyntaxError- For syntax errorsLuaSandboxRuntimeError- For runtime errors
You can access additional LuaSandbox features through getSandbox():
- Monitor resource usage:
getMemoryUsage(),getCPUUsage(),getPeakMemoryUsage() - Enable profiling:
enableProfiler(),disableProfiler(),getProfilerFunctionReport() - Load precompiled binaries:
loadBinary() - Pause/unpause CPU timer:
pauseUsageTimer(),unpauseUsageTimer()
For complete documentation, see the official PHP LuaSandbox documentation.
This package provides comprehensive error handling that wraps LuaSandbox exceptions with user-friendly messages.
The following exceptions may be thrown:
LuaSandboxSyntaxError- Caught and wrapped inExceptionwith message "Lua syntax error: ..."LuaSandboxRuntimeError- Caught and wrapped inExceptionwith message "Lua runtime error: ..."LuaSandboxTimeoutError- Caught and wrapped inExceptionwith message "Lua execution timeout: ..."LuaSandboxMemoryError- Caught and wrapped inExceptionwith message "Lua memory limit exceeded: ..."LuaSandboxError- Generic error caught and wrapped inExceptionwith message "Lua script error: ..."
Errors are automatically logged according to your configuration:
try {
$result = LuaEngine::execute('invalid syntax');
} catch (\Exception $e) {
// Error message
echo 'Error: ' . $e->getMessage();
// Get last error (same as exception message in this case)
echo 'Last error: ' . LuaEngine::getLastError();
// Error is also logged to Laravel logs based on log_level config
}If you need access to the original LuaSandbox exceptions, you can catch them directly:
use LuaSandbox\LuaSandboxSyntaxError;
use LuaSandbox\LuaSandboxRuntimeError;
use LuaSandbox\LuaSandboxTimeoutError;
use LuaSandbox\LuaSandboxMemoryError;
try {
$sandbox = LuaEngine::getSandbox();
$function = $sandbox->loadString('invalid syntax');
} catch (LuaSandboxSyntaxError $e) {
// Handle syntax error
echo 'Syntax error: ' . $e->getMessage();
}Run the test suite:
php vendor/bin/phpunitOr using composer scripts (if configured):
composer testTests require the LuaSandbox extension to be installed. If the extension is not available, tests will be skipped automatically.
Contributions are welcome! Please feel free to submit a Pull Request.
This package is open-sourced software licensed under the MIT license.