Smarty Forum Index Smarty
WARNING: All discussion is moving to https://reddit.com/r/smarty, please go there! This forum will be closing soon.

Make template syntax errors non-fatal

 
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    Smarty Forum Index -> Feature Requests
View previous topic :: View next topic  
Author Message
c960657
Smarty Regular


Joined: 07 May 2003
Posts: 75
Location: Copenhagen, Denmark

PostPosted: Wed Dec 22, 2004 2:08 pm    Post subject: Make template syntax errors non-fatal Reply with quote

I am using Smarty in a project, where users can edit templates using a web interface.

Users often forget to escape { when writing Javascript and CSS, so I would like to add a feature that verifies the Smarty code.

What I would like to do is something like this:

Code:
@$s = $smarty->fetch('foobar.tpl');
if ($smarty->_error) {
    echo "compilation failed :-( the error was " . $smarty->_error;
} else {
    echo "compilation succeeded :-)";
}


Currently, a template syntax error yields an E_USER_ERROR that makes PHP terminate immediately. I would like this to be changed to an E_USER_WARNING instead, so that execution of the PHP script can continue afterwards.

I have made a patch that demonstrates this feature. It is only meant as a demonstration.

Code:

Index: Smarty.class.php
===================================================================
RCS file: /repository/smarty/libs/Smarty.class.php,v
retrieving revision 1.504
diff -u -r1.504 Smarty.class.php
--- Smarty.class.php    1 Oct 2004 15:26:44 -0000       1.504
+++ Smarty.class.php    22 Dec 2004 13:59:44 -0000
@@ -561,6 +561,13 @@
      */
     var $_cache_including = false;

+    /**
+     * string describing the last template error (if any)
+     *
+     * @var string
+     */
+    var $_error = false;
+
     /**#@-*/
     /**
      * The class constructor.
@@ -1246,18 +1253,16 @@
         $_cache_including = $this->_cache_including;
         $this->_cache_including = false;
         if ($display && !$this->caching && count($this->_plugins['outputfilter']) == 0) {
-            if ($this->_is_compiled($resource_name, $_smarty_compile_path)
-                    || $this->_compile_resource($resource_name, $_smarty_compile_path))
-            {
-                include($_smarty_compile_path);
+            if (!$this->_is_compiled($resource_name, $_smarty_compile_path)) {
+                $this->_compile_resource($resource_name, $_smarty_compile_path);
             }
+            include($_smarty_compile_path);
         } else {
-            ob_start();
-            if ($this->_is_compiled($resource_name, $_smarty_compile_path)
-                    || $this->_compile_resource($resource_name, $_smarty_compile_path))
-            {
-                include($_smarty_compile_path);
+            if (!$this->_is_compiled($resource_name, $_smarty_compile_path)) {
+                $this->_compile_resource($resource_name, $_smarty_compile_path);
             }
+            ob_start();
+            include($_smarty_compile_path);
             $_smarty_results = ob_get_contents();
             ob_end_clean();

@@ -1415,22 +1420,19 @@
         $_source_content = $_params['source_content'];
         $_cache_include    = substr($compile_path, 0, -4).'.inc';

-        if ($this->_compile_source($resource_name, $_source_content, $_compiled_content, $_cache_include)) {
-            // if a _cache_serial was set, we also have to write an include-file:
-            if ($this->_cache_include_info) {
-                require_once(SMARTY_CORE_DIR . 'core.write_compiled_include.php');
-                smarty_core_write_compiled_include(array_merge($this->_cache_include_info, array('compiled_content'=>$_compiled_content, 'resource_name'=>$resource_name)),  $this);
-            }
+        $ok = $this->_compile_source($resource_name, $_source_content, $_compiled_content, $_cache_include);

-            $_params = array('compile_path'=>$compile_path, 'compiled_content' => $_compiled_content);
-            require_once(SMARTY_CORE_DIR . 'core.write_compiled_resource.php');
-            smarty_core_write_compiled_resource($_params, $this);
-
-            return true;
-        } else {
-            return false;
+        // if a _cache_serial was set, we also have to write an include-file:
+        if ($this->_cache_include_info) {
+            require_once(SMARTY_CORE_DIR . 'core.write_compiled_include.php');
+            smarty_core_write_compiled_include(array_merge($this->_cache_include_info, array('compiled_content'=>$_compiled_content, 'resource_name'=>$resource_name)),  $this);
         }

+        $_params = array('compile_path'=>$compile_path, 'compiled_content' => $_compiled_content);
+        require_once(SMARTY_CORE_DIR . 'core.write_compiled_resource.php');
+        smarty_core_write_compiled_resource($_params, $this);
+
+        return $ok;
     }

    /**
@@ -1483,6 +1485,7 @@


         $_results = $smarty_compiler->_compile_file($resource_name, $source_content, $compiled_content);
+        $this->_error = $smarty_compiler->_error;

         if ($smarty_compiler->_cache_serial) {
             $this->_cache_include_info = array(
@@ -1802,16 +1805,13 @@
     function _trigger_fatal_error($error_msg, $tpl_file = null, $tpl_line = null,
             $file = null, $line = null, $error_type = E_USER_ERROR)
     {
-        if(isset($file) && isset($line)) {
-            $info = ' ('.basename($file).", line $line)";
+        if (isset($file) && isset($line)) {
+            $this->_error = '[in ' . $tpl_file . ' line ' . $tpl_line . ']: ' .
+                $error_msg . ' (' . basename($file) . ', line ' . $line . ')';
         } else {
-            $info = '';
-        }
-        if (isset($tpl_line) && isset($tpl_file)) {
-            $this->trigger_error('[in ' . $tpl_file . ' line ' . $tpl_line . "]: $error_msg$info", $error_type);
-        } else {
-            $this->trigger_error($error_msg . $info, $error_type);
+            $this->_error = $error_msg;
         }
+        $this->trigger_error($this->_error, $error_type);
     }


@@ -1858,12 +1858,10 @@

         $_smarty_compile_path = $this->_get_compile_path($params['smarty_include_tpl_file']);

-
-        if ($this->_is_compiled($params['smarty_include_tpl_file'], $_smarty_compile_path)
-            || $this->_compile_resource($params['smarty_include_tpl_file'], $_smarty_compile_path))
-        {
-            include($_smarty_compile_path);
+        if (!$this->_is_compiled($params['smarty_include_tpl_file'], $_smarty_compile_path)) {
+            $this->_compile_resource($params['smarty_include_tpl_file'], $_smarty_compile_path);
         }
+        include($_smarty_compile_path);

         // pop the local vars off the front of the stack
         array_shift($this->_config);
Index: Smarty_Compiler.class.php
===================================================================
RCS file: /repository/smarty/libs/Smarty_Compiler.class.php,v
retrieving revision 1.352
diff -u -r1.352 Smarty_Compiler.class.php
--- Smarty_Compiler.class.php   17 Dec 2004 08:30:56 -0000      1.352
+++ Smarty_Compiler.class.php   22 Dec 2004 13:59:44 -0000
@@ -393,7 +393,12 @@
             $this->_init_smarty_vars = false;
         }

+        if ($this->_error) {
+            $compiled_content .= "<?php \$this->_error = '" . addslashes($this->_error) . "' ?>\n";
+        }
+
         $compiled_content = $template_header . $compiled_content;
+
         return true;
     }

@@ -414,7 +419,8 @@
                 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
                       (?:\s+(.*))?$
                     ~xs', $template_tag, $match)) {
-            $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
+            $this->_syntax_error("unrecognized tag: $template_tag", E_USER_WARNING, __FILE__, __LINE__);
+            return false;
         }

         $tag_command = $match[1];


The new behaviour is triggered when trying to compile a template that looks like this:
Code:
testing 1-2-3 { }


The idea is that a template is always compiled and written to a file, so that even in the case of an error the template is only compiled once (unless $smarty->force_compile is true). If an error occurred, the error message is written to the compiled file as well.

One could argue than trigger_error() should only be called when the template is actually compiled (this is what happens in my implementation). On the other hand it may make debugging easier for the template designer if an error is triggered, even if the template is already compiled when fetch() is called.

What do you think - is this a feasible idea?
Back to top
View user's profile Send private message Visit poster's website
c960657
Smarty Regular


Joined: 07 May 2003
Posts: 75
Location: Copenhagen, Denmark

PostPosted: Mon Jan 31, 2005 1:13 pm    Post subject: Reply with quote

Appearently there is currently no interest in this feature.

However, I would like to contribute a patch that adds this feature to Smarty.

I don't think it will add much bloat to the code. I don't intend to make incompatible changes to existing functionality.

Would such a patch be likely to be incorporated in the official Smarty releases?
Back to top
View user's profile Send private message Visit poster's website
boots
Administrator


Joined: 16 Apr 2003
Posts: 5611
Location: Toronto, Canada

PostPosted: Thu Sep 15, 2005 4:50 pm    Post subject: Reply with quote

Interesting but dangerous. If we did support such a thing, I think it would be better to have a config switch to determine if errors should be warnings or fatal -- but that is a slippery slope. It also assumes that all plugins will call $smarty->trigger_error, where in fact they may call trigger_error directly.

There is also another problem: writing the error to file. This assumes that the error wasn't with the file writing in the first place. Wouldn't it be better to echo the error? At anyrate, I think it needs more thought or discussion.

I do appreciate what this tries to do. It would give the application a chance to try to handle the error but in the end I wonder if you would be just as well served using a custom error handler?
Back to top
View user's profile Send private message
c960657
Smarty Regular


Joined: 07 May 2003
Posts: 75
Location: Copenhagen, Denmark

PostPosted: Fri Sep 16, 2005 8:48 am    Post subject: Reply with quote

boots wrote:
Interesting but dangerous. If we did support such a thing, I think it would be better to have a config switch to determine if errors should be warnings or fatal -- but that is a slippery slope.

Another way may be to add a public method, e.g. check_syntax(), that checks the syntax in a non-fatal mode and returns true or false, while fetch() and display() dies with a fatal error like today.

boots wrote:
It also assumes that all plugins will call $smarty->trigger_error, where in fact they may call trigger_error directly.

AFAICS plugins will only trigger run-time errors. My main interest here is compile-time errors. But in general there probably isn't much to do about plugins that doesn't follow the proper conventions (apart from not using these not-so-high-quality plugins).

boots wrote:
There is also another problem: writing the error to file. This assumes that the error wasn't with the file writing in the first place. Wouldn't it be better to echo the error?

Writing the error was meant as one way make sure that the error message is available on each invocation and not only on the first load. Another way is to make sure that the compiled file isn't written to the compile cache, or that it is wiped afterwards.

boots wrote:
It would give the application a chance to try to handle the error but in the end I wonder if you would be just as well served using a custom error handler?

Interesting idea. That may be a simpler solution to my problem. I'll look into it.

Thanks for commenting.
Back to top
View user's profile Send private message Visit poster's website
c960657
Smarty Regular


Joined: 07 May 2003
Posts: 75
Location: Copenhagen, Denmark

PostPosted: Wed Oct 05, 2005 2:08 pm    Post subject: Reply with quote

Using a custom error handler works pretty good.

I have to do a little parsing on the error message to extract the template name and line number and remove the reference to the line number in Smarty.class.php, but this is pretty simple (and if it breaks some day, it is not the end of the world).
Back to top
View user's profile Send private message Visit poster's website
c960657
Smarty Regular


Joined: 07 May 2003
Posts: 75
Location: Copenhagen, Denmark

PostPosted: Mon May 22, 2006 1:05 pm    Post subject: Reply with quote

c960657 wrote:
Using a custom error handler works pretty good.

I have a small problem with this approach.

If I have the following template code:
Code:
{$foo|bar}

and bar is not a valid function or modifier, the code compiles alright, i.e. $smarty->_compile_resource($resource, $compile_path) does not trigger any errors.

But when calling fetch(), a fatal error (E_ERROR) is thrown ("Call to undefined function smarty_modifier_snaps() in /var/tmp/smarty/templates_c/..."). I cannot catch this using a custom error handler.

Would it be possible to check the modifier, either during compile time or during run-time?

A compile-time check could be inserted into Smarty_Compiler::_parse_modifiers() like this:
Code:
@@ -1913,17 +1913,22 @@ class Smarty_Compiler extends Smarty {
             if (empty($this->_plugins['modifier'][$_modifier_name])
                 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
                 && function_exists($_modifier_name)) {
                 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
                     $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
                 } else {
                     $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name,  null, null, false);
                 }
+            } else {
+                require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
+                $_params = array('plugins' => array(array('modifier', $_modifier_name, $this->_current_file, $this->_current_line_no, false)));
+                smarty_core_load_plugins($_params, $this);
             }
+
             $this->_add_plugin('modifier', $_modifier_name);

             $this->_parse_vars_props($_modifier_args);

             if($_modifier_name == 'default') {
                 // supress notifications of default modifier vars and args
                 if(substr($output, 0, 1) == '$') {
                     $output = '@' . $output;

The idea is to try to load the plugin at compile time. If the modifier doesn't exist, smarty_core_load_plugins() calls $smarty->_trigger_fatal_error().

This will not change the validity of existing templates. It will only trigger the error a little earlier than before.

What do you think of this?
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    Smarty Forum Index -> Feature Requests All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group
Protected by Anti-Spam ACP