Category Archives: php

php 成长记录

[翻译]PHP序列化跟踪栈和异常

原文地址:http://fabien.potencier.org/php-serialization-stack-traces-and-exceptions.html

昨天我修复了一个看起来非常奇怪的bug。在这篇文章里,我将描述这个问题,并说明我找到的解决之道,并说明PHP在这个场景的一些表现。

缺陷报告

这个bug首先提到:当试图序列化一个symfony表单实例的时候,一个PDO的异常就会被抛出:

"You cannot serialize or unserialize PDO instances"

这个异常之所以被PDO抛出,是因为POD的实例不能被正确的序列化。

但奇怪的是,sfForm这个类并不依赖于PDO。那这到底是怎么回事呢?

这个问题的本质

经过一番研究,我们发现,这个bug只会出现在用户使用PDO将session信息存储在数据库的时候。因此,我们在不是瞎猜的情况下,试图验证这个表单实例是否在某种程度上和session有关联。sfForm类除了widget、验证类、验证异常类外和其他并没有依赖。因此我们试图在只引入一个widget、一个验证类(validator)和一个验证异常类的情况下复现这个bug。

令人惊讶的是,问题居然出在验证的异常类上。在symfony中,验证的异常类直接继承自PHP的Exception类。

当代码中包含一个PDO的实例时,试图序列化一个异常的实例就能复现这个bug,可以用如下的代码证明:

$dbh = new PDO('sqlite:memory:');

function will_crash($dbh)
{
  // serialize an exception
  echo serialize(new Exception());
}

// this will throw a PDOException
will_crash($dbh);

发生了什么?当PHP序列化一个异常时,除了序列化异常的错误码,异常信息外,还需要包含调用栈

这个调用栈是一个数组,包含了所有这个脚本在这个时刻执行过的所有函数和方法。跟踪信息包含文件名,文件的行数,函数名,以及一个包含所有传递给函数的所有参数的数组。你发现问题了吗?

这个跟踪栈包含了一个PDO实例的引用,当它传递给will_crash()函数的时候,因此当PDO的实例不能被序列化时,PHP试图序列化调用栈的时候一个异常就抛出了。

因此,当一个调用栈中出现一个非序列化的对象时,这个异常都不能被序列化。

解决办法

在PHP中,你可以通过实现一个序列化的接口来覆盖序列化的过程。解决方案可以用如下的代码说明:

class sfValidatorError extends Exception implements Serializable
{
  // class code

  public function serialize()
  {
    return serialize(array($this->validator, $this->arguments, $this->code, $this->message));
  }

  public function unserialize($serialized)
  {
    list($this->validator, $this->arguments, $this->code, $this->message) = unserialize($serialized);
  }
}

serialize()函数应该返回一个表示对象的字符串。在我们的使用场景下, 我们序列化了所有的属性出现跟踪栈。

unserialize()反序列化函数将序列化过的字符串当做参数,应该可以构造出一个对象就像执行了构造函数 __construct()一样。

一些验证测试

到目前为止,一切都很如意。但是为了确保问题已经修复,我还是需要找到写一些测试脚本。但是我又不想在测试中继续依赖PDO。只要能仿造PDO的特性就足够了。我们只需要写一个不能被序列化的类即可:

class NotSerializable implements Serializable
{
  public function serialize()
  {
    throw new LogicException('You cannot serialize or unserialize NotSerializable instances');
  }

  public function unserialize($serialized)
  {
    throw new LogicException('You cannot serialize or unserialize NotSerializable instances');
  }
}

快速搭建一个PHP7学习平台

最近php7已经发布一定时间了,但是这么多新特性总需要大家学习一下才能在自己的项目中或者工作中利用起来,那么怎么搭建一个php7的学习平台呢?新浪云就是一个合适的地方。

php7的新特性

由于本文的重点不是来介绍php7的新特性的,这里就推荐两个PHP官方的介绍链接:

了解新浪云的容器平台

前段时间新浪云上线了容器平台,作为常规runtime(支持php5.3版本、php5.6版本,Python2.7版本,Java1.7版本的补充),终于可以让开发者在容器中自由运行node.js、go等语言环境了。近期又支持了直接上传dockerfile方式的部署。小伙伴们终于可以在容器中部署一个php7的应用了。

如何部署

创建一个容器类的应用

首先登陆SAE的管理面板(没有账号的就先用微博账号登录一下注册啦:))创建一个容器类的应用,如下图所示。

在sae上部署

只需要下载这个http://opensource.changes.com.cn/php7a.zip,然后解压到本地。然后通过git提交到SAE的应用仓库里去就行。看一下其中我写好的dockfile:

FROM skiychan/nginx-php7:latest
COPY index.php /data/www/
COPY code/ /data/www/code/
COPY start.sh /
RUN chmod +x /start.sh
RUN sed -i 's/php-fpm$/php-fpm -F/g' /etc/supervisord.conf && sed -i 's/80;$/5050 default;/g' /usr/local/nginx/conf/nginx.conf

现在只需要把解压后的文件提交到git即可,怎么部署呢?参考下面的步骤吧,进到解压后的目录,点击右键,进入“git bash”【注:windows下没有安装的同学请下载安装git for windows,这里是官网https://git-for-windows.github.io/

在你应用的git代码目录里,添加一个新的git远程仓库 sae
$ git remote add sae https://git.sinacloud.com/APPNAME (这个是你创建的应用名)
$ git add .
$ git commit -am "make it better"
$ git push sae master:1
然后输入您的SAE安全邮箱和安全密码即可,如果忘记的从这里 http://www.sinacloud.com/ucenter/profile.html?from=sidebar 可以查看和修改密码。
后面就可以看到部署中的状态信息,看到最后的OK就完成了。

部署完成后从sae的管理面板,进入应用首页,选择容器管理,应该可以看到如下的页面:

还可以通过web版本的终端进入您的docker容器哦:

这样部署后应该就可以访问了,比如我的应用是php7a,访问的地址是 http://php7a.applinzi.com/code/(一定要有这个/code/路径哦,因为我制作dockerfile的时候特意安装到这个路径了。),把php7a替换为你的应用名即可。

上手练习

这个时候就可以登录这个了,访问 http://php7a.applinzi.com/code/ 可以看到:

默认的用户名和密码都是 admin ,登录后记得修改哦。

这个时候比如我想测试一下PHP7的新特性 <=> 符,可以创建一个文件,在线输入代码:

<?php
$a = 1;
$b = 2;
$c = $a <=> $b;
var_dump($c);

如图所示:

现在就可以通过 http://php7a.applinzi.com/php7.php 访问啦。

注意事项

由于这些在线新建的应用没有提交到git的管理中,因此如果你重启了应用这些文件将会丢失,如果不想丢弃这些文件,你可以通过新建一个共享存储把共享存储映射到/data/www/test 这个目录下,然后在test目录下在线写php 的代码即可,这样重启后就不会丢失文件。

花开的四月

本是花开飘香的四月,似乎在精神和能力增长的重压下有点吐不过气来。最近看了些《怪诞心理学》,觉得挺有意思。想想也觉得挺悲哀,好多的发泄渠道居然一个都不好使了,想想还是在这里多记录一些成长中的感悟比较好,一来不用在社交媒体上展示自己也有透不过气的时候,而来也不用指望在梦里去发泄情绪。

人之所以会感觉到压力,主要是接下来要做的事情可能是自己能力所不及的,这就跟当初在高中做数学试卷的效果一样,那时候从来没有感觉到压力,原因就在于感觉每次的考试不是挑战而是验收。但生活确实不能像应试时那么单一,需要面临来自工作、交际、爱情、家庭各方面的压力。

工作上,由于“机会主义”的作祟,近期负责了远远高于之前开发的事情,背负了重重的绩效压力。昨晚和老大喝酒后在厕所交谈的谈话是互相鼓励大家都要加油,都需要勇气面对未知的挑战。

感情上,我尽可能的避免冲突,但是现在发现并不能,有时心直口快的说出自己的观点后迎来的就是无边的冷战,这确实让人感觉很恼怒,你把她当心里的方舟,希望在你最无助的时候给你力量的时候她却做了把你拉下水的最后一根稻草。不过人都不是完人,说不定别人也把你当做万能的方舟呢。

生活上,母亲对我的依赖远远大于之前,主要可能是她觉得只有向我才能倾诉她对种种事的不满并期望我能一一解决。但是这确实超出了我的能力,在某些事情上我也只能做墙头草,到处调停,避免事情进一步的恶化。所谓解铃还须系铃人,真正解决问题的确实需要当事人自己,只有自己摆正心态了才能积极的面对所遇到的各种事情。

除了嘈杂的生活,还有音乐和泡面:)

用自己的域名访问访问storage资源

到目前为止SAE上的storage还不支持绑定自有的域名,这给需要使用https或者使用自己域名访问存储的附件文件的开发者们出了一个很大的难题。但是如果灵活的使用SAE的appconfig功能和storage的内置API,其实也可以简单的实现。

文件服务器实现原理

所谓知其然不知其所以然,为什么能通过rewrite的方式将storage的访问地址转换成从应用的二级域名输出呢?如果聊到这个话题,那就不得不稍带一下文件服务器的工作流程了。其实这跟大家熟知的HTTP通信协议一样,HTTP在返回的时候会通过【header】和【body】各自带上一部分东西。其中比较关键的是header中的【content-type】,它告诉浏览器下载的东西是个什么类型的文件,【content-length】它告诉浏览器或者其他的终端待下载的文件有多大,大家平时看到的chrome下载的那个圈圈的进度就是这么计算的,完成的比例就是已经下载的文件大小和总的content-length相比得到的百分比。当然服务端如果不输出这个content-length有没有问题呢?当然是没有的,但这是一种很不好的习惯,特别是输出一些比较大的文件例如图片或者音视频时。

上面讲了平时通过浏览器访问资源时的两个基本要素。大家最熟知的就是打开网页了,当然网页html便是其中的一个文件,它的content-type是【text/html】,它的实体就是大家通过【查看源代码】看到的那堆HTML文本。正如下面访问www.baidu.com看到的response header一样,

当然其中还包含很多其他的头例如Set-cookie表示要求浏览器在哪个域名下写下什么cookie等。不再一一讲述。

那么访问一张图片是什么流程呢,其实和上面讲述的访问【百度】的首页并没有任何差别,同样还是返回两个关键的点,一个header中的content-type,告诉访问者下载的是个什么类型的图片,比如是PNG还是JPG,body中包含的是文件的实体。对于图片而言就是一些二进制。那么大家熟知的文件服务器(apache、nginx、ftp服务器、各种云存储)到底在输出文件时干了哪些工作呢?

其实所有的文件服务器基本都是按如下的几个流程响应一次请求的:

  • 将访问的uri映射到本地文件的uri
  • 准备输出文件的header头信息,包括上面所述的content-type、content-length、缓存的策略信息、server信息、cookie等等。
  • 准备文件的实体信息
  • 输出header信息、输出实体信息
  • 刷新缓冲区,把所有的内容发送出去

有了以上的知识储备就可以开始了,那么无外乎以上的几个步骤,首先将输入的url路径映射到本地的文件资源路径;然后准备header头、准备实体,然后将文件发送出去就行了。本文的演示代码以php实现,其他语言的实现可以类似处理。

URI映射

大家都知道SAE storage访问的url是类似【http://skirt-wordpress.stor.sinaapp.com/uploads/2015/12/1.png】这个样子的路径,我们的目标是转换成【http://skirt.sinaapp.com/uploads/2015/12/1.png】这种访问路径的格式。但是我们的代码中明明没有uploads/2015/12/1.png这个路径的文件,那怎么能访问的到呢?这个时候Appconfig(类似于apache的.htaccess)的rewrite功能就派上用场了,有了这个玩意,欺骗世界都不再是梦想,url想怎么转换怎么转换,就算明明是个.html结尾的文件也可以给它转成lmth的:)。

我们通过构造http://skirt.sinaapp.com/域某种特定形式的url,然后将其请求重定向到代码中的一个php文件去处理就可以了。为了和本地的文件不冲突,我们特意构造【http://skirt.sinaapp.com/.storage/uploads/2015/12/1.png】这种路径的文件表示我们这个文件其实是storage中的一个文件而不是本地真实存在的文件。作为写程序的人也能一眼看到这个和代码文件中存在的图片的路径是明显不一致的,因为我们本地根本没有.storage这个目录。那么怎么写这个config.yaml文件呢,下面我写了一个例子,假设我们把所有类似【http://skirt.sinaapp.com/.storage/****】的请求都映射到file_server.php去处理。

// config.yaml 文件内容
name: skirt
version: 2
handle:
- rewrite: if(!is_dir() && !is_file() && path ~ "^/.storage/(.*)$") goto "/file_server.php?__file__=$1"
<?php
// 获取请求的路径
var_dump($_GET['__file__']);

这个时候我们简单请求一个路径【http://skirt.sinaapp.com/.storage/upload/1.txt】,神奇的一幕发生了。发现请求已经交给了file_server.php处理了。看到的效果如下:

从这里我们也可以看到,我们第一步已经大工告成了,因为我们已经从http://skirt.sinaapp.com域中的uri映射到了storage中的路径。

准备header和body

从上文中我们已经将文件的路径成功的映射到了storage中的文件路径,那么接下来我们就需要进行第二步,就是按照文件路径从storage中获取文件的meta信息和实体了。本文为了简单,还是按照上面所述,只需要准备文件的content-type和二进制内容,这些都可以从SAE提供的storage api【storage的使用文档可以参考:http://apidoc.sinaapp.com/class-sinacloud.sae.Storage.html】中获取,请参考以下代码:

<?php
// 需要到SAE对应的应用下创建一个bucket,以下是bucket的名字
$bucket = 'wordpress';
use sinacloud\sae\Storage as Storage;
$instance = new Storage();

// 检查文件是否存在
if (!array_key_exists('__file__', $_GET)) {
        // 参数不正确直接输出404
        header('HTTP/1.1 404 Not Found');
        exit();
}
$file_path = $_GET['__file__'];

// 通过storage的函数判断文件存在否
$file_info = $instance->getObjectInfo($bucket, $file_path);

if (!$file_info) {
        // 文件不存在
        header('HTTP/1.1 404 Not Found');
        exit();
}
// 通过这个大概获取到的文件信息类似于
// array(4) { ["size"]=> int(37428) ["time"]=> int(1431926062) ["type"]=> string(9) "image/png" ["date"]=> int(1455890358) }
// 可以看到此时就取到了文件的content-type和content-length
// 我们直接输出文件的两个头
header(sprintf('content-type: %s', $file_info['type']));
header(sprintf('content-length:%d', $file_info['size']));

$tmp = tempnam(SAE_TMP_PATH, 'tmpfile');
// 获取文件内容
$instance->getObject($bucket, $file_path, $tmp);

// 输出文件内容
echo(file_get_contents($tmp));

// 大工告成啦!

此时访问就可以发现,我们成功的把【http://skirt-wordpress.stor.sinaapp.com/uploads/2015/05/utf8mb4.png】替换成了【http://skirt.sinaapp.com/.storage/uploads/2015/05/utf8mb4.png】,这时候就可以用自己的独立域名或者是https了。从下面的效果看,https也可以使用了!

其他的一些工作

当然本文只是简单的讲述了一下怎么实现, 其实还有很多的细节需要处理,例如需要带上缓存过期的头,这样可以避免频繁的请求消耗服务器的资源。这里就需要带上etag的header头,在处理请求的时候也需要返回304的http code,诸如此类的问题还有很多,希望聪明的读者自行摸索。

实例代码的下载地址

http://skirt.sinaapp.com/tmp_code/tmp_code.zip

几种方式在php中实现定时器

使用场景

在用php写逻辑代码的时候一般很少碰到需要严格定时器的场景,因为大家总是希望在一次同步的请求中完成所有的任务,如果不是逼不得已,可能也很少将业务拆分到异步的任务去完成。我认为主要还是自己搭建一套靠谱的、通用的异步任务执行框架比较困难,也比较麻烦。但是现在有很多创意十足的php扩展或者是PaaS平台已经有好的实现了。利用这些工具或者框架构建一套自己的异步任务执行框架就简单的多了。这时候定时器就派上了用场,例如在我们拆分子任务执行的时候将其执行的时间设置为一个固定的值,在任务调度时时如果在设定的时间后没有完成的重新触发,这样就可以保证其反复执行直到成功。下面会提到几种方式分别限制一段php代码的执行时间,各有利弊。

使用max_execution_time

这个可能是大家最熟悉的方式了,通过php.ini能配置的一个参数来限制脚本的最大执行时间。给出测试的代码:

<?php
$i = 1;
while (1) {
    $i++;
    usleep(1);
}

这时候通过命令行执行我们的代码,并通过-d参数限制最大的执行时间就可以看到:

[root@localhost /]# php -d "max_execution_time=1" test.php
PHP Fatal error:  Maximum execution time of 1 second exceeded in /data1/www/htdocs/sae.changes.com.cn/test.php on line 5

很显然是起到了限制的作用,这时候我们稍微修改下测试的代码。看看在这种强执行时间限制之下能不能执行我们设置的register_shutdown_function,测试代码如下:

<?php
function shutdown()
{
    echo 'success exec';
}
register_shutdown_function('shutdown');

$i = 1;
while (1) {
    $i++;
    usleep(1);
}

执行测试代码可以看到:

[root@localhost /]# php -d "max_execution_time=1" test.php
PHP Fatal error:  Maximum execution time of 1 second exceeded in /data1/www/htdocs/sae.changes.com.cn/test.php on line 11
success exec

太好了,说明这种方式做执行时间的限制还不赖。但是还是有些不方便的地方,这主要在于会同安全组的策略相违背,至少我们在php安全的设置的初期是把ini_set函数屏蔽了的。这时候使用这种方式可能会在部分云平台上会出现问题。

使用php中的declare ticks指令完成

这个东西可以简单的理解为php为开发者为基本的执行单元执行后加了可以hook的函数,具体的介绍可以参考php的文档http://php.net/manual/zh/control-structures.declare.php,先看例子和执行结果:

<?php
declare(ticks = 1);
function shutdown()
{
    echo "safe shutdown \n";
}
register_shutdown_function('shutdown');
define('max_exectime', 1);
$time_start = microtime(true);
var_dump($time_start);
function check_exec_time()
{
    global $time_start;
    $time_now = microtime(true);
    if ($time_now - max_exectime > $time_start) {
        var_dump($time_now);
        echo "bye";
        exit();
    }

}
register_tick_function('check_exec_time');

for ($i = 0; $i < 10000100; $i++) {
    $i++;
    usleep(1);
}

我们可以看到执行的结果:

[root@localhost /]# php tick.php
float(1442126693.6926)
float(1442126694.6926)
byesafe shutdown

可以看到借助使用ticks指令实现定时器比max_execution_time的好处在于前者可以轻松实现毫秒级别的定时器但是max_execution_time最小的粒度只能到秒级别。另外这种方式不会和大部分的安全策略相违背。但是缺点也是很明显了,指定的周期设置的越短定时越精确,但是对性能的损耗也越大,因为总要跳出来去检测时间。

swoole的定时器

使用过swoole扩展的同学肯定知道这个扩展也提供了毫秒定时器的功能,具体可以参考http://wiki.swoole.com/wiki/page/174.html。当然在使用时必须要编译安装swoole扩展,给出测试代码:

<?php
var_dump('swoole version:'.swoole_version()."\n");
swoole_timer_tick(1000, 'exec_function');
swoole_timer_after(1, function(){
     echo(1);
});

function exec_function()
{
    // 这里直接退出了
    echo "timer exec\n";
    var_dump(getmypid());
    exit();
}

这时我们执行可以得到结果:

[root@localhost /]# php swoole.php
string(22) "swoole version:1.7.19"
1timer exec
int(19908)

可以看到1000毫秒的定时器工作了,swoole_timer_after的回调函数中用于执行需要执行的代码逻辑,这种用法有许多的依赖,首先swoole在实现的时候使用了timerfd,但是这个东西在内核版本2.6.25后才加入,如果内核版本低于这个应该是无法工作的,另外swoole的定时器需要在“全异步”的环境中才能工作,暂时不能用于apache等环境中,不能使用usleep、sleep函数阻塞进程,否则就无法工作了。

不改数据库编码也能支持Emoji表情

看到很多的介绍都说非要数据库支持utf8mb4才能支持Emoji表情,其实用PHP的pack函数可以规避掉数据库的编码支持问题。

首先看一下编码函数:

if (!function_exists('encode_str'))
{
    function encode_str($str)
    {
        return serialize(unpack('c*', $str));
    }
}

先unpack得到一个数组,然后序列化一下,看到的大体是这样的:

a:162:{i:1;i:123;i:2;i:34;i:3;i:116;i:4;i:97;i:5;i:114;i:6;i:103;i:7;i:101;i:8;i:116;i:9;i:34;i:10;i:58;i:11;i:34;i:12;i:104;i:13;i:116;i:14;i:116;i:15;i:112;i:16;i:58;i:17;i:47;i:18;i:47;i:19;i:102;i:20;i:105;i:21;i:108;i:22;i:101;i:23;i:46;i:24;i:105;i:25;i:108;i:26;i:105;i:27;i:103;i:28;i:104;i:29;i:116;i:30;i:115;i:31;i:104;i:32;i:97;i:33;i:114;i:34;i:101;i:35;i:46;i:36;i:99;i:37;i:111;i:38;i:109;i:39;i:47;i:40;i:98;i:41;i:54;i:42;i:53;i:43;i:56;i:44;i:57;i:45;i:102;i:46;i:99;i:47;i:54;i:48;i:97;i:49;i:98;i:50;i:47;i:51;i:100;i:52;i:97;i:53;i:49;i:54;i:49;i:55;i:50;i:56;i:52;i:57;i:100;i:58;i:100;i:59;i:57;i:60;i:48;i:61;i:100;i:62;i:102;i:63;i:97;i:64;i:52;i:65;i:48;i:66;i:51;i:67;i:100;i:68;i:98;i:69;i:50;i:70;i:54;i:71;i:57;i:72;i:55;i:73;i:99;i:74;i:53;i:75;i:52;i:76;i:48;i:77;i:56;i:78;i:57;i:79;i:100;i:80;i:50;i:81;i:97;i:82;i:53;i:83;i:46;i:84;i:106;i:85;i:112;i:86;i:103;i:87;i:34;i:88;i:44;i:89;i:34;i:90;i:116;i:91;i:105;i:92;i:116;i:93;i:108;i:94;i:101;i:95;i:34;i:96;i:58;i:97;i:34;i:98;i:-16;i:99;i:-97;i:100;i:-115;i:101;i:-114;i:102;i:-16;i:103;i:-97;i:104;i:-111;i:105;i:-106;i:106;i:34;i:107;i:44;i:108;i:34;i:109;i:100;i:110;i:97;i:111;i:116;i:112;i:101;i:113;i:34;i:114;i:58;i:115;i:34;i:116;i:50;i:117;i:48;i:118;i:49;i:119;i:53;i:120;i:45;i:121;i:48;i:122;i:50;i:123;i:45;i:124;i:49;i:125;i:49;i:126;i:34;i:127;i:44;i:128;i:34;i:129;i:117;i:130;i:115;i:131;i:101;i:132;i:114;i:133;i:110;i:134;i:97;i:135;i:109;i:136;i:101;i:137;i:34;i:138;i:58;i:139;i:34;i:140;i:-24;i:141;i:-81;i:142;i:-73;i:143;i:-27;i:144;i:-113;i:145;i:-85;i:146;i:-26;i:147;i:-120;i:148;i:-111;i:149;i:-24;i:150;i:-115;i:151;i:-119;i:152;i:-28;i:153;i:-72;i:154;i:-127;i:155;i:-27;i:156;i:-92;i:157;i:-89;i:158;i:-28;i:159;i:-70;i:160;i:-70;i:161;i:34;i:162;i:125;}

哎呀呀呀好长一串,但是大家会发现都是一些数字字母等等,任意的数据库编码都能支持,然后就到了解码的时候了。下面看一下解码函数,

if (!function_exists('decode_str'))
{
    function decode_str($str)
    {
        $ds = unserialize($str);
        if (!$ds) {
            return false;
        }
        $ret = '';
        $len = count($ds);
        for ($i=1; $i <= $len; $i++) {
            $ret .= pack('c*', $ds[$i]);
        }
        return $ret;
    }
}

这样又把输入的字符串还原啦,包括其中的表情。

下面贴张我测试的效果吧,希望对大家有帮助。

小熊猫微信开发框架快速上手

框架是什么

我开发这个框架是为了更简易的上手微信公众平台的开发中,除了能完成一些基本的处理上行信息外,还顺手完成了一个适配手机端的CMS文章发布系统,为了更好的运营公众账号。

快速安装

如果通过github安装,需要在SAE中创建一个应用,开启MySQL服务,创建一个domain名为upload的storage,然后开启至少2M的memcache用于缓存。然后将src目录下的代码上传到应用中,访问 http://您的应用名.sinaapp.com/sae/install.php完成安装。

如果通过应用商店安装,以上的事情都帮你完成了以上的手工操作步骤,但是仍然需要访问http://您的应用名.sinaapp.com/sae/install.php完成安装。

开发规范

此套框架基于LazyPHP3开发,如果对这个php开发框架不熟悉的可以参考 LazyPHP框架文档

另外上行信息处理类需要以你创建的微信应用名+Chat作为类名,例如你创建了一个应用名叫test,那么你的类名就得叫 testChat,如果你不想这么办,那么就只能修改 /controller/message.class.php 下面的类加载规则了。另外,这个类需要继承Wechat,并实现类似 onSubscribe等一系列函数,可以参考/lib/default_chat.class.php的实现。顺便说条约束条件,你实现的处理类也必须放在在/lib/目录中。

关于文章管理系统

大家都知道其实微信官方的微信公众平台一天发布的文章数量是极少的,但是我们常用的文章发布系统类似wordpress其实不太适合直接分享到微信,因为浏览起来会变形,在这种情况下,我们顺手做了一个小的文章发布系统,并在电脑访问文章的时候给每一篇文章加了一个分享到微信的按扭,这样大家扫一扫就可以轻松分享到微信了。

关于广告

其实吧,这里的广告也不能单纯的广告,可以用做每一篇文章的签名,例如叫大家关注公众账号之类的,我们实现的版本就是通过上传一个图片实现的。

实现的效果范例

说明

欢迎各位基于框架实现更多有价值的好玩的应用。如果发现我们的不足之处可以在github中给我们提issues。

在线二维码生成接口

好久没有写博客了,最近一直在忙于一些内部产品的实现,最近为了一个开源的产品,移植了一个在线生成二维码的应用放到了sae上,并提供了一个api调用接口。如果有兴趣的朋友可以调用,此接口本人提供维护,用不下线,但还是希望在生成完二维码之后取回本地存储,防止此接口不可用是二维码失效。

接口地址:https://qrcodeonline.sinaapp.com/rest.php?data=
请求参数:

  • data:需要附件的数据,可以是任何文本,建议用url
  • level:生成二维码的质量,由低到高分别是’L',’M',’Q',’H',默认为H
  • size:二维码大小,可以从1-10,默认为8

例如:

[lazy@lazy-laptop~/workspace/svnmy/qrcodeonline]$curl "https://qrcodeonline.sinaapp.com/rest.php?data=test"
{"code":0,"message":"success","data":"https:\/\/qrcodeonline.sinaapp.com\/rest1a44d204ab5ec6e0a580f88e7f36f913.png"}

取回数据中的文件就是生产的二维码文件。

Typecho 0.9忘记密码重置脚本

昨天装了一个Typecho,今天登录的时候发现密码忘记了==!(是有多老这么健忘),然后稍微看了一下Typecho生成密码的代码。改写了如下这么一段脚本可以重新生成你想要的密码,然后把生成的字符串替换数据中typecho_users表的password字段即可。

<?php
namespace test;
function hash($string, $salt = NULL)                          
{                                                                          
    $salt = empty($salt) ? \test\randString(9) : $salt;
    $length = strlen($string);                                              
    $hash = '';                                                            
    $last = ord($string[$length - 1]);                                      
    $pos = 0;                                                              

    /** 判断扰码长度 */                                                    
    if (strlen($salt) != 9) {                                              
        /** 如果不是9直接返回 */                                            
        return;                                                            
    }                                                                      

    while ($pos < $length) {                                                
        $asc = ord($string[$pos]);                                          
        $last = ($last * ord($salt[($last % $asc) % 9]) + $asc) % 95 + 32;  
        $hash .= chr($last);                                                
        $pos ++;                                                            
    }                                                                      

    return '$T$' . $salt . md5($hash);                                      
}

function randString($length, $specialChars = false)          
{                                                                          
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    if ($specialChars) {                                                    
        $chars .= '!@#$%^&*()';                                            
    }                                                                      

    $result = '';                                                          
    $max = strlen($chars) - 1;                                              
    for ($i = 0; $i < $length; $i++) {                                      
        $result .= $chars[rand(0, $max)];                                  
    }                                                                      
    return $result;                                                        
}      
// The password u wanna use
$password = '123456';
var_dump(\test\hash($password));

sae支持的pear包以及版本

如果你能找到这篇文章,说明实在是一个很优秀的开发者了。对的,SAE支持了部分常用的pear,当然pear本来就是纯PHP实现的东西,你也可以直接到pear的网站上下载后传到自己的代码空间使用,我们提供只是为了更简化您的开发。目前支持的包以及版本如下:

package version state
Archive_Tar 1.3.7 stable
Auth 1.6.4 stable
Console_Getopt 1.2.3 stable
HTML_Common2 2.1.0 stable
HTML_QuickForm2 2.0.0 stable
HTTP 1.4.1 stable
HTTP2 1.1.1 stable
HTTP_Download 1.1.4 stable
HTTP_Header 1.2.1 stable
Log 1.12.7 stable
MDB2 2.4.1 stable
MIME_Type 1.3.3 stable
Numbers_Roman 1.0.2 stable
PEAR 1.9.4 stable
PHPUnit 1.3.2 stable
PHP_CodeSniffer 1.4.7 stable
Structures_Graph 1.0.4 stable
Text_CAPTCHA 0.5.0 beta
Text_Password 1.1.1 stable
Validate 0.8.5 beta
XML_RPC 1.5.4 stable
XML_Util 1.2.1 stable