Best Practices
Code organization
Module imports first
Always place include statements at the top of the file:
ZYMBA
include 'zymba:date,sql,var,string,http';
include 'resource:lib';
// Rest of your code...
Guard clauses before main logic
Validate inputs and check preconditions at the top before the main logic. This avoids deeply nested conditionals and makes the happy path easy to follow:
ZYMBA
unless (@HTTP.getRequestMethod() == "POST") {
@HTTP.setStatusCode(405);
return;
}
$body = @HTTP.getRequestBody();
if (empty $body) {
@HTTP.setStatusCode(400);
echo @Var.toJSON([error: "Request body required"]);
return;
}
$data = null;
try {
$data = @Var.fromJSON($body);
} catch ($e) {
@HTTP.setStatusCode(400);
echo @Var.toJSON([error: "Invalid JSON"]);
return;
}
// Main logic — all preconditions met
$result = $processData($data);
echo @Var.toJSON($result);
Extract reusable logic into functions
ZYMBA
function $sendJSON($data, $status = 200) {
@HTTP.setStatusCode($status);
@HTTP.setHeader("Content-Type", "application/json");
echo @Var.toJSON($data);
}
$sendJSON([users: $users]);
$sendJSON([error: "Not found"], 404);
Shared code in resources
Place reusable code in resources/ and import with include:
ZYMBA
// resources/helpers.zy
function $formatCurrency($amount, $currency = "EUR") {
return $currency . " " . @Number.format($amount, 2, ".", ",");
}
function $sendJSONResponse($data, $status = 200) {
@HTTP.setStatusCode($status);
@HTTP.setHeader("Content-Type", "application/json");
echo @Var.toJSON($data);
}
// services/api-handler.zy
include 'resource:helpers';
$sendJSONResponse([total: $formatCurrency(1234.5)]);
Naming conventions
Variables — camelCase
ZYMBA
// Good
$orderTotal = 0;
$customerName = "Alice";
$isActive = true;
$maxRetries = 3;
// Avoid
$x = 0;
$temp = "Alice";
$flag = true;
Functions — verb-based camelCase
ZYMBA
// Good
function $calculateTotal($items) { ... }
function $formatAddress($address) { ... }
function $validateEmail($email) { ... }
function $createApiClient($url) { ... }
// Avoid
function $total($items) { ... }
function $addr($a) { ... }
Class-like objects — PascalCase
ZYMBA
$ApiClient = new object() { ... };
$OrderProcessor = new object() { ... };
$EmailValidator = new object() { ... };
Database patterns
Always use parameterized queries
Never build SQL strings by concatenating user input — this is a SQL injection vulnerability.
ZYMBA
// WRONG — SQL injection risk
$sql = "SELECT * FROM contacts WHERE name = '" . $userInput . "'";
// CORRECT
$sql = @SQL.prepare("SELECT * FROM contacts WHERE name = ?", $userInput);
$result = $db.fetchAll($sql, true);
Use backtick strings for complex SQL
Backtick strings preserve newlines and make multi-line queries readable:
ZYMBA
$sql = @SQL.prepare(`
SELECT c.name, c.email, COUNT(t.ID) as orderCount
FROM contacts c
LEFT JOIN transactions t ON t.contactID = c.ID
WHERE c.status = ?
GROUP BY c.ID
HAVING orderCount > ?
ORDER BY orderCount DESC
LIMIT ?
`, $status, $minOrders, $limit);
Transaction context for multi-step operations
ZYMBA
with (new @ZeyOS.TransactionContext) {
$order = new @ZeyOS.ObjectTransaction();
$order.setFields([
name: $data.name,
contactID: $data.customerId
]);
$order.save();
for ($data.items as $item) {
$lineItem = new @ZeyOS.ObjectTransaction();
$lineItem.setFields($item);
$lineItem.save();
}
}
// If any step throws, the entire transaction rolls back automatically
Error handling patterns
Structured error responses
ZYMBA
function $apiHandler() {
try {
$data = @Var.fromJSON(@HTTP.getRequestBody());
$result = $processRequest($data);
@HTTP.setStatusCode(200);
return @Var.toJSON([success: true, data: $result]);
} catch ($e) {
@HTTP.setStatusCode(500);
@Console.log("API Error: " . $e.getMessage());
return @Var.toJSON([success: false, error: $e.getMessage()]);
}
}
Fail early with clear messages
ZYMBA
function $updateUser($id, $data) {
unless ($id is numeric) {
throw new @Exception("User ID must be numeric");
}
unless (exists $data.email) {
throw new @Exception("Email is required");
}
if (!@Regex.test($data.email, "/^[^@\\s]+@[^@\\s]+$/")) {
throw new @Exception("Invalid email format: " . $data.email);
}
// Safe to proceed...
}
Don't silently swallow errors
ZYMBA
// BAD — bug becomes impossible to diagnose
try {
$data = @Var.fromJSON($input);
} catch ($e) {
// Nothing here
}
// GOOD — log and re-throw or handle
try {
$data = @Var.fromJSON($input);
} catch ($e) {
@Console.log("JSON parse error: " . $e.getMessage());
throw new @Exception("Invalid input data");
}
Security patterns
Input validation
ZYMBA
function $sanitizeInput($value) {
if (!($value is string)) {
return "";
}
return @String.trim($value);
}
$name = $sanitizeInput(@HTTP.getRequestVariable("name"));
if (empty $name) {
throw new @Exception("Name is required");
}
if (count $name > 255) {
throw new @Exception("Name is too long");
}
Password hashing
ZYMBA
// NEVER store plain passwords
$hash = @Crypt.hashPassword($plainPassword);
// Store $hash in the database
// Verify on login
if (!@Crypt.verifyPassword($inputPassword, $storedHash)) {
throw new @Exception("Invalid credentials");
}
Constant-time comparison for secrets
ZYMBA
// WRONG — timing attack vulnerable
if ($token == $expectedToken) { ... }
// CORRECT — constant-time comparison
if (@Crypt.equalsTimeConstant($expectedToken, $token)) { ... }
Performance patterns
Batch database operations
ZYMBA
// BAD — N+1 queries
for ($ids as $id) {
$item = $db.fetchOne(@SQL.prepare("SELECT * FROM items WHERE ID = ?", $id), true);
$process($item);
}
// GOOD — single query with IN clause
$placeholders = @Array.joinValues(@Array.map($ids, function() { return "?"; }), ",");
$sql = @SQL.prepare("SELECT * FROM items WHERE ID IN ($placeholders)", ...$ids);
$items = $db.fetchAll($sql, true);
for ($items as $item) {
$process($item);
}
Use lookup maps for repeated searches
ZYMBA
// BAD — O(n) linear scan repeated for every order
for ($orders as $order) {
$customer = @Array.findValue($customers, function($c) use ($order) {
return $c.ID == $order.customerID;
});
}
// GOOD — build a lookup map first: O(1) per lookup
$customerMap = [];
for ($customers as $c) {
$customerMap[$c.ID] = $c;
}
for ($orders as $order) {
$customer = $customerMap[$order.customerID] ?? null;
}
Limit result sets
ZYMBA
// BAD — fetches all records
$sql = "SELECT * FROM transactions";
// GOOD — paginate
$sql = @SQL.prepare(
"SELECT * FROM transactions ORDER BY creationdate DESC LIMIT ? OFFSET ?",
$perPage, ($page - 1) * $perPage
);
Configuration management
Use @APPSETTINGS for configuration
ZYMBA
$config = @APPSETTINGS.myapp;
$apiUrl = $config.apiUrl ?? "https://api.default.com";
$timeout = $config.timeout ?? 30;
$debug = $config.debug ?? false;
if ($debug) {
@Console.log("Using API: " . $apiUrl);
}
Never hard-code credentials
ZYMBA
// WRONG
$apiKey = "sk-1234567890";
// CORRECT — use settings
$apiKey = @APPSETTINGS.myapp.apiKey;
unless (exists $apiKey) {
throw new @Exception("API key not configured");
}
Anti-patterns to avoid
Don't use + for string building
ZYMBA
// WRONG — numeric addition of string parts → likely 0
$msg = "User " + $name + " has " + $count + " items";
// CORRECT — interpolation
$msg = "User $name has $count items";
// CORRECT — concatenation
$msg = "User " . $name . " has " . $count . " items";
Don't ignore return types
ZYMBA
// BAD — crashes if no results
$user = $db.fetchOne($sql, true);
echo $user.name;
// GOOD — check before use
$user = $db.fetchOne($sql, true);
if ($user is null) {
throw new @Exception("User not found");
}
echo $user.name;
Don't build SQL by string concatenation
ZYMBA
// NEVER do this
$sql = "DELETE FROM users WHERE id = " . $id;
// ALWAYS use prepared statements
$sql = @SQL.prepare("DELETE FROM users WHERE id = ?", $id);
Don't mix concerns
Separate data retrieval, business logic, and output:
ZYMBA
// BAD — all concerns mixed
$users = $db.fetchAll("SELECT * FROM contacts WHERE status = 1", true);
for ($users as $user) {
if ($user.age > 18) {
echo "<div>" . $user.name . "</div>";
}
}
// BETTER — separated
function $getActiveAdults($db) {
$sql = @SQL.prepare("SELECT * FROM contacts WHERE status = ?", 1);
$users = $db.fetchAll($sql, true);
return @Array.filter($users, function($u) { return $u.age > 18; });
}
$adults = $getActiveAdults($db);
echo @Var.toJSON($adults);
Summary
| Category | Do | Don't |
|---|---|---|
| Strings | Use . for concatenation | Use + for concatenation |
| SQL | Use @SQL.prepare() | Build SQL with string concatenation |
| Errors | Validate early, fail with clear messages | Silently swallow exceptions |
| Security | Hash passwords, use constant-time comparison | Store plain passwords, compare secrets with == |
| Performance | Batch queries, build lookup maps | N+1 queries, linear scans |
| Organization | Guard clauses, extracted helpers, shared resources | Deep nesting, copy-pasted logic |
| Config | Use @APPSETTINGS | Hard-code URLs and credentials |
See also
- Testing and Debugging — SDK runner, debugging tools, test patterns
- Security —
@Cryptin depth - Database —
@SQL.prepare(), transactions