> ## 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.

# Custom functions

> Extend FiQueLa with your own scalar or aggregate functions and register them with the global FunctionRegistry.

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

| Interface           | Namespace                              | Use when                                                                            |
| ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------- |
| `ScalarFunction`    | `FQL\Functions\Core\ScalarFunction`    | Your function runs once per row (e.g. `LOWER`, `CONCAT`, `ROUND`).                  |
| `AggregateFunction` | `FQL\Functions\Core\AggregateFunction` | Your 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.

```php theme={null}
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.

```php theme={null}
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.

```php theme={null}
use FQL\Functions\FunctionRegistry;

FunctionRegistry::register(CustomSuffix::class);
FunctionRegistry::register(StdDev::class);
```

The registry exposes a small public API:

| Method                          | Description                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------ |
| `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

```sql theme={null}
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:

```php theme={null}
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');
```

<Note>
  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.
</Note>

## Migrating from 2.x

If you wrote custom functions for FiQueLa 2.x, the following pieces have been removed and replaced:

| 2.x                                                                                                                                                                                                           | 3.0 replacement                                                                           |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `BaseFunction`, `SingleFieldFunction`, `MultipleFieldsFunction`, `NoFieldFunction`, abstract `AggregateFunction`, `BaseFunctionByReference`, `SingleFieldFunctionByReference`, `SingleFieldAggregateFunction` | Implement `Functions\Core\ScalarFunction` or `Functions\Core\AggregateFunction` directly. |
| `Interface\Invokable`, `InvokableAggregate`, `InvokableByReference`, `InvokableNoField`, `IncrementalAggregate`                                                                                               | New static-only contracts in `Functions\Core`.                                            |
| `Interface\Query::custom(Function $fn)`                                                                                                                                                                       | `FunctionRegistry::register(MyFn::class)` at bootstrap.                                   |
| `applyValue()` / `applyValues()` instance methods                                                                                                                                                             | A single static `execute(...)` method.                                                    |
