Zum Hauptinhalt springen

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​