Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TwigException "Unexpected end of object" when re-render compiled template #901

Open
dmitry-k-asb opened this issue Sep 5, 2024 · 2 comments

Comments

@dmitry-k-asb
Copy link

I have template and run "test" function 2 times. First time it work, second time i have TwigException "Unexpected end of object". What i doing wrong?

Test function:

function test()
{   
    var task = {}
    task.id = 'TEST_TASK_ID'
    task.status = TASK_STATUS_SUCCESS
    task.name = 'TEST TASK NAME'
    task.result_count = 0
    task.create_at = 1725445537
    task.start_at = 1725445537
    task.end_at = 1725448000
    task.random = 123456789
    task.node_info = 'localhost'
    task.description = 'TEST DESCRIPTION'
    task.important = 1

    var env = {
        'page_type': 'tools',
        'select': 1,
        'can_select': 1,
        'status_onclick': 'test_status_onclick_func()'
    }

    var _DATA = {
        'task': task,
        'env':  env
    }

    TwigTemplateStorage.render('history_task_item', _DATA).then(rendered => console.log(rendered))
}

My class:

class TwigTemplateStorage {

    static _config = {
        'history_task_item': '/templates/task_item.twig',

    }

    static templates = {}

    static async _load(template_id, path)
    {
        const response = await fetch(path)
        const text = await response.text()

        TwigTemplateStorage.templates[template_id] = Twig.twig({
            'data': text.trim(),
        });
    };

    static async render(template_id, data)
    {
        if (!(template_id in TwigTemplateStorage.templates)) {
            if (template_id in TwigTemplateStorage._config) {     
                let path = TwigTemplateStorage._config[template_id]
                await TwigTemplateStorage._load(template_id, path)
            } else {
                throw new Error(template_id + ' not registered in TwigTemplateStorage _config')
            }
        }

        var compiled = TwigTemplateStorage.templates[template_id]
        var out = compiled.render({ '_DATA': data })
        return out.trim()
    }
}

My template:

{% set TASK_STATUS_WAIT    = 0  %}
{% set TASK_STATUS_RUN     = 1  %}
{% set TASK_STATUS_FAIL    = 9  %}
{% set TASK_STATUS_SUCCESS = 10 %}

{% set TASK_CAN_SELECT    = 0 %}
{% set TASK_SELECTED      = 1 %}
{% set TASK_CANNOT_SELECT = 2 %}

{% set status_icon_map = {
	'cases': {
		(TASK_STATUS_WAIT): {
			(TASK_CANNOT_SELECT): 'si si-clock',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-check'
		},
		(TASK_STATUS_RUN): {
			(TASK_CANNOT_SELECT): 'si si-clock',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-check'
		},
		(TASK_STATUS_FAIL): {
			(TASK_CANNOT_SELECT): 'icon-exclamation'
		},
		(TASK_STATUS_SUCCESS): {
			(TASK_CANNOT_SELECT): 'si si-check',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-check'
		},
	},
	'tools': {
		(TASK_STATUS_WAIT): {
			(TASK_CANNOT_SELECT): 'si si-clock',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-close'
		},
		(TASK_STATUS_RUN): {
			(TASK_CANNOT_SELECT): 'si si-clock',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-close'
		},
		(TASK_STATUS_FAIL): {
			(TASK_CANNOT_SELECT): 'icon-exclamation',
		},
		(TASK_STATUS_SUCCESS): {
			(TASK_CANNOT_SELECT): 'si si-check',
			(TASK_CAN_SELECT): 'si si-plus',
			(TASK_SELECTED): 'si si-close'
		},
	},
	'my_tasks': {
		(TASK_STATUS_WAIT): {
			(TASK_CANNOT_SELECT): 'si si-clock',
		},
		(TASK_STATUS_RUN): {
			(TASK_CANNOT_SELECT): 'si si-clock',
		},
		(TASK_STATUS_FAIL): {
			(TASK_CANNOT_SELECT): 'icon-exclamation'
		},
		(TASK_STATUS_SUCCESS): {
			(TASK_CANNOT_SELECT): 'si si-check',
		},
	},
} %}
{% if status_icon_map[_DATA.env.page_type][_DATA.task.status][_DATA.env.select] is empty %}
	{% set icon_class = 'icon-question' %}
{% else %}
	{% set icon_class = status_icon_map[_DATA.env.page_type][_DATA.task.status][_DATA.env.select] %}
{% endif %}


{% if _DATA.task.status == TASK_STATUS_SUCCESS and _DATA.task.result_count > 0 %}
    {% set group = 'ok' %}
{% elseif _DATA.task.status == TASK_STATUS_SUCCESS and _DATA.task.result_count == 0 %}
    {% set group = 'empty' %}
{% elseif _DATA.task.status == TASK_STATUS_FAIL %}
	{% set group = 'fail' %}
{% elseif _DATA.task.status == TASK_STATUS_RUN or _DATA.task.status == TASK_STATUS_WAIT %}
	{% set group = 'wait' %}
{% endif %}

{% set status_group_map = {
	'ok': {
		'icon_color_class': 'task2-status-blue-icon',
		'history2_group': 'history2-description-status-ok'
	},
	'empty': {
		'icon_color_class': 'task2-status-red-icon',
		'history2_group': 'history2-description-status-empty'
	},
	'fail': {
		'icon_color_class': 'task2-status-red-icon',
		'history2_group': 'history2-description-status-fail'
	},
	'wait': {
		'icon_color_class': 'task2-status-grey-icon',
		'history2_group': 'history2-description-status-wait'
	}
} %}
{% if status_group_map[group] is empty %}
	{% set icon_color_class = 'task2-status-red-icon' %}
	{% set history2_group = 'history2-description-status-fail' %}
{% else %}
	{% set icon_color_class = status_group_map[group]['icon_color_class'] %}
	{% set history2_group = status_group_map[group]['history2_group'] %}
{% endif %}

{% if _DATA.task.end_at is not empty and _DATA.task.start_at is not empty %}
	{% set work_time  = _DATA.task.end_at - _DATA.task.start_at %}
	{% set work_hours = work_time > 3600 ? (work_time // 3600) : 0 %}
	{% set work_mins  = (work_time % 3600) // 60 %}
	{% set work_secs  = (work_time % 3600)  % 60 %}

	{% set work_hours = "%02d"|format(work_hours) %}
	{% set work_mins  = "%02d"|format(work_mins)  %}
	{% set work_secs  = "%02d"|format(work_secs)  %}

	{% set work_time = (work_hours > 0) ? work_hours ~ ':' ~ work_mins ~ ':' ~ work_secs : work_mins ~ ':' ~ work_secs %}
{% else %}
	{% set work_time = 'err' %}
{% endif %}

{% if _DATA.task.create_at is not empty %}
	{% set create_at      =  _DATA.task.create_at|date('d.m.Y') %}
	{% set create_at_full =  _DATA.task.create_at|date('H:i d.m.Y') %}
{% else %}
	{% set create_at      =  'err' %}
	{% set create_at_full =  'err' %}
{% endif %}

<div class="history2-one-task" data-id="{{_DATA.task.id}}">
	<div class="history2-card-and-toolbar">
		<div class="history2-card">
			<div class="history2-base-row">
				<span
                    class="task-icon {{icon_color_class}} task2-status-tooltip"
                    data-info="{}"
                    data-status="{{_DATA.task.status}}"
                    data-id="{{_DATA.task.id}}"
                    data-output=""
                    data-time="{{create_at_full}}"
                    data-name="{{_DATA.task.name}}"
                    data-count="{{_DATA.task.result_count|default('err')}}"
                    data-select="{{_DATA.env.select}}"
                    onclick="{{_DATA.env.status_onclick}}"
                    data-history2-group="{{history2_group}}"
					{% if _DATA.env.can_select == 1 %}
                    	style="cursor: pointer"
					{% endif %}
                >
					<i class="{{ icon_class }}"></i>
				</span>
				<div class="history2-name">
					{% if _DATA.task.status == TASK_STATUS_SUCCESS and _DATA.task.result_count > 0 %}
						<a class="link-primary" onclick="view.open('vk_users_array_id','{{_DATA.task.id}}','')">{{_DATA.task.name}}</a>
					{% else %}
						{{_DATA.task.name}}
					{% endif %}
				</div>
			</div>
			<div class="history2-info-row" data-id="{{_DATA.task.id}}">

				{% if _DATA.task.status in [TASK_STATUS_RUN, TASK_STATUS_WAIT] %}
					<div class="task-description" data-id="{{_DATA.task.id}}">{{_DATA.task.description|default('err')}}</div>
				{% else %}
					<span title="{{create_at_full}}">
						<i class="si si-calendar" style="padding-right: 2px;"></i>
						{{create_at}}
					</span>
					<div>
						<i class="si si-docs"></i>
						{{_DATA.task.result_count |default('err')}}
					</div>
					<div title="Время выполнения задачи">
						<i class="si si-reload"></i>
						{{work_time}}
					</div>
				{% endif %}
			</div>
			{% if _DATA.task.node_info is not empty %}
				{{_DATA.task.id}} / {{_DATA.task.node_info}}
			{% endif %}
		</div>
		<div class="history2-toolbar">
			{% if _DATA.task.important == 0 %}
				<i title="Пометить как важную" class="si si-star history2-toolbar-items" onclick="task.important(this,'{{_DATA.task.id}}');"></i>
			{% else %}
				<i title="Важная задача" class="fa fa-star task-important history2-toolbar-items" onclick="task.important(this,'{{_DATA.task.id}}');"></i>
			{% endif %}
			<i class="si si-equalizer history2-toolbar-items" title="Параметры задачи" onclick="task.params('{{_DATA.task.id}}');"></i>
			<i class="si si-reload history2-toolbar-items" title="Перезагрузить" onclick="task.restart('{{_DATA.task.id}}');"></i>
			<i class="si si-trash history2-toolbar-items" title="Удалить задачу" onclick="task.delete('{{_DATA.task.id}}');"></i>
		</div>
	</div>
	<div class="history2-progress-bar">
		<div class="progress progress-mini">
			<div class="progress-bar task-progress" data-id="{{_DATA.task.id}}" data-random="{{_DATA.task.random}}"></div>
		</div>
	</div>
</div>

@willrowe
Copy link
Collaborator

willrowe commented Sep 5, 2024

Are you able to simplify this to a minimal example that still causes the error?

@dmitry-k-asb
Copy link
Author

After studying the problem, it turned out that the error occurs when trying to use a variable with a value of 0 as a map key.

The class is the same as in the starting topic.

Test function:

function test()
{   
    TwigTemplateStorage.render('history_task_item', {'key': 10}).then(rendered => console.log(rendered))
}

Short test pattern causing problem on 2nd run:

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    (TEST_VAR_A_AS_MAP_KEY): 'test_value_A',
    (TEST_VAR_B_AS_MAP_KEY): 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

Dump result when running the test function for the first time:

object(2) {
  [10]=> 
  string(12) "test_value_B"
  [_keys]=> 
    object(1) {
    [0]=> 
    number(10)
  }
}

Render result when running the test function for the first time:

<div>test_value_B</div>

Dump result when running the test function for the second time:

undefined

Render result when running the test function for the second time:

Uncaught (in promise) Object { message: "Unexpected end of object.", name: "TwigException", type: "TwigException", file: undefined }

If you don't use 0 it works:

{% set TEST_VAR_A_AS_MAP_KEY = 1  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    (TEST_VAR_A_AS_MAP_KEY): 'test_value_A',
    (TEST_VAR_B_AS_MAP_KEY): 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

Dump result for any number of runs:

 object(3) {
  [1]=> 
  string(12) "test_value_A"
  [10]=> 
  string(12) "test_value_B"
  [_keys]=> 
    object(2) {
    [0]=> 
    number(1)
    [1]=> 
    number(10)
  }
}

Render result for any number of runs:

<div>test_value_B</div>

There is no error, but the key values ​​are not taken from the variables:

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    TEST_VAR_A_AS_MAP_KEY: 'test_value_A',
    TEST_VAR_B_AS_MAP_KEY: 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

Dump result for any number of runs:

object(3) {
  [TEST_VAR_B_AS_MAP_KEY]=> 
  string(12) "test_value_B"
  [_keys]=> 
    object(2) {
    [0]=> 
    string(21) "TEST_VAR_A_AS_MAP_KEY"
    [1]=> 
    string(21) "TEST_VAR_B_AS_MAP_KEY"
  }
  [TEST_VAR_A_AS_MAP_KEY]=> 
  string(12) "test_value_A"
}

This works for any number of runs:

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    0: 'test_value_A',
    10: 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

This works for any number of runs, but the key type is "string":

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    (TEST_VAR_A_AS_MAP_KEY|number_format): 'test_value_A',
    (TEST_VAR_B_AS_MAP_KEY|number_format): 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

This causes an error on the second run:

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    (TEST_VAR_A_AS_MAP_KEY + 0): 'test_value_A',
    (TEST_VAR_B_AS_MAP_KEY + 0): 'test_value_B',
} %}

{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

Results as in example 1.

With a zero value, the brackets work fine when dumping a variable, the error occurs specifically when working with the map:

{% set TEST_VAR_A_AS_MAP_KEY = 0  %}
{% set TEST_VAR_B_AS_MAP_KEY = 10  %}

{% set test_map = {
    (TEST_VAR_A_AS_MAP_KEY): 'test_value_A',
    (TEST_VAR_B_AS_MAP_KEY): 'test_value_B',
} %}

{{ dump(TEST_VAR_A_AS_MAP_KEY) }}
{{ dump((TEST_VAR_A_AS_MAP_KEY)) }}
{{ dump(test_map) }}

<div>{{ test_map[_DATA.key] }}</div>

Dump result:

number(0)

number(0)

object(2) {
  [10]=> 
  string(12) "test_value_B"
  [_keys]=> 
    object(1) {
    [0]=> 
    number(10)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants