标签 yii 下的文章

今天遇到一个神奇的现象

项目使用yii1.1版本,遇到莫名的异常结束,200状态,内容为空

报表页面,ajax请求数据,时而正常返回数据,时而异常

无任何报错信息,php-fpm也无报错,PHP 7.0版本,PHP报错级别已设置为E_ALL,各种方法都尝试了,找不到原因

本地同请求测试一切正常,生产环境异常

一天时间已经过去,无解

第2天,决定在代码中打断点试的,一步一步排查

最终确定原因:

在报表数据查询后处理过程中,使用了4层循环,加上数据查了4个月的日报表,数据量并不高

循环中,把PHP设置的内存用尽,导致异常结束

配置为128M,代码中没有修改内存大小

解决方法:在代码前使用 ini_set('memory_limit', '256M'); 设置内存解决;

难排查的原因在于,找不到任何报错信息。。。。。

对了,报错信息是用Yii::log()方法输出到文件中,tail -f 查看文件的内容来找,可能是这个原因,由于PHP内存已经用尽,后面代码执行不了,所以没有输出内容到文件?(还有个疑点,就是控制器都没执行?这不应该啊)

记录一下

注意这里使用的是yii 1.1版本
有时候我们要批量写入数据,同时还要去重复,如下的方法可能有用(数据库设置了唯一索引)

$this->db = new CDbConnection('mysql:host=dev.database.local;port=3306;dbname=xxx', 'xxx', 'xxx');

$data = [
    ['areaId'=>1, 'title'=>'text1'],
    ['areaId'=>2, 'title'=>'text2'],
    ['areaId'=>3, 'title'=>'text3'],
];

$builder = $this->db->schema->getCommandBuilder();
$command = $builder->createMultipleInsertCommand("{{ztags}}", $data);
$sql = $command->getText();
$sql = str_replace('INSERT INTO', 'INSERT IGNORE INTO', $sql);

$params = [];
foreach ($data as $i=>$d) {
    foreach ($d as $k=>$v) {
        $params[":{$k}_{$i}"] = $v;
    }
}

$this->db->createCommand($sql)->execute($params);

文档参考:

  1. https://www.yiiframework.com/doc/api/1.1/CDbCommandBuilder#createMultipleInsertCommand-detail
  2. https://www.yiiframework.com/doc/api/1.1/CDbCommand#getText-detail

$builder = Yii::app()->db->schema->commandBuilder; // 创建builder对象
$command = $builder->createMultipleInsertCommand('{{umeng_message}}', array( // umeng_message为数据库表
    array(// 格式为:'字段' => '值', 不包括主键ID,一个array为一条记录
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
    array(
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
));
$command->execute(); // 执行成功返回true

扩展文件名: CDbBICommand.php ,将文件放于 \protected\components 目录下,确定项目会自动引入这个目录的文件,然后用法:
StatVisitor 是 CActiveRecord 类的数据表模型

$bi = new CDbBICommand(new StatVisitor());
$bi->batchInsert([['name'=>123],['name'=>234]]);

扩展文件代码如下:

<?php

/**
 * class for sql batch insert
 * eg:
 * $bi = new CDbBICommand(new StatVisitor());
 * $bi->batchInsert([['name'=>123],['name'=>234]]);
 */
class CDbBICommand {

    /**
     * CDbBICommand constructor.
     * @param CActiveRecord $acModel
     */
    public function __construct($acModel) {
        $this->ac = $acModel;
        $this->tablename = $this->ac->tableName();
        $this->db = $this->ac->getDbConnection();
        $this->command = $this->db->createCommand();
    }

    public function batchInsert($array_columns) {
        $sql    = '';
        $params = array();
        $i      = 0;
        foreach ($array_columns as $columns) {
            $names        = array();
            $placeholders = array();
            foreach ($columns as $name => $value) {
                if (!$i) {
                    $names[] = $this->db->quoteColumnName($name);
                }
                if ($value instanceof CDbExpression) {
                    $placeholders[] = $value->expression;
                    foreach ($value->params as $n => $v) {
                        $params[$n] = $v;
                    }
                } else {
                    $placeholders[]           = ':' . $name . $i;
                    $params[':' . $name . $i] = $value;
                }
            }
            if (!$i) {
                $sql = 'INSERT INTO ' . $this->db->quoteTableName($this->tablename)
                       . ' (' . implode(', ', $names) . ') VALUES ('
                       . implode(', ', $placeholders) . ')';
            } else {
                $sql .= ',(' . implode(', ', $placeholders) . ')';
            }
            $i++;
        }
        return $this->command->setText($sql)->execute($params);
    }
}

框架自带的批量插入方法,很少有网络资料:

$builder = Yii::app()->db->schema->commandBuilder; // 创建builder对象
$command = $builder->createMultipleInsertCommand('{{umeng_message}}', array( // umeng_message为数据库表
    array(// 格式为:'字段' => '值', 不包括主键ID,一个array为一条记录
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
    array(
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
));
$command->execute(); // 执行成功返回true

下载框架:https://github.com/yiichina/yii

创建一个新的空白应用(cmd 命令):

framework/yiic [appname] [path/to]
  • 入口配置:protected/config/main.php
'components'=>[
    //伪静态配置
    'urlManager'=>array(
            'urlFormat'=>'path',
            'rules'=>array(
                '<controller:\w+>/<id:\d+>'=>'<controller>/view',
                '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
                '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
            ),
        ),
]
  • 伪静态:.htaccess
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(.*)\.(css|jpg|js|gif|png|bmp)/
RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
</IfModule>
//获取配置文件main中的配置值和应用组件
Yii::app()->basePath;
Yii::app()->name;
Yii::app()->params->adminEmail;
//组件配置在main中的components键中
Yii::app()->db;
Yii::app()->cache;
Yii::app()->user;
  • 可以在controller 目录创建 actions (可自定义)目录用于存放action类,达到action复用的目的:
//protected/controllers/post/UpdateAction.php
class UpdateAction extends CAction {
    public function run() {
        // place the action logic here
    }
}

//protected/controllers/ArticleController.php
class ArticleController extends Controller {

    public function actions() {
        return [
            'edit'=>'application.controllers.post.UpdateAction'
        ];
    }
}
  • 可以在action方法中加入参数,可直接获取到get来的参数:
public function actionView($id, $act, $name='joyber') {
    // main中的路由配置中的$id动态参数
    echo $id;
    // $_GET['act']
    echo $act;
    // $_GET['name'] 不存在name参数时默认值:joyber
    echo $name;
    // post xxy
    $xxy = Yii::app()->request->getPost('xxy');
}
  • widget 自定义组件 可以放在components 目录 视图中应用:<?php $this->widget('MyWidget', ['data'=>123]) ?>
//components/MyWidget.php
class MyWidget extends CWidget {

    public $btn;

    public function init() {

        // 此方法会被 CController::beginWidget() 调用
    }

    public function run() {
        // 此方法会被 CController::endWidget() 调用
        parent::run(); // TODO: Change the autogenerated stub
        $this->render('my', ['btn'=>$this->btn, 'name'=>'my test widget']);
    }

}

//view file: components/views/my.php
<input type="text" value="<?=$name?>">
<button><?=$btn?></button>