Coding Guideline
Note
For regular QFQ users, these are just ‘best practices’. For persons in our team, these are guidelines which we ask to follow.
‘T3 BE’ means ‘Typo3 Backend’.
The following shows some best practices.
App Constants
Definition of constants (reused on different places) is a good practice of structured programming. In QFQ, custom values can be set in STORE_SYSTEM, see User defined constants/variables.
Set values:
Define constants in
T3 BE > Settings > Configure Extensions > QFQ > Custom > ...Dynamic values under
T3 BE > Settings > Configure > Extensions > QFQ > Dynamic > ...
QFQ content record
In T3 BE, label the tt-content record in the header field with a title which describes the functionality and prefix the title with:
Regular content:
[QFQ] ...Content in the left column:
[QFQ,L] ...Content in english:
[QFQ,E] ...API Content:
[QFQ,api] ...
Comment / Signature: The first lines of the bodytext should be comments, explaining what the record does and list all passed variables. Optional variables are indicated by using STORE_EMPTY or STORE_ZERO:
#
# Shows list of Persons living in {{country:SE}}
#
# {{country:SE}} - name of country
#
Comment Query: best above the query, not inline (see above).
Normalize: A good practice is to define all possible STORE_SIP Parameter in a SQL SELECT at the beginning and copy them to STORE_RECORD:
normalize {
# Normalize variables
sql = SELECT '{{country:SE}}' AS _country
# List selected persons per country
sub {
sql = SELECT p.name FROM Person AS p WHERE p.country LIKE '{{country:R}}'
}
}
Indention nested queries: Nested queries should be indented by 2 spaces (not tabs).
Indention long query
80 chars: If the query is longer than one row / 80 chars, split the query over several lines.
Logical block: Split each block of a query on a single row and indent them according to the block:
SELECT …
[LEFT] JOIN <tables>
ON …
WHERE …
AND|OR …
GROUP BY …
ORDER BY …
LIMIT …
SELECT Columns:
Column per row: After the SELECT, put one column per row. This improves readability of the column names (=variable names).
Coma: Unusual, but handy: place the coma (column separator) at the beginning of the row. With this small trick, it’s very easy to comment/uncomment a block of rows, without worrying about the correct number of coma’s.
QFQ Form
Mandatory SIP parameter should to be mentioned in Form.requiredNew and/or Form.requiredEdit. This helps to understand a form functionality better.
Every form should show a descriptive title (shown to the user) to identify the task and current record. E.g. Not ‘Person’ but ‘Person: John Doe’.
The record id is always offered as a tooltip on the save button (together with created and modified).
Put a
<span class="badge">in the title, if a project/record/application id is important for the user.If the title of a FormElement isn’t descriptive enough, use tooltip, note or extraButtonInfo to explain to a user.
Often the length of a pill title if not sufficient, use a tooltip to give a more descriptive hint.
Table structure
Table names are written in camel-case with starting upper case letter.
Colum names are written in camel-case with starting lower case letter.
The first column is always
idwithauto incrementandprimary key.The last two columns are always
modified(datetime, current_timestamp(), ON UPDATE CURRENT_TIMESTAMP())created(datetime, current_timestamp())
(Foreign) Keys
In our databases we mostly don’t use hard coded foreign keys.
keycolumn name: each table, referenced by another table column (key), should have an abbreviation. Such abbreviation becomes the column prefix together with id and optional a description. E.g. in tableBookthe column who points to a person (table Person, abbr. p) who is the book author, is namedpIdAuthor. Herepis the prefix,Idindicates this a key to a table, andAuthorgives the meaning of the column.
Multi purpose tables & column keys
There are many situations where only a few records per normalized table are given. In a clean database design, it is wished to create a table with a meaningful name for each use case. A side effect of this clean design is that you get a lot of tables in bigger databases and these tables often differ only in small parts. The result, many tables, lead to the situation that no one has an overview anymore what table supports what, and the programmer, who needs some new records to be stored, creates the next specialized table, and so on. Also the table names becomes longer and longer.
Our best practice is somewhat strange, but very powerful and it becomes so handy, that we really appreciate the design.
Table Grp
Used to partition other tables. The meaning is similar the MariaDB partitioning
but has nothing to do with it - both mean splitting the records of a table by a filter criteria, for MariaDB this has performance reasons, for our best practice it’s flexibility.
In a table, just define a column
grId.
Example:
In table
Bookyou like to assign a book to a category.For each category (Novel, Poem, Science, …) create a record in table
Grp.For better understanding, name the column not only
grIdbutgrIdCategory.
You might suggest to use an ENUM for book category - but think of: adding a new category by creating a new record can easily be done, but a changing a table definition need more effort like required privileges and documentation.
Why not define a separate table BookCategory? Maybe we need other categories too.
Like different address categories, or cities per country and so on, this is an
endless list. The Grp / partitioning concept solves it a general way.more
Why the name Grp? Meaning is group, but group is a reserved SQL word, so Grp
Extension 1: Parent Grp record
We define a column
Grp.grId.This can be understand as grouping a set of records under a parent record.
Example: create a
Grprecord Book categories. Put the id of this record in each of the individual book categories Novel, Poem, Science, … - this is the next partition, this time inside of theGrptable.
Extension 2: Generic settings UI
Create a form for table
Grpwith an input for name and a FormElement subrecord for all it’s child records.Ready is your general settings store. Define as many settings , grouping, categories, … as you like.
Extension 3: Use descriptive strings instead of hard coded id’s
Create a column
Grp.referencewith an uniq index, nullable, default is ‘null’.Instead of selecting your subset of records by a fixed numeric id, use the reference.
Example:
SELECT grCat.name FROM Grp AS grCat, Grp AS gr WHERE 'Book categories'=gr.reference AND gr.id=grCat.grIdThat’s pretty cool, no hard coded id’s anymore - nowhere.
Column reference
Main usage is already described in grp-string-reference
Add a column
referenceto each table, if it is necessary to select a specific record via application logic (=SQL statements).Hint: In the FormElement for column
referenceset FE.parameter.emptyMeansNull - this will save NULL if there is nothing given.
Entity Relation Model (ERD)
All ERD Schemas are drawn with https://draw.io/
SQL Queries
Write all SQL keywords uppercase:
SELECT Grp.name FROM Grp WHERE reference='Book categories'<tablename> AS ‘<prefix>’ SELCT … FROM Grp AS grp
BPMN - Workflow
Custom CSS
QFQ comes with a set of predefined CSS classes QFQ CSS Classes and QFQ Icons.
If further classes / functionality / icons are required, please create a ticket for the request - the main idea is to not expand custom.css, but to expand QFQ itself. So please report your requirements.
Custom Javascript
If further Javascript is required, please create a ticket for the request - the main idea implement functionality into the QFQ core. Otherwise, the logic has to reimplemented every time from scratch. So please report your requirements.