Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fiquela.io/llms.txt

Use this file to discover all available pages before exploring further.

As of FiQueLa 3.0, custom functions are static-only utility classes that implement one of two interfaces and are registered with the global FunctionRegistry. Once registered, they are available everywhere — both in the fluent API and in FQL string queries — and dispatch goes through the same expression evaluator as the built-ins.

Function contracts

InterfaceNamespaceUse when
ScalarFunctionFQL\Functions\Core\ScalarFunctionYour function runs once per row (e.g. LOWER, CONCAT, ROUND).
AggregateFunctionFQL\Functions\Core\AggregateFunctionYour function accumulates state across a group (e.g. SUM, AVG, GROUP_CONCAT).
Each class is a static-only container — no constructor, no __invoke. A scalar function exposes name() and a static execute(...). An aggregate function exposes name() plus static initial(), accumulate(...), and finalize(...) methods.

Example: scalar function

The following example appends a _custom suffix to any string value.
use FQL\Functions\Core\ScalarFunction;

final class CustomSuffix implements ScalarFunction
{
    public static function name(): string
    {
        return 'CUSTOM_SUFFIX';
    }

    public static function execute(mixed $value): string
    {
        return ((string) $value) . '_custom';
    }
}

Example: aggregate function

The following example computes a population standard deviation across a group.
use FQL\Functions\Core\AggregateFunction;

final class StdDev implements AggregateFunction
{
    public static function name(): string
    {
        return 'STDDEV';
    }

    public static function initial(): array
    {
        return ['count' => 0, 'sum' => 0.0, 'sumSquares' => 0.0];
    }

    public static function accumulate(array $state, mixed $value): array
    {
        if ($value === null) {
            return $state;
        }
        $value = (float) $value;
        $state['count']++;
        $state['sum'] += $value;
        $state['sumSquares'] += $value * $value;
        return $state;
    }

    public static function finalize(array $state): ?float
    {
        if ($state['count'] === 0) {
            return null;
        }
        $mean = $state['sum'] / $state['count'];
        return sqrt($state['sumSquares'] / $state['count'] - $mean * $mean);
    }
}

Registering your function

Register the class once at application bootstrap — typically right after the Composer autoloader. The registry is process-global and bootstraps from src/Functions/functions.neon for built-ins.
use FQL\Functions\FunctionRegistry;

FunctionRegistry::register(CustomSuffix::class);
FunctionRegistry::register(StdDev::class);
The registry exposes a small public API:
MethodDescription
register(class-string $class)Register a new scalar or aggregate function.
override(class-string $class)Replace an existing function (built-in or user-registered) with the same name.
unregister(string $name)Remove a previously registered function.
loadConfig(string $path)Bulk-load function classes from a NEON config file.
has(string $name)Check whether a function is registered.
isAggregate(string $name)Check whether a registered function is an aggregate.
all()Return the full registry map.
reset()Reset the registry to the built-in defaults.
setCacheDir(string $path)Configure the resolved class-map cache directory.
If you try to register a class that is missing one of the contracts, the registry throws FQL\Exception\FunctionRegistrationException. Calling an unknown function at runtime throws FQL\Exception\UnknownFunctionException.

Using a registered function

Once registered, the function is available by name in both APIs.

FQL string syntax

SELECT
  id,
  CUSTOM_SUFFIX(name) AS suffixed_name
FROM json(./data/products.json).data.products

Fluent API

The fluent helpers parse SQL expression strings, so you can call your custom function inline anywhere a built-in works:
use FQL\Query\Provider;

$query = Provider::fromFile('./data/products.json')
    ->from('data.products')
    ->select('id, CUSTOM_SUFFIX(name) AS suffixed_name')
    ->groupBy('category')
    ->select('STDDEV(price) AS price_stddev');
Custom functions are dispatched through Sql\Provider::parseExpression() together with built-ins. The same code path powers both the fluent helpers and the FQL string parser, so a registered function behaves identically in either form.

Migrating from 2.x

If you wrote custom functions for FiQueLa 2.x, the following pieces have been removed and replaced:
2.x3.0 replacement
BaseFunction, SingleFieldFunction, MultipleFieldsFunction, NoFieldFunction, abstract AggregateFunction, BaseFunctionByReference, SingleFieldFunctionByReference, SingleFieldAggregateFunctionImplement Functions\Core\ScalarFunction or Functions\Core\AggregateFunction directly.
Interface\Invokable, InvokableAggregate, InvokableByReference, InvokableNoField, IncrementalAggregateNew static-only contracts in Functions\Core.
Interface\Query::custom(Function $fn)FunctionRegistry::register(MyFn::class) at bootstrap.
applyValue() / applyValues() instance methodsA single static execute(...) method.