分类目录归档:PHP

php学习练习(2)-微信版宝宝睡前故事

小的功能需求在生活的周围存在很多,这些功能需求的编程实现是学习的一种良好方式。对于给孩子读睡前故事是现阶段我这个家庭的需求,买了不少故事书,也有读,就想着将这些故事放到手机上也是一个不错的选择。

时下比较火热的「微信」是手机上呈现的一个不错的方式,于是着手规划这个小的功能需求,这个小的需求分为两部分,第一部分是服务器端对于故事的组织与管理;第二部分是服务器端与微信公众号之间的接口实现。

服务器端需要一个存储故事的数据库,用于存储故事数据,一组页面及其脚本来完成故事的管理,完成对应于数据库的ADU相关操作。剩下的就是与微信的接口部分,通过微信公众号的自定义菜单获取每日睡前故事,通过脚本依据当日信息组成微信公众号的图文信息,点击后展现故事。

最终的阅读操作如图所示「如果你也有这样的需求,可通过微信搜索smallsoftware关注即可使用,也可在微信中扫描本Blog右侧二维码直接关注」:

babystorys

微信接口这部分主要是从数据库中获得数据,然后组织成图文信息即可,根据微信自定义菜单的KEY完成组装即可。

$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
if(!empty($postStr)) {
  $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
  $fromUser = $postObj->FromUserName;
  //详细请参看微信公众帐号开发接口文档
  //...
  $fromMsgType = $postObj->MsgType;
  //...
  if($fromMsgType == "event") {
    $formEvent = $postObj->Event;
    if($formEvent == "CLICK") {
      $eventKey = $postObj->EventKey;
      if($eventKey == 'YOUR_KEY') {
        //取数据
        //组织图文信息数据
        //输出
      } else { //... }
    }
  }
//......

服务器端包含故事的添加、修改、删除、查看,用于后台对于故事的管理。使用了Bootstrap前端框架的部分内容,具体的结构如下。

babystory archite

数据库类基于PDO,故事类继承数据库类,完成故事的数据相关操作封装及故事的展现,admin类完成权限和管理的封装,前端页面通过init入口完成对于数据的管理工作,init入口部分完成数据的初始化工作与上下交互操作。

没有其他可供的选择,每天一篇睡前故事,对于选择性故事的定制阅读及过往的查询不支持,对于用户阅读这部分的统计也没有完成,可以说留下了不少的坑等着填。

至于微信自定义菜单,根据其接口文档中的说明,先要由appid和secret通过一个URL用Get方式获得AccessToken,然后将准备好的菜单和获得的AccessToken通过一个URL用POST的方式提交数据,从而创建自定义菜单。

<!--?php
  class WechatMenu {
    private $_appid = "";  //你的appid
    private $_secret = ""; //你的secret
    function __construct($menu) {
      //先获得AccessToken
      $result = $this--->getAccessToken();
      if(array_key_exists('errcode', $result)) {
       throw new Exception($result['errmsg'], 1);
      } else {
         $url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" . $result['access_token'];
         $result = $this-&gt;Post($url, $menu);
         if($result['errcode'] == 0) {
           //ok
         } else {
           throw new Exception($result['errcode'], 1);
         }
      }
    }
    
    private function Post($url, $data) {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
      $data = curl_exec($ch);
      curl_close($ch);
      return json_decode($data,true);
    }

    private function Get($url) {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
      $data = curl_exec($ch);
      curl_close($ch);
      return json_decode($data,true);
    }

    private function getAccessToken() {
      $grant_type = "client_credential";
      $appid = $this-&gt;appid;
      $secret = $this-&gt;secret;
      $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type={$grant_type}&amp;appid={$appid}&amp;secret={$secret}";
      $result = $this-&gt;Get($url);
      return $result;
    }
  }
?&gt;

调用 (1) 生成json格式的菜单数据 (2)new WechatMenu($menu);

wechatmenu

-EOF-

php中的魔术方法

php中内置了不少被赋予魔术性质的「魔术方法」,魔术方法以双下划线开头「__」,在类中使用,在特定的条件下自动在程序中被调用,由于具有在特定条件下自动被调用的特性,使得编程过程得以简化。

在PHP中魔术方法有:__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone(), __autoload()

1. __construct() 和 __destruct()

__construct()和__destruct()分别称为构造函数与析构函数,在对象被创建和销毁的时候由系统自动调用,跟其他编程语言中构造函数与析构函数一样,也是在面向对象编程中使用最频繁的。

构造函数主要完成对象在创建时候的初始化工作,一般用于初始化对象的成员,在PHP中,子类使用父类的构造函数必须使用parent::__construct()显示调用,不支持隐式自动调用。

对应的析构函数是对象在被系统销毁时自动调用。包括对象被显式销毁或者该对象的所用引用都被删除。跟构造函数一样,子类析构函数使用父类析构函数同样使用parent::__destruct()显示调用。

construct

2. __call()和__callStatic()

当调用对象不可访问的方法或者没有的方法时自动调用__call()魔术方法。该魔术方法有两个参数,第一个是调用的方法名称,第二个参数是枚举数组参数列表。用静态方式调用不可访问的方法或者没有的方法时自动调用__callStatic()魔术方法。

测试后发现对于存在但不可访问的方法并不能通过__call来调用,调用顺序为,先在自己类中寻找方法,如没找到,向父类中寻找,如果在父类中找到则调用父类中方法,如果没有找到而本类中有__call方法则执行__call魔术方法。

call_01

该魔术方法另外一个比较重要的用途就是实现重载,在PHP手册中这两个魔术方法介绍的标题也是方法重载。

call_02

3. __get() 和 __set()

当读取不可访问或者未定义的属性时,访问__get()方法。当设置不可访问或者未定义的属性时,访问__set()方法。

4. __isset()和__unset()

当在一个未定义或者不可访问的属性中调用isset()函数时访问__isset()方法。当在一个未定义或者不可访问的属性中调用unset()函数时访问__unset()方法。

 5. __sleep()和__wakeup()

这两个魔术方法在进行序列化的时候使用,通过序列化可以将任何类型数据转化为字符串格式,我们可以通过序列化对象,将其转化为字符串格式保存起来。在进行序列化的时候,检查类中是否有__sleep()魔术方法,如果有就调用该方法。在进行反序列化的时候会检查类中是否有__wakeup()魔术方法,如果有就调用该方法。

通常在__sleep()方法中处理不需要序列化的部分内容或者做一些对象的清理工作,而在__wakeup()方法中进行一些初始化操作。这两个方法是配合序列化和反序列化使用的。

6. __toString()

将对象当初字符串在程序中使用时,会自动调用该方法,例如我们echo一个对象时将调用这个方法,如果不存在该方法,则出现错误。

7. __invoke()

当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。

8. __set_state()

当调用var_export()导出类时,该方法被自动调用。

9. __clone()

复制对象时使用clone方法,在复制完成时,如果定义了__clone方法,则会自动被调用,可用于修改新复制对象的属性或进行初始化工作。

10. __autoload()

使用这个方法可以进行类的自动加载,比如我们可以在该方法中完成包含文件的自动加载。

autoload

mysqli中多语句查询insert_id和affected_rows

在php的mysqli中有多条SQL语句执行的函数multi_query,可以一次执行多条SQL语句,对于不返回结果集的SQL语句在使用中发现获取insert_id和affected_rows不能自动进行多语句的累加数据,还得配合more_results「是否还有结果集」和next_result「下一个结果集」来进行计算,按照预计的来讲对于不返回结果集的SQL语句来说,应该能直接返回最后插入的ID和受影响的行数,结果却不然,在此记录一下。

<!--?php
/*
 *  使用mysqli的多语句查询multi_query中获得最后插入ID和受影响的行数
 *  数据库表为shops,字段有id,name,price,nums,desc,id为子增长字段
 */
header('Content-Type: text/html; charset=utf-8');

$mysqli = new mysqli("localhost", "root", "liutao", "test");

if($mysqli--->connect_error) {
  echo '错误:' . $mysqli-&gt;connect_error;
  exit;
}

$sqls = "insert into shops(name,price,nums,desc) 
  values('笔记本','1.2','1000','B5 笔记本');
insert into shops(name,price,nums,desc) 
  values('0.5中性笔','1.8','30','0.5的黑色中性笔');
insert into shops(name,price,nums,desc) 
  values('档案袋','2.2','100','A4透明塑料档案袋');
insert into shops(name,price,nums,desc) 
  values('订书钉','3.5','50','订书钉');";

if($mysqli-&gt;multi_query($sqls)) {
  $affectRows = 1; 
  do {
    $affectRows += $mysqli-&gt;affected_rows;
    $mysqli-&gt;next_result();
  } while($mysqli-&gt;more_results());
  echo "执行成功!<br> 最后插入的id:{$mysqli-&gt;insert_id} <br> ";
  echo "共影响的行数:{$affectRows} <br>";
} else {
  echo '错误:' . $mysqli-&gt;errno . '(' . $mysqli-&gt;error . ')';
}

$mysqli-&gt;close();

?&gt;

执行结果:

mutiquery_id_affectedrows