Search by

lossik / replacer

Lossik

Replaces '$object->property' strings with values of registered objects and evaluates simple functions.

Package info

github.com/Lossik/replacer

pkg:composer/lossik/replacer

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.1.0 2026-10-22 01:42 UTC

This package is auto-updated.

Last update: 2026-10-22 01:51:15 UTC


README

Replaces '$object->property' strings with values of registered objects and evaluates simple functions written as arrays, e.g. ['eqval()', '$user->age', 30]. Useful for configuration-driven conditions and values.

Installation

composer require lossik/replacer

Supported PHP

branch minimum patch version
8.2 8.2.35
8.3 8.3.36
8.4 8.4.27
8.5 8.5.12

Older patch versions are vulnerable to PHP2Shell (CVE-2026-109618, CVE-2026-109619) and are not supported. composer.json requires only php: ^8.2, because distributions backport security fixes without bumping the version number; the CI enforces the patched minimums above. Projects on older PHP stay on 1.0.x.

Quick start

<?php

require 'vendor/autoload.php';

use Lossik\Replacer\Functions;
use Lossik\Replacer\Replacer;

$replacer = (new Replacer())
    ->addObject((object) ['name' => 'Alice', 'age' => 30], 'user')
    ->addObject(['city' => 'Prague'], 'address');

echo json_encode($replacer->replace('$user->name')), "\n";         // "Alice"
echo json_encode($replacer->replace('$address->city')), "\n";      // "Prague"
echo json_encode($replacer->replace('$nobody', false, '-')), "\n"; // "-"

$functions = new Functions($replacer);

echo json_encode($functions->callFunction(['eqval()', '$user->age', 30])), "\n";                   // true
echo json_encode($functions->callFunction(['concat()', '$user->name', ', ', '$address->city'])), "\n"; // "Alice, Prague"
echo json_encode($functions->callFunction(['and()', ['is()', '$user->name'], ['no()', false]])), "\n";  // true

API

Lossik\Replacer\Replacer

An expression is a string $name (the whole registered value) or $name->property (an object property or an array key). Only one -> is allowed.

method description
addObject($obj, $name) registers an object, array, scalar or null under $name (without $); fluent
removeObject($name) removes it again; the name as passed to addObject(), optionally with a leading $ ('row' or '$row'; an object added as addObject($o, '$x') is stored as $$x); fluent
isReplaceable($string, $syntaxOnly = true) checks the syntax; with $syntaxOnly = false also that the object is registered and has the property / key
replace($string, $need = true, $default = null) returns the value; when the expression cannot be replaced it throws LogicException, or returns $default if $need is false
Replacer::replaceable($string) static syntax check, same as isReplaceable($string)
<?php

require 'vendor/autoload.php';

use Lossik\Replacer\Replacer;

$replacer = (new Replacer())->addObject(['0' => 'zero', '' => 'empty', 'x' => 1], 'row');

echo json_encode(Replacer::replaceable('$row->x')), "\n";           // true
echo json_encode(Replacer::replaceable('$a->b->c')), "\n";          // false
echo json_encode($replacer->isReplaceable('$row->y', false)), "\n"; // false
echo json_encode($replacer->replace('$row->0')), "\n";              // "zero"
echo json_encode($replacer->replace('$row->')), "\n";               // "empty"

$replacer->removeObject('row');
echo json_encode($replacer->replace('$row', false, 'GONE')), "\n";  // "GONE"

Lossik\Replacer\Functions

A call is an array whose first element is the function name with (), followed by the arguments: ['name()', arg1, arg2, …]. Before the call, each argument that is itself a call is evaluated, and each argument that is an expression is replaced (replace($arg, false), so a missing variable becomes null, see below). Other arguments are passed as they are.

method description
__construct(Replacer $replacer) registers the built-in functions; expressions are resolved by $replacer
callFunction($function) evaluates the call; throws LogicException when $function is not a call or the name is not registered
isFunction($function) checks the syntax of a call (not whether the name is registered)
addFunction($callable, $funcName) registers a callable under $funcName (without ()); fluent
Functions::isFunctionS($string) static syntax check, same as isFunction()

Built-in functions:

function result
is(value) (bool) value
no(value) !value
eqval(a, b) a === b
no_eqval(a, b) a !== b
strpos_is(haystack, needle, position) strpos(haystack, needle) === position
in(value, array) in_array(value, array) (loose comparison)
group_concat(array, delimiter) implode(delimiter, array)
column(rows, key) list of unique non-empty values of key in the rows (arrays or objects)
explode(delimiter, string) explode(delimiter, string)
get_value(arrayOrObject, key) the array key or object property, null when missing
remove(string, search) string without all occurrences of search
concat(a, …) all arguments concatenated
and(…) true when all arguments are truthy
or(…) true when any argument is truthy
<?php

require 'vendor/autoload.php';

use Lossik\Replacer\Functions;
use Lossik\Replacer\Replacer;

$functions = (new Functions(new Replacer()))->addFunction(fn ($a, $b) => $a + $b, 'sum');

echo json_encode($functions->callFunction(['sum()', 1, 2])), "\n";                          // 3
echo json_encode($functions->callFunction(['is()', ['no()', false]])), "\n";                // true
echo json_encode($functions->callFunction(['strpos_is()', 'abc', 'b', 1])), "\n";           // true
echo json_encode($functions->callFunction(['column()', [['id' => 1], ['id' => 1], (object) ['id' => 3]], 'id'])), "\n"; // [1,3]
echo json_encode(Functions::isFunctionS(['nope()'])), "\n";                                 // true

Missing variables in function arguments

An expression whose object (or property) is not registered is passed to the function as null. The built-in functions accept such a null without a warning; any other wrong type fails as before.

function missing argument result
is, no, eqval, no_eqval, concat, and, or any null is used as the value
strpos_is haystack or needle treated as '', so a missing needle with expected position 0 gives true
in array false
in value loose comparison (in_array): matches any element loosely equal to null — '', 0, 0.0, false, null, []
group_concat array ''
group_concat delimiter treated as '': the values joined without a separator
column rows / key [] / lookup of the key ''
explode delimiter [] (a literal '' delimiter still throws ValueError)
explode string ['']
get_value array null
get_value key lookup of the key ''
remove string ''
remove search treated as '': the string is returned unchanged
<?php

require 'vendor/autoload.php';

use Lossik\Replacer\Functions;
use Lossik\Replacer\Replacer;

$functions = new Functions(new Replacer());

echo json_encode($functions->callFunction(['in()', '$missing', ['', 'a']])), "\n";      // true
echo json_encode($functions->callFunction(['in()', 'a', '$missing'])), "\n";            // false
echo json_encode($functions->callFunction(['strpos_is()', 'abc', '$missing', 0])), "\n"; // true
echo json_encode($functions->callFunction(['explode()', '$missing', 'a,b'])), "\n";     // []
echo json_encode($functions->callFunction(['explode()', ',', '$missing'])), "\n";       // [""]
echo json_encode($functions->callFunction(['group_concat()', ['a', 'b'], '$missing'])), "\n"; // "ab"
echo json_encode($functions->callFunction(['remove()', 'abc', '$missing'])), "\n";      // "abc"

Lossik\Replacer\TReplacer

A trait for classes that resolve configuration values: initReplacer() creates $this->replacer and $this->functions; getValue($item, $need = true) returns the replaced expression, the result of a call, or $item unchanged.

<?php

require 'vendor/autoload.php';

use Lossik\Replacer\TReplacer;

final class Condition
{
    use TReplacer;

    public function __construct(array $row)
    {
        $this->initReplacer();
        $this->replacer->addObject($row, 'row');
    }

    public function evaluate(mixed $item): mixed
    {
        return $this->getValue($item);
    }
}

$condition = new Condition(['state' => 'active']);

echo json_encode($condition->evaluate('$row->state')), "\n";                       // "active"
echo json_encode($condition->evaluate(['eqval()', '$row->state', 'active'])), "\n"; // true
echo json_encode($condition->evaluate('plain')), "\n";                              // "plain"

Lossik\Utils\Arrays

Eight static helpers: assoc, getValue, difference, leftMerge, column, unsetNull, first, unsetEach. Their behaviour is characterized by tests/ArraysTest.php; rely only on what the tests hold. column() returns the unique non-empty values of a column (falsy values such as 0 and '' are dropped).

<?php

require 'vendor/autoload.php';

use Lossik\Utils\Arrays;

$rows = [['id' => 1], ['id' => 2], ['id' => 1], ['id' => 0], (object) ['id' => 3]];

echo json_encode(array_values(Arrays::column($rows, 'id'))), "\n";             // [1,2,3]
echo json_encode(Arrays::getValue(['k' => 'v'], 'k')), "\n";                   // "v"
echo json_encode(Arrays::first(['b' => 2, 'c' => 3], ['a', 'b', 'c'])), "\n"; // 2

Known issues

  • concat() without any argument (Functions::_concat()) throws ArgumentCountError.
  • Properties are looked up with property_exists(): only real (declared or dynamic) properties and array keys are visible. Magic properties served by __get() are not, so $row->name on such an object cannot be replaced and get_value() / column() return null / []; ArrayAccess objects are not read as arrays.

License

MIT, see LICENSE.