infobipcth / forms-computed-language
A PHP library for safely interpreting user-inputted arbitrary code, specifically designed for supporting logic in forms.
Installs: 6
Dependents: 0
Suggesters: 0
Security: 0
Stars: 3
Watchers: 3
Forks: 0
Open Issues: 5
pkg:composer/infobipcth/forms-computed-language
Requires
- php: ^8.3
- nikic/php-parser: ^5.1
Requires (Dev)
- pestphp/pest: ^2.28
- php-parallel-lint/php-console-highlighter: ^1.0.0
- php-parallel-lint/php-parallel-lint: ^1.3.2
- squizlabs/php_codesniffer: ^3.9
- dev-main
- 2.7.1
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.0
- 2.3.0
- 2.2.0
- 2.1.0
- 2.0.0
- v1.0
- v0.02
- v0.01
- dev-renovate/actions-checkout-6.x
- dev-renovate/pestphp-pest-4.x
- dev-feature/update-composer-type
- dev-feature/oss-preparation
- dev-feature/improvements-to-types
- dev-feature/casting-and-improvements
- dev-feature/numberformat
- dev-feature/modify-functions
- dev-feature/externally-provided-functions
- dev-feature/workflow-additions
This package is auto-updated.
Last update: 2025-12-20 17:41:26 UTC
README
Forms Computed Language (FCL) is an interpreted language designed to be safe to execute when the code is arbitrary user input, while allowing users to manipulate variables, use flow control features and run functions.
FCL is based on PHP syntax and relies on @nikic/php-parser to produce an abstract syntax tree, while reimplementing an evaluator for a subset of PHP's tokens in PHP itself.
Supported features and tokens
- Scalar variables (numeric, boolean and string types)
- Arrays and
foreachloops without references - Fetching constants from PHP
- Arithmetic and logical operators (
+, -, /, *, !, &&, ||) - Assignment operators (
+=, .=etc.) - Comparision operators (
<, <=, ==), string concatenation if/elseif/elseblocks- The ternary
if ? then : elseoperator - Unary plus and minus (e.g.
-1, +1are valid) - Function calls to FCL-provided functions (currently,
countSelectedItems,roundandisSelected) andFunctionStorefunctions
Notably missing or different
++,--and===operators (an easy PR :))switchandmatchblocks- User-defined functions (developers integrating FCL can use
FunctionStoreto provide custom functions) - OOP and namespaces
- References and unpacking
- Superglobals (
$_GETetc.) - Output to stdio, files etc. (you can not echo anything)
- Anonymous arrays in loops (e.g.
foreach([1, 2, 3] as $value){...})
Getting started
You can install FCL using Composer by running a composer require command in your project:
composer require infobipcth/forms-computed-language
As with any other package, you can also install it by adding it manually to composer.json or installing it manually by downloading a release.
Running FCL code
Basic example of running FCL code:
$lr = LanguageRunner::getInstance(); $lr->setCode('$a = round($a);'); $lr->setVars(['a' => 3.14]); $lr->evaluate(); // ['a' => 3] var_dump($lr->getVars());
Constants and security
IMPORTANT SECURITY NOTE: for booleans to work, and so that users can use constants such as PHP_ROUND_UP etc., you need to have some sort of access to constants (at least true and false constants). HOWEVER, if your project contains sensitive information in constants or PHP is exposing sensitive constants, this will prove to be a security risk!
To mitigate this, you can provide a list of allowed or disallowed constants to the Language Runner prior to code evaluation.
Disallowlist example:
$lr = LanguageRunner::getInstance(); $lr->setCode('$a = DB_USER;'); $lr->setVars([]); $lr->setDisallowedConstants(['DB_USER', 'DB_HOST', 'DB_PASSWORD', 'DB_NAME']); // IMPORTANT: IF YOU DO NOT SET CONSTANT BEHAVIOUR ALL CONSTANTS ARE ALLOWED! $lr->setConstantBehaviour('blacklist'); // throws FormsComputedLanguage\Exceptions\UndeclaredVariableUsageException $lr->evaluate(); var_dump($lr->getVars());
Allowlist example - throws an error when a non-allowlisted constant is accessed:
$lr = LanguageRunner::getInstance(); $lr->setCode('$a = DB_USER;'); $lr->setVars([]); $lr->setAllowedConstants(['true', 'false']); // IMPORTANT: IF YOU DO NOT SET CONSTANT BEHAVIOUR ALL CONSTANTS ARE ALLOWED! $lr->setConstantBehaviour('whitelist'); // throws FormsComputedLanguage\Exceptions\UndeclaredVariableUsageException $lr->evaluate(); var_dump($lr->getVars());
Misconfiguration example - DO NOT USE!:
$lr = LanguageRunner::getInstance(); $lr->setCode('$a = DB_USER;'); $lr->setVars([]); // wrong wrong wrong $lr->setDisallowedConstants(['true', 'false']); // very wrong $lr->setConstantBehaviour('blacklist'); // does not throw $lr->evaluate(); // ['a' => 'root'] var_dump($lr->getVars());
Writing FCL code
You can write FCL code similarly as you would write PHP. You can use all of the defined tokens, if for flow control and call our functions.
A notable difference is that FCL does not require an opening tag (no need to write <?php or similar).
Defining callee-provided functions (FunctionStore)
Users can not define functions on the fly, but the calling program can define additional functions that are available to users. These are shared across all language runner instances, and aren't isolated in any way.
You can do this in your project at any time prior to evaluating an FCL program.
Rules:
- You can not redeclare functions
- You can not override standard library functions
- You always get an array of arguments
- You need to throw
FormsComputedLanguage\Exceptions\ArgumentCountExceptionif the count of args is wrong - You need to throw
FormsComputedLanguage\Exceptions\TypeExceptionif argument type is wrong
Example:
<?php namespace MyCustomFunctions; use FormsComputedLanguage\Functions\FunctionInterface; use FormsComputedLanguage\Exceptions\ArgumentCountException; use FormsComputedLanguage\Exceptions\TypeException; $testFunction = new class implements FunctionInterface { public const string FUNCTION_NAME = 'testFunction'; public static function getName(): string { return self::FUNCTION_NAME; } public static function getArguments(): array { return [ '$firstNum' => 'int|float', '$secondNum' => 'int|float', ]; } public static function run(array $args) { if (count($args) !== 2) { throw new ArgumentCountException("The function expects exactly two arguments!"); } if (!is_numeric($args[0]) || !is_numeric($args[1])) { throw new TypeException("The function arguments must be numeric!"); } return $args[0] + $args[1]; } }; FunctionStore::addFunction($testFunction::FUNCTION_NAME, $testFunction); $this->languageRunner->setCode('$a = testFunction(1, 2);'); $this->languageRunner->evaluate();
Developer Tools
FCL includes command-line tools to help you debug and test your FCL programs:
fcldump.php
Dumps the Abstract Syntax Tree (AST) of an FCL file for debugging purposes.
php fcldump.php <file.fcl> <variables.json>
Example:
php fcldump.php scratches/min.fcl scratches/min.vars.json
fcleval.php
Evaluates an FCL file with provided variables and outputs the resulting variable state as JSON.
php fcleval.php <file.fcl> <variables.json>
Example:
php fcleval.php scratches/a_program.fcl scratches/a_program.vars.json
Testing
FCL uses Pest for testing with comprehensive test coverage.
Running Tests
# Run all tests (standards, linting, and unit tests) composer test # Run only unit tests composer test:unit # Run code standards check with PHPCS composer test:standards # Run syntax linting composer test:lint # Run tests with coverage report composer test:coverage
Test Structure
Tests are organized in the tests/Unit/ directory and cover:
- ArrayTest.php - Array operations and manipulation
- CastingTest.php - Type casting functionality
- ControlFlowTest.php - If/elseif/else and ternary operators
- ControlStructuresTest.php - Complex control structures
- DisallowBehaviorTest.php - Constant allowlist/disallowlist security
- FunctionsTest.php - Built-in and custom functions
- LoopTest.php - Foreach loops and iteration
- OperatorsTest.php - Arithmetic, logical, and comparison operators
- ProgramTest.php - Complete program execution
- UnknownTokensTest.php - Error handling for unsupported syntax
Writing Tests
All tests extend Pest's test case and automatically configure a LanguageRunner instance with safe defaults:
test('your test description', function () { $this->languageRunner->setCode('$a = 2 + 2;'); $this->languageRunner->setVars([]); $this->languageRunner->evaluate(); expect($this->languageRunner->getVars())->toBe(['a' => 4]); });
Contributing
We welcome all contributions to Forms Computed Language!
Here's how you can help:
Getting Started
- Fork the repository
- Clone your fork:
git clone https://github.com/your-username/forms-computed-language.git - Install dependencies:
composer install - Create a feature branch:
git checkout -b feature/your-feature-name
Development Workflow
- Write tests first - Add tests for new features or bug fixes in the appropriate test file
- Implement your changes - Follow the existing code style and patterns
- Run the test suite - Ensure all tests pass:
composer test - Check code standards - Fix any style issues:
composer test:standards - Commit your changes - Use clear, descriptive commit messages
- Push and create a PR - Submit a pull request with a description of your changes
Code Style
- Follow PSR-12 coding standards, but with tabs (see phpcs.xml)
- Use meaningful variable and method names
- Add PHPDoc comments for public methods
- Keep methods focused and single-purpose
Adding New Features
Adding a new operator or token:
- Create a new Visitor in
src/Visitors/implementingVisitorInterface - Add the visitor to
Evaluator.phpin the appropriateenterNode()orleaveNode()section - Add comprehensive tests in
tests/Unit/
Adding a new built-in function:
- Create a new function class in
src/Functions/implementingFunctionInterface - Register it in
FuncCallVisitor::FUNCTION_CALLBACKS - Add tests in
tests/Unit/FunctionsTest.php
Pull Request Guidelines
- Ensure your PR has a clear title and description
- Reference any related issues
- Include tests for new functionality
- Make sure all CI checks pass
- Keep PRs focused on a single feature or fix
- Update documentation if needed
Reporting Issues
When reporting bugs, please include:
- FCL code that reproduces the issue
- Expected behavior
- Actual behavior
- PHP version and environment details
Security
If you discover a security vulnerability which is unsuitable to be reported publicly, please email us at web@infobip.com or use the Infobip Coordinated Vulnerability Disclosure program.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation
For detailed documentation, please see the docs directory:
- Integrating FCL into Your Project
- Understanding How FCL Evaluates a Program
- FCL Internals
- Developer Tools
- Debugging FCL
Note that the documentation is AI-assisted and might be inaccurate at times. If you have any questions, feel free to open an issue.
About
This project is maintained by Infobip's Creative Tech Hub - the team behind infobip.com.