Skip to content

Recipes

The recipe language describes how fields (or more generally: variables) are evaluated. In the context of Brezel, recipes bind a fieldā€™s value to the values of other fields that are referenced in the recipe. For example, it allows you to automatically calculate totals in invoices, define field values inside workflows, and even evaluate expressions inside bakery planner configuration files.

You can test recipes in the Brezel CLI by opening the interactive shell or by opening the DevTools in the Brezel UI and evaluating the recipe in the console.

Terminal window
php bakery shell
> recipe("'Hello world!'")
= "Hello world!"

Syntax

The syntax of the recipe language is based on the EcmaScript syntax with a few additions. Supported are mathematical, logical and bitshift operations, function calls, property, subscript and method access and of course common literals.

Math and logic

Say we want a field total to always be the sum of two other fields net and tax. Then we define the recipe of total to be:

net + tax

If we do not know the total tax but the tax rate, we write something like:

net * (1 + tax_rate)

and if we know that the tax rate is constant, we can of course write:

net * 1.19

These operations work the same way you are probably accustomed to from other languages or Excel. We can also test for truth values:

net > 10

would return either true or false. We can also negate the result:

!(net > 10)

Testing for equality is done using ==:

net == 10

And its negation

!(net == 10)

or

net != 10

This would return true if and only if net is not equal to 10.

We can also test multiple conditions:

net < 10.25 && net > 5.75

Would return true if net is below 10.25 and greater than 5.75.

Available operators: + (add), - (subtract), * (multiply), / (divide), > (greater than), >= (greater than or equal), < (less than), <= (less than or equal), == (equals), != (not equals), ! (not), && (and), || (or), & (bitwise and), | (bitwise or), ^ (bitwise xor), >> (right bitshift), << (left bitshift).

String operations

Recipes are not limited to numbers. If we have two fields first_name and last_name but want the field full_name to be automatically calculated, we define the following recipe:

first_name + " " + last_name

Note that we can also use single quotes instead:

first_name + " " + last_name

Properties

Fields can not only be numbers or text, but also objects that have properties. Fields of type select for example hold relations to other Brezel entities. Say we want to define a field total that depends on the price of the selected product product and another field quantity. Then we can write the following recipe:

product.price * quantity

Subscript syntax works too in this case:

product["price"] * quantity

Functions

Often, we want to transform values in some way. There are pre-defined functions available for that, for example when we want to round numbers for a currency field:

round(product.price * quantity)

Conditionals

Ternary operator

Sometimes we want the value to be something in one case, but be something different in another case. Then we can write:

condition ? x : y

If the condition is true, it will return x, otherwise it will return y. Example:

category == "Food" ? 0.07 : 0.19

will return 0.07 if the category in question is Food, otherwise we get 0.19.

Null-coalescing operator

If you just want the resulting expression if it does not evaluate to null and another expression otherwise, you can use the null-coalescing operator:

specific_tax ?? default_tax

This will return the value of specific_tax if specific_tax is not null, otherwise it returns default_tax.

List fields

Fields of type list are expressed as arrays. Having a field positions consisting of list entries with child fields product, unit_price and quantity, we can express the sum position_total of one single list entry to be:

unit_price * quantity

Imagine then that we have the following positions, here illustrated in the form (product, position_total): (Donut, 4.50), (Pretzel, 2.80) and (Bread, 6.00). Applying the following recipe:

positions[0].position_total

returns 4.50. But we can do more, because instead of specifying the index inside [], writing something likeā€¦

positions[*].position_total

will give us the following result:

[4.5, 2.8, 6.0]

This is called wildcard expression, because it maps each entry in the list to whatever is defined after .. Here, each position in the list is mapped to position.position_total.

If we now want to sum all position_total fields of the positions list, we can write

sum(positions[*].position_total)

which gives us 13.30.

Arrow functions

Some functions can also accept functions as input, which are defined in the form of (a, b, ..., c) => y where a, b, ..., c) are parameters andy is the return value. This is especially useful when we want to filter a list by some criterium:

[1, 2, 3, 4, 5].filter((a) => a > 3)

which yields [4, 5].

Math

sum

Signature

sum(items: list)

Description

Returns the sum of all items in the list.

Example
>>> sum([1, 2, 3, 5, 8])
19

avg

Signature

avg(items: list)

Description

Returns the average of all items in the list.

Example
>>> avg([1, 2, 3, 5, 8])
3.8

round

Signature

round(value: number, precision: number)

Description

Returns the value rounded to the specified precision.

Example
>>> round(3.1415, 2)
3.14

floor

Signature

floor(value: number, precision: number)

Description

Returns the value rounded down to the specified precision.

Example
>>> floor(3.1415, 2)
3.14

ceil

Signature

ceil(value: number, precision: number)

Description

Returns the value rounded up to the specified precision.

Example
>>> ceil(3.1415, 2)
3.15

max

Signature

max(items: list)

Description

Returns the maximum of all items in the list.

Example
>>> max([1, 2, 3, 5, 8])
8

min

Signature

min(items: list)

Description

Returns the minimum of all items in the list.

Example
>>> min([1, 2, 3, 5, 8])
1

sin

Signature

sin(value: number, precision: number)

Description

Returns the sine of the value as a number between -1 and 1.

Example
>>> sin(30, 5)
0.500

cos

Signature

cos(value: number, precision: number)

Example
>>> cos(30, 5)
0.866

exp

Signature

exp(value: number, precision: number)

Example
>>> exp(1, 5)
2.718

pow

Signature

pow(value: number, exponent: number)

Example
>>> pow(2, 3)
8

sqrt

Signature

sqrt(value: number, precision: number)

Example
>>> sqrt(9, 5)
3.0000

abs

Signature

abs(value: number)

Example
>>> abs(-3)
3

vector

Signature

vector(values: list)

Example
>>> vector([[1, 2, 3]])
Vector {
[[1, 2, 3]]
}

rand

Signature

rand(start: number, end: number)

Example
>>> rand(1, 10)
6

String

len

Signature

len(value: string)

Example
>>> len("hello")
5

substr

Signature

substr(value: string, start: number, end: number)

Example
>>> substr("hello", 1, 3)
ell

contains

Signature

contains(value: string, search: string)

Example
>>> contains("hello", "ell")
true

toUpperCase

Signature

toUpperCase(value: string)

Example
>>> toUpperCase("hello")
HELLO

random.password(minLength, maxLength)

Signature

random.password(minLength: number, maxLength: number)

Example
>>> random.password(6, 8)
cR9mVzp6

Object

len

Signature

len(value: object)

Example
>>> len({"hello": "world", "foo": "bar"})
2

contains

Signature

contains(value: object, key: string)

Example
>>> contains({"hello": "world", "foo": "bar"}, "hello")
true

list

Signature

list(value: object)

Example
>>> list([{"hello": "world", "foo": "bar"}])
ListWrapper([{"hello": "world", "foo": "bar"}])

relation

Returns a new object with only the required keys for storing relations.

Signature

relation(value: object)

Example
>>> relation({"id": 4, "module_id": 12, "hello": "world", "foo": "bar"})
{"id": 4, "module_id": 12}

join

Signature

join(glue: string, pieces: list)

Example
>>> join(', ', ['a', 'b', 'c', 'd'])
'a, b, c, d'

Date

today

Signature

today()

Example
>>> today()
2018-09-12

now

Signature

now()

Example
>>> now()
2018-09-12T13:22:02-05:00

date

Signature

date(value: string, timezone: string)

Example
>>> date('2018-06-19')
Date {
date: 2018-09-12 00:00:00.0 UTC (+00:00)
}

duration

Signature

duration(value: string)

Example
>>> duration("1h30m")
90m

User

user -> User

Returns the current user from local store. The user gets updated on every refresh.

Signature

user()

Example
>>> user()
User {
id: 4,
module_id: 1,
email: "steffen@brezel.io",
name: "Steffen"
}

hasRole

Signature

hasRole(role: string)

Example
>>> hasRole("ROLE_ADMIN")
true

JSON

json.decode

Signature

json.decode(value: string)

Example
>>> json.decode("[1, 2, 3]")
[1, 2, 3]

json.encode

Signature

json.encode(value: string)

Example
>>> json.encode("[1, 2, 3]")
[1, 2, 3]

Regex

regex.quote

Signature

regex.quote(value: string, delimiter: string)

Example
>>> regex.quote("-=hello world=-", "/")
\\-=hello\\ world\\=-

regex.matchAll

Signature

regex.matchAll(pattern: string, subject: string)

Example
>>> regex.matchAll("hello (\\w+)", "hello world")
[
{ "0": "hello world", "1": "world" }
]

Misc

env API

Signature

env(name: string)

Example
>>> env("SERVICE_URL")
"https://api.my-service.com"
  • Math functions: sum, avg, round, floor, ceil, max, min, sin, cos, exp, pow, sqrt, abs, vector, rand
  • String functions: len, substr, contains, toUpperCase, random.password(minLength, maxLength)
  • Object functions: len, contains, list, relation, join
  • Date functions: today, now, date, duration
  • User functions: user, hasRole
  • JSON functions: json.decode, json.encode
  • Regex functions: regex.quote(string, delimiter), regex.matchAll(pattern, subject)
  • Misc functions: env