__toString魔术方法和类型强制转换

3

我创建了一个模板类来管理视图及其相关数据。它实现了迭代器数组访问,并允许使用“子模板”轻松使用,如下所示:

<p><?php echo $template['foo']; ?></p>
<?php foreach($template->post as $post): ?>
    <p><?php echo $post['bar']; ?></p>
<?php endforeach; ?>

无论如何,与其使用内联核心函数,比如hash()date(),我认为创建一个名为TemplateData的类会非常有用,该类将作为模板中存储的任何数据的包装器。

这样,我可以添加一系列常见的格式化方法,例如:

echo $template['foo']->asCase('upper');
echo $template['bar']->asDate('H:i:s');
//etc..

当控制器通过$template['foo'] = 'bar';设置数值时,数值'bar'被存储在其自身的TemplateData对象中。
我使用了神奇的__toString()方法,这样当你输出一个TemplateData对象时,它会被强制转换成(string)并且输出其数值。但是,尽管有“控制器和视图不应该修改数据”的口号,每当我像这样做时:
$template['foo'] = 1;
echo $template['foo'] + 1; //exception

出现错误信息:无法将类TemplateData的对象转换为int; 除非我将$template ['foo']重新定义为字符串(recast):

echo ((string) $template['foo']) + 1; //outputs 2

需要跳过这个环节有点失去了目的。

是否存在任何解决这种行为的方法,或者我应该将其视为一种偶然性,防止在视图中修改数据?
3个回答

1
Echo 正试图回显 $template['foo'] + 1 的结果。由于 $template['foo'] 是一个 TemplateData 类型的对象,而 1 是一个 int,因此您会收到一个错误。在这种情况下,((string) $template['foo']) 不是 "重新转换",而是第一次强制类型转换。

1
啊,我明白你为什么要使用(string)来强制转换为字符串再相加。但是,对于echo语句,你在尝试执行加法时还没有调用__toString(),因为echo会尝试将整个表达式的结果输出。 - thetaiko
1
或者我想,根据您之前的命名约定,添加一个 asInt() 方法会更好。 - thetaiko
我在这里遇到的另一个限制是:Method TemplateData::__toString() must return a string value - 否则,我可能会在实例化时使用 gettype() 或一系列的 is_*() 进行存储类型作为属性,然后在输出时相应地进行转换。当然,这样做是不起作用的。 - Dan Lugg
1
很遗憾,在这种情况下 __toString() 对你不起作用。你可以在 Template 类中添加另一个方法。你可以重写 getOffset 方法来强制转换为整数,但我怀疑你不希望在每种情况下都这样做。 - thetaiko
没错,我想尽可能地模拟PHP自己的标量类型转换,而不仅仅是整数。也许我在追求不可能的目标。我也不能依赖扩展;我见过很多运算符重载、__cast()魔法等等。 - Dan Lugg
显示剩余6条评论

1

PHP不支持运算符重载。如果您想执行加法(或任何其他与运算符相关的操作),则需要为要支持的每个运算符提供一个方法。很可能在基本模板类上实现它就足够了,如果需要,可以稍后扩展它。虽然在对象上使用(string)是一种快捷方式,但我建议不要这样做。


感谢Kevin Peno;是的,正如我所提到的,我已经看到了支持运算符重载的扩展,以及补充魔术方法(例如__cast($type)),但当然这些都是扩展,我不能在生产中依赖它们。我想完全避免使用(string)快捷方式。现在我正在考虑,除了TemplateData对象之外,如果Template类使用thetaiko的建议维护类型(在TemplateData对象创建时确定),我可以在TemplateoffsetGet()方法中实现一些类型转换功能...嗯... - Dan Lugg

0

我稍微改变了我的实现方式,目前似乎可以工作;我添加了 __invoke() 方法到 TemplateData 类中,该方法返回 $_data 属性,在不需要类型转换的情况下保持不变。因此在实际应用中:

//in controller
$template['foo'] = 'bar';
$template['abc'] = 123;

//in view
echo $template['foo']();                       //bar
echo $template['foo']->asHash('md5');          //37b51d194a7513...
echo $template['abc']();                       //123
echo $template['abc']() + 123;                 //246
echo $template['abc']->asHash('md5');          //202cb962ac5907...

//__invoke also takes $function and $arguments for alternative syntax
echo $template['foo']('asHash', array('md5')); //37b51d194a7513...

我想既然我已经攀登上这座山了,接下来的步骤应该是允许链式调用:

echo $template['foo']->asHash('md5')->asCase('upper');

然而,这似乎是一个难以实现的任务。跟踪“链状态”是我之前尝试过但没有成功的事情。在某种意义上,使用__invoke()会相对容易(但很混乱):

echo $template['foo'](
    array('asHash', 'asCase'),          //ordered array of method names
    array(array('md5'), array('upper')) //ordered array of method argument arrays
);

有人能看出这些问题吗,或者有其他理由支持另一种方法吗?

欢迎大家畅所欲言,我不想只接受自己的答案。我相信有人能够超越我所处的思维框架。 - Dan Lugg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接