Skip to main content

Loops and Iteration

Loops are the foundation of code generation. They iterate over database elements — fields, tables, constraints — and repeat a block of template code for each element. Without loops, you couldn't generate models for multiple fields or handle databases with dozens of tables.

Loop Types Overview

Scoriet provides seven loop types, each iterating over a different collection:

LoopIterates OverUse CaseCount
{:for nmaxitems:}All fields in current tableModel properties, form inputsVariable per table
{:for nmaxfiles:}All tables in databaseMultiple model classes, DB setupTotal table count
{:for keys:}Primary/unique key fields onlyKey validation, unique constraintsUsually 1
{:for foreign:}Foreign key fields onlyRelationship models, JOINsVariable
{:for fieldsnokey:}Non-key fieldsRegular propertiesVariable
{:for fieldsnoblob:}Non-BLOB fieldsSearchable/indexable fieldsVariable
{:for constraints:}All constraintsConstraint definitionsVariable

Loop Syntax

All loops follow the same basic structure:

{:for COLLECTION:}
Content here — repeats for each item
Access current item via {:item.*:}
{:endfor:}

The loop starts with {:for and the collection name, and ends with {:endfor:}. Everything between executes once per item.

Loop Variables

Inside a loop, you have access to three special variables:

VariableTypeStarts AtIncrementsUse Case
{:nCount:}integer1+1 per iteration1st item, 2nd item, 3rd item
{:i:}integer0+1 per iterationArray indexing (array[0], array[1])
{:item.*:}objectAccess current item's properties

Counter vs Index

  • {:nCount:} is 1-based: first iteration = 1, second = 2, third = 3
  • {:i:} is 0-based: first iteration = 0, second = 1, third = 2

When to use which:

  • Display purposes: Item #{:nCount:} → "Item #1", "Item #2"
  • Array indexing: $array[{:i:}]$array[0], $array[1]
  • Skip last item (comma-separated lists): Use {:nCount:} to detect last

Loop Variable Example

{:for nmaxitems:}
Iteration {:nCount:}: Index {:i:} = {:item.name:}
{:endfor:}

Output:

Iteration 1: Index 0 = id
Iteration 2: Index 1 = username
Iteration 3: Index 2 = email

Loop Type 1: {:for nmaxitems:} — All Fields

Iterate over every field in the current table. This is the most common loop type.

Syntax

{:for nmaxitems:}
Code here repeats for each field
Current field: {:item.name:}
{:endfor:}

Examples

Example 1: Class Properties

class User {
{:for nmaxitems:}
public \${:item.name:};
{:endfor:}
}

Output (assuming 5 fields):

class User {
public $id;
public $username;
public $email;
public $created_at;
public $updated_at;
}

Example 2: SQL INSERT Fields

INSERT INTO {:tablename:} (
{:for nmaxitems:}
{:item.name:}{:if nCount != nmaxitems:},{:endif:}
{:endfor:}
) VALUES (...)

Output:

INSERT INTO users (
id,
username,
email,
created_at,
updated_at
) VALUES (...)

Example 3: Function Parameters with Commas

function create({:for nmaxitems:}${:item.name:}{:if nCount != nmaxitems:}, {:endif:}{:endfor:}) {
}

Output:

function create($id, $username, $email, $created_at, $updated_at) {
}

Loop Type 2: {:for nmaxfiles:} — All Tables

Iterate over every table in the database. Use this to generate models, controllers, or documentation for multiple tables.

Syntax

{:for nmaxfiles:}
Code here repeats for each table
Current table: {:item.name:}
{:endfor:}
info

Within {:for nmaxfiles:}, the context changes. System variables like {:nmaxitems:} now refer to the current table's field count, not the global count. This enables nested loops.

Examples

Example 1: Multiple Model Classes

{:for nmaxfiles:}
class {:pascalcase(item.name):} {
{:for nmaxitems:}
public \${:item.name:};
{:endfor:}
}

{:endfor:}

Output:

class Users {
public $id;
public $username;
public $email;
}

class Products {
public $id;
public $name;
public $price;
}

class Orders {
public $id;
public $user_id;
public $product_id;
}

Example 2: Database Migration Files

{:for nmaxfiles:}
// Migration for table: {:item.name:}
public function up() {
Schema::create('{:item.name:}', function (Blueprint $table) {
{:for nmaxitems:}
$table->column('{:item.name:}', '{:item.type:}');
{:endfor:}
});
}
{:endfor:}

Example 3: API Endpoint List

# API Endpoints

{:for nmaxfiles:}
## {:pascalcase(item.name):} Resource

- GET /api/{:snakecase(item.name):} - List all
- POST /api/{:snakecase(item.name):} - Create
- GET /api/{:snakecase(item.name):}/{:id:} - Get one
- PUT /api/{:snakecase(item.name):}/{:id:} - Update
- DELETE /api/{:snakecase(item.name):}/{:id:} - Delete

{:endfor:}

Loop Type 3: {:for keys:} — Key Fields Only

Iterate only over fields that are primary keys or unique keys.

Syntax

{:for keys:}
Code here repeats only for key fields
Key field: {:item.name:}
{:endfor:}

Examples

Example 1: Primary Key Validation

public function validatePrimaryKey($data) {
{:for keys:}
if (!isset($data['{:item.name:}'])) {
throw new Exception('Missing primary key: {:item.name:}');
}
{:endfor:}
}

Output (if id is the only key):

public function validatePrimaryKey($data) {
if (!isset($data['id'])) {
throw new Exception('Missing primary key: id');
}
}

Example 2: Unique Constraint Enforcement

CREATE TABLE {:tablename:} (
{:for keys:}
{:item.name:} {:item.type:} PRIMARY KEY,
{:endfor:}
)

Loop Type 4: {:for foreign:} — Foreign Key Fields

Iterate only over foreign key fields (fields that reference other tables).

Syntax

{:for foreign:}
Code here repeats for each foreign key
FK: {:item.name:} → {:item.linktable:}({:item.linkfield:})
{:endfor:}

Examples

Example 1: Relationship Models

class Order {
{:for foreign:}
public ${:snakecase(item.linktable):}; // Relationship to {:item.linktable:}
{:endfor:}
}

Output:

class Order {
public $user; // Relationship to users
public $product; // Relationship to products
}

Example 2: Foreign Key Constraints in SQL

ALTER TABLE {:tablename:} ADD CONSTRAINT (
{:for foreign:}
FOREIGN KEY ({:item.name:}) REFERENCES {:item.linktable:}({:item.linkfield:})
{:endfor:}
);

Loop Type 5: {:for fieldsnokey:} — Non-Key Fields

Iterate over fields that are NOT primary or unique keys.

Syntax

{:for fieldsnokey:}
Regular fields (excludes keys)
Field: {:item.name:}
{:endfor:}

Examples

Example 1: Form Input Fields

<form>
{:for fieldsnokey:}
<input type="text" name="{:item.name:}" placeholder="{:item.caption:}" />
{:endfor:}
<button type="submit">Save</button>
</form>

Loop Type 6: {:for fieldsnoblob:} — Non-BLOB Fields

Iterate over fields that are NOT BLOB/TEXT types. Use for searchable or indexable fields.

Syntax

{:for fieldsnoblob:}
Searchable fields (excludes TEXT, BLOB)
Field: {:item.name:}
{:endfor:}

Examples

Example 1: Database Indexes

CREATE TABLE {:tablename:} (
{:for fieldsnoblob:}
INDEX idx_{:item.name:} ({:item.name:}),
{:endfor:}
);

Loop Type 7: {:for constraints:} — Constraints

Iterate over all constraints defined on the table.

Syntax

{:for constraints:}
Constraint: {:item.name:}
{:endfor:}

Nested Loops

You can nest loops to handle multi-level iteration. A common pattern: iterate tables, then iterate fields within each table.

Nested Loop Pattern

{:for nmaxfiles:}
Processing table: {:tablename:}

Fields in this table:
{:for nmaxitems:}
- {:item.name:}
{:endfor:}

{:endfor:}

How it works:

  1. Outer loop iterates each table
  2. For each table, context changes: {:nmaxitems:} now = field count of current table
  3. Inner loop iterates fields of current table
  4. After inner loop completes, outer loop moves to next table

Nested Loop Example: Multi-File Generation

{:for nmaxfiles:}
// File: {:pascalcase(item.name):}Model.php
class {:pascalcase(item.name):} {
{:for nmaxitems:}
public \${:item.name:};
{:endfor:}
}

{:endfor:}

Output:

// File: UsersModel.php
class Users {
public $id;
public $username;
public $email;
}

// File: ProductsModel.php
class Products {
public $id;
public $name;
public $price;
}

Advanced Nested Example: Full ORM Generation

{:for nmaxfiles:}
// Generated Model for table: {:tablename:}
class {:pascalcase(item.name):} {
protected $table = '{:tablename:}';
protected $fillable = [
{:for nmaxitems:}
'{:item.name:}',
{:endfor:}
];

// Relationships
{:for foreign:}
public function {:camelcase(item.linktable):}() {
return $this->belongsTo({:pascalcase(item.linktable):}::class, '{:item.name:}', '{:item.linkfield:}');
}
{:endfor:}
}

{:endfor:}

Output (3 tables, 2 with foreign keys):

// Generated Model for table: users
class Users {
protected $table = 'users';
protected $fillable = [
'id',
'username',
'email',
];

// Relationships
}

// Generated Model for table: products
class Products {
protected $table = 'products';
protected $fillable = [
'id',
'name',
'price',
];

// Relationships
}

// Generated Model for table: orders
class Orders {
protected $table = 'orders';
protected $fillable = [
'id',
'user_id',
'product_id',
];

// Relationships
public function users() {
return $this->belongsTo(Users::class, 'user_id', 'id');
}
public function products() {
return $this->belongsTo(Products::class, 'product_id', 'id');
}
}

Comma-Separated Lists Pattern

A very common pattern: generate a comma-separated list of items, with commas between items but NOT after the last item.

The Problem

{:for nmaxitems:}
{:item.name:},
{:endfor:}

Output (wrong — trailing comma):

id,
username,
email,

The Solution: Use nCount

{:for nmaxitems:}
{:item.name:}{:if nCount != nmaxitems:},{:endif:}
{:endfor:}

Output (correct — no trailing comma):

id,username,email

Or with line breaks:

{:for nmaxitems:}
{:item.name:}{:if nCount != nmaxitems:},{:endif:}
{:endfor:}

Output:

id,
username,
email

Real-World Example: SQL Column List

SELECT
{:for nmaxitems:}
{:item.name:}{:if nCount != nmaxitems:},{:endif:}
{:endfor:}
FROM {:tablename:}

Output:

SELECT
id,
username,
email
FROM users

Empty Loops

If a loop collection is empty (e.g., {:for keys:} on a table with no primary key), the loop body doesn't execute. This is safe — no output is generated.

{:for keys:}
Primary key: {:item.name:}
{:endfor:}

If there are no keys, this produces no output. This is the desired behavior — don't generate code that doesn't apply.

Loop Performance Considerations

caution

Nested loops are powerful but not infinite. A template with 3 nested levels over a database with 50 tables and 100 fields per table would generate roughly 50 × 100 × 100 = 500,000 iterations. Keep nesting shallow for performance.

In practice:

  • 2 levels deep: Always fine
  • 3 levels deep: Fine for small databases (< 100 tables)
  • 4+ levels: Consider JavaScript code blocks instead

Loop Best Practices

tip

1. Start simple, add nesting gradually Test with one loop level first. Add nested loops once you confirm the outer loop works.

2. Use the right loop type Use {:for keys:} instead of looping all fields and filtering with {:if item.isprimarykey:}. It's clearer and more efficient.

3. Combine loops with conditionals Loops iterate; conditionals filter. Use both together:

{:for nmaxitems:}
{:if item.type=="varchar":}
Generate code only for varchar fields
{:endif:}
{:endfor:}

4. Test output incrementally Generate, review, refine. Don't write a massive template and hope it works.

5. Document loop intent in comments

{:for nmaxitems:}
// Looping: Generate validators for all fields
$this->validate...
{:endfor:}

Troubleshooting Loops

Issue: No Output from Loop

Possible causes:

  1. Loop collection is empty (e.g., no foreign keys)
  2. Typo in loop marker: {:for nmaxitem:} instead of {:for nmaxitems:}
  3. Using wrong loop type

Solution: Check that the collection exists. If generating multiple tables with different field counts, test with a table that has the expected fields.

Issue: Wrong Item Properties in Nested Loop

Problem: Inside a nested loop, {:item.name:} shows the outer item, not the inner item.

Cause: Scoriet uses the same item variable for all nested loops. This is a known limitation.

Workaround: Use JavaScript code blocks for complex nested iteration. See JavaScript Code Blocks.

Issue: Extra Blank Lines in Output

Problem: Loop produces extra blank lines.

Cause: Blank lines in the loop body template are preserved in output.

Solution: Remove blank lines from template if you don't want them in output. Or use a code block to handle formatting.


Next: Add logic to your loops with Conditionals, or explore Built-in Functions for text transformation.