标签:PHP

为 YII2 GridView 的 ActionColumn 添加自定义链接

Yii2 的 GridView 可以解决后台展示绝大多数问题,其中的 ActionColumn 定制性也很强,官方文档没有说明如何操作,下面的代码可以做个参考。

以下代码为 ActionColumn 添加了 lock 的链接,并使用 glyhpicon-lock 作为图标

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        'id',
        'username',
        [
            'class' => 'yii\grid\ActionColumn',
            'template' => '{view} {update} {lock}', 
            'buttons' => [
                // The new action
                'lock' => function ($url, $model, $key) {
                    // Creates glyphicon, optional
                    $icon = Html::tag('span', '', ['class' => "glyphicon glyphicon-lock"]);
                    // Creates URL
                    $url = Url::to(['/admin/user/lock', 'id' => $model->id]);

                    return Html::a($icon, $url);
                }
            ],
        ],
    ],
]);

Yii2 自定义 RESTful API 返回格式

Yii 2 framework 提供了一套 REST API ,通过简单配置即可对外提供服务,支持 JSON、XML 多种格式选择输出;支持 HTTP Basic auth、OAuth 2 等认证方式,用起来确实很爽。

在定义 API 的时候,最常见的就是自定义返回格式,我找到 4 种方式,纪录如下:

第一种:来自官方文档,通过修改 config 文件

return [
    // ...
    'components' => [
        'response' => [
            'class' => 'yii\web\Response',
            'on beforeSend' => function ($event) {
                $response = $event->sender;
                if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) {
                    $response->data = [
                        'success' => $response->isSuccessful,
                        'data' => $response->data,
                    ];
                    $response->statusCode = 200;
                }
            },
        ],
    ],
];

第二种:使用 controller 中的 afterAction 方法,在响应完 action 之后,对数据格式化

use Yii;
class MobileController extends yii\rest\Controller
{
    public function afterAction($action, $result)
    {
        $rs = parent::afterAction($action, $result);
        // 也可以再定义 response
        // $response = Yii::$app->response;
        // $response->statusCode = 200;
        // $response->data = ['message' => 'hello world'];
        return ['data' => $rs, 'error' => '0'];
    }
}

第三种:自定义 Error handler ,来自 github

public function init()
{
    parent::init();

    $handler = new \app\components\ApiErrorHandler;
    \Yii::$app->set('errorHandler', $handler);
    $handler->register();
}

第四种:与第一种类似,在 controller 中绑定 response 的 beforeSend 事件,不同是它不是 global ,更灵活

public function init()
{
    parent::init();

    Event::on(Response::className(), Response::EVENT_BEFORE_SEND, [$this, 'formatDataBeforeSend']);
}

public function formatDataBeforeSend($event)
{
    $response = $event->sender;
    // do something
}

That’s all.
Fin.

composer升级后不能用,更新fxp插件后修复

今天把composer升级到 d401f6e95cca835d165c61648327fc1fa062ba31 版本之后,再安装其他包的时候,出现错误提示

[ReflectionException]
Class Fxp\Composer\AssetPlugin\Repository\NpmRepository does not exist

[ErrorException]
Declaration of Fxp\Composer\AssetPlugin\Repository\AbstractAssetsRepository::whatProvides() should be compatible with Composer\Repository\ComposerRepository::whatProvides(Composer\DependencyResolver\Pool $pool, $name, $bypassFilters = false)

显然是FXP包又出问题了,上次也出现过一次,也是这个插件的问题…… 单独把这个包升级一下吧,执行

composer global update fxp/composer-asset-plugin --no-plugins

OK,已经没问题了。

查了一下,果然是fxp的bug:https://github.com/composer/composer/issues/2661

composer果然是越用越喜欢,人都变懒了……

自定义Zend Framework模块的异常处理

Zend Framework(ZF)可以根据业务需求配置成多模块的方式,这时我们可能需要在不同模块中采用不同的方式来处理各种错误和异常。在处理错误的时候,可以使用_forward直接跳转和显示,但是如何处理异常呢?官方手册中对此没有明确的说明。经过一番测试之后,发现可以通过以插件的方式进行处理。

比如有一个名为API的module,需要在此模块下对异常进行单独处理。我们只要在application/bootstrap.php中添加一个初始化方法,将错误处理以插件的方式注册就可以了,内容见下:

<?php
class Api_Bootstrap extends Zend_Application_Module_Bootstrap
{
	/**
	 * 注册错误处理插件,使此Module的错误处理定向到apiModule/errorController/errorAction
	 * 
	 */
	protected function _initPluginErrorHandler()
	{
		$front = Zend_Controller_Front::getInstance();
		$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(array(
		    'module'     => 'api',
		    'controller' => 'error',
		    'action'     => 'error'
		)));
	}
	
}

此时,如果api模块下有任何异常抛出,都将使用modules/api/controllers/errorController/errorAction进行处理了~

判断PHP运行系统是32位还是64位

有些时候根据业务的需求会在32位和64位系统上采用不同的处理方式,在PHP中也有有很多方法可以实现,下面是我以前用到过的一种方式:

/**
 * 判断运行环境是否为64位系统
 *
 * @return bool
 */
function isOn64bitsSystem(){
    // 左移32位之后会超过32位系统的限制,如果64位系统则不会
    return (1<<32)==1 ? false : true;
}

更简单的方法,判断PHP_INT_SIZE:

echo PHP_INT_SIZE===8 ? 'on 64 bit System' : 'on 32 bit System';

记录Zend Framework运行时间

前几天看到有人问在Zend Framework中如何获取运行时间,如果只是用于debug,非常简单,我们只要在public/index.php中的第一行加入:

define('ZF_START_TIME', microtime(true));

最后一行加入:

echo number_format(microtime(true)-ZF_START_TIME, 8, '.', '');

这样,运行时间就会直接打印到页面的最下方。

当然,这会破坏框架的整体结构,但也是最直接,最简单的方式,debug么,嘿嘿。

F5旁路模式下nginx获取真实IP

在公司的网络中,使用F5工作在旁路模式,这样的好处是在不调整网络的基础架构上做负载均衡。但是在调整好网络之后,发现作为node(节点)的nginx已经不能正确获取到用户的IP地址,这导致之前使用nginx做的IP白名单策略失效。后来发现这是由于F5在旁路模式下将请求的头信息进行了修改,将源地址放到了X-Real-IP或X-Forwarded-For中。以这种情况来看,通过web应用程序应该可以很容易的获取这个地址,但这样就需要修改程序的业务逻辑,最好的办法还是让nginx支持读取真实IP。写到这里,回想起在使用HAProxy做LB的时候,需要配置类似于x-forwarded-for的选项了,所以,nginx还是有办法的。

nginx提供了这种获取方法,但是默认的编译参数中并没有支持,需要在编译的时候增加“–http-realip-module”作为参数。在重新编译完成之后,还需要对配置文件做一些修改,增加如下2项配置:

set_real_ip_from   192.168.160.8;
real_ip_header     X-Forwarded-For;

其中的192.168.160.8为F5的IP地址。OK,大功告成,再次查看access log,发现client IP地址已经生效,IP白名单也工作正常了。

在nginx的官方wiki中有关于“http realip module”的说明,文中提到:“如果nginx工作在一些7层负载均衡机之后,收到的请求来自本地IP(代理服务器),但是代理服务器又在头信息中添加了源IP,这时此模块就有用啦”,言外之意,如果有些L7的代理没有在头信息中增加源地址,这个模块也就没辙了。

PHP中兼容JAVA的DES加密

在项目中PHP与JAVA通信时需要使用DES进行数据加密,但PHP对字符串进行DES加密之后,使用JAVA无法解开,反之也是。这是因为PHP的DES加密与JAVA略有不同。JAVA在使用DES方式加密的时候,如果字符串长度不足将补位,但是PHP并未这样做。因此我们只要在PHP进行DES加密时进行补位处理,就解决了兼容性的问题。CBC模式下采用的是pkcs#5补位方式,就是差N位补几个0xN,比如说差3位,就补充3个0x03。在网上也看到文章说补00或FF的,但是测试结果不好用。

// 定义key与iv
define('DES_KEY', '12345678');
define('DES_VI', '12345678');
/**
* des加密:兼容java
*/
function des_encrypt_java($str)
{
	$blocksize = mcrypt_get_block_size(MCRYPT_DES, MCRYPT_MODE_CBC);
	// 计算相差位数,并补位
	$slice = $blocksize - (strlen($str) % $blocksize);
	$str .= str_repeat(chr($slice), $slice);
	return mcrypt_cbc(MCRYPT_DES, DES_KEY, $str, MCRYPT_ENCRYPT, DES_VI);
}
/**
* des解密:兼容java
*/
function des_decrypt_java($str)
{
	$str = mcrypt_cbc(MCRYPT_DES, DES_KEY, $str, MCRYPT_DECRYPT, DES_VI);
	// 解密的结果可能还含有补位数据,所以要把补位去掉
	$slice = ord($str{strlen($str) - 1});
	return substr($str, 0, -1*$slice);
}

PHP数组按键值大小排序

在PHP中,想要将数组按照键值大小进行排序,使用array_multisort很容易就可以完成。但是这个函数会打乱原有key值,比如下面这段程序:

$data[5] = array('volume' => 67, 'edition' => 2);
$data[4] = array('volume' => 86, 'edition' => 1);
$data[2] = array('volume' => 85, 'edition' => 6);
$data[3] = array('volume' => 98, 'edition' => 2);
$data[1] = array('volume' => 86, 'edition' => 6);
$data[6] = array('volume' => 67, 'edition' => 7);
// 准备要排序的数组
foreach ($data as $k => $v) {
    $edition[] = $v['edition'];
}
array_multisort($edition, SORT_ASC, $data);
print_r($data);

将输出:
(更多…)

Mac OSX上重新编译PHP后提示Syntax error的解决办法

Mac OS X 对于非开发人员提供了很好的体验,对于Web开发人员也很厚道。不但提供了Apache Web Server、FTP Server,还集成了很多开发环境:C、Python、PHP等等,而且版本比较新,扩展也很完善。可套餐终归是不如单点。在产品经理的各式各样需求前面,终于还是闹饥荒了。

不久之前由于需要PHP扩展的freetype模块,但是系统本身没有内建,所以只能自己动手。首先想到的是编译成动态扩展库的方式,按照装各种lib都很顺畅,也成功的编译gd.so文件。但是由于系统的PHP已经内建了gd,再load此文件会报错,google一番无解,所以只好放弃。重新编译吧,没有别的办法了。编译过程比较顺利,和Linux下过程一样,不在啰嗦了。但是重启Apache的时候出现下面的提示:

httpd: Syntax error on line 117 of /private/etc/apache2/httpd.conf: Cannot load /usr/libexec/apache2/libphp5.so into server: dlopen(/usr/libexec/apache2/libphp5.so, 10): Symbol not found: __cg_png_create_info_struct
Referenced from: /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/ImageIO
Expected in: /usr/lib/libpng15.15.dylib
in /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/ImageIObogon:php-5.3.6

(更多…)