Skip to main content

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 ([email protected]($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 ([email protected]($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

CategoryDoDon't
StringsUse . for concatenationUse + for concatenation
SQLUse @SQL.prepare()Build SQL with string concatenation
ErrorsValidate early, fail with clear messagesSilently swallow exceptions
SecurityHash passwords, use constant-time comparisonStore plain passwords, compare secrets with ==
PerformanceBatch queries, build lookup mapsN+1 queries, linear scans
OrganizationGuard clauses, extracted helpers, shared resourcesDeep nesting, copy-pasted logic
ConfigUse @APPSETTINGSHard-code URLs and credentials

See also