Pular para o conteúdo principal

Code Generator Template Customization

Guide to customizing the code generator templates to match your project's specific needs.

Table of Contents​

Overview​

The code generator uses JinjaPhp templates shipped inside byjg/gluo-core (vendor/byjg/gluo-core/templates/codegen/), so template improvements arrive with composer update.

Template Engine: JinjaPhp (Python Jinja2 syntax for PHP) Default location: vendor/byjg/gluo-core/templates/codegen/

Overriding: create a templates/codegen/ directory in your project and the generator uses it instead of the package templates. To start customizing, copy the package templates:

mkdir -p templates
cp -r vendor/byjg/gluo-core/templates/codegen templates/codegen

Delete the directory to go back to the package defaults.

Template Types​

TemplateGeneratesPattern
model.php.jinjaModel classRepository & ActiveRecord (the activerecord variable switches the trait)
repository.php.jinjaRepository classRepository only
service.php.jinjaService classRepository only
controller.php.jinjaREST controllerRepository pattern
controlleractiverecord.php.jinjaREST controllerActiveRecord pattern
test.php.jinjaTest classBoth patterns

Template Structure​

templates/
└── codegen/
├── model.php.jinja # Model class
├── repository.php.jinja # Repository class
├── service.php.jinja # Service class
├── controller.php.jinja # Repository REST controller
├── controlleractiverecord.php.jinja # ActiveRecord REST controller
└── test.php.jinja # Test class

Available Variables​

These are the variables produced by the generator (BaseScripts::buildCodegenData()) and available in every template.

Common Variables​

{{ namespace }} # Project namespace (e.g., MyRest)
{{ className }} # PascalCase class name (e.g., ProductItem)
{{ tableName }} # Database table name (e.g., product_item)
{{ varTableName }} # camelCase variable name (e.g., productItem)
{{ restPath }} # REST route path (e.g., product/item)
{{ restTag }} # OpenAPI tag (e.g., Product)
{{ fields }} # Array of field definitions (see below)
{{ primaryKeys }} # Array of primary key column names
{{ nullableFields }} # camelCase property names of nullable, non-PK columns
{{ nonNullableFields }} # camelCase property names of required columns
{{ indexes }} # Table indexes (each with camelColumnName)
{{ autoIncrement }} # "yes" when the PK auto-increments, "no" otherwise
{{ activerecord }} # True in ActiveRecord mode
{{ hasCreatedAt }} # True when the table has a created_at column
{{ hasUpdatedAt }} # True when the table has an updated_at column
{{ hasDeletedAt }} # True when the table has a deleted_at column

Field Variables​

Each field in {{ fields }} mirrors the MySQL EXPLAIN output plus derived keys:

{{ field.field }} # Raw column name (e.g., is_active)
{{ field.property }} # camelCase property name (e.g., isActive)
{{ field.type }} # Raw database type (e.g., varchar(120), tinyint(1))
{{ field.php_type }} # PHP type (string, int, float)
{{ field.openapi_type }} # OpenAPI type (string, integer, number)
{{ field.openapi_format }} # OpenAPI format (string, int32, int64, double, date-time)
{{ field.null }} # "YES" when NULL allowed, "NO" otherwise
{{ field.key }} # "PRI" for primary key columns
{{ field.default }} # Default value (or null)
{{ field.extra }} # e.g., auto_increment

Common tests, as used by the package templates themselves:

{% if field.null == "YES" %}nullable: true{% endif %}
{% if field.key == "PRI" %}primaryKey: true{% endif %}
{% if 'auto_increment' in field.extra %}...{% endif %}
{% if 'binary' in field.type %}...UUID handling...{% endif %}

Customizing Templates​

Example: Adding Custom Header​

Edit templates/codegen/model.php.jinja:

<?php

/**
* Auto-generated Model
* Table: {{ tableName }}
* Do not edit manually!
*/

namespace {{ namespace }}\Model;

// rest of template...

Example: Adding Custom Methods​

Add custom methods to templates/codegen/model.php.jinja:

// ... existing getters/setters ...

{% if not activerecord %}
/**
* Custom validation method
*/
public function validate(): bool
{
// Add validation logic
return true;
}

/**
* Convert to array
*/
public function toArray(): array
{
return [
{% for field in fields %}
'{{ field.property }}' => $this->{{ field.property }},
{% endfor %}
];
}
{% endif %}

Example: Customizing REST Endpoints​

Edit templates/codegen/controller.php.jinja to add custom endpoints:

// ... existing CRUD methods ...

/**
* Search {{ className }}
*/
#[OA\Get(
path: "/{{ restPath }}/search",
security: [["jwt-token" => []]],
tags: ["{{ restTag }}"]
)]
#[OA\Parameter(
name: "q",
in: "query",
required: true,
schema: new OA\Schema(type: "string")
)]
#[RequireAuthenticated]
public function search{{ className }}(HttpResponse $response, HttpRequest $request): void
{
$searchTerm = $request->query('q');
$service = Config::get({{ className }}Service::class);

// Implement search logic
$results = $service->search($searchTerm);
$response->write($results);
}

Creating New Templates​

Custom Template Example​

Create templates/codegen/dto.php.jinja for Data Transfer Objects:

<?php

namespace {{ namespace }}\DTO;

/**
* {{ className }} Data Transfer Object
*/
class {{ className }}DTO
{
{% for field in fields %}
public {{ field.php_type }}|null ${{ field.property }} = null;
{% endfor %}

public static function fromModel(\{{ namespace }}\Model\{{ className }} $model): self
{
$dto = new self();
{% for field in fields %}
$dto->{{ field.property }} = $model->get{{ field.property | capitalize }}();
{% endfor %}
return $dto;
}

public function toArray(): array
{
return [
{% for field in fields %}
'{{ field.property }}' => $this->{{ field.property }},
{% endfor %}
];
}
}

Render Custom Templates​

The rendering seams live in ByJG\Gluo\Builder\BaseScripts — your project's builder/Scripts.php extends it. Use renderCodegenTemplate() (it already resolves the local templates/codegen/ override) from a custom entry point:

// builder/Scripts.php
public function generateDto(string $table, array $data): void
{
$code = $this->renderCodegenTemplate('dto.php', $data);
file_put_contents($this->workdir . "/src/DTO/{$data['className']}DTO.php", $code);
}

Template Examples​

Conditional Code Based on Field Type​

{% for field in fields %}
{% if field.php_type == 'string' %}
/**
* @param {{ field.php_type }}|null ${{ field.property }}
* @return $this
*/
public function set{{ field.property | capitalize }}({{ field.php_type }}|null ${{ field.property }}): static
{
// Trim strings
$this->{{ field.property }} = ${{ field.property }} !== null ? trim(${{ field.property }}) : null;
return $this;
}
{% elif field.php_type == 'int' or field.php_type == 'float' %}
/**
* @param {{ field.php_type }}|null ${{ field.property }}
* @return $this
*/
public function set{{ field.property | capitalize }}({{ field.php_type }}|null ${{ field.property }}): static
{
// Validate positive numbers
if (${{ field.property }} !== null && ${{ field.property }} < 0) {
throw new \InvalidArgumentException('{{ field.property }} must be positive');
}
$this->{{ field.property }} = ${{ field.property }};
return $this;
}
{% else %}
public function set{{ field.property | capitalize }}({{ field.php_type }}|null ${{ field.property }}): static
{
$this->{{ field.property }} = ${{ field.property }};
return $this;
}
{% endif %}
{% endfor %}

Adding Timestamp Traits Based on Fields​

The generator already detects the timestamp columns for you — use the switches directly (this is exactly what the package model.php.jinja does):

class {{ className }}
{
{% if hasCreatedAt %}
use OaCreatedAt;
{% endif %}
{% if hasUpdatedAt %}
use OaUpdatedAt;
{% endif %}
{% if hasDeletedAt %}
use OaDeletedAt;
{% endif %}

// ... rest of class
}

Custom OpenAPI Documentation​

/**
* Create a new {{ className }}
*/
#[OA\Post(
path: "/{{ restPath }}",
summary: "Create new {{ className }}",
description: "Creates a new {{ className }} record with the provided data",
security: [["jwt-token" => []]],
tags: ["{{ restTag }}"]
)]
#[OA\RequestBody(
description: "{{ className }} data",
required: true,
content: new OA\JsonContent(
required: [{% for field in fields %}{% if field.null == "NO" and 'auto_increment' not in field.extra %}"{{ field.property }}"{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}],
properties: [
{% for field in fields %}
{% if field.key != "PRI" %}
new OA\Property(
property: "{{ field.property }}",
type: "{{ field.openapi_type }}",
format: "{{ field.openapi_format }}",
{% if field.null == "YES" %}nullable: true,{% endif %}
description: "The {{ field.property }} field"
){% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}
]
)
)]

Best Practices​

1. Keep Templates Maintainable​

{# Good - Clear and readable #}
{% for field in fields %}
{% if field.key != "PRI" %}
protected {{ field.php_type }} ${{ field.property }};
{% endif %}
{% endfor %}

{# Bad - Complex nested logic #}
{% for field in fields %}{% if field.key != "PRI" %}protected {{ field.php_type }} ${{ field.property }};{% endif %}{% endfor %}

2. Use Comments in Templates​

{# Generate getters and setters for all non-primary fields #}
{% for field in fields %}
{% if field.key != "PRI" %}
{# Getter #}
public function get{{ field.property | capitalize }}() { }

{# Setter #}
public function set{{ field.property | capitalize }}($value) { }
{% endif %}
{% endfor %}

3. Maintain Consistent Formatting​

{# Always use proper indentation #}
class {{ className }}
{
{% for field in fields %}
protected {{ field.php_type }} ${{ field.property }};
{% endfor %}

{% for field in fields %}
public function get{{ field.property | capitalize }}()
{
return $this->{{ field.property }};
}
{% endfor %}
}

4. Test Template Changes​

After modifying templates, test generation:

# Generate with modified template
composer codegen -- --env=dev --table=test_table all --save

# Review generated code
cat src/Model/TestTable.php
cat src/Controller/TestTableController.php

# Run tests
composer test