标签 php 下的文章

今天遇到一个神奇的现象

项目使用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内存已经用尽,后面代码执行不了,所以没有输出内容到文件?(还有个疑点,就是控制器都没执行?这不应该啊)

记录一下

先引入类IOFactory.php

require_once '../PHPExcel/IOFactory.php';
$filePath = "test.xlsx";  // 测试文件

加载测试文件

$inputFileType = PHPExcel_IOFactory::identify($filePath) 判断文件类型
$objReader = PHPExcel_IOFactory::createReader($inputFileType); 实例化类型对象
$objPHPExcel = $objReader->load($filePath); 加载文件

下面主要判断Excel2007和Excel5类型,即xlsx/xlsm/xltx/xltm和xls/xlt格式文件

try {
    $inputFileType = PHPExcel_IOFactory::identify($filePath);
    if ($inputFileType !== "Excel5" && $inputFileType !== "Excel2007" ) {
        unlink($filePath) && str_alert(-1,"请确保导入的文件格式正确!");
    }
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objPHPExcel = $objReader->load($filePath);
} catch(Exception $e) {
    unlink($filePath) && str_alert(-1,'加载文件发生错误:”'.pathinfo($filePath,PATHINFO_BASENAME).'”: '.$e->getMessage());
    }

获取当前工作表

$sheet = $objPHPExcel->getSheet(0);
//或者
$sheet = $objPHPExcel->getActiveSheet();

获取工作表行数和列数

$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();

注意:有时候你会发现你的表格明明有内容的行数就5行,但是获取到的$highestRow却有7,8行或者更多,这可能是因为你在操作你的表格的时候不小心点击了其它行数,虽然没有填写内容,但getHighestRow也是能够识别出行数;
想要获取有内容的行数应该使用getHighestDataRow和getHighestDataColumn
源码注释是这样介绍的string Highest row number that contains data,即包含数据的字符串最高行数.

单元格具体内容
xlsx类型的表格单元格是通过类似xy轴坐标来获取的,

可通过类似

 $sheet->getCell("A1")->getValue();
 $sheet->getCell("B2")->getValue();

获取相应位置的内容,
如果不想通过字母了来遍历获取,可以用数字索引方法

  $sheet->getCellByColumnAndRow(0,1);
  $sheet->getCellByColumnAndRow(1,2);

注意坐标中第一个参数从0开始,0代表A,1代表B...,第二个参数从1开始.
下面是遍历表格获取全部单元格内容:

$dataSet=array();
for ($column = "A"; $column <= $highestColumn; $column++) {//列数是以A列开始
    for ($row = 4; $row <= $highestRow; $row++) { //行数是以第4行开始
        $cell = $sheet->getCell($column . $row)->getValue();
        if($cell instanceof PHPExcel_RichText) { //富文本转换字符串
            $cell = $cell->__toString();
        }
        $dataSet[$row][] = $cell;
    }
}

其中富文本转换字符串,是使用$cell instanceof PHPExcel_RichText判断是否为富文本,查阅资料发现如果你的单元格中字符串包含两种以上的字体会自动被设为富文本,这时候需要__toString()转换

判断合并单元格是否位于最左上角
当我们循环输出所有单元格后发现,一些被合并的单元格只有最左上坐标的是有内容的,其他都是null
例如A4,A5合并成一个单元格,getCell("A4")是有正常内容的,但是getCell("A5")是null.
isMergeRangeValueCell可以用来判断某个具体的单元格是否为最左上角

$sheet->getCell('A' . $row)->isMergeRangeValueCell()

当$row为4的时候是返回true,5的时候返回false

转换时间
获取表格中时间格式的内容,需要PHPExcel_Shared_Date::ExcelToPHP()来转换为php可识别的时间格式

date('Y-m-d',PHPExcel_Shared_Date::ExcelToPHP($sometime);

有时候我们需要在后台配置某个时间段做什么事情,那么人性化的配置一般是: 21:30-8:30 这样的配置,那么我们使用的时候怎么来判断当前的时间是否在这个时间段内呢

下面是我的一段参考代码,仅作参考:


$mp_lyEnable = '21-8:30'; //后台设置的时间段
$lyEnable = false; //客服是否在离线时段
if ($mp_lyEnable) {
    $mp_lyEnable = explode('-', $mp_lyEnable);
    if (count($mp_lyEnable) == 2) {
         @list($startHour, $startMinute) = explode(':', $mp_lyEnable[0]);
         @list($endHour, $endMinute) = explode(':', $mp_lyEnable[1]);
         if (is_null($startMinute)) $startMinute = 0;
         if (is_null($endMinute)) $endMinute = 59;
         $startTime = strtotime(sprintf(date('Y-m-d %\d:%\d:00'), $startHour, $startMinute));
         $endTime = strtotime(sprintf(date('Y-m-d %\d:%\d:00'), $endHour, $endMinute));
         if ($endHour < $startHour) {
             //跨天
             $endTime += 86400;
         }
         $now = time();
         if ($now > $startTime && $now < $endTime) $lyEnable = true;
         var_dump(date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', $endTime));
    }
}

中文乱码问题
  1、下载类库语言安装脚本
    https://github.com/dompdf/utils.git

  2、将里面的load_font.php文件拷贝到dompdf根目录,和autoload.inc.php同级
3、下载一个宋体字体 simsun.ttf,也放到项目根目录,这时load_font.php和simsun.ttf应该在同一级目录
4、在根目录执行命令安装字体

php load_font.php simsun.ttf simsun

 命令说明 php load_font.php 调用这个文件,并传入字体路径,字体名字
  注意这一步,最后的simsun最好不要添加引号,看有一些例子写了引号,导致后面声明字体使用不上。
进入到这个目录:C:wamp64wwwtp5vendordompdfdompdflibfonts
    找到这个文件:dompdf_font_family_cache.php
    看下是否有以下这段,没有就添加上,有就最好。

中文换行问题

1、寻找到Text.php文件
    我的目录:C:wamp64wwwtp5vendordompdfdompdfsrcFrameReflowerText.php

  2、找到相关代码

// split the text into words
    $words = preg_split('/([\s-]+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
    $wc = count($words);

  3、替换以上代码,或者注释掉以上代码,添加我们的代码。

    preg_match_all("/./u", $text, $array);
    $words = array(0);
    $wc = count($words);

  4、小提示,可能因为版本的变迁,文件、代码位置不一样,大家最好使用目录全文搜索那一段代码,而不是直接去找那个文件。

页面设置问题

 1、设置页面

  //网上你可能看到很多这样的例子,我要竖着的看啊,坑。
    $dompdf->setPaper('A4', 'landscape');
//找了类库说明,发现还有一个参数,这下终于可以正常显示了。
    $dompdf->setPaper('A4', 'portrait');

  2、为了更好将HTML转换成PDF,建议将HTML宽高设置成和A4大小接近,避免不必要的麻烦。个人还是要根据实际pdf显示情况来调节。

body {
    margin: 0;
    width: 820px;
       height: 1160px;
}

表格table向下合并td后,再分页情况下第2页有接着分页就会乱了
这是由于使用了rowspan>1 的话,那下边的相应的行就必然少了一些td,如果分页dompdf就没有加以处理,导致下一页少了td造成,那我们就应该换一种思路,不要使用rowspan,可以通过边框border样式来达到合并格子的需求,只不过无法实现垂直居中了,不过效果还成:

.pure-table {
    border-collapse: collapse;
    border-spacing: 0;
    empty-cells: show;
    border-right: 1px solid #cbcbcb;
    border-bottom: 1px solid #cbcbcb;
}
.pure-table td,.pure-table th{
    border-left: 1px solid #cbcbcb;
    border-top: 1px solid #cbcbcb;
}


        <table class="pure-table pure-table-bordered pact-but" style="width: 800px;">
            <thead>
            <tr style="background-color: #ffff00;">
                <th>分区</th>
                <th>名称</th>
                <th>数量</th>
                <th>单位</th>
                <th>备注</th>
            </tr>
            </thead>
            <tbody>
            <?php foreach ($hetong->fields['supplies'] as $zone => $items) :
                foreach ($items as $k => $item) :
                    $_desc = zmf::mbChunkString($item['desc'], 20);
                    ?>
                    <tr>

                        <?php if ($k == 0) : ?>
                        <td>
                            <span><?= $item['zone'] ?></span>
                        </td>
                        <?php else: ?>
                        <td style="border-top: 0;">&nbsp;</td>
                        <?php endif ?>
                        <td><?= $item['title'] ?></td>
                        <td><?= $item['num'] ?></td>
                        <td><?= $item['unit'] ?></td>
                        <td>
                            <?php foreach ($_desc as $v):?>
                                <span style="display: block;"><?= $v ?></span>
                            <?php endforeach; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
            <?php endforeach; ?>
            </tbody>
        </table>