背景介绍
看了不下十个主题的代码,一个通病,就是控制代码和模板混写在一起。如下图
这么写的问题是什么?
当输出结构比较复杂的时候,html会变得非常难以修改。
typecho自己是如何做的
作为一个typecho新手,很好奇为什么系统内置的文章输出就可以如此优雅。一个while($this->next())实现文章遍历,模板随意修改,数据控制代码也随意修改,互不干涉。
以下是所有模板中都用到的,输出文章列表的代码。
<?php while($this->next()): ?>
<article class="post_summary">
<h3 class="title"><a target="_blank" itemtype="url" href="<?php $this->permalink() ?>"><?php $this->title() ?></a></h3>
<div class="row">
<div class="col-lg-3">
<a href="<?php $this->permalink() ?>" class="post_thumbnail">
<?php if ($this->options->RandomPicChoice !=='0' && array_key_exists('thumb',unserialize($this->___fields()))): ?>
<img class="img-responsive" src="<?php echo $this->fields->thumb; ?>"/>
<?php else: ?>
<?php $thumb = showThumbnail($this); if(!empty($thumb)): ?>
<img class="img-responsive" src="<?php echo $thumb ?>"/>
<?php endif; ?>
<?php endif; ?>
</a>
</div>
<div class="col-lg-9">
<div class="post_content_preview">
<?php $this->excerpt(200, '...'); ?>
</div>
</div>
</div>
<div class="clearfix topic-footer">
<span class="pull-right post_time"><?php $this->date('F j, Y'); ?></span>
</div>
</article>
<? endwhile ?>
他是怎么实现的,我能不能也这么干
先看看这个next()方法都是怎么使用的吧,整个项目目录搜索"->next("。interesting,果然看到了一种用法
// theme/default/sidebar.php 16行
<ul class="widget-list">
<?php $this->widget('Widget_Comments_Recent')->to($comments); ?>
<?php while($comments->next()): ?>
<li><a href="<?php $comments->permalink(); ?>"><?php $comments->author(false); ?></a>: <?php $comments->excerpt(35, '...'); ?></li>
<?php endwhile; ?>
</ul>
这段代码的大致意思很明显,$this->widget('Widget_Comments_Recent')加载了一个widget获得数据,然后用to方法赋值给$comments。接着我们就可以用$comments来做遍历输出了。
现在出现了两个关键函数next()和to(),继续全项目搜索,发现这俩方法都是在Widget基类中定义的。
var/Typecho/Widget.php
/**
* 将类本身赋值
*
* @param string $variable 变量名
* @return self
*/
public function to(&$variable)
{
return $variable = $this;
}
/**
* 返回堆栈每一行的值
*
* @return array
*/
public function next()
{
if ($this->stack) {
$this->row = @$this->stack[key($this->stack)];
next($this->stack);
$this->sequence ++;
}
if (!$this->row) {
reset($this->stack);
if ($this->stack) {
$this->row = $this->stack[key($this->stack)];
}
$this->sequence = 0;
return false;
}
return $this->row;
}
/**
* 返回堆栈是否为空
*
* @return boolean
*/
public function have()
{
return !empty($this->stack);
}
/**
* 将每一行的值压入堆栈
*
* @param array $value 每一行的值
* @return array
*/
public function push(array $value)
{
//将行数据按顺序置位
$this->row = $value;
$this->length ++;
$this->stack[] = $value;
return $value;
}
上面的代码还有注释,意思也很明显了。Widget提供了一系列对内部数据进行操作的函数。have,是否有对象;next,下一个对象;to,赋值到一个变量;push,添加一个对象。
到这里,我们就有了一种很简单的实现遍历方式。functions.php中定义一个方法,先创建一个widget对象,通过push方法把查询结果添加进去,最后返回widget对象。模板中调用此方法得到一个widget对象,而后have to next随我们用了。
动手
我们试着把d8模板中获取热帖的方法hotpost()修改成上述方式实现。不废话上代码
// 首页置顶
function hotpost() {
// 获得数据,来自D8模板
$options = Typecho_Widget::widget('Widget_Options');
if ((!empty($options->hot)) && floor($options->hot)==$options->hot) {
$tjids = $options->hot;
}
$hot_arr = explode(",", $tjids);
if(count($hot_arr) < 2){
$hot_arr[] = 1;
}
$db = Typecho_Db::get();
$results = $db->fetchAll(
$db->select()->from('table.contents')
->orWhere('cid = ?',$hot_arr[0])
->orWhere('cid = ?', $hot_arr[1])
->where('type = ? AND status = ? AND password IS NULL', 'post', 'publish')
);
rsort( $results );//对数组逆向排序,即大ID在前
// !!!! 创建我们的Widget对象
$widget = Typecho_Widget::widget('Widget_Abstract_Contents');
foreach ($results as $result) {
// 数据处理
$result['title'] = htmlspecialchars($result['title']);
$result['permalink'] = htmlspecialchars($result['permalink']);
$result['created'] = date('M d,Y', $result['created']);
$result['views'] = number_format($result['views']);
preg_match_all( "/\[.*?\]:\s*(http(s)?:\/\/.*?(jpg|png|gif))/i", $result['text'], $matches );
if(isset($matches[1][0])){
$result['thumb'] = $matches[1][0];
}else{
$result['thumb']= $options->themeUrl .'/images/sj/' . rand(1, 5) . '.jpg';
}
// !!!! push到widget里面去
$widget->push($result);
}
// 返回widget对象
return $widget;
}
模板代码修改
<div class="row hots">
<!--导出$hot变量--->
<?php hotpost()->to($hot); ?>
<!--$hot变量是否包含对象,没有不输出对吧--->
<?php if ($hot->have()): ?>
<h2>Hot</h2>
<!--hot->next()遍历有没有 。。。。-->
<?php while ( $hot->next() ) : ?>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-12">
<a href="<?php $this->permalink() ?>" class="post_thumbnail">
<?php if ($this->options->RandomPicChoice !=='0' && array_key_exists('thumb',unserialize($this->___fields()))): ?>
<img class="img-responsive" src="<?php echo $this->fields->thumb; ?>"/>
<?php else: ?>
<?php $thumb = showThumbnail($this); if(!empty($thumb)): ?>
<img class="img-responsive" src="<?php echo $thumb ?>"/>
<?php endif; ?>
<?php endif; ?>
</a>
<p class="title"><a target="_blank" itemtype="url" href="<?php $this->permalink() ?>"><?php $hot->title() ?></a></p>
</div>
<?php endwhile ?>
<?php endif ?>
</div>
最后来个效果截图
========================================================================================
后续:修复bug
后台设置了三篇hotpost,分别cid=1,5,24。然而前台只显示了1和5,24没显示。回过头重新看一下hotpost()的实现,也是惊呆了,居然还可以这么搞。
// 获得数据,来自D8模板
$options = Typecho_Widget::widget('Widget_Options');
if ((!empty($options->hot)) && floor($options->hot)==$options->hot) {
$tjids = $options->hot;
}
$hot_arr = explode(",", $tjids);
if(count($hot_arr) < 2){
$hot_arr[] = 1;
}
$db = Typecho_Db::get();
$results = $db->fetchAll(
$db->select()->from('table.contents')
->orWhere('cid = ?',$hot_arr[0])
->orWhere('cid = ?', $hot_arr[1])
->where('type = ? AND status = ? AND password IS NULL', 'post', 'publish')
);
我们来一段一段仔细看这短短10来行代码
$options = Typecho_Widget::widget('Widget_Options');
if ((!empty($options->hot)) && floor($options->hot)==$options->hot) {
$tjids = $options->hot;
}
var_dump($hot_arr)可以看到$hot_arr = [1,5,24],说明后台设置以及数据库数据都正常。
接着往下看这段
if(count($hot_arr) < 2){
$hot_arr[] = 1;
}
当后台设置hotpost少于两篇的时候,系统会自动增加cid=1文章,防止hotpost出现空的情况发生。
问题是如果我删除了cid=1的文章会怎样?
修改:没数据就不显示hotpost呗,既然不设置说明不需要
接着往下看
$db = Typecho_Db::get();
$results = $db->fetchAll(
$db->select()->from('table.contents')
->orWhere('cid = ?',$hot_arr[0])
->orWhere('cid = ?', $hot_arr[1])
->where('type = ? AND status = ? AND password IS NULL', 'post', 'publish')
);
简直坑爹有没有,不管你怎么设置,他就只取得前两篇。后台设置和前台展示严重不匹配
另外,我发现不需要手动创建Widget对象,可以通过下面方式获取,以及push操作
// 获得widget对象
$widget = Typecho_Widget::widget('Widget_Abstract_Contents');
// widge的push方法增加文章,增加文章操作会自动生成$result['permlink']
// $result['permlink'] 是 null
$result = Typecho_Widget::widget('Widget_Abstract_Contents')->push($result);
// $result['permlink']包含文章url
解决方案:
重写hotpost()函数
function hotpost() {
$options = Typecho_Widget::widget('Widget_Options');
if ((!empty($options->hot)) && floor($options->hot)==$options->hot) {
$tjids = $options->hot;
}
$hot_arr = explode(",", $tjids);
if (count($hot_arr) > 0) {
$db = Typecho_Db::get();
$query = $db->select()->from('table.contents');
foreach ( $hot_arr as $cid ){
$query->orWhere('cid = ?', $cid);
}
$query->where('type = ? AND status = ? AND password IS NULL', 'post', 'publish');
$results = $db->fetchAll($query);
rsort( $results );//对数组逆向排序,即大ID在前
foreach ($results as $result) {
// 数据处理
$result['title'] = htmlspecialchars($result['title']);
$result['created'] = date('M d,Y', $result['created']);
$result['views'] = number_format($result['views']);
preg_match_all( "/\[.*?\]:\s*(http(s)?:\/\/.*?(jpg|png|gif))/i", $result['text'], $matches );
if(isset($matches[1][0])){
$result['thumb'] = $matches[1][0];
}else{
$result['thumb']= $options->themeUrl .'/images/sj/' . rand(1, 5) . '.jpg';
}
// 加入widget
$result = Typecho_Widget::widget('Widget_Abstract_Contents')->push($result);
// 再加入widget之前,$result['permalink']没有数据,然而加进去了我在对url进行htmlspecialchars处理?逻辑不大对
//var_dump($result['permalink']);
//$result['permalink'] = htmlspecialchars($result['permalink']);
}
}
return Typecho_Widget::widget('Widget_Abstract_Contents');
}
现在好了,总算是正常了。
学习到了,非常感谢用心整理哦!