Developer Guide

This guide is intended for developers who want to understand the internals, extend the engine, or contribute to CandidTemplate. It covers architecture, design decisions, class responsibilities, rendering pipeline, and roadmap.

---

1. Core Philosophy

Structure → Composition → Binding → Rendering
HTML is the source of truth. The engine manipulates real DOM — never string-replaces or rewrites templates.
---

2. Core Classes

Class Role Responsibility
CandidTemplate Core Engine DOM manipulation, slot/include/loop rendering, assignment application, data store, hook system, debug output. Self-sufficient — works without adaptor.
CandidTemplateAdaptor File System Bridge File path resolution, body extraction, singleton access, data file auto-loading. Thin layer over CandidTemplate — no rendering logic.
CandidDomContext DOM Parser Wraps DOMDocument, protects <script> and <style> content, handles fragment vs full document detection, Vue event attribute protection, serialization.
CandidElement Element API Fluent API for single element manipulation. Reads pending assignments before DOM for accurate chained operations. Wraps CandidTemplate assignment methods.
CandidElements Collection API Iterable collection of CandidElement instances. Supports bulk operations, first/last detection, filtering, and counting.
CandidViewResolver Path Resolver Resolves template file paths using hierarchical directory walking. Supports base path, app path, common path, and result caching.
---

3. Class Responsibility Boundary

The most important boundary is between CandidTemplate and CandidTemplateAdaptor:

Concern CandidTemplate CandidTemplateAdaptor
DOM manipulation
Slot rendering
Loop rendering
Assignment application
Hook system (onLoad)
Data store (set/get/share)
File path resolution
Body extraction
Data file auto-loading
Singleton access
---

4. Rendering Pipeline

CandidTemplate::render()
    │
    ├── applyDataFiles()
    │       └── execute each registered .data.php file
    │           ($this = template instance)
    │
    ├── renderIncludes()  ← pass 1
    │       └── resolve include selectors → replaceTargetWithHtml()
    │
    ├── renderSlots()
    │       └── for each registered slot:
    │               └── slot->render()  ← recursive
    │                       ├── applyDataFiles()
    │                       ├── renderIncludes()
    │                       ├── renderSlots()
    │                       ├── renderIncludes()
    │                       ├── applyLoops()
    │                       └── applyAssignments()
    │           replaceTargetWithHtml() ← wrapper removed
    │
    ├── renderIncludes()  ← pass 2 (catches slot-introduced includes)
    │
    ├── applyLoops()
    │       └── for each loop container:
    │               ├── remove original container
    │               └── for each item:
    │                       ├── renderIncludes()
    │                       ├── renderSlots()
    │                       └── applyAssignments()
    │
    ├── applyAssignments()
    │       └── apply all pick() queued bindings to DOM
    │
    └── ctx->render()
            └── serialize DOM → HTML string
                restore Vue event attributes (@click etc.)
                html_entity_decode
---

5. Slot Wrapper Removal

A key design decision — the data-slot wrapper element is replaced by its content, not filled. This keeps rendered HTML clean with no leftover placeholder divs.


// replaceTargetWithHtml() — used by both renderSlots() and renderIncludes()
private function replaceTargetWithHtml(DOMNode $target, CandidTemplate $tpl): void
{
    $parent = $target->parentNode;
    if (!$parent) return;

    $html = $tpl->render();
    foreach ($this->importHtml($html) as $n) {
        $parent->insertBefore($n, $target);
    }
    $parent->removeChild($target);
}
---

6. Assignment System

All pick() calls are queued — not applied immediately. The internal structure:


// Internal assignments map
$assignments = [
    'selector' => [
        0 => ['data' => 'plain string or array'],   // index 0
        1 => ['data' => [...]]                       // index 1 (pickAll)
    ]
];

// Array data structure (for content + attribute assignments)
[
    'content'    => 'Hello World',
    'attributes' => ['class' => 'active', 'href' => '/home']
]

Priority during applyAssignments():

  1. If remove: true — element removed from DOM
  2. If array with attributes — attributes applied first
  3. If array with content — inner HTML replaced
  4. If plain string — element itself replaced via replaceNode()
---

7. Selector Resolution

findNodes() converts selectors to XPath internally:

Selector Type Example XPath Generated
Simple (legacy) nav_home .//*[@id='nav_home'] | .//*[@name='nav_home'] | .//nav_home
ID #nav_home .//*[@id='nav_home']
Class .nav-link .//*[contains(@class,'nav-link')]
Attribute [data-id] .//*[@data-id]
Attribute value [data-id=123] .//*[@data-id='123']
Tag + Class a.nav-link .//a[contains(@class,'nav-link')]
Combinators (div > a), pseudo-selectors (:first-child), and tag+id (div#main) are not supported. Use plain XPath via DOMXPath directly for advanced cases.
---

8. Data Store — Inheritance Model

root->share('appName', 'Sony News')     ← stored on root
    │
    ├── content template
    │       get('appName') ✅            ← walks parent chain to root
    │       ├── slider
    │       │       get('appName') ✅    ← inherited through chain
    │       └── sidebar
    │               get('appName') ✅
    └── footer
            get('appName') ✅

root->set('theme', 'dark')
    ├── content → get('theme') ✅        ← parent data flows down
    └── footer  → get('theme') ✅

content->set('slides', 5)
    root   → get('slides') ❌            ← child data does not flow up
    footer → get('slides') ❌
    slider → get('slides') ✅            ← only child of content
---

9. Script and Style Protection

DOMDocument mangles content inside <script> and <style> blocks. CandidDomContext protects them using token substitution:

Load HTML
    ↓
Replace <script>/<style> content with ___CANDID_SCRIPT_N___ tokens
    ↓
DOMDocument::loadHTML()
    ↓
Restore original content from token map
    ↓
Safe serialization via saveHTML()
---

10. Vue Event Compatibility

Vue event bindings use @ prefix which DOMDocument cannot parse. They are transparently protected and restored:

@click="handler"
    ↓  protectVueEvents()
data-candid-on-click="handler"
    ↓  DOMDocument parses safely
data-candid-on-click="handler"
    ↓  restoreVueEvents() in render()
@click="handler"
---

11. Key Design Decisions

Decision Reason Alternative Considered
DOM-based, not string-based Accurate, no regex edge cases Regex/str_replace templating
Deferred assignments Allows chaining, order independence Immediate DOM mutation
Slot wrapper removal Clean output, no leftover divs Keep wrapper, clear contents
Two-pass include rendering Supports includes inside slots Single-pass only
data-slot attribute Valid HTML, browser-safe Custom tags, comments
Adaptor as thin file bridge Core engine stays framework-agnostic File loading inside CandidTemplate
Script/style token protection Prevents DOMDocument mangling Custom HTML parser
---

12. Extension Points

Recommended ways to extend without modifying core:

---

13. Error Handling Strategy

Situation Behaviour Exception Type
View file not found Throws immediately RuntimeException
Invalid base path Throws immediately RuntimeException
Data file not found Throws immediately InvalidArgumentException
Forbidden superglobal in data file Throws before execution InvalidArgumentException
Duplicate loop container Throws immediately InvalidArgumentException
Loop container not found Throws immediately InvalidArgumentException
Selector not found in pick() Silent — returns null element None
Slot placeholder not found Silent — slot skipped None
render() on loop item Throws immediately LogicException
---

14. Debugging

Both CandidTemplate and CandidElement provide built-in debug output:


// Full template debug — shows assignments, slots, loops, data store
echo $tpl->debugHtml();

// Specific slot debug
echo $tpl->debugHtml('slider');

// Element debug — shows pending assignments for one element
echo $tpl->pick('#newsCard')->debugHtml();

Debug output includes:

---

15. Coding Guidelines

---

16. Testing Strategy

---

17. Performance Considerations

---

18. Planned Features — Roadmap

Feature Description Priority
Output caching Cache rendered slot output with TTL 🟢 High
XSS escaping safeContent() — auto-escape user input 🟢 High
Partial render renderSlot('content') for AJAX responses 🟡 Medium
Asset versioning Auto append ?v=hash to asset URLs 🟡 Medium
Meta/head injection setMeta(), addScript(), addStyle() 🟡 Medium
i18n support Auto-replace data-i18n keys from language files 🟡 Medium
Component system data-component attribute — semantic reusable includes 🔵 Low
Template inheritance Blade-style extends() / section() 🔵 Low
Event system on('beforeRender'), on('afterRender') 🔵 Low
---

19. Versioning Strategy

Version Status Commitment
v0.x Experimental API may change without notice
v1.0 Stable No breaking changes within major version
v1.x Active Backward compatible features and fixes
v2.0+ Future Breaking changes with migration guide
---

20. Contribution Guidelines

---
CandidTemplate is DOM-first and assignment-deferred — keep new features consistent with these principles. The engine manipulates real structure, not strings. The adaptor handles files. The element handles the API. Each class owns exactly one concern.