PHPackages                             qoliber/tsuku - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. qoliber/tsuku

ActiveLibrary[Templating &amp; Views](/categories/templating)

qoliber/tsuku
=============

A lightweight PHP templating library for transforming data into multiple formats (CSV, XML, TSV, etc.) with XSLT-like simplicity

1.2.0(6mo ago)869—0%1MITPHPPHP ^8.1

Since Nov 16Pushed 6mo agoCompare

[ Source](https://github.com/qoliber/tsuku)[ Packagist](https://packagist.org/packages/qoliber/tsuku)[ RSS](/packages/qoliber-tsuku/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (4)Versions (5)Used By (0)

Tsuku (つく)
==========

[](#tsuku-つく)

**A lightweight PHP templating library for transforming data into ANY text format**

Tsuku is a powerful template processing library built with a clean **Lexer → Parser → Compiler** architecture. Transform your data into CSV, XML, JSON, XSD, or any text format you need using simple, intuitive templates.

Perfect for e-commerce exports, API responses, configuration files, and data transformations.

Features
--------

[](#features)

- 🎯 **Any text format**: CSV, XML, JSON, YAML, TOML, HTML, Markdown, INI, XSD, or custom formats
- 🔄 **Control flow**: Loops (`@for`), conditionals (`@if`, `@unless`, `@else`), pattern matching (`@match`)
- 🪺 **Deep nesting**: Unlimited levels of nested directives
- 🎨 **Smart object/array access**: Automatic getter detection, method calls, property access
- 🔧 **Custom functions**: Register your own `@function()` handlers
- 🎭 **Widget support**: Build Magento-style widgets with custom functions
- 🛠️ **Clean architecture**: Lexer → Parser → Compiler pipeline (AST-based)
- 🚀 **PHP 8.1+**: Modern PHP with zero dependencies
- ✅ **Production-ready**: 196 tests, 423 assertions, 88% mutation score
- 📦 **Preserves formatting**: Exact whitespace and newline control
- ⚡ **Fast**: Single-pass compilation, efficient AST walking
- 🔒 **Type-safe**: Full PHP 8.1+ type hints and strict types

Performance
-----------

[](#performance)

Tsuku is **fast** - designed for high-volume data transformations:

BenchmarkPerformanceThroughput**Simple templates**0.2ms per render**~5,000 renders/sec****Complex templates**1.0ms per render**~1,000 renders/sec****1,000 variables**1.8ms per render**~550 renders/sec****CSV export (1,000 products)**3.9ms per export**~250 exports/sec****Real-world capacity:**

- **250,000+ products/second** for CSV exports
- Sub-millisecond rendering for typical templates
- Low memory footprint (~60KB per render)

Run benchmarks yourself:

```
php benchmarks/run-all.php
```

See [benchmarks/](benchmarks/) for detailed performance tests.

Requirements
------------

[](#requirements)

- PHP 8.1 or higher

Installation
------------

[](#installation)

```
composer require qoliber/tsuku
```

Quick Start
-----------

[](#quick-start)

### Simple Variables

[](#simple-variables)

```
use Qoliber\Tsuku\Tsuku;

$data = ['product' => 'Widget', 'price' => 29.99];

$template = 'Product: {product}, Price: ${price}';

$tsuku = new Tsuku();
echo $tsuku->process($template, $data);
// Output: Product: Widget, Price: $29.99
```

### Loops with `@for`

[](#loops-with-for)

```
$template = 'Products:
@for(products as product)
- {product.name}: ${product.price}
@end';

$data = [
    'products' => [
        ['name' => 'Widget A', 'price' => '29.99'],
        ['name' => 'Widget B', 'price' => '39.99'],
    ],
];

echo $tsuku->process($template, $data);
// Output:
// Products:
// - Widget A: $29.99
// - Widget B: $39.99
```

### Conditionals with `@if` and `@else`

[](#conditionals-with-if-and-else)

```
$template = '@for(items as item)
{item.name}: @if(item.stock > 0)
✓ Available
@else
✗ Out of Stock
@end
@end';
```

### Smart Object/Array Access

[](#smart-objectarray-access)

```
// Works with both arrays AND objects!
class Product {
    private $price = 99.99;
    public function getPrice() { return $this->price; }
    public function isAvailable() { return true; }
}

$template = 'Price: ${product.price}, Available: {product.available}';
$tsuku->process($template, ['product' => new Product()]);
// Output: Price: $99.99, Available: 1
```

### Custom Functions

[](#custom-functions)

```
// Register your own functions
$tsuku->registerFunction('currency', fn($amount, $code = 'USD') =>
    match($code) {
        'USD' => '$' . number_format($amount, 2),
        'EUR' => '€' . number_format($amount, 2),
        default => $code . ' ' . number_format($amount, 2)
    }
);

$template = 'Total: @currency(price, "EUR")';
$tsuku->process($template, ['price' => 99.99]);
// Output: Total: €99.99
```

Real-World Examples
-------------------

[](#real-world-examples)

### CSV Export with Escaping

[](#csv-export-with-escaping)

```
$template = 'SKU,Name,Price,Stock
@for(products as product)
@csv(product.sku),@csv(product.name),$@number(product.price, 2),{product.stock}
@end';

$data = [
    'products' => [
        ['sku' => 'WID-001', 'name' => 'Widget', 'price' => 29.99, 'stock' => '100'],
        ['sku' => 'GAD-002', 'name' => 'Gadget, Premium', 'price' => 1299.50, 'stock' => '50'],
    ],
];

file_put_contents('export.csv', $tsuku->process($template, $data));
// Output:
// SKU,Name,Price,Stock
// WID-001,Widget,$29.99,100
// GAD-002,"Gadget, Premium",$1,299.50,50
```

### XML Product Catalog

[](#xml-product-catalog)

```
$template = '

@for(products as product)

    {product.name}
    {product.price}
@if(product.stock > 0)
    in-stock
@end

@end
';
```

### YAML Configuration

[](#yaml-configuration)

```
$template = 'services:
@for(services as service)
  {service.name}:
    image: {service.image}
    @if(service.ports)ports:
@for(service.ports as port)
      - "{port}"
@end
    @end
@end';
```

### HTML Product List with XSS Protection

[](#html-product-list-with-xss-protection)

```
$template = '
@for(products as product)

    @html(product.name)
    $@number(product.price, 2)
    @html(product.description)
    @if(product.stock > 0)
    Available
    @else
    Out of Stock
    @end

@end
';

$data = [
    'products' => [
        [
            'name' => 'Premium Widget',
            'price' => 1299.99,
            'description' => 'A powerful widget',
            'stock' => 5,
        ],
    ],
];

// Output: HTML entities escaped to prevent XSS
// Premium Widget
// $1,299.99
// A &lt;strong&gt;powerful&lt;/strong&gt; widget
```

Template Syntax
---------------

[](#template-syntax)

### Variables

[](#variables)

Use `{variableName}` or `{object.property}` for dot notation:

```
{name}
{product.name}
{category.products.0.name}

```

**Smart Object/Array Access:**

```
// All of these work:
{product.price}     // Array: $product['price'] OR Object: $product->getPrice()
{user.name}         // Array: $user['name'] OR Object: $user->getName()
{product.available} // Array: $product['available'] OR Object: $product->isAvailable()
{item.total}        // Array: $item['total'] OR Object: $item->total() OR $item->getTotal()
```

### For Loops

[](#for-loops)

```
@for(collection as item)
  {item.property}
@end

```

With key/value (value first, then key):

```
@for(items as item, key)
  {key}: {item}
@end

```

### Conditionals

[](#conditionals)

**If/Else:**

```
@if(variable > 0)
  Content when true
@else
  Content when false
@end

```

**Unless:**

```
@unless(variable > 0)
  Content when false
@else
  Content when true
@end

```

**Match (Pattern Matching):**

```
@match(status)
@case("active")
  ✓ Active
@case("pending")
  ⏳ Pending
@case("suspended")
  ⚠ Suspended
@default
  ❌ Unknown
@end

```

**Match with multiple values:**

```
@match(user.role)
@case("admin", "moderator")
  Full Access
@case("user", "guest")
  Limited Access
@default
  No Access
@end

```

**Supported operators:** `>`, `=`, `registerFunction('badge', function(string $text, string $color = 'blue'): string {
    return "{$text}";
});

// Use in template:
// @badge(status, "green")
```

### Deep Nesting

[](#deep-nesting)

Nest directives as deep as you need:

```
@for(categories as category)
  Category: {category.name}
  @for(category.products as product)
    Product: {product.name}
    @for(product.variants as variant)
      Variant: {variant.sku} - ${variant.price}
    @end
  @end
@end

```

Architecture &amp; Design
-------------------------

[](#architecture--design)

Tsuku uses a clean **three-stage compiler pipeline** inspired by traditional programming language design:

### The Pipeline

[](#the-pipeline)

```
Template String → Lexer → Tokens → Parser → AST → Compiler → Output String

```

#### 1. **Lexer (Lexical Analyzer)**

[](#1-lexer-lexical-analyzer)

> **What it means:** "Lexer" comes from "lexical analysis" - breaking text into meaningful chunks

**Location:** `src/Lexer/Lexer.php`

The Lexer reads the raw template string character by character and breaks it into **tokens** (meaningful units):

```
Input:  "Hello {name}, @if(admin)welcome@end"

Tokens: [
  TEXT("Hello "),
  VARIABLE("name"),
  TEXT(", "),
  DIRECTIVE_IF("admin"),
  TEXT("welcome"),
  DIRECTIVE_END
]
```

**Why?** Makes parsing easier by converting a string into structured chunks.

#### 2. **Parser (Syntax Analyzer)**

[](#2-parser-syntax-analyzer)

> **What it means:** Builds a tree structure showing how pieces relate to each other

**Location:** `src/Ast/Parser.php`

The Parser takes tokens and builds an **AST (Abstract Syntax Tree)** - a tree structure representing the template's logical structure:

```
Tokens: [TEXT("Hello "), VARIABLE("name"), DIRECTIVE_IF(...)]

AST:
TemplateNode
├── TextNode("Hello ")
├── VariableNode("name")
└── IfNode(condition: "admin")
    └── TextNode("welcome")
```

**Why?** The tree structure makes it easy to handle nesting and execute directives in the correct order.

#### 3. **Compiler (Code Generator)**

[](#3-compiler-code-generator)

> **What it means:** Walks the tree and generates the final output

**Location:** `src/Compiler/Compiler.php`

The Compiler **walks** the AST tree using the **Visitor Pattern** and generates the output string:

```
AST Tree → Visitor Pattern → Final Output

TemplateNode.accept(compiler)
  ├── TextNode.accept(compiler) → "Hello "
  ├── VariableNode.accept(compiler) → "John"  (looks up data)
  └── IfNode.accept(compiler)
      └── if (condition) TextNode.accept(compiler) → "welcome"

Output: "Hello John, welcome"
```

**Why?** Clean separation: data lookup, conditionals, loops all handled in one place.

### Key Concepts Explained

[](#key-concepts-explained)

**AST (Abstract Syntax Tree)**

- A tree representation of your template structure
- Each node = one piece (text, variable, loop, condition)
- Example: `@if(x)@for(items)...@end@end` becomes a tree with IfNode containing ForNode

**Node**

- One element in the AST tree
- Types: `TextNode`, `VariableNode`, `ForNode`, `IfNode`, `FunctionNode`, etc.
- Each node knows how to compile itself

**Token**

- Smallest meaningful unit from Lexer
- Like words in a sentence
- Types: `TEXT`, `VARIABLE`, `DIRECTIVE_IF`, `DIRECTIVE_FOR`, etc.

**Visitor Pattern**

- Design pattern where nodes "accept" a visitor (the compiler)
- Allows separating tree structure from processing logic
- Each node has `accept(NodeVisitor $visitor)` method

### Benefits of This Architecture

[](#benefits-of-this-architecture)

✅ **Exact whitespace preservation** - Lexer captures everything ✅ **Proper nesting validation** - Parser builds correct tree or throws error ✅ **Clean separation of concerns** - Each stage has one job ✅ **Easy to extend** - Add new node types without breaking existing code ✅ **Fast execution** - Single pass through the tree ✅ **Type safety** - PHP 8.1+ types ensure correctness

### Class Naming Conventions

[](#class-naming-conventions)

Tsuku follows industry-standard naming for compiler components:

Class NamePurposeLocation`Lexer`Lexical analyzer - breaks text into tokens`src/Lexer/``Token`One meaningful unit (like a word)`src/Lexer/Token.php``TokenType`Enum of all token types`src/Lexer/TokenType.php``Parser`Syntax analyzer - builds AST from tokens`src/Ast/Parser.php``*Node`AST tree nodes (`TextNode`, `ForNode`, etc.)`src/Ast/``NodeVisitor`Interface for visiting AST nodes`src/Ast/NodeVisitor.php``Compiler`Code generator - walks AST to create output`src/Compiler/Compiler.php``Tsuku`Main API entry point`src/Tsuku.php`**Naming Philosophy:**

- **Lexer/Parser/Compiler** - Standard compiler pipeline terms
- **Node suffix** - Indicates AST node type (`TextNode`, `IfNode`)
- **Registry suffix** - Stores and manages items (`FunctionRegistry`)
- **Visitor suffix** - Implements visitor pattern (`NodeVisitor`)
- **Exception suffix** - Error types (`TsukuException`, `ParseException`)

### How It All Works Together

[](#how-it-all-works-together)

```
$tsuku = new Tsuku();
$result = $tsuku->process('@if(admin){name}@end', ['admin' => true, 'name' => 'John']);

// Internally:
// 1. Lexer::tokenize() → [DIRECTIVE_IF("admin"), VARIABLE("name"), DIRECTIVE_END]
// 2. Parser::parse() → IfNode(condition: "admin", children: [VariableNode("name")])
// 3. Compiler::compile() → Walks tree:
//    - IfNode: evaluate condition (true) → execute children
//    - VariableNode: lookup "name" in data → "John"
// 4. Output: "John"
```

This architecture is the same used by:

- Programming languages (PHP, JavaScript, Python)
- Template engines (Twig, Blade, Smarty)
- Markup processors (Markdown, BBCode)

**Further Reading:**

- [Compilers: Principles, Techniques, and Tools](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools) (Dragon Book)
- [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)
- [Visitor Pattern](https://refactoring.guru/design-patterns/visitor)

Development
-----------

[](#development)

```
# Install dependencies
composer install

# Run tests
composer test

# Run mutation testing
composer test:mutation

# Run static analysis
composer analyse

# Check code style
composer cs:check

# Fix code style
composer cs:fix
```

License
-------

[](#license)

MIT License - see LICENSE file for details

Credits
-------

[](#credits)

Created by [qoliber](https://qoliber.com) - Like a hummingbird (koliber), swift and precise in data transformation.

**Tsuku** (つく) means "to create" or "to make" in Japanese, reflecting the library's purpose of creating text output from data.

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance69

Regular maintenance activity

Popularity18

Limited adoption so far

Community4

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

3

Last Release

181d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e1127b1eba3c8b9dce8b6f329f14afb6af88aafa8f1e08aac8f23f97ec8f68b5?d=identicon)[qoliber](/maintainers/qoliber)

---

Tags

xmlcsvtemplatetransformationecommercetsvdata-export

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/qoliber-tsuku/health.svg)

```
[![Health](https://phpackages.com/badges/qoliber-tsuku/health.svg)](https://phpackages.com/packages/qoliber-tsuku)
```

###  Alternatives

[phpoffice/phpword

PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)

7.6k34.7M186](/packages/phpoffice-phpword)[rize/uri-template

PHP URI Template (RFC 6570) supports both expansion &amp; extraction

420137.3M46](/packages/rize-uri-template)[faisalman/simple-excel-php

Easily parse / convert / write between Microsoft Excel XML / CSV / TSV / HTML / JSON / etc formats

582599.4k1](/packages/faisalman-simple-excel-php)[figdice/figdice

Template Engine, XML-centric and attribute-driven

222.4k1](/packages/figdice-figdice)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
