WordPress到hexo搬家小记

WordPress是一个以PHP和MySQL为平台的自由开源的博客软件和内容管理系统。WordPress具有插件架构和模板系统。Alexa排行前100万的网站中有超过16.7%的网站使用WordPress。到了2011年8月,约22%的新网站采用了WordPress。WordPress是目前因特网上最流行的博客系统。

前言

从08年开始玩WordPress,着迷于她的强大和灵活,臣服于她强大的themes机制和plugins机制。当是还有不少博客专门研究wordpress,我也曾经深入的研究过其中的一些原理,还开发了sablog-x转wordpress的工具。

我为什么要放弃WordPress?

时过境迁,不再是曾经意气风发的少年,折腾的少了,内容更新也缓慢,由于WordPress太过庞大,插件质量也参差不齐,导致WordPress出现过很多的漏洞,经常发现内容被篡改,文件被篡改,被垃圾评论骚扰,甚至有些内容被改丢了,而发现的时候可能已经过去几个月了。

我为什么选择hexo?

  • 其实最主要的原因,是最近开始关注Node,而hexo是目前最流行的Node博客系统。而为什么关注Node,说来话长,这可能要从PHP为什么是“最好的语言”开始说起,而我不得不承认JavaScript将成为下一个“最好的语言”。

  • 生成纯静态网站,可以直接发布到Github,几乎不存在性能问题和安全问题,而那些你觉得不可能静态化的内容,用服务就好了,比如:评论、搜索

  • 基于Markdown文件的博客系统,Markdown让写作更美好,曾起何时我也有过这样一个想法,想开发一个基于文本的博客系统,不过最终没有开发完那个系统,后来我用那个思想开发了 http://qltx.cn ,里面有一个从目录文件生成数据的算法。

  • 轻量,有完善的中文文档 https://hexo.io/zh-cn/ ,作者是台湾开发者@tommy351

迁移过程

这里并不想写安装过程,安装步骤可以去官方看文档,简单记录一下数据迁移过程中遇到的一些问题

  • npm 安装速度慢,经常卡住,遇到这种情况可以把vpn调整到全局模式重试

  • 数据转换官方提供了migration插件,通过命令行导入wordpress是非常快的,不过转换完毕后我的hexo就无法访问了,这种情况通常是因为文章中的某些字符无法解析,排除法找到那篇文章处理一下就可以了。

  • hexo中的分类和WordPress中的分类不同,一篇文章只能属于一个分类,所以要调整所有文章的分类内容

  • 官方推荐的很多theme安装后都无法使用,这是版本兼容性的问题

  • 迁移过来之后WordPress中的附件需要处理,我的做法是把整个wp-content目录原样放到了public目录下,这会有一个问题,就是每次generate都会被清理掉,所以更好的办法是放到第三方

  • 评论使用disqus,注册完毕要验证邮箱,创建app,并且要设置安全域名,这样才能成功加载,在disqus中导入WordPress的备份数据可以完成评论的迁移

  • WordPress中的gallery标签在hexo中全部失效,可以用hexo中的photos属性,因为缩略图布局的缘故,所以我改动了theme,增加了一个wpphotos属性,并且用脚本将WordPress中的gallery全部转成了url,caption的格式

  • 后期我对默认theme做了大量的改动,如果想找到更多的themes,可以去官网或者看这篇文章 http://www.zhihu.com/question/24422335

说点儿题外话

在迁移的过程中我又重新整理了我写过的所有文章,一共有500多篇,感慨自己曾经的执着、稚嫩,而那大概400篇文章,将永远沉睡在草稿中。

从我第一次搭建博客到现在已经有10个年头,维护这个网站花掉了我大量的精力,几乎伴随着我整个大学时代和职业生涯,甚至还有高中的一段时间,从一个技术小白到一名web工程师,我在整个过程中学到了非常多的技术知识,也对人生有了很多新的感悟,虽然这里并没有写出特别好的技术类文章。

如果说有什么东西见证过我的青春,那一定就是这里。

公益项目志愿者招募(PHP,UI)

我们是谁?

中国•支教联盟(CNAEF),创办于2006年4月。由自愿支持农村中小学教育的社会各界爱心人士自发组织的全国性民间公益机构。以联系和提供支教为主题,主要发布支教信息,传播贫困地区教育现状,共同关注孩子成长。自成立以来,长期致力于为发达地区爱心咨询寻找资助对象,为欠发达地区教育引入社会各界力量。

我们要做什么?

我们想继续完善一下我们的网站,如果有可能会有自己的博客和手机应用,我们都是业余的志愿者,每个人都不能在这个事情上花上很多时间,所以,我们需要你的加入。

你最好有以下一种或多种技能:

  • 能做一些简单web页面
  • 能写一些PHP代码,最好用过一些框架
  • 能写一些JavaScript脚本
  • 会用git或者想学习如何使用git
  • 能够开发简单的手机app(android/ios不限)

如果你是个大牛当然最好,如果你是个菜鸟,我们会提供全方位的技术指导。需要强调的是:我们不能提供报酬,不过欢迎各位来北京约饭。

我的联系方式

  • QQ 35650697
  • makerwang#gmail.com
  • 也可以加入我们的QQ群:249822315

相关链接

中国支教联盟官方网站:http://www.go9999.com

中国支教联盟代码仓库:https://github.com/CNAEF

关于我的支教经历请移步:https://blog.foolbird.net/4503.html

说说PHP在Web上的运行模式

在各种环境上配置过无数次运行环境之后,我们好像还是搞不清PHP到底是怎么被执行的以及在不同的环境中有什么区别,曾经还和一个架构师因为PHP到底有几种运行模式这个事儿理论过,所以今天花一点时间研究一下。

一. 常见的运行模式

在Apache中,PHP通常有以下3种运行模式:

  1. CGI
  2. apache2handler(mod_php)
  3. FastCGI

在IIS中,PHP通常有以下3种运行模式:

  1. CGI
  2. ISAPI
  3. FastCGI

在Nginx中,默认支持的模式只有 FastCGI。

二. 那么问题来了,到底什么是CGI,什么是FastCGI还有ISAPI

CGI

CGI 全称 Common Gateway Interface, 中文是“通用网关接口”,在维基百科上的说明是这样的:

CGI描述了客户端和服务器程序之间传输数据的一种标准。
但实际上这个说明是有明显歧义的,不利于我们理解CGI这个概念,百度百科上的说明是这样的:
CGI是外部应用程序(CGI程序)与Web服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的规程。
简单的说,CGI规定了Web服务器程序如何调用PHP(或者其他程序),以及PHP该如何响应服务器程序的调用。

FastCGI

解释FastCGI之前必须要了解CGI的运行原理,Web服务器每次接收到CGI请求都要去创建(fork)一个PHP进程,然后执行程序,返回结果,再销毁进程,网上把这种执行模式叫做fork-and-execute。

而这种执行模式的问题在于,每次都要消耗大量的系统资源去创建和销毁PHP进程。所以就出现了FastCGI标准,FastCGI通常是一个进程管理器,他会初始化很多PHP进程,如果服务器发来CGI请求,进程管理器会把这个请求分配到其中一个PHP进程,返回结果后并不销毁进程,而是等待下一次分配请求。

我们常用的PHP-FPM实际上是一个实现了FastCGI标准的进程管理器,另外还有PHP-CGI也是。

Apache2handler

这是我们在Linux下部署环境常用的一种模式,也是Apache默认的模式,Apache通过mod_php5模块来调用PHP。

Apache中还有一个模式的概念,一般指的是prefork,worker,event,不过这和我们这里要研究的PHP运行模式没有太大关系,其实PHP-FPM也有几种不同的工作模式,这样说下去天要黑了。

ISAPI

好吧,其实我从来没接触过ISAPI,这是微软设计的一套CGI标准,只适用于Windows平台。

在PHP源码中的sapi目录还有cliphpdbgembed,litespeed等执行模式,不过都是web之外的东西。关于这些不同的运行模式的区别与优劣,其实是个很深奥的学问,待我研究明白再和大家娓娓道来。

参考:http://segmentfault.com/q/1010000000256516

mkdir的前世今生

前几日有同事询问我Yii2中是否有封装递归创建目录的方法,我告知了他CFileHelper的用法,对这个事情我从未有过怀疑,任何成熟的框架必然会封装各种文件操作的方法,但过后我寻思良久,mkdir明明有$recursive参数,为何所有的框架都要封装创建文件的方法呢?

用过PHP4的人可能还记得,PHP4中的mkdir定义是这样的:

1
bool mkdir ( string $pathname [,int $mode = 0777 ] )

而PHP5中的定义如下:

1
bool mkdir ( string $pathname [,int $mode = 0777 [, bool$recursive = false [, resource$context ]]] )

这两个参数最早出现在2003年的php源码中,并在php5.0.0中发布,但是一直到PHP4的最后一个版本4.4.9这两个参数也没有被添加。

那么我们有了第一个答案,使用FileHelper是有兼容性方面考虑的,如果你的程序有可能在低版本的PHP中运行,需要使用外部的文件创建方法。

那么下一个问题,众所周知,Yii对PHP的版本要求是5.1.0,Yii2的版本要求是5.4.0,大多新版框架和系统都已经放弃了对PHP4的支持,那么在高版本PHP上运行的为什么要用FileHelper?

第二个答案是这样的,如果你仅仅是用来创建文件夹,那么mkdir的确更方便,但是mkdir有着几个猪一样的队友,比如rmdir和copy,他们都不支持递归,所以FileHelper更方便,他提供了内置函数外的各种强大功能,还能兼顾兼容性。

Yii2\helpers\CFileHelper 定义如下:

1
2
3
4
5
6
7
8
9
10
public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
public static function localize($file, $language = null, $sourceLanguage = null)
public static function getMimeType($file, $magicFile = null, $checkExtension = true)
public static function getMimeTypeByExtension($file, $magicFile = null)
public static function getExtensionsByMimeType($mimeType, $magicFile = null)
public static function copyDirectory($src, $dst, $options = [])
public static function removeDirectory($dir, $options = [])
public static function findFiles($dir, $options = [])
public static function filterPath($path, $options)
public static function createDirectory($path, $mode = 0775, $recursive = true)

什么?没看出来强大?好,请看copyDirectory方法关于$option参数的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
* @param array $options options for directory copy. Valid options are:
*
* - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
* - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting.
* - filter: callback, a PHP callback that is called for each directory or file.
* The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
* The callback can return one of the following values:
*
* * true: the directory or file will be copied (the "only" and "except" options will be ignored)
* * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored)
* * null: the "only" and "except" options will determine whether the directory or file should be copied
*
* - only: array, list of patterns that the file paths should match if they want to be copied.
* A path matches a pattern if it contains the pattern string at its end.
* For example, '.php' matches all file paths ending with '.php'.
* Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
* If a file path matches a pattern in both "only" and "except", it will NOT be copied.
* - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
* A path matches a pattern if it contains the pattern string at its end.
* Patterns ending with '/' apply to directory paths only, and patterns not ending with '/'
* apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
* and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
* both '/' and '\' in the paths.
* - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
* - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
* - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
* If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file to be copied from, while `$to` is the copy target.
* - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file copied from, while `$to` is the copy target.

@bobhero 通过另一角度分析了这个事情,他认为mkdir的这些队友如此设计是有原因的,但最终我没有找到答案。因为Python3关于文件操作的方法是这样的:

1
2
3
4
5
6
7
os.mkdir(path, mode=0o777, *, dir_fd=None)
os.mkdirs(name, mode=0o777, exist_ok=False)
os.unlink(path, *, dir_fd=None)
os.remove(path, *, dir_fd=None)
os.removedirs(name)
os.rmdir(path, *, dir_fd=None)
shutil.rmtree(path[, ignore_errors[, onerror]])

而Ruby2.2.2的文件操作是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Dir.mkdir(string [, integer])
Dir.rmdir(string)
Dir.unlink(string)
Dir.delete(string)
FileUtils.cd(dir, options)
FileUtils.cd(dir, options) {|dir| .... }
FileUtils.pwd()
FileUtils.mkdir(dir, options)
FileUtils.mkdir(list, options)
FileUtils.mkdir_p(dir, options)
FileUtils.mkdir_p(list, options)
FileUtils.rmdir(dir, options)
FileUtils.rmdir(list, options)
FileUtils.ln(old, new, options)
FileUtils.ln(list, destdir, options)
FileUtils.ln_s(old, new, options)
FileUtils.ln_s(list, destdir, options)
FileUtils.ln_sf(src, dest, options)
FileUtils.cp(src, dest, options)
FileUtils.cp(list, dir, options)
FileUtils.cp_r(src, dest, options)
FileUtils.cp_r(list, dir, options)
FileUtils.mv(src, dest, options)
FileUtils.mv(list, dir, options)
FileUtils.rm(list, options)
FileUtils.rm_r(list, options)
FileUtils.rm_rf(list, options)
FileUtils.install(src, dest, mode = <src's>, options)
FileUtils.chmod(mode, list, options)
FileUtils.chmod_R(mode, list, options)
FileUtils.chown(user, group, list, options)
FileUtils.chown_R(user, group, list, options)
FileUtils.touch(list, options)

出于什么样的考虑每个语言对此设计有这样的差异我们不得而知,但这的确引发了我不少的思考,你呢?

注:文中提到的FileHelper不仅仅指Yii的CFileHelper,泛指所有第三方文件操作库。

循环中的unset和splice

昨日为了满足运营姑凉们的排序需求,写了一段js代码进行数据筛选,却因为循环出现问题耽误了很久,事情大概是这样的:

在javascript中,没有和php中一样的unset方法,如果要释放掉数组中指定元素要使用.splice()方法。由于循环语句的机制导致如果在循环内splice掉其中的元素,循环指针可能出现蹿位,最终就是导致某些元素没有被遍历到。javascript中for in与jquery中的.each都有这个问题,php中也有splice方法,并且php的循环在使用unset和splice的时候都会出现“意外”,而foreach由于他的特殊性幸免遇难。还是看代码吧,这个很有趣的低级错误常常被我们忽略。

1
2
3
4
5
6
7
8
9
10
11
<script>
window.onload = function () {
var a = ['a','b','c','d','e','f','g'];
for(k in a){
alert(a[k]);
if (a[k] == 'c' || a[k] == 'd')
a.splice(k, 1);
}
// output is abcefg
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
$a = array( 'a','b','c','d','e','f','g');
foreach($a as $k=&gt;$v) {
echo $v;
if ($v == 'c' || $v == 'd') {
unset($a[$k]);
}
}
// output is abcdefg
echo "\n";
$a = array( 'a','b','c','d','e','f','g');
foreach($a as $k=&gt;$v) {
echo $v;
if ($v == 'c' || $v == 'd') {
array_splice($a, $v, 1);
}
}
// output is abcdefg
echo "\n";
$a = array( 'a','b','c','d','e','f','g');
for($i=0;$i&lt;count($a);$i++) {
echo $a[$i];
if ($a[$i] == 'c' || $a[$i] == 'd') {
unset($a[$i]);
}
}
// output is abcde
echo "\n";
$a = array( 'a','b','c','d','e','f','g');
for($i=0;$i&lt;count($a);$i++) {
echo $a[$i];
if ($a[$i] == 'c' || $a[$i] == 'd') {
unset($a[$i]);
}
}
// output is abcde
echo "\n";
$a = array( 'a','b','c','d','e','f','g');
for($i=0;$i&lt;count($a);$i++) {
echo $a[$i];
if ($a[$i] == 'c' || $a[$i] == 'd') {
array_splice($a, $i, 1);
}
}
// output is abcefg