toga-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +119 -0
- package/.claude-plugin/marketplace.json +87 -0
- package/.claude-plugin/plugin.json +22 -0
- package/CLAUDE.md +161 -0
- package/README.md +72 -0
- package/agents/framework-pattern-checker.md +67 -0
- package/agents/harness-optimizer.md +102 -0
- package/agents/knowledge-writer.md +62 -0
- package/agents/php-build-resolver.md +51 -0
- package/agents/php-reviewer.md +51 -0
- package/agents/planner.md +88 -0
- package/agents/session-capture.md +101 -0
- package/agents/sql-reviewer.md +67 -0
- package/contexts/dev.md +43 -0
- package/contexts/research.md +49 -0
- package/contexts/review.md +37 -0
- package/knowledge/1.0/apps/library/INDEX.md +5 -0
- package/knowledge/1.0/apps/library/architecture.md +105 -0
- package/knowledge/1.0/apps/worker/INDEX.md +5 -0
- package/knowledge/1.0/apps/worker/architecture.md +223 -0
- package/knowledge/1.0/standards/backend-php.md +450 -0
- package/knowledge/2.0/apps/_underscore/INDEX.md +6 -0
- package/knowledge/2.0/apps/_underscore/architecture.md +183 -0
- package/knowledge/2.0/apps/_underscore/features/recursive-item-fulfillments.md +111 -0
- package/knowledge/2.0/apps/api2/INDEX.md +5 -0
- package/knowledge/2.0/apps/api2/architecture.md +162 -0
- package/knowledge/2.0/apps/worker2/INDEX.md +6 -0
- package/knowledge/2.0/apps/worker2/architecture.md +127 -0
- package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +135 -0
- package/knowledge/2.0/standards/backend-php.md +710 -0
- package/knowledge/CONVENTIONS.md +117 -0
- package/knowledge/INDEX.md +19 -0
- package/knowledge/clients/.gitkeep +0 -0
- package/knowledge/registry.json +7 -0
- package/knowledge.js +384 -0
- package/mcp-configs/README.md +72 -0
- package/mcp-configs/mcp-servers.json +23 -0
- package/package.json +50 -0
- package/rules/README.md +53 -0
- package/rules/common/coding-style.md +123 -0
- package/rules/common/git-workflow.md +72 -0
- package/rules/common/security.md +118 -0
- package/rules/common/testing.md +74 -0
- package/rules/php/app-framework.md +104 -0
- package/rules/php/underscore-framework.md +111 -0
- package/scripts/harness.js +605 -0
- package/scripts/hooks/evaluate-session.js +55 -0
- package/scripts/hooks/post-edit-validate.js +102 -0
- package/scripts/hooks/session-end.js +13 -0
- package/scripts/hooks/session-start.js +57 -0
- package/scripts/install.js +611 -0
- package/scripts/pre-commit +46 -0
- package/skills/capture/SKILL.md +294 -0
- package/skills/code-review/SKILL.md +140 -0
- package/skills/create-elastic-beanstalk/SKILL.md +217 -0
- package/skills/harness-audit/SKILL.md +152 -0
- package/skills/kickoff/SKILL.md +151 -0
- package/skills/php-patterns/SKILL.md +296 -0
- package/skills/session-resume/SKILL.md +156 -0
- package/skills/session-save/SKILL.md +158 -0
- package/skills/sync-team-skills/SKILL.md +87 -0
- package/sync-skills.js +71 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Back-End Coding Standards
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-08
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
files: []
|
|
11
|
+
related:
|
|
12
|
+
- ../apps/_underscore/architecture.md
|
|
13
|
+
- ../apps/api2/architecture.md
|
|
14
|
+
- ../apps/worker2/architecture.md
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## General Principals
|
|
18
|
+
|
|
19
|
+
### Modularity and Reusability
|
|
20
|
+
|
|
21
|
+
* Ensure that the code is modular, making use of functions and classes to promote reusability and separation of concerns.
|
|
22
|
+
|
|
23
|
+
## Database/SQL
|
|
24
|
+
|
|
25
|
+
### Table Design
|
|
26
|
+
|
|
27
|
+
#### **Order of Fields**
|
|
28
|
+
|
|
29
|
+
1. `id` (mandatory)
|
|
30
|
+
2. `uuid` (mandatory)
|
|
31
|
+
3. Foreign Keys
|
|
32
|
+
4. Record Identifiers (references like order number)
|
|
33
|
+
5. Date/Time fields
|
|
34
|
+
6. Date fields
|
|
35
|
+
7. Flag/Boolean fields
|
|
36
|
+
8. ENUM-type fields (lists)
|
|
37
|
+
9. Char/Varchar/Integer/Decimal fields
|
|
38
|
+
10. Text/Blob fields
|
|
39
|
+
11. Custom fields
|
|
40
|
+
|
|
41
|
+
### UUIDs
|
|
42
|
+
|
|
43
|
+
* All records inserted in the 2.0 database must be given a random UUID in the second column of each table called `uuid`
|
|
44
|
+
* Do not use the built-in `UUID()` function provided by MySQL because they are not fully random and instead are time-based which causes UUIDs to be nearly identical, making it hard for developers to read.
|
|
45
|
+
* Instead, generate a fully-random UUID in PHP with **`_String::generateUuid()`** (defined in `_underscore/String.php`). It builds a random 32-hex value formatted as a standard UUID. Do not rely on a MySQL-side function for this.
|
|
46
|
+
|
|
47
|
+
#### **Custom Fields**
|
|
48
|
+
|
|
49
|
+
* A non-standard field requested or required by the client/3rd party is called a custom field. When adding custom fields to a table, keep this in mind:
|
|
50
|
+
* The custom field should be at the end of the table
|
|
51
|
+
* Start the field name with `c_` , for example `c_employeeId`
|
|
52
|
+
* Maintain camelCase after the `c_` prefix
|
|
53
|
+
|
|
54
|
+
#### **Table & Field Comments**
|
|
55
|
+
|
|
56
|
+
* All database tables and fields in 2.0 databases must be given a short, but descriptive comment which will be visible to clients and the public.
|
|
57
|
+
* Example SQL query for altering a table comment:
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
ALTER TABLE AclLogicGroups COMMENT 'ACL logic groups of a record';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
* Field commenting rules:
|
|
64
|
+
* Do not begin a comment with "The"
|
|
65
|
+
* Some fields have a specific format that must be followed:
|
|
66
|
+
* `id`: Must be 'Internal unique identifier of the record'
|
|
67
|
+
* `uuid`: Must be 'External unique identifier of the record'
|
|
68
|
+
* Foreign keys such as `purchaseOrderId`: Must begin with the database (Core/Client/Logs) with exact table name, a `.`, and a descriptive comment such as: 'Client.PurchaseOrders record this ASN is for'
|
|
69
|
+
* Foreign keys such as `shipToAddressId`: Must begin with the database (Core/Client/Logs) with exact table name, a `.`, and a descriptive comment using the specific type in the second part of the sentence. For example: 'Client.Addresses record this ASN is shipping to'
|
|
70
|
+
|
|
71
|
+
#### **Engine, Character Set & Collation**
|
|
72
|
+
|
|
73
|
+
* All tables must use the `InnoDB` engine.
|
|
74
|
+
* All tables must use `utf8mb4` as the character set and `utf8mb4_0900_ai_ci` as the collation except in specific cases where a different character set or collation is required such as a case sensitive field or different encoding.
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
# Syntax for engine, character set, and collation
|
|
78
|
+
ENGINE=InnoDB
|
|
79
|
+
DEFAULT CHARSET=utf8mb4
|
|
80
|
+
COLLATE=utf8mb4_0900_ai_ci
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Queries
|
|
84
|
+
|
|
85
|
+
* While it's ok to use `SELECT *` in your MySQL client, it should NEVER be used in code because this puts undue stress on the servers and uses up undue resources.
|
|
86
|
+
|
|
87
|
+
#### **Capitalization**
|
|
88
|
+
|
|
89
|
+
* All SQL keywords need to be capitalized.
|
|
90
|
+
* This includes `SELECT`, `FROM`, `INNER JOIN`, `ON`, `WHERE`, `AND`, `OR`, `NOT`, `LIKE`, `GROUP_CONCAT`, `AS`, `ORDER BY`, `DESC`, `LIMIT`, `IS`, `IN`, `GROUP BY`, `HAVING`, etc.
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
# Bad
|
|
94
|
+
select column1 From table1
|
|
95
|
+
|
|
96
|
+
# Good
|
|
97
|
+
SELECT column1 FROM table1
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### **Spacing**
|
|
101
|
+
|
|
102
|
+
1. ALWAYS use tabs for indentation.
|
|
103
|
+
2. Every clause should start on a new line.
|
|
104
|
+
3. Indent the column list that follows a `SELECT` clause, and the condition that follows a `WHERE` or `ON` clause.
|
|
105
|
+
4. Do not leave trailing spaces at the end of lines.
|
|
106
|
+
5. Use a line break after each comma in the `SELECT` clause or column list.
|
|
107
|
+
6. `SELECT`, `WHERE`, `GROUP BY`, `HAVING`, and `ORDER BY` should be on their own lines by themselves.
|
|
108
|
+
7. `LIMIT` should be on a new line but may be followed by the count and offset on the same line.
|
|
109
|
+
8. When ordering by multiple columns, each column should be on a new line.
|
|
110
|
+
|
|
111
|
+
```sql
|
|
112
|
+
# Bad
|
|
113
|
+
SELECT
|
|
114
|
+
column1,
|
|
115
|
+
column2
|
|
116
|
+
FROM table1
|
|
117
|
+
WHERE val > 0
|
|
118
|
+
GROUP BY column1
|
|
119
|
+
HAVING column2 < 0
|
|
120
|
+
ORDER BY column1 DESC,
|
|
121
|
+
column2 ASC LIMIT 10
|
|
122
|
+
|
|
123
|
+
# Good
|
|
124
|
+
SELECT
|
|
125
|
+
column1,
|
|
126
|
+
column2
|
|
127
|
+
FROM table1
|
|
128
|
+
WHERE
|
|
129
|
+
val > 0
|
|
130
|
+
GROUP BY
|
|
131
|
+
column1
|
|
132
|
+
HAVING
|
|
133
|
+
column2 < 0
|
|
134
|
+
ORDER BY
|
|
135
|
+
column1 DESC,
|
|
136
|
+
column2 ASC
|
|
137
|
+
LIMIT 10
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### SQL within PHP
|
|
141
|
+
|
|
142
|
+
* When using SQL within PHP, make sure to put the quotations (`"`) on their own lines and indent the query.
|
|
143
|
+
|
|
144
|
+
```php
|
|
145
|
+
$sql = "
|
|
146
|
+
SELECT
|
|
147
|
+
column1,
|
|
148
|
+
column2
|
|
149
|
+
FROM table1
|
|
150
|
+
WHERE
|
|
151
|
+
val > 0
|
|
152
|
+
GROUP BY
|
|
153
|
+
column1
|
|
154
|
+
HAVING
|
|
155
|
+
column2 < 0
|
|
156
|
+
ORDER BY
|
|
157
|
+
column1 DESC,
|
|
158
|
+
column2 ASC
|
|
159
|
+
LIMIT 10
|
|
160
|
+
";
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### **Aliasing**
|
|
164
|
+
|
|
165
|
+
* Tables should only be aliased when necessary – for example, if there is more than one table with the same name in the same query.
|
|
166
|
+
* Column aliases should be used when the column name is derived from a function or complex expression. Use the `AS` keyword to assign aliases.
|
|
167
|
+
|
|
168
|
+
```sql
|
|
169
|
+
# Bad
|
|
170
|
+
SELECT column1, column2 FROM table1
|
|
171
|
+
|
|
172
|
+
# Good
|
|
173
|
+
SELECT
|
|
174
|
+
column1 AS alias1,
|
|
175
|
+
column2 AS alias2
|
|
176
|
+
FROM table1 AS t1
|
|
177
|
+
INNER JOIN table1 AS aliasTable1 ON aliasTable1.id = t1.id
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Joining
|
|
181
|
+
|
|
182
|
+
* The join condition should be clearly mentioned in the ON clause.
|
|
183
|
+
* Each `JOIN` clause should start on a new line.
|
|
184
|
+
* When using `INNER JOIN`s and `OUTER JOIN`s in the same query, always put your `INNER JOIN`s first.
|
|
185
|
+
* Whenever possible, use `LEFT OUTER JOIN`s instead of `RIGHT OUTER JOIN`
|
|
186
|
+
|
|
187
|
+
```sql
|
|
188
|
+
# Bad
|
|
189
|
+
SELECT
|
|
190
|
+
column1
|
|
191
|
+
FROM table1
|
|
192
|
+
INNER JOIN table2 ON table1.id = table2.id
|
|
193
|
+
RIGHT OUTER JOIN table4 ON table1.id = table2.id
|
|
194
|
+
INNER JOIN table3 ON table1.id = table2.id
|
|
195
|
+
|
|
196
|
+
# Good
|
|
197
|
+
SELECT
|
|
198
|
+
column1
|
|
199
|
+
FROM table1
|
|
200
|
+
INNER JOIN table2 ON table1.id = table2.id
|
|
201
|
+
INNER JOIN table3 ON table1.id = table2.id
|
|
202
|
+
LEFT OUTER JOIN table4 ON table1.id = table2.id
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### **Subqueries**
|
|
206
|
+
|
|
207
|
+
* Use indentation for subqueries to clearly distinguish them from the main query.
|
|
208
|
+
* Start subqueries on a new line.
|
|
209
|
+
* Enclose subqueries in parentheses.
|
|
210
|
+
|
|
211
|
+
```sql
|
|
212
|
+
# Bad
|
|
213
|
+
SELECT
|
|
214
|
+
column1,
|
|
215
|
+
SELECT column2
|
|
216
|
+
FROM table2
|
|
217
|
+
WHERE table2.id = table1.id AS alias2
|
|
218
|
+
FROM table1
|
|
219
|
+
|
|
220
|
+
# Good
|
|
221
|
+
SELECT
|
|
222
|
+
column1,
|
|
223
|
+
(
|
|
224
|
+
SELECT
|
|
225
|
+
column2
|
|
226
|
+
FROM table2
|
|
227
|
+
WHERE
|
|
228
|
+
table2.id = table1.id
|
|
229
|
+
) AS alias2
|
|
230
|
+
FROM table1
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### **Conditions**
|
|
234
|
+
|
|
235
|
+
* Multiple conditions in a `WHERE` clause should be on separate lines.
|
|
236
|
+
* Use parentheses to group conditions when necessary for clarity and to control the logical order of operations.
|
|
237
|
+
* When mixing `AND` and `OR`s together, you MUST use parentheses.
|
|
238
|
+
* When adding `AND` and `OR`, start them on a new line.
|
|
239
|
+
* SQL operators like `>` and `<` should be surrounded by spaces for readability.
|
|
240
|
+
|
|
241
|
+
```sql
|
|
242
|
+
# Bad
|
|
243
|
+
SELECT
|
|
244
|
+
column1
|
|
245
|
+
FROM table1 WHERE column1 > 10 AND
|
|
246
|
+
column2<20 OR column3='value'
|
|
247
|
+
|
|
248
|
+
# Good
|
|
249
|
+
SELECT
|
|
250
|
+
column1
|
|
251
|
+
FROM table1
|
|
252
|
+
WHERE
|
|
253
|
+
column1 > 10
|
|
254
|
+
AND (
|
|
255
|
+
column2 < 20
|
|
256
|
+
OR column3 = 'value'
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### **SQL Comments**
|
|
261
|
+
|
|
262
|
+
* If necessary, add comments to clarify complex sections of SQL code. Comments should be concise and informative.
|
|
263
|
+
* Prefer to use the `#` character to begin a comment rather than `--`.
|
|
264
|
+
|
|
265
|
+
```sql
|
|
266
|
+
# Bad
|
|
267
|
+
SELECT * # This query selects all columns from table1
|
|
268
|
+
FROM table1
|
|
269
|
+
WHERE column1 = 'value'
|
|
270
|
+
|
|
271
|
+
# Good
|
|
272
|
+
# This query selects all columns from table1
|
|
273
|
+
SELECT
|
|
274
|
+
column1
|
|
275
|
+
FROM table1
|
|
276
|
+
WHERE
|
|
277
|
+
column1 = 'value'
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### **Encoding**
|
|
281
|
+
|
|
282
|
+
* All data inserted into the databases must already be decoded unless otherwise specified and intended. Ensure the data is not HTML or URL-encoded before inserting.
|
|
283
|
+
* For example, the actual `<` character should be in the database instead of the HTML-encoded value of `<`
|
|
284
|
+
|
|
285
|
+
### **SQL Naming Conventions**
|
|
286
|
+
|
|
287
|
+
#### **Bridge Tables**
|
|
288
|
+
|
|
289
|
+
* Most bridge tables need to include an underscore between the two tables they are joining. If you are joining a parent with a child table, the parent should come first. Exceptions may be made for inherent child records like `SalesOrderItems` being a child yet also a bridge of `SalesOrders`
|
|
290
|
+
* All bridge tables should exactly line up with the tables they refer to, including plurals.
|
|
291
|
+
* When in doubt, collaborate!
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
# Bad
|
|
295
|
+
LocationAttributes_Locations # (parent should be first)
|
|
296
|
+
Order_OrderLineItem # (missing plural, not aligned with table names)
|
|
297
|
+
Contacts-Addresses # (should use underscore)
|
|
298
|
+
|
|
299
|
+
# Good
|
|
300
|
+
# Locations table with child table called LocationAttributes:
|
|
301
|
+
Locations_LocationAttributes
|
|
302
|
+
|
|
303
|
+
# Two sibling tables called Posts and Authors (either are valid)
|
|
304
|
+
Posts_Authors
|
|
305
|
+
Authors_Posts
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### **Human-readable text fields**
|
|
309
|
+
|
|
310
|
+
* All human-readable fields should be called `name` . Don't use `label` or `type` or `displayName` or `niceName`.
|
|
311
|
+
* Bad examples:
|
|
312
|
+
* In a table called `Tags`, the human-readable field should NOT be `label`
|
|
313
|
+
* In a table called `Companies`, the human-readable field should NOT be `companyName`
|
|
314
|
+
* In a table called `ServiceRequestTypes`, the human-readable field should NOT be `ServiceType`
|
|
315
|
+
* Good Examples:
|
|
316
|
+
* In a table called `Tags`, the human-readable field should be `name`
|
|
317
|
+
* In a table called `Companies`, the human-readable field should be `name`
|
|
318
|
+
* In a table called `ServiceRequestTypes`, the human-readable field should be `name`
|
|
319
|
+
|
|
320
|
+
#### **Naming Foreign Keys**
|
|
321
|
+
|
|
322
|
+
* When using a Foreign key to refer to another table:
|
|
323
|
+
1. Take the table that it's referring, such as: `Customers`
|
|
324
|
+
2. Make it singular: `Customer`
|
|
325
|
+
3. Add `id` to the end and convert to camelCase: `customerId`
|
|
326
|
+
* Bad Examples (Didn't convert to camelCase, didn't convert to singular):
|
|
327
|
+
* Table name: `PurchaseOrderItems`
|
|
328
|
+
* Table you're referencing: `PurchaseOrders`
|
|
329
|
+
* Foreign key field name: `PurchaseOrdersId`
|
|
330
|
+
* Good Examples:
|
|
331
|
+
* Table name: `PurchaseOrderItems`
|
|
332
|
+
* Table you're referencing: `PurchaseOrders`
|
|
333
|
+
* Foreign key field name: `purchaseOrderId`
|
|
334
|
+
|
|
335
|
+
#### **Case**
|
|
336
|
+
|
|
337
|
+
* Database names in PascalCase or their formal name (ie: `TOGa`)
|
|
338
|
+
* Table names in PascalCase
|
|
339
|
+
* Field names in camelCase
|
|
340
|
+
|
|
341
|
+
**SQL Data Types**
|
|
342
|
+
|
|
343
|
+
* In code you rarely write these raw types directly. A `_Model` declares each column as a `FIELD_*` constant that maps to the underlying SQL type — e.g. `FIELD_PRIMARYKEY` / `FIELD_FOREIGNKEY` (unsigned int keys), `FIELD_INTEGER`, `FIELD_BOOLEAN` (the `is*` tinyint flag), `FIELD_DECIMAL`, `FIELD_CHAR` / `FIELD_CHAR_UUID`, `FIELD_LIST` (ENUM), `FIELD_DATETIME_CREATED` / `FIELD_DATETIME_UPDATED` (auto-managed timestamps), `FIELD_BLOB`, `FIELD_STORAGE`, and `FIELD_SQL` (calculated). The SQL types below are the storage targets these constants resolve to; follow both this section and the model field-type conventions when defining columns.
|
|
344
|
+
|
|
345
|
+
**Flags/Booleans**
|
|
346
|
+
|
|
347
|
+
* Flag fields should begin with `is`, for example: `isVisible`
|
|
348
|
+
* Data type must be set as `UNSIGNED TINYINT` with `0` representing FALSE and `1` representing TRUE
|
|
349
|
+
|
|
350
|
+
#### **Varchars**
|
|
351
|
+
|
|
352
|
+
* A max of 255 chars, then you must switch to a text data type
|
|
353
|
+
* Should allocate in binary intervals so the only options for length would be: 1, 2, 4, 8, 16, 32, 64, 128, 255
|
|
354
|
+
|
|
355
|
+
#### **Blobs**
|
|
356
|
+
|
|
357
|
+
| Type | Max Length |
|
|
358
|
+
| ------------ | ------------------- |
|
|
359
|
+
| `TINYBLOB` | 255 bytes |
|
|
360
|
+
| `BLOB` | 65,535 bytes |
|
|
361
|
+
| `MEDIUMBLOB` | 16,777,215 bytes |
|
|
362
|
+
| `LONGBLOB` | 4,294,967,295 bytes |
|
|
363
|
+
|
|
364
|
+
#### **Integers** When storing integers, make sure to answer the following questions:
|
|
365
|
+
|
|
366
|
+
**Am I storing a foreign or primary key?**\
|
|
367
|
+
All keys must be unsigned integers.
|
|
368
|
+
|
|
369
|
+
**Do I need to store positives and negatives, or just positives?**\
|
|
370
|
+
Signed integers can reference positive and negative numbers, but unsigned can only reference positive numbers.
|
|
371
|
+
|
|
372
|
+
**How large are the numbers I'm storing?**
|
|
373
|
+
|
|
374
|
+
| Type | Bytes | Min **Signed** Value | Max **Signed** Value | Min **Unsigned** Value | Max **Unsigned** Value |
|
|
375
|
+
| ----------- | ----- | -------------------- | -------------------- | ---------------------- | ---------------------- |
|
|
376
|
+
| `TINYINT` | 1 | `-128` | `127` | `0` | `255` |
|
|
377
|
+
| `SMALLINT` | 2 | `-32,768` | `32,767` | `0` | `65,535` |
|
|
378
|
+
| `MEDIUMINT` | 3 | `-8,388,608` | `8,388,607` | `0` | `16,777,215` |
|
|
379
|
+
| `INT` | 4 | `-2,147,483,648` | `2,147,483,647` | `0` | `4,294,967,295` |
|
|
380
|
+
| `BIGINT` | 8 | `-2^63` | `2^63 - 1` | `0` | `2^64 - 1` |
|
|
381
|
+
|
|
382
|
+
### SQL Injection Prevention
|
|
383
|
+
|
|
384
|
+
* Never interpolate raw user input into a query string. Two acceptable approaches, in order of preference:
|
|
385
|
+
1. **Use the `_Model` layer.** The metadata-driven ORM builds and escapes queries for you — prefer it over hand-written SQL whenever possible.
|
|
386
|
+
2. **When you must write SQL by hand, escape every interpolated value with `_Database::escape()`** before placing it in the query string.
|
|
387
|
+
* `_Database::escape()` adds MySQL escape characters (`\`, `'`, `"`, newlines, etc.) and is the correct helper for query interpolation. Do **not** confuse it with `_Database::protect()`, which *strips* `% \ / * " '` and the literal ` or` — that is lossy filtering, not escaping, and will corrupt data you intend to store intact.
|
|
388
|
+
|
|
389
|
+
```php
|
|
390
|
+
// Bad — raw interpolation
|
|
391
|
+
$sql = "SELECT name FROM Users WHERE username = '$username'";
|
|
392
|
+
|
|
393
|
+
// Good — escape before interpolating
|
|
394
|
+
$username = _Database::escape($username);
|
|
395
|
+
$sql = "
|
|
396
|
+
SELECT
|
|
397
|
+
name
|
|
398
|
+
FROM Users
|
|
399
|
+
WHERE
|
|
400
|
+
username = '$username'
|
|
401
|
+
";
|
|
402
|
+
|
|
403
|
+
// Better — let the model layer handle it
|
|
404
|
+
$user = new _Model_Client_User();
|
|
405
|
+
$user->username = $username;
|
|
406
|
+
if ($user->load()) { /* found */ }
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## PHP
|
|
410
|
+
|
|
411
|
+
### PHP Tags
|
|
412
|
+
|
|
413
|
+
* Short tags (`<?`) are not permitted.
|
|
414
|
+
* All PHP files MUST NOT end in a closing tag like this `?>` but instead with PHP code or with an open `<?php` tag followed by a line break.
|
|
415
|
+
|
|
416
|
+
```html
|
|
417
|
+
</html>
|
|
418
|
+
<?php
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Code Formatting
|
|
423
|
+
|
|
424
|
+
#### **Indentation**
|
|
425
|
+
|
|
426
|
+
* Use tabs for indentation, not spaces.
|
|
427
|
+
|
|
428
|
+
#### **Braces**
|
|
429
|
+
|
|
430
|
+
* Opening braces for classes, methods and control structures (`if`, `else`, `for`, `while`, etc.) should go on the same line.
|
|
431
|
+
|
|
432
|
+
```php
|
|
433
|
+
if ($condition) {
|
|
434
|
+
// code
|
|
435
|
+
} else {
|
|
436
|
+
// code
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Naming Conventions
|
|
441
|
+
|
|
442
|
+
**Variables**
|
|
443
|
+
|
|
444
|
+
* Use camelCase for variable names.
|
|
445
|
+
* Names should be descriptive, avoiding abbreviations unless they are well-known.
|
|
446
|
+
|
|
447
|
+
**Functions**
|
|
448
|
+
|
|
449
|
+
* Use camelCase for function names.
|
|
450
|
+
* Function names should be descriptive and reflect what the function does.
|
|
451
|
+
|
|
452
|
+
**Classes**
|
|
453
|
+
|
|
454
|
+
* Use PascalCase for class names.
|
|
455
|
+
* Class names should be nouns, in singular form, and clearly represent their purpose.
|
|
456
|
+
|
|
457
|
+
#### Named Arguments
|
|
458
|
+
|
|
459
|
+
* The 2.0 codebase uses PHP 8 **named arguments** pervasively for built-in and framework calls to make call sites self-documenting. Follow this convention.
|
|
460
|
+
|
|
461
|
+
```php
|
|
462
|
+
// Preferred
|
|
463
|
+
$uuid = substr(string: $value, offset: 0, length: 8);
|
|
464
|
+
$list = implode(separator: ",\n", array: $fields);
|
|
465
|
+
|
|
466
|
+
// Avoid (positional, harder to read at the call site)
|
|
467
|
+
$uuid = substr($value, 0, 8);
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Documentation
|
|
471
|
+
|
|
472
|
+
#### **Comments**
|
|
473
|
+
|
|
474
|
+
* Use comments to explain the "why" behind the code, not the "how".
|
|
475
|
+
* Inline comments should be used sparingly.
|
|
476
|
+
|
|
477
|
+
#### **DocBlocks**
|
|
478
|
+
|
|
479
|
+
* Every class and method should have a DocBlock that describes its purpose.
|
|
480
|
+
* Parameters and return types should be clearly documented.
|
|
481
|
+
* This is the required standard for all new and modified code. Existing coverage is uneven (strong in utility classes, sparse in some model classes) — bring code up to standard as you touch it.
|
|
482
|
+
|
|
483
|
+
```php
|
|
484
|
+
/**
|
|
485
|
+
* Calculates the sum of two integers.
|
|
486
|
+
*
|
|
487
|
+
* @param int $a The first integer.
|
|
488
|
+
* @param int $b The second integer.
|
|
489
|
+
* @return int The sum of the two integers.
|
|
490
|
+
*/
|
|
491
|
+
function add($a, $b) {
|
|
492
|
+
return $a + $b;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Security
|
|
497
|
+
|
|
498
|
+
* Validate and sanitize user inputs.
|
|
499
|
+
|
|
500
|
+
### Performance
|
|
501
|
+
|
|
502
|
+
* Use single quotes for strings unless interpolation is needed.
|
|
503
|
+
|
|
504
|
+
```php
|
|
505
|
+
// Bad
|
|
506
|
+
$a = "Hello";
|
|
507
|
+
|
|
508
|
+
// Good
|
|
509
|
+
$a = 'Hello';
|
|
510
|
+
$b = "Hello $world";
|
|
511
|
+
$c = 'Hello ' . $world;
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Miscellaneous
|
|
515
|
+
|
|
516
|
+
* Use `require_once` for including files that must only be included once.
|
|
517
|
+
* Avoid using global variables and global state.
|
|
518
|
+
|
|
519
|
+
### PHP Resources
|
|
520
|
+
|
|
521
|
+
* [PHP The Right Way](https://phptherightway.com/)
|
|
522
|
+
* [PHP Standards Recommendations](https://www.php-fig.org/psr/)
|
|
523
|
+
|
|
524
|
+
## Error Handling
|
|
525
|
+
|
|
526
|
+
### PHP Error Handling
|
|
527
|
+
|
|
528
|
+
* Always use exceptions to handle unexpected conditions.
|
|
529
|
+
|
|
530
|
+
### Comprehensive Error Handling
|
|
531
|
+
|
|
532
|
+
* Implement comprehensive error handling to gracefully manage exceptions and provide meaningful error messages.
|
|
533
|
+
|
|
534
|
+
### Logging
|
|
535
|
+
|
|
536
|
+
* Use structured logging to capture detailed information about application behavior and errors.
|
|
537
|
+
|
|
538
|
+
## Security Best Practices
|
|
539
|
+
|
|
540
|
+
### Input Validation and Sanitization
|
|
541
|
+
|
|
542
|
+
* Validate and sanitize all user inputs to prevent common vulnerabilities such as injection attacks.
|
|
543
|
+
|
|
544
|
+
### Secure Communication
|
|
545
|
+
|
|
546
|
+
* Always use HTTPS to encrypt data in transit.
|
|
547
|
+
|
|
548
|
+
## Performance Optimization
|
|
549
|
+
|
|
550
|
+
### Caching
|
|
551
|
+
|
|
552
|
+
* To improve performance, store frequently accessed data in the appropriate layer: cookies, local storage, and sessions on the client/session side; variables within a request.
|
|
553
|
+
* The framework provides **built-in query-result caching** in `_Database` (enabled by default). Reuse it rather than re-running identical reads; use `_Database::useQueryCache(false)` to disable it when you need a guaranteed-fresh read (e.g. immediately after a write in the same request).
|
|
554
|
+
|
|
555
|
+
### Asynchronous Processing
|
|
556
|
+
|
|
557
|
+
* Use asynchronous processing for tasks that can be performed in the background to improve responsiveness.
|
|
558
|
+
* Use AJAX for front-end interactions.
|
|
559
|
+
* For back-end background work, **dispatch a job to the Worker system** via `_Worker::runTask($action, $parameters)` (the `worker2` queue) — do not fork processes or run long tasks inline in a request. The action maps to a `_Worker_*` class/method; webhook and cron entry points are supported. See the Worker architecture for the queue, retry, and always-HTTP-200 conventions.
|
|
560
|
+
|
|
561
|
+
## API Design
|
|
562
|
+
|
|
563
|
+
### RESTful API Design
|
|
564
|
+
|
|
565
|
+
* Follow RESTful principles for designing APIs, ensuring that they are stateless and provide clear, consistent endpoints.
|
|
566
|
+
* TOGa 2.0 API is entirely RESTful but other clients' and third party APIs may not be.
|
|
567
|
+
|
|
568
|
+
### Record Scripts
|
|
569
|
+
|
|
570
|
+
* Use Record Scripts within our 2.0 API when you need to build custom logic for an action on a particular record and a standard table-based RESTful approach isn't feasible.
|
|
571
|
+
* They should be used very sparingly as they require custom development. They should only be used when you have a need for a scalable solution but have complex logic which wouldn't make sense to use many API calls for and handling on the front-end.
|
|
572
|
+
* A good use case of Record Scripts is for returning the meta data for a page or table. This is because without a record script, the front-end would need to perform dozens of API calls to be able to gather the data necessary to render the page.
|
|
573
|
+
|
|
574
|
+
#### **Procedure**
|
|
575
|
+
|
|
576
|
+
1. You will need to add a record to either the `Core.RecordScripts` or `Client.CustomRecordScripts` table.
|
|
577
|
+
* If you want the record script to apply to ALL records system-wide, add it to the `Core.RecordScripts` table.
|
|
578
|
+
* If you only want the record script to apply to one client, add it to the appropriate `Client.CustomRecordScripts` table.
|
|
579
|
+
2. Determine these field values and insert into either the `Core.RecordScripts` or `Client.CustomRecordScripts` table.
|
|
580
|
+
* `recordId` - Foreign key of the `Records.id` for the record you want to add the script to
|
|
581
|
+
* `method` - HTTP method you want the script to apply to (eg: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`)
|
|
582
|
+
* `route` - The string in the URL route following the record name
|
|
583
|
+
* `phpMethod` - The PHP method name within the record's model which will be called
|
|
584
|
+
3. In the \_underscore framework, open/create the PHP class for the model of the record you want to add the script to.
|
|
585
|
+
4. Add a public static function to the PHP class.
|
|
586
|
+
1. Use the value in `phpMethod` for the name of the method/function.
|
|
587
|
+
2. Setup parameters
|
|
588
|
+
1. The first parameter of the method **MUST be `&$api`**, an object passed by reference which contains meta and record information regarding the API transaction.
|
|
589
|
+
2. The remaining parameters are of your choice but these will be passed into the function after having been parsed from the keys in the query string. For example, passing `?slug=hello&app=supply` in the query string will be passed to the PHP method under a variable called `$slug` and `$app`.
|
|
590
|
+
5. The `return` value from this function will be directly JSON-encoded and returned in the `data` element of the response payload.
|
|
591
|
+
* Note that it will not be validated for content or security, but simply JSON-encoded and returned.
|
|
592
|
+
|
|
593
|
+
```php
|
|
594
|
+
public static function meta(&$api, string $slug) {
|
|
595
|
+
// do a bunch of stuff...
|
|
596
|
+
|
|
597
|
+
// return the response
|
|
598
|
+
return (object)[
|
|
599
|
+
'hello' => 'world',
|
|
600
|
+
'customVariable' => 'goodbye'
|
|
601
|
+
];
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### API Payload Interceptors
|
|
606
|
+
|
|
607
|
+
* An API payload interceptor is similar to a Record Script however it is meant to be used to intercept payloads to/from the table-based features of our API.
|
|
608
|
+
* It would be preferred to have clients alter their payloads or response handling but an API interceptor can be used in lieu of having a client alter their programs.
|
|
609
|
+
* An API payload interceptor accepts exactly two parameters and does not return anything. Both parameters **MUST be passed by reference** (`&$api` and `&$payload`).
|
|
610
|
+
* `&$api` - An object, passed by reference, which contains meta and record information regarding the API transaction.
|
|
611
|
+
* `&$payload` - The payload, passed by reference, which can be read and modified.
|
|
612
|
+
* Interception Options
|
|
613
|
+
* The table and fields you insert determines whether all or some clients and all or some APIs will be intercepted.
|
|
614
|
+
* **All Clients/All APIs** - Insert into `Core.ApiPayloadInterceptors` with a `clientId` and `apiId` set to *null*.
|
|
615
|
+
* **One Client/All APIs** - Insert into `Clients.ApiPayloadInterceptors` with an `apiId` set to *null*.
|
|
616
|
+
* **One Client/One API** - Insert into `Clients.ApiPayloadInterceptors` with an `apiId` set to the `Apis.id` record you want the interceptor applied to.
|
|
617
|
+
* Intercepting the *request* or *response* payload
|
|
618
|
+
1. `prePostProcessing` value of `PRE` - Intercepts a payload *BEFORE* it gets processed where you can read the incoming request payload, alter it, and let the request payload continue through the API as if the client had made the alteration.
|
|
619
|
+
2. `prePostProcessing` value of `POST` - Intercepts a payload *AFTER* it gets processed where you can read the outgoing response payload, alter it, and return the response payload to the client with your alterations.
|
|
620
|
+
|
|
621
|
+
#### **Procedure**
|
|
622
|
+
|
|
623
|
+
1. Determine these field values and insert into either the `Core.ApiPayloadInterceptors` or `Client.ApiPayloadInterceptors` table.
|
|
624
|
+
* `clientId` - The `Core.ApiPayloadInterceptors` table contains a `clientId` field which is a foreign key to `Core.Clients.id` and is meant to specify which client the next field, `apiId`, is referring to. Leave *null* to intercept all APIs for all clients.
|
|
625
|
+
* `apiId` - Foreign key of the `Apis.id` for the API key you want to intercept. Leave *null* to intercept all APIs for all clients.
|
|
626
|
+
* `recordId` - Foreign key of the `Records.id` for the record you want to intercept
|
|
627
|
+
* `prePostProcessing` - Can be either `PRE` or `POST`. This will determine whether the payload will be intercepted before or after the processing. In other words, `PRE` will intercept the *request* payload while `POST` will intercept the *response* payload.
|
|
628
|
+
* `httpMethod` - HTTP method you want to intercept (eg: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`)
|
|
629
|
+
* Note: unlike Record Scripts, the interceptor table has **no `phpMethod` field**. The method name is **derived by convention** from `prePostProcessing` + `httpMethod` (other relevant fields: `isActive`, `minDepth`).
|
|
630
|
+
2. In the \_underscore framework, open/create the PHP class for the model of the record you want to add the interceptor to.
|
|
631
|
+
3. Add a public static function to the PHP class.
|
|
632
|
+
1. Name the method by convention: `strtolower(prePostProcessing)` + `ucfirst(strtolower(httpMethod))`. So a `PRE`/`PUT` interceptor is `prePut`, a `POST`/`GET` interceptor is `postGet`, etc. The engine resolves and calls this name automatically — you do not register it anywhere.
|
|
633
|
+
2. Setup the two required parameters. Both **MUST be passed by reference**.
|
|
634
|
+
* `&$api` - An object, passed by reference, which contains meta and record information regarding the API transaction.
|
|
635
|
+
* `&$payload` - The payload, passed by reference, which can be read and modified. The method returns nothing.
|
|
636
|
+
|
|
637
|
+
```php
|
|
638
|
+
// Convention: PRE + PUT => prePut
|
|
639
|
+
public static function prePut(&$api, &$payload) {
|
|
640
|
+
if (isset($payload->hello)) {
|
|
641
|
+
$payload->hello = 'world'; // overwrites value to world
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### API Versioning
|
|
647
|
+
|
|
648
|
+
* Implement a new version (ie: `/v1`, `/v2`, `/v3`) when a major API framework is introduced to manage the new API framework without breaking existing clients.
|
|
649
|
+
|
|
650
|
+
## Deployment and Scalability
|
|
651
|
+
|
|
652
|
+
### Scalability
|
|
653
|
+
|
|
654
|
+
* Design your application to scale horizontally by distributing the load across multiple cloud servers or instances.
|
|
655
|
+
|
|
656
|
+
### Deployment Best Practices
|
|
657
|
+
|
|
658
|
+
* Use continuous integration/continuous deployment (CI/CD) pipelines to automate the deployment process and ensure consistent deployments.
|
|
659
|
+
|
|
660
|
+
## Logging and Monitoring
|
|
661
|
+
|
|
662
|
+
### Structured Logging
|
|
663
|
+
|
|
664
|
+
* Ensure logs are structured and include relevant context for easier debugging.
|
|
665
|
+
|
|
666
|
+
### Monitoring and Alerts
|
|
667
|
+
|
|
668
|
+
* Implement and alerting for your applications to catch issues early.
|
|
669
|
+
|
|
670
|
+
## Configuration Management
|
|
671
|
+
|
|
672
|
+
### Environment Configuration
|
|
673
|
+
|
|
674
|
+
* Manage configuration through environment variables and configuration files.
|
|
675
|
+
|
|
676
|
+
### Secrets Management
|
|
677
|
+
|
|
678
|
+
* Use secret management tools to handle sensitive information like API keys and passwords.
|
|
679
|
+
|
|
680
|
+
## Code Quality and Maintainability
|
|
681
|
+
|
|
682
|
+
### Code Reviews
|
|
683
|
+
|
|
684
|
+
* Establish a thorough code review process to ensure code quality and knowledge sharing.
|
|
685
|
+
|
|
686
|
+
### Static Code Analysis
|
|
687
|
+
|
|
688
|
+
* Integrate static code analysis tools to catch issue early.
|
|
689
|
+
|
|
690
|
+
## Security
|
|
691
|
+
|
|
692
|
+
### Authentication and Authorization
|
|
693
|
+
|
|
694
|
+
* Implement robust authentication and authorization methods such as OAuth2 and JWT.
|
|
695
|
+
* Ensure role-based access control (RBAC) is implemented.
|
|
696
|
+
|
|
697
|
+
### Data Encryption
|
|
698
|
+
|
|
699
|
+
* Ensure sensitive data is encrypted both in transit and at rest.
|
|
700
|
+
|
|
701
|
+
## Documentation
|
|
702
|
+
|
|
703
|
+
### API Documentation
|
|
704
|
+
|
|
705
|
+
* Maintain clear and comprehensive API documentation.
|
|
706
|
+
* Use Postman for API testing and storage of API scripts and clients' API documentation.
|
|
707
|
+
|
|
708
|
+
### Code Documentation
|
|
709
|
+
|
|
710
|
+
* Ensure code is well-documented with comments and usage examples.
|