Mac OS 打不开 PhpStorm 和 Aptana 的解决办法

我的 OSX 版本是 10.9.4

确认已经安装了 JDK 1.8(从 java 官网下载),命令行输入 java -version 以及 javac -version 都是能够正确输出的,也设置了 JAVA_HOME 环境的值为 /usr/libexec/java_home,但是安装完 PhpStorm 后,双击 PhpStorm 没有反应,而安装完 Aptana 后双击提示找不到 jvm 虚拟机。但是下载的 Eclipse 以及 adt-bundle 里面的 Android Studio 这两个 IDE 都能正常打开。

搜了一下 mac java,在官网上找到这个链接:http://support.apple.com/kb/DL1572,抱着试一试的心态下载安装,安装完毕后 PhpStorm 和 Aptana 都能正常打开了。为什么会这样呢?

两个 TP-Link 无线路由器桥接连接,无线路由器连接无线路由器

我目前和同事一起租了个三房一厅,房子是复式的,我住楼上,家里有个 TP-Link 无线路由器,但是我在楼上离得太远,信号不是很好,网速不稳定,正好我手上还多有一个无线路由器,就想着两个路由器连接起来,增强信号范围。在网上搜索了一番,配置起来并不复杂,下面就总结一下,连接外网的路由器在这里命名为路由器 A,桥接的路由器我们命名为路由器 B。

路由器 A,正常采用拨号连接互联网,然后设置无线连接,路由器 A 不需要做任何特殊设置,路由器 A 的管理地址为 192.168.1.1,开启无线,开启 DHCP 服务,地址池采用默认的 192.168.1.100 – 192.168.1.199。

接下来是对路由器 B 的设置。

首先更改路由器 B 的管理页面地址(默认也为 192.168.1.1 会与路由器 A 的 ip 冲突,因此进行更改),更改为 192.168.1.X 其中 X 可以为 0-255 之间的任何数字,只要不与当前连接无线路由器 A 的电脑手机等的 IP 冲突即可,我这里设置为 192.168.1.2:QQ20140713-1

其次,给路由器 B 设置 WDS,勾选上开启 WDS 就会出现它下面的设置项,点击扫描可以找到我们的无线路由器 A 的 wifi 名称,路由器 A 设置了无线密码就输入密码进行连接点击保存即可,这里注意,网上有些教程会提到说路由器 B 的 SSID 号和信道要设置成和路由器 A 一样的,我认为是无稽之谈,因为路由器 B 的无线和路由器 A 的无线并没有什么联系。QQ20140713-2

然后,关闭路由器 B 的 DHCP 服务,见图,QQ20140713-3

到这里就大功告成啦,现在笔记本或手机连接路由器 A 的无线和路由器 B 的无线都可以正常上网啦。其实我也不明白这里面的道理,路由器 B 充当了一个交换机的角色?

使用 Shell 花括号展开

我们先来简单看下,什么是 Shell 花括号展开,在终端输入如下内容回车:

$ echo {1..5}
1 2 3 4 5

可以看到 {1..5} 被 Shell 展开为 1 2 3 4 5,我们再来看几个例子:

$ echo person_{1..5}
person_1 person_2 person_3 person_4 person_5

$ echo hello_{1..5}_word
hello_1_word hello_2_word hello_3_word hello_4_word hello_5_word

接下来是一个带有多个 {} 的例子:

$ echo a{1..5}b{3..6}c
a1b3c a1b4c a1b5c a1b6c a2b3c a2b4c a2b5c a2b6c a3b3c a3b4c a3b5c a3b6c a4b3c a4b4c a4b5c a4b6c a5b3c a5b4c a5b5c a5b6c

Shell 会根据 {} 的个数产生其笛卡尔积个串。{} 里面不仅可以由 .. 来表示一个序列中任一个字符,也可以使用逗号表示其列表中任一个字符,看这个例子:

$ echo a{1,8,b}c
a1c a8c abc

OK, 这就是 Shell 花括号展开式的作用啦,下面我们来看一个实际应用场景。

有这样一个需求,我们需要在一张数据表里面构造一些数据,假设是一张成绩表(users),分别有语文(chinese),数学(math),英语(english),这三个字段。手动往数据库里面当然是可以做到的,但是太费时间,用 PHP 写个循环插多条记录也比较快,但是至少还是得需要十几行代码,需要连数据库啊,需要在循环体里面插记录啊。这种情况 Shell 的花括号展开就可以派上用场啦。我们先写插入一条记录的 SQL:

insert user (chinese,math,english) values (80, 90, 70);

然后这样:

$ echo "insert user (chinese,math,english) values ("{80..90}", "{70..98}", "{68,70}");"

就会输出多条插入语句,在 MySQL 里面运行一下就 OK 啦。

有空多学一下这些技巧,虽然前期需要花点时间学习,但是一旦学会使用后在以后的工作中可以节约大量时间。

最近在看《The Linux Comand Line》这本书,这是一本开源的讲 Linux 命令行的书。以前都是用到什么看下手册或者在网上搜下,没有系统的学习过,书里面的内容大部分比较清楚(本人命令还算用得比较熟练啦),这两天感觉最大的收获就是知道了 Shell 花括号展开这个神器。

PHP 使用 Redis 来做队列服务

为客户端开发 Api,采用 PHP,我们使用了一个叫做 Swoole_framework 的框架,同时使用了 swoole 扩展。在开发接口时使用了框架提供的模型类,但是,服务端除了接口,不可避免还有一些后台脚本,比如每日任务初始化,用户分数结算,后台脚本直接 require 相关配置文件,然后手动 update 数据库数据。这两天做需求的时候就发现,有些业务逻辑在服务端 api 层需要实现,在后台脚本里面也要实现,而这两处的代码不能复用,因此同样地逻辑处理了两遍,随着后续功能的增加,重复的代码还会增加。上周因为比较急就先采用这种方法完成了代码,实现了功能,这两天测试联调,总感觉同样地逻辑在多个地方出现,不仅写代码时麻烦,测试时也麻烦,下午灵光一现想到可以使用任务队列来完成这个工作。

简单来说,就是在原先逻辑处理的地方,只是简单增加一个任务,不再进行具体的业务逻辑,后台脚本和 api 都如此,然后再单独跑一个脚本,拉取任务,进行业务逻辑处理。这个的思路好处很明显,同样地逻辑归到了一处,开发以及调试找错时会清晰很多。

需要实现一个任务队列,使用 redis ,简单封装了一个队列:

<?php

class Queue
{
    protected $redis;
    protected $key;

    public function __construct(\Redis $redis, $key)
    {
        $this->redis = $redis;
        $this->key = $key;
    }

    public function pop()
    {
        return $this->redis->lPop($this->key); // 左边出
    }

    public function push($task)
    {
        return $this->redis->rPush($this->key, $task); // 右边入
    }
}

队列的一个特点就是先进先出(FIFO),很显然,先产生的任务需要被先处理,redis 的 List 可以保证这一点。

晚上将代码重新组织,用任务队列加单独脚本的方式实现了需要的业务功能,顿时感觉浑身舒畅了许多。

nginx php-fpm 捕获记录 php 的错误日志

请确认 php-fpm.conf 的如下配置为:

catch_workers_output = yes # 捕获进程的输出,这个值默认为 no
error_log = log/php-fpm.log # 这个是默认值,指定 php-fpm 输出 log 的文件路径

确认 php.ini 的如下配置为:

log_errors = On # 开启记录错误日志,这个值默认为 Off
error_reporting=E_ALL&~E_NOTICE # 默认就是这个值,php 的 error_reporting 级别

参考网址:http://www.nginx.cn/666.html

终端输入 Shell 命令时可用的快捷键

编辑命令:

  1. Ctrl – a 移动光标到行首
  2. Ctrl – e 移动光标到行尾
  3. Ctrl – l 清屏(功能同 clear 命令)
  4. Ctrl – d 删除光标所在位置的字符
  5. Ctrl – t 光标所在位置的字符和其前面的字符进行交换
  6. Ctrl – k 剪切从光标所在位置到行尾的字符
  7. Ctrl – u 剪切从光标所在位置到行首的字符
  8. Ctrl – y 粘贴由上两个命令删除的字符到当前光标所在位置

搜索命令:

  1. Ctrl – r 搜索历史命令
  2. Ctrl – j 搜索到后,按 Ctrl – j 可以将命令复制当前命令行

命令历史展开:

  1. !! 重复执行上一次的命令,和先按方向键上然后回车效果一样
  2. !number 重复执行指定 number 的历史命令
  3. !string 重复执行以 string 开头的历史命令
  4. !?string 重复执行包含 string 的历史命令

腾讯 PHP 笔试题,写一个 is_writable 函数的替代函数

今年三月份去腾讯面试过。笔试题有一题是,PHP 中的 is_writable 函数不可信,请写一个函数来替代这个函数。

拿到这个题目,我便想起之前读 CI 框架实现的时候看到 CI 里面写了个 is_really_writable 函数,当时没有注意,只看这个函数的功能,至于 CI 为什么要自己实现一个 is_writable 函数的功能没有去深究。当时,我的解题思路是这样的,既然 is_writable 不可信,那么我就实实在在的尝试去那个文件夹里进行写入,如果可以写则返回 true,否则返回 false,思路是对的,但代码并不是完全无误,有瑕疵。

回到家后我便开始搜索,同时重新仔细查看 CI 的那个函数实现以及说明,原来是因为在 Windows 服务器上如果一个文件夹有只读属性,is_writable 这个函数还是会返回 true,而实际上 php 是不能进行写入的,另外一种情况是,在 unix 服务器上,如果 safe_mode 选项打开,那么 is_writable 函数也是不可信的。

这个事情提醒我,当遇到一个问题时,一定要追查到底,搞明白它的原理以及那样做的理由。

附上 CI 里面 is_really_writable 函数的实现:

function is_really_writable($file)
{
    // If we're on a Unix server with safe_mode off we call is_writable
    if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == false) {
        return is_writable($file);
    }

    // For windows servers and safe_mode "on" installations we'll actually
    // write a file then read it.  Bah...
    if (is_dir($file)) {
        $file = rtrim($file, '/') . '/' . md5(mt_rand(1, 100) . mt_rand(1, 100));

        if (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === false) {
            return false;
        }

        fclose($fp);
        @chmod($file, DIR_WRITE_MODE);
        @unlink($file);
        return true;
    } elseif (!is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === false) {
        return false;
    }

    fclose($fp);
    return true;
}

 

珍爱生命,拥抱 PHPUnit

在公司负责服务端 Api 开发,为安卓客户端提供相应的接口,在开发的过程中,免不了要测试完成的接口是否可用。最开始的测试手段是使用 POSTMAN 等工具发送请求。使用这类工具的流程一般是,先构造 url 请求接口进行登录,登录后拿到服务器返回的一个标识(可以看做是一个 session,但这个 session 的概念和传统的网站开发中的 session 概念不一样),在后续的接口调用中,都需要在请求 header 头里面加上那个标识。这个流程会比较繁琐,至少需要多次鼠标点击以及复制粘贴的过程才能完成。偶尔这样做还好,这种事情做多了便让我觉得烦躁无比。

了解了下 PHPUnit,先读了读官网的 manual,大体明白怎么写测试了就开始动手。

具体到我们的业务,首先写了一个 Client 类,这个类主要作用是模仿客户端发一个请求,当然这个类会进行一些简单的封装,比如 curl 请求的时候带上我们的标识(上文说的 session),还有其它的模拟安卓客户端的一些请求头。刚开始我在每个测试方法中都 new 一个 Client 对象,后来想想不对,既然是模仿客户端,而一个客户端永远是一个实例,每次 new 一个出来显然不符合现实逻辑,另外一个问题是,测试登录接口后,session 标识需要在后续的接口测试中反复使用。想到这,于是将 Client 类改为了单例实现,这样就能保证在多个测试方法的 Client 为同一个实例对象,并能共享 session 标识状态。

下面是 Client 的实现及一个简单的测试用例文件示例:

class Client
{
    private function __construct()
    {
    }

    private static $instance;

    public $session;

    public function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    public function curl($url)
    {
        $ch = curl_init();
        if (!is_null($this->session)) {
            // 如果 session 不为空,则在 curl 请求时加上保存 session 信息的 header 头
        }
        // 使用 curl 发送请求
        $response = curl_exec($ch);
        return $response;
    }
}

class Test extends PHPUnit_Framework_TestCase
{
    public function testLogin()
    {
        $client = Client::getInstance();
        $response = $client->curl($login_url);
        // 下面就可以进行一些 assert 啦

        // 登录成功后设置 session 为服务器返回的 session
        $client->session = $session_get_from_response;
    }

    /**
     * @depends testLogin
     */
    public function testGetUserInfo()
    {
        $client = Client::getInstance();
        $response = $client->curl($get_user_info_url);
        // 接下来的代码进行后续处理
    }
}

要测试新的接口,只需要增加新的方法就好了,多次测试多次运行单元测试命令就 OK 了,可以节约大量时间啊。

另外,我发现在单元测试类里面的不同方法好像是单独运行的,比如下面的这个代码:

class Test2 extends PHPUnit_Framework_TestCase
{
    public function testOne()
    {
        $GLOBALS['count'] += 1;
        echo $GLOBALS['count'];
    }

    public function testTwo()
    {
        $GLOBALS['count'] += 2;
        echo $GLOBALS['count'];
    }
}

输出结果为 1 2 ,并不是想象中的 1 3,这是为什么呢?

Ubuntu Server 14.04 编译安装 PHP

每次编译安装 PHP 过程中出现了一些错误,都是直接谷歌相应的解决方案。刚才又在 Linode 上编译了一遍最新的 PHP,记录过程如下,以做备忘。

首先安装 build-essential 包:

apt-get install build-essential

进入 php 源码目录,我的 configure 命令选项如下:

./configure --with-bz2 --with-curl --with-jpeg-dir --with-gd --enable-shared --enable-mbstring --with-mcrypt --with-mysql=mysqlnd --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --enable-fpm --enable-phar --enable-bcmath --with-zlib --enable-zip --enable-ftp --with-gettext --enable-sockets --with-freetype-dir --with-fpm-user=www-data --with-fpm-group=www-data --with-config-file-path=/usr/local/etc/php/php.ini --with-config-file-scan-dir=/usr/local/etc/php/conf.d

配置过程中,会出现若干错误,提示各种头文件找不到,下面分别给出错误提示和对应的需要安装的包:

#Cannot find libz
apt-get install zlib1g-dev

#Please reinstall the BZip2 distribution
apt-get install libbz2-dev

#Please reinstall the libcurl distribution -
#    easy.h should be in <curl-dir>/include/curl/
apt-get install libcurl4-gnutls-dev

#jpeglib.h not found
apt-get install libjpeg-dev

#png.h not found
apt-get install libpng12-dev

#freetype-config not found
apt-get install libfreetype6-dev

#mcrypt.h not found. Please reinstall libmcrypt
apt-get install libmcrypt-dev

直到没有错误为止。最后:

make && make install

至此编译安装完成。

 

PHP 的 print_r 函数并不是仅能输出数组或对象

大家都知道 PHP 中,print_r 函数能够打印输出数组,有些公司还会这样的笔试题:print 和 print_r 的区别,一般的回答便是:print 输出字符串,print_r 输出数组。前两天 coding 时偶然发现给 print_r 传了字符串,竟然正常输出,没有报 warning 和 notice,我就有点奇怪了,print_r 不是只能输出数组和对象的吗,怎么传个字符串也正常输出了。于是看手册,手册说

print_r() displays information about a variable in a way that’s readable by humans. … If given a stringinteger or float, the value itself will be printed. If given an array, values will be presented in a format that shows keys and elements. Similar notation is used for objects.

并没有任何地方说明 print_r 只能用于输出数组和对象,对于普通的 string integer float 型参数直接输出,相当于 print。

结论?结论就是完全没有理由使用 print 这个函数了。