动态令牌解析 [英] dynamic token parsing

查看:140
本文介绍了动态令牌解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个我在我的一个项目中使用的邮件脚本,我想允许自定义这封信。问题是电子邮件的部分是从数据库中动态生成的。我有预定义的令牌,我用来描述什么应该替换令牌,但我想简化这一点,但写一个更好的解析器,可以解释令牌,并找出用来替换它的变量。



现在我有一个非常大的数组,包含所有可能的令牌和相应的值,如下所示:

  $ tokens ['[property_name]'] = $ this-> name; 

然后我运行模板,并用键值代替键的任何实例。 >

我宁愿只运行模板,寻找[]或任何我用来定义一个令牌,然后阅读里面的内容并将其转换为一个变量名。 / p>

我需要能够匹配几个关系级别,所以 $ this-> account-> owner-> name; code>
为例,我需要引用方法。 $ this-> account-> calcTotal();



我以为我可以拿例如 [property_name] ,并将 _ 的实例替换为 - > 然后调用它作为一个变量,但我不相信它可以使用方法。

解决方案

创建一个模板系统。您可以通过自己编写这个代码来重新发明轮子,或者使用一个轻量级的模板系统,如胡子



对于非常轻量级的方法,您可以使用正则表达式来制定模板变量的语法。只需定义如何编写变量,然后提取所使用的名称/标签,并随意替换。



用于此的函数是 以下是一些小型示例代码(演示),它只反映了简单的替换,但是您可以修改替换例程以访问你需要的值(在这个例子中,我使用一个变量,它是一个 Array 或实现 ArrayAccess ):

 <?php 
$ template =<< EOD
这是我的模板,

我可以使用[vars]自由意志]。
EOD;

class Template
{
private $ template;
private $ vars;
public function __construct($ template,$ vars)
{
$ this-> template = $ template;
$ this-> vars = $ vars;
}
public function replace(array $ matches)
{
list(,$ var)= $ matches;
if(isset($ this-> vars [$ var]))
{
return $ this-> vars [$ var];
}
return sprintf('<< undefined:%s>>',$ var);

}
public function substituteVars()
{
$ pattern ='〜\ [([a-z _] {3,})\]〜 ;
$ callback = array($ this,'replace');
return preg_replace_callback($ pattern,$ callback,$ this-> template);
}

}

$ templ = new Template($ template,array('vars'=>'variables'));

echo $ templ-> substituteVars();

到目前为止,这看起来不是很壮观,只是将模板标签替换成一个值。但是,如前所述,您现在可以将一个解析器注入模板,将模板标签解析为一个值,而不是使用一个简单的数组。



您想要使用 _ 符号与对象成员/函数分离的问题。以下是一个解析器类,它会将所有全局变量解析为该符号。它显示了如何处理对象成员和方法以及如何遍历变量。但是,它不能解析为 $ this 而是全局命名空间:

  / ** 
*从全局命名空间解析模板变量
* /
class GlobalResolver实现ArrayAccess
{
私有函数resolve($ offset)
{
$ stack = explode('_',$ offset);

return $ this-> resolveOn($ stack,$ GLOBALS);
}

私有函数resolveOn($ stack,$ base)
{
$ c = count($ stack);
if(!$ c)
返回数组(false,NULL);

$ var = array_shift($ stack);
$ varIsset = isset($ base [$ var]);

#非设置变量不计数
如果(!$ varIsset)
{
返回数组($ varIsset,NULL);
}

#简单变量
如果(1 === $ c)
{
返回数组($ varIsset,$ base [$ var] );
}

#descendant
$ operator = $ stack [0];
$ subject = $ base [$ var];
$ desc = $ this-> resolvePair($ subject,$ operator);

if(2 === $ c ||!$ desc [0])
return $ desc;

$ base = array($ operator => $ desc [1]);
return $ this-> resolveOn($ stack,$ base);
}

私有函数resolvePair($ subject,$ operator)
{
if(is_object($ subject))
{
if (property_exists($ subject,$ operator))
{
return array(true,$ subject-> $ operator);
}
if(method_exists($ subject,$ operator))
{
return array(true,$ subject-> $ operator());
}
}
if(is_array($ subject))
{
if(array_key_exists($ operator,$ subject))
{
return array(true,$ subject [$ operator]);
}
}
返回数组(false,NULL);
}

public function offsetExists($ offset)
{
list($ isset)= $ this-> resolve($ offset);
return $ isset;
}
public function offsetGet($ offset)
{
list($ isset,$ value)= $ this-> resolve($ offset);
return $ value;
}
public function offsetSet($ offset,$ value)
{
throw new BadMethodCallException('Read only。');
}
public function offsetUnset($ offset)
{
throw new BadMethodCallException('Read only。');
}
}

这个解析器类可以用来使用一些示例值:

  / ** 
*使用一些类和变量填充全局命名空间
* /
class Foo
{
public $ member ='object member';
public function func()
{
return'function result';
}
public function child()
{
$ child-> member ='child member';
return $ child;
}
}

$ vars ='variables';
$ foo = new Foo;

$ template =<< EOD
这是我的模板,

我可以使用[vars]免费[foo_func]或[foo_member]甚至[foo_child_member]。
EOD;

/ **
*这次使用自己的解析器类的模板
* /
$ templ = new Template($ template,new GlobalResolver);

echo $ templ-> substituteVars();

请参阅完整的演示动作



这只需要稍微修改才能最终满足您的需求。


I have a mail script that I use in one of my projects, and I'd like to allow customization of this letter. the problem is that parts of the email are dynamically generated from the database. I have predefined tokens that I use to describe what should replace the token, but I'd like to simplify this but writing a better parser that can interpret the token and figure out which variable to use to replace it.

Right now I have a very large array with all the possible tokens and their corresponding values, like so:

$tokens['[property_name]'] = $this->name;

and then I run through the template and replace any instance of the key with it's value.

I'd prefer to just run through the template, look for [] or whatever I use to define a token, and then read what's inside and convert that to a variable name.

I need to be able to match a few levels of relationships so $this->account->owner->name; as an example, and I need to be able to reference methods. $this->account->calcTotal();

I thought I might be able to take the example [property_name] and replace the instance of _ with -> and then call that as a variable, but I don't believe it works with methods.

解决方案

You're creating sort of a template system. You can either re-invent the wheel (sort of) by coding this on your own or just using a lighweight template system like mustache.

For a very lightweight approach you can make use of regular expressions to formulate the syntax of your template variables. Just define how a variable can be written, then extract the names/labels used and replace it at will.

A function to use for this is preg_replace_callback. Here is some little example code (Demo) which only reflects simple substitution, however, you can modify the replace routine to access the values you need (in this example, I'm using a variable that is either an Array or implements ArrayAccess):

<?php
$template = <<<EOD
This is my template,

I can use [vars] at free [will].
EOD;

class Template
{
    private $template;
    private $vars;
    public function __construct($template, $vars)
    {
        $this->template = $template;
        $this->vars = $vars;
    }
    public function replace(array $matches)
    {
        list(, $var) = $matches;
        if (isset($this->vars[$var]))
        {
             return $this->vars[$var];
        }
        return sprintf('<<undefined:%s>>', $var);

    }
    public function substituteVars()
    {
        $pattern = '~\[([a-z_]{3,})\]~';
        $callback = array($this, 'replace');
        return preg_replace_callback($pattern, $callback, $this->template );
    }

}

$templ = new Template($template, array('vars' => 'variables'));

echo $templ->substituteVars();

This does not look spectacular so far, it's just substituting the template tags to a value. However, as already mentioned you can now inject a resolver into the template that can resolve template tags to a value instead of using an simple array.

You've outlined in your question that you would like to use the _ symbol to separate from object members / functions. The following is a resolver class that will resolve all global variables to that notation. It shows how to handle both, object members and methods and how to traverse variables. However, it does not resolve to $this but to the global namespace:

/**
 * Resolve template variables from the global namespace
 */
class GlobalResolver implements ArrayAccess 
{
    private function resolve($offset)
    {
        $stack = explode('_', $offset);

        return $this->resolveOn($stack, $GLOBALS);
    }

    private function resolveOn($stack, $base)
    {
        $c = count($stack);
        if (!$c)
            return array(false, NULL);

        $var = array_shift($stack);
        $varIsset = isset($base[$var]);

        # non-set variables don't count
        if (!$varIsset)
        {
            return array($varIsset, NULL);
        }

        # simple variable
        if (1 === $c)
        {
            return array($varIsset, $base[$var]);
        }

        # descendant    
        $operator = $stack[0];
        $subject = $base[$var];
        $desc = $this->resolvePair($subject, $operator);

        if (2 === $c || !$desc[0])
            return $desc;

        $base = array($operator => $desc[1]);
        return $this->resolveOn($stack, $base);
    }

    private function resolvePair($subject, $operator)
    {
        if (is_object($subject))
        {
            if (property_exists($subject, $operator))
            {
                return array(true, $subject->$operator);
            }
            if (method_exists($subject, $operator))
            {
                return array(true, $subject->$operator());
            }
        }
        if (is_array($subject))
        {
            if (array_key_exists($operator, $subject))
            {
                return array(true, $subject[$operator]);
            }
        }
        return array(false, NULL);
    }

    public function offsetExists($offset)
    {
        list($isset) = $this->resolve($offset);
        return $isset;
    }
    public function offsetGet($offset)
    {
        list($isset, $value) = $this->resolve($offset);
        return $value;
    }
    public function offsetSet ($offset, $value)
    {
        throw new BadMethodCallException('Read only.');
    }
    public function offsetUnset($offset)
    {
        throw new BadMethodCallException('Read only.');
    }
}

This resolver class can be used then to make use of some example values:

/**
 * fill the global namespace with some classes and variables
 */
class Foo
{
   public $member = 'object member';
   public function func()
   {
       return 'function result';
   }
   public function child()
   {
       $child->member = 'child member';
       return $child;
   }
}

$vars = 'variables';
$foo = new Foo;

$template = <<<EOD
This is my template,

I can use [vars] at free [foo_func] or [foo_member] and even [foo_child_member].
EOD;

/**
 * this time use the template with it's own resolver class
 */
$templ = new Template($template, new GlobalResolver);

echo $templ->substituteVars();

See the full demo in action.

This will only need a slight modification to fit your needs then finally.

这篇关于动态令牌解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆