formula func variable aware

This commit is contained in:
Yuri Kuznetsov
2024-04-10 10:54:33 +03:00
parent 98253fb0a8
commit a84962d96d
6 changed files with 170 additions and 30 deletions

View File

@@ -85,7 +85,9 @@ class Argument implements Evaluatable
}
if ($this->data instanceof Variable) {
return new ArgumentList([$this->data->getName()]);
$value = new Value($this->data->getName());
return new ArgumentList([$value]);
}
if ($this->data instanceof Attribute) {

View File

@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Formula;
use Espo\Core\Formula\Exceptions\Error;
/**
* A function aware of variables.
*
* @since 8.3.0
*/
interface FuncVariablesAware
{
/**
* Process.
*
* @throws Error
*/
public function process(EvaluatedArgumentList $arguments, Variables $variables): mixed;
}

View File

@@ -30,7 +30,6 @@
namespace Espo\Core\Formula;
use Espo\Core\Formula\Exceptions\UnknownFunction;
use Espo\Core\Formula\Functions\Base;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\ORM\Entity;
@@ -59,8 +58,12 @@ class FunctionFactory
/**
* @throws UnknownFunction
*/
public function create(string $name, ?Entity $entity = null, ?stdClass $variables = null): Func|BaseFunction|Base
{
public function create(
string $name,
?Entity $entity = null,
?stdClass $variables = null
): Func|FuncVariablesAware|BaseFunction|Base {
if ($this->classNameMap && array_key_exists($name, $this->classNameMap)) {
$className = $this->classNameMap[$name];
}
@@ -77,7 +80,7 @@ class FunctionFactory
$typeName = implode('\\', $arr);
/** @var class-string<Func|BaseFunction|Base> $className */
/** @var class-string<Func|FuncVariablesAware|BaseFunction|Base> $className */
$className = 'Espo\\Core\\Formula\\Functions\\' . $typeName . 'Type';
}
@@ -85,7 +88,12 @@ class FunctionFactory
throw new UnknownFunction("Unknown function: " . $name);
}
if ((new ReflectionClass($className))->implementsInterface(Func::class)) {
$class = new ReflectionClass($className);
if (
$class->implementsInterface(Func::class) ||
$class->implementsInterface(FuncVariablesAware::class)
) {
return $this->injectableFactory->create($className);
}

View File

@@ -29,18 +29,20 @@
namespace Espo\Core\Formula\Functions;
use Espo\Core\Formula\EvaluatedArgumentList;
use Espo\Core\Formula\Exceptions\Error;
use Espo\Core\Formula\ArgumentList;
use Espo\Core\Formula\FuncVariablesAware;
use Espo\Core\Formula\Variables;
class VariableType extends BaseFunction
class VariableType implements FuncVariablesAware
{
public function process(ArgumentList $args)
public function process(EvaluatedArgumentList $arguments, Variables $variables): mixed
{
if (!count($args)) {
if (!count($arguments)) {
throw new Error("No variable name.");
}
$name = $args[0]->getData();
$name = $arguments[0];
if (!is_string($name)) {
throw new Error("Bad variable name.");
@@ -50,10 +52,6 @@ class VariableType extends BaseFunction
throw new Error("Empty variable name.");
}
if (!property_exists($this->getVariables(), $name)) {
return null;
}
return $this->getVariables()->$name;
return $variables->tryGet($name);
}
}

View File

@@ -94,20 +94,8 @@ class Processor
$function = $this->functionFactory->create($item->getType(), $this->entity, $this->variables);
if ($function instanceof Func) {
$evaluatedArguments = array_map(
fn($item) => $this->process($item),
iterator_to_array($item->getArgumentList())
);
try {
return $function->process(new EvaluatedArgumentList($evaluatedArguments));
}
catch (TooFewArguments|BadArgumentType|BadArgumentValue $e) {
$message = sprintf('Function %s; %s', $item->getType(), $e->getLogMessage());
throw new Error($message);
}
if ($function instanceof Func || $function instanceof FuncVariablesAware) {
return $this->processFunc($item, $function);
}
if ($function instanceof DeprecatedBaseFunction) {
@@ -156,6 +144,7 @@ class Processor
/**
* @return mixed[]
* @throws Error
* @throws ExecutionException
*/
private function processList(ArgumentList $args): array
{
@@ -167,4 +156,33 @@ class Processor
return $list;
}
/**
* @throws Error
* @throws ExecutionException
*/
private function processFunc(Argument $item, Func|FuncVariablesAware $function): mixed
{
$rawEvaluatedArguments = array_map(
fn ($item) => $this->process($item),
iterator_to_array($item->getArgumentList())
);
try {
$evaluatedArguments = new EvaluatedArgumentList($rawEvaluatedArguments);
if ($function instanceof FuncVariablesAware) {
$variables = new Variables($this->variables ?? (object) []);
return $function->process($evaluatedArguments, $variables);
}
return $function->process($evaluatedArguments);
}
catch (TooFewArguments|BadArgumentType|BadArgumentValue $e) {
$message = sprintf('Function %s; %s', $item->getType(), $e->getLogMessage());
throw new Error($message);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Formula;
use Espo\Core\Formula\Exceptions\Error;
use stdClass;
class Variables
{
public function __construct(
private stdClass $variables
) {}
public function has(string $name): bool
{
return property_exists($this->variables, $name);
}
/**
* @throws Error
*/
public function get(string $name): mixed
{
if (!property_exists($this->variables, $name)) {
throw new Error("Variable $name is not defined.");
}
return $this->variables->$name;
}
public function tryGet(string $name): mixed
{
return $this->variables->$name ?? null;
}
public function set(string $name, mixed $value): void
{
$this->variables->$name = $value;
}
}