mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
feat: add Liquid filters where_exp and map_to_i
This commit is contained in:
parent
4af4bfe14a
commit
93dacb0baf
5 changed files with 629 additions and 3 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Liquid\Filters;
|
||||
|
||||
use App\Liquid\Utils\ExpressionUtils;
|
||||
use Keepsuit\Liquid\Filters\FiltersProvider;
|
||||
|
||||
/**
|
||||
|
|
@ -89,4 +90,47 @@ class Data extends FiltersProvider
|
|||
{
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a collection using an expression
|
||||
*
|
||||
* @param mixed $input The collection to filter
|
||||
* @param string $variable The variable name to use in the expression
|
||||
* @param string $expression The expression to evaluate
|
||||
* @return array The filtered collection
|
||||
*/
|
||||
public function where_exp(mixed $input, string $variable, string $expression): array
|
||||
{
|
||||
// Return input as-is if it's not an array or doesn't have values method
|
||||
if (! is_array($input)) {
|
||||
return is_string($input) ? [$input] : [];
|
||||
}
|
||||
|
||||
// Convert hash to array of values if needed
|
||||
if (ExpressionUtils::isAssociativeArray($input)) {
|
||||
$input = array_values($input);
|
||||
}
|
||||
|
||||
$condition = ExpressionUtils::parseCondition($expression);
|
||||
$result = [];
|
||||
|
||||
foreach ($input as $object) {
|
||||
if (ExpressionUtils::evaluateCondition($condition, $variable, $object)) {
|
||||
$result[] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array of strings to integers
|
||||
*
|
||||
* @param array $input Array of string numbers
|
||||
* @return array Array of integers
|
||||
*/
|
||||
public function map_to_i(array $input): array
|
||||
{
|
||||
return array_map('intval', $input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
159
app/Liquid/Utils/ExpressionUtils.php
Normal file
159
app/Liquid/Utils/ExpressionUtils.php
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace App\Liquid\Utils;
|
||||
|
||||
/**
|
||||
* Utility class for parsing and evaluating expressions in Liquid filters
|
||||
*/
|
||||
class ExpressionUtils
|
||||
{
|
||||
/**
|
||||
* Check if an array is associative
|
||||
*/
|
||||
public static function isAssociativeArray(array $array): bool
|
||||
{
|
||||
if (empty($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_keys($array) !== range(0, count($array) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a condition expression into a structured format
|
||||
*/
|
||||
public static function parseCondition(string $expression): array
|
||||
{
|
||||
$expression = mb_trim($expression);
|
||||
|
||||
// Handle logical operators (and, or)
|
||||
if (str_contains($expression, ' and ')) {
|
||||
$parts = explode(' and ', $expression, 2);
|
||||
|
||||
return [
|
||||
'type' => 'and',
|
||||
'left' => self::parseCondition(mb_trim($parts[0])),
|
||||
'right' => self::parseCondition(mb_trim($parts[1])),
|
||||
];
|
||||
}
|
||||
|
||||
if (str_contains($expression, ' or ')) {
|
||||
$parts = explode(' or ', $expression, 2);
|
||||
|
||||
return [
|
||||
'type' => 'or',
|
||||
'left' => self::parseCondition(mb_trim($parts[0])),
|
||||
'right' => self::parseCondition(mb_trim($parts[1])),
|
||||
];
|
||||
}
|
||||
|
||||
// Handle comparison operators
|
||||
$operators = ['>=', '<=', '!=', '==', '>', '<', '='];
|
||||
|
||||
foreach ($operators as $operator) {
|
||||
if (str_contains($expression, $operator)) {
|
||||
$parts = explode($operator, $expression, 2);
|
||||
|
||||
return [
|
||||
'type' => 'comparison',
|
||||
'left' => mb_trim($parts[0]),
|
||||
'operator' => $operator === '=' ? '==' : $operator,
|
||||
'right' => mb_trim($parts[1]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// If no operator found, treat as a simple expression
|
||||
return [
|
||||
'type' => 'simple',
|
||||
'expression' => $expression,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a condition against an object
|
||||
*/
|
||||
public static function evaluateCondition(array $condition, string $variable, mixed $object): bool
|
||||
{
|
||||
switch ($condition['type']) {
|
||||
case 'and':
|
||||
return self::evaluateCondition($condition['left'], $variable, $object) &&
|
||||
self::evaluateCondition($condition['right'], $variable, $object);
|
||||
|
||||
case 'or':
|
||||
return self::evaluateCondition($condition['left'], $variable, $object) ||
|
||||
self::evaluateCondition($condition['right'], $variable, $object);
|
||||
|
||||
case 'comparison':
|
||||
$leftValue = self::resolveValue($condition['left'], $variable, $object);
|
||||
$rightValue = self::resolveValue($condition['right'], $variable, $object);
|
||||
|
||||
return match ($condition['operator']) {
|
||||
'==' => $leftValue === $rightValue,
|
||||
'!=' => $leftValue !== $rightValue,
|
||||
'>' => $leftValue > $rightValue,
|
||||
'<' => $leftValue < $rightValue,
|
||||
'>=' => $leftValue >= $rightValue,
|
||||
'<=' => $leftValue <= $rightValue,
|
||||
default => false,
|
||||
};
|
||||
|
||||
case 'simple':
|
||||
$value = self::resolveValue($condition['expression'], $variable, $object);
|
||||
|
||||
return (bool) $value;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a value from an expression, variable, or literal
|
||||
*/
|
||||
public static function resolveValue(string $expression, string $variable, mixed $object): mixed
|
||||
{
|
||||
$expression = mb_trim($expression);
|
||||
|
||||
// If it's the variable name, return the object
|
||||
if ($expression === $variable) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
// If it's a property access (e.g., "n.age"), resolve it
|
||||
if (str_starts_with($expression, $variable.'.')) {
|
||||
$property = mb_substr($expression, mb_strlen($variable) + 1);
|
||||
if (is_array($object) && array_key_exists($property, $object)) {
|
||||
return $object[$property];
|
||||
}
|
||||
if (is_object($object) && property_exists($object, $property)) {
|
||||
return $object->$property;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to parse as a number
|
||||
if (is_numeric($expression)) {
|
||||
return str_contains($expression, '.') ? (float) $expression : (int) $expression;
|
||||
}
|
||||
|
||||
// Try to parse as boolean
|
||||
if (in_array(mb_strtolower($expression), ['true', 'false'])) {
|
||||
return mb_strtolower($expression) === 'true';
|
||||
}
|
||||
|
||||
// Try to parse as null
|
||||
if (mb_strtolower($expression) === 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return as string (remove quotes if present)
|
||||
if ((str_starts_with($expression, '"') && str_ends_with($expression, '"')) ||
|
||||
(str_starts_with($expression, "'") && str_ends_with($expression, "'"))) {
|
||||
return mb_substr($expression, 1, -1);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue