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.
Files changed (62) hide show
  1. package/.claude/settings.json +119 -0
  2. package/.claude-plugin/marketplace.json +87 -0
  3. package/.claude-plugin/plugin.json +22 -0
  4. package/CLAUDE.md +161 -0
  5. package/README.md +72 -0
  6. package/agents/framework-pattern-checker.md +67 -0
  7. package/agents/harness-optimizer.md +102 -0
  8. package/agents/knowledge-writer.md +62 -0
  9. package/agents/php-build-resolver.md +51 -0
  10. package/agents/php-reviewer.md +51 -0
  11. package/agents/planner.md +88 -0
  12. package/agents/session-capture.md +101 -0
  13. package/agents/sql-reviewer.md +67 -0
  14. package/contexts/dev.md +43 -0
  15. package/contexts/research.md +49 -0
  16. package/contexts/review.md +37 -0
  17. package/knowledge/1.0/apps/library/INDEX.md +5 -0
  18. package/knowledge/1.0/apps/library/architecture.md +105 -0
  19. package/knowledge/1.0/apps/worker/INDEX.md +5 -0
  20. package/knowledge/1.0/apps/worker/architecture.md +223 -0
  21. package/knowledge/1.0/standards/backend-php.md +450 -0
  22. package/knowledge/2.0/apps/_underscore/INDEX.md +6 -0
  23. package/knowledge/2.0/apps/_underscore/architecture.md +183 -0
  24. package/knowledge/2.0/apps/_underscore/features/recursive-item-fulfillments.md +111 -0
  25. package/knowledge/2.0/apps/api2/INDEX.md +5 -0
  26. package/knowledge/2.0/apps/api2/architecture.md +162 -0
  27. package/knowledge/2.0/apps/worker2/INDEX.md +6 -0
  28. package/knowledge/2.0/apps/worker2/architecture.md +127 -0
  29. package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +135 -0
  30. package/knowledge/2.0/standards/backend-php.md +710 -0
  31. package/knowledge/CONVENTIONS.md +117 -0
  32. package/knowledge/INDEX.md +19 -0
  33. package/knowledge/clients/.gitkeep +0 -0
  34. package/knowledge/registry.json +7 -0
  35. package/knowledge.js +384 -0
  36. package/mcp-configs/README.md +72 -0
  37. package/mcp-configs/mcp-servers.json +23 -0
  38. package/package.json +50 -0
  39. package/rules/README.md +53 -0
  40. package/rules/common/coding-style.md +123 -0
  41. package/rules/common/git-workflow.md +72 -0
  42. package/rules/common/security.md +118 -0
  43. package/rules/common/testing.md +74 -0
  44. package/rules/php/app-framework.md +104 -0
  45. package/rules/php/underscore-framework.md +111 -0
  46. package/scripts/harness.js +605 -0
  47. package/scripts/hooks/evaluate-session.js +55 -0
  48. package/scripts/hooks/post-edit-validate.js +102 -0
  49. package/scripts/hooks/session-end.js +13 -0
  50. package/scripts/hooks/session-start.js +57 -0
  51. package/scripts/install.js +611 -0
  52. package/scripts/pre-commit +46 -0
  53. package/skills/capture/SKILL.md +294 -0
  54. package/skills/code-review/SKILL.md +140 -0
  55. package/skills/create-elastic-beanstalk/SKILL.md +217 -0
  56. package/skills/harness-audit/SKILL.md +152 -0
  57. package/skills/kickoff/SKILL.md +151 -0
  58. package/skills/php-patterns/SKILL.md +296 -0
  59. package/skills/session-resume/SKILL.md +156 -0
  60. package/skills/session-save/SKILL.md +158 -0
  61. package/skills/sync-team-skills/SKILL.md +87 -0
  62. 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 `&lt;`
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.