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.
php bakery shell> recipe("'Hello world!'")= "Hello world!"
> 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