解释:以下代码非我所写,我只是添加部分注释和说明,不要用来说事,若能帮助到,自己实现所需的功能就行了。
1. 说明
PHPExcel是一个由纯PHP写的函数库,从不同的电子表格中读取和写回数据。PHPExcel这个项目围绕微软的OpenXML和PHP标准创建,GitHub的源码托管地址。
网上有详细的设置教程,并且源代码中提供了很多的使用样例,几乎能用到的场景都有现成的例子。汉老师的yiifans也列有好几篇,我大体看过,都是很好的教程,对初学者都是不错的选择。
这里只提供一种简单粗暴的解决方案,目前也是我们正在使用的,没有对文件迭代一类的思路给予处理。
不推荐动不动就要把这些第三方扩展集成到框架中,直接使用最基本的引用就足够。网上看了一些成年旧帖,没准还让你怎么修改框架的配置文件,设置各种参数才达到简单的目的,浪费时间不说,还破坏了原本干净的框架,维护交接都是问题。
我们使用的是yii2高级模板,目录类似:
zoulu -
- backend
- common
----extend 第三方扩展存放文件夹
--------PHPExcel PHPExcel扩展文件夹
------------ PHPExcel 扩展内部代码,非必要时不推荐修改
------------ .readme 简单的说明文件
------------ PHPExcel.php 扩展入口文件 非必要不推荐修改
------------ Excel.php 自己封装的针对该扩展的调用类,以后对该扩展的引用全部通过该类来完成
- console
- frontend
- tests
- vendor
...
2. 封装
// 项目本身需要的命名空间
namespace common\extend\phpexcel;
// 在这里引入扩展入口文件
// @todo 不记得什么时候,网上还有教程说,为了防止扩展和框架的依赖注入冲突,使用了各种“骇客”手段
// 现在看来,技术的东西,会就简单,不会就稀里糊涂。
require 'PHPExcel.php';
class Excel extends \PHPExcel
{
// 单例
public static $instance;
public static function getInstance()
{
if (empty(self::$instance)) self::$instance = new self();
return self::$instance;
}
/**
* 获取Excel表单数据
* 说明:通常来说,数据量不是很大(多大算大?)按照这种方式一次性读入内存中是没什么关系的,
* 扩展提供了文件迭代器协议的方式循环读取,可参见开头给出的慕课网免费视频,几乎常用的都提到了
* @param int $inFile 读取文件路径
* @param bool $index 读取表格索引,默认读取所有数据 合并后返回
* @return array
*/
public function readSheet($inFile, $index = false)
{
// 得到文件类型
$type = \PHPExcel_IOFactory::identify($inFile);
$reader = \PHPExcel_IOFactory::createReader($type);
// 导入文件
$sheet = $reader->load($inFile);
// 获取文件中表格sheet的数量
$sCount = $sheet->getSheetCount();
// 如果指定了获取某一个子表格(表格中,可能有多个子表格)的数据,转化为数组返回
if (is_int($index) && $index < $sCount && $index >= 0) {
return $sheet->getSheet($index)->toArray();
}
// 只有一个子表格
if ($sCount == 1) {
return $sheet->getSheet(0)->toArray();
}
$data = [];
// 所有表格数据全部以数组格式返回
for ($i=0; $i < $sCount; $i++) {
$data[] = $sheet->getSheet($i)->toArray();
}
unset($sheet,$reader, $type);
return $data;
}
/**
* 将数据保存至Excel表格
* 说明:只是一个子表时请自行处理,道理类似
* @param string $outFile 要保存的文件路径
* @param array $data 需要保存的数据 二维数组
* @return bool
*/
public function saveSheet($outFile, array $data)
{
$path = explode('/',$outFile);
unset($path[count($path)-1]);
// DIRECTORY_SEPARATOR 常量是框架定义的目录分隔符,(服务器环境自知,可以不用这么麻烦)
$path = implode('/',$path) . DIRECTORY_SEPARATOR;
//目录不存在 则创建目录 需要父目录有写权限才可以创建子目录,
// Linux基础现在几乎都多多少少会了,不再是什么高深的知识
if (!file_exists($path)) {
@mkdir($path, 0777, TRUE);
@chmod($path, 0777);
}
// 实例化一个PHPExcel对象
$newExcel = new \PHPExcel();
// 得到一个默认的激活表格,预备写入数据
$newSheet = $newExcel->getActiveSheet();
$newSheet->fromArray($data);
// 格式按自己需要,源码文件样例中有写,(下面这个其实是excel2003的标准)
$objWriter = \PHPExcel_IOFactory::createWriter($newExcel, 'Excel5');
// 保存数据到表格中
$objWriter->save($outFile);
unset($objWriter,$newSheet, $newExcel);
return true;
}
/**
* @param array $data 需要过滤处理的数据 二维数组
* @param int $cols 取N列
* @param int $offset 排除 N 行,比如读取一个表格数据时,标题这一行可能是不希望读出来的,
* 毕竟这部分和存入数据库中没什么关系,就排除这一行
* @param bool|int $must 某列不可为空 0 - index
* @return array
*/
public function handleSheetArray(array $data, $cols = 10, $offset = 1, $must = false)
{
$final = [];
if ($must && $must >= $cols) {
$must = false;
}
foreach($data as $key => $row) {
if ($key < $offset) {
continue;
}
$t = [];
for ($i = 0; $i < $cols; $i++) {
if (isset($row[$i])) {
$t[$i] = trim(strval($row[$i]));
} else {
$t[$i] = '';
}
}
if (is_array($row) && implode('', $t) && ($must===false || $t[$must])) {
$final[] = $t;
continue;
}
}
return $final;
}
}
3. 导出数据
有这么一个场景,数据库中存有帖子信息,根据搜索条件搜索出一部分帖子,想把它导出到Excel表格中,来帅选…
namespace backend\modules\feed\controllers;
use yii\web\Controller;
class FeedController extends Controller
{
public function actionExport()
{
// 各种搜索条件过滤后得到一个相对较大的数组,数据太大不合适,上千条也能导出,但是如果
// 数据偏大,可能用浏览器触发来操作就不合适了,可以考虑写好后用控制台或者命令行导出
// 得到搜索结果集$list
// 这一行定义导出后的表格头部,相当于table 的 th ,标题需要设置,这里没有标题显得简单
$final = [['小区','帖子ID','用户昵称', '用户类型','用户ID','标签','内容','赞','评论','分享','收藏','发帖时间']];
foreach ($list as $feed) {
// 把需要处理的数据都处理一下
$communityName = '';
$nickname = '';
$userType = '';
$tagName = '';
$final[] = [
$communityName, $feed['feedid'], $nickname, $userType, $feed['userid'], $tagName,
$feed['content'], $feed['diggcount'], $feed['commentcount'], $feed['sharecount'],
$feed['favcount'], $feed['ctime'],
];
}
unset($list);
// 使用我们写好的saveSheet()方法导出数据
$outFile = \Yii::$app->getRuntimePath().'/fileIO/feed/' . date("YmdHis") . '.xls';
$ret = \common\extend\phpexcel\Excel::getInstance()->saveSheet($outFile, $final);
if ($ret) {
// 导出成功了,其它操作
}
// 看了代码其实都知道,这个是恒返回真,一般来说只要不是权限问题,即是你的数据不对,都会导出成功
}
}
4. 读入数据
有这么一个场景,客户需求提供一个文件上传功能,可以上传Excel表格,上传完成后希望看到上传的结果列表再执行其它操作。上传文件对服务器来说本身是很危险的,包括图片,因此可能需要做很多限制,以防发生危险。我也没什么好的处理方式,自己
namespace backend\modules\feed\controllers;
use yii\web\Controller;
class Article extends Controller
{
public function actionLoad($communityid)
{
// 其它逻辑
//保存sheet表记录数据
$data = [];
if (\Yii::$app->getRequest()->getIsPost() && $model->load(\Yii::$app->request->post())) {
$redis = \common\core\Redis::getInstance();
$lock = '';
if ($redis && $redis->get($lock)) {
\Yii::$app->getSession()->setFlash('error', '请不要频繁导入文件');
return $this->render();
} else {
$redis->set($lock, 1, 30);
}
//保存临时文件,建议保存一份文件下来,以免出错时手足无措,保存后查问题时还有补救的可能
$inFile = \yii\web\UploadedFile::getInstance($model, 'inFile');
if (!$inFile->name || (strpos($inFile->type,'excel') === false && strpos($inFile->type, 'sheet') === false)) {
\Yii::$app->getSession()->setFlash('error', '无法获取上传文件,请重试或者检查文件格式是否正确');
return $this->render('');
}
$fileName = date("Hi-") . $inFile->name;
// 自定义方法保存文件
$tempFile = \common\core\File::getInstance()->saveTempFile($fileName, $inFile);
//排除标题行数
$number = $_POST['num'];
//限制文件大小在2M以内,以小值为界
$size = 2 * 1000 * 1000;
if (!$inFile->size || $inFile->size > $size) {
\Yii::$app->getSession()->setFlash('error', '上传文件请控制在2M以内.');
return $this->render('');
}
// 从excel中读取所有信息
// 这里说明一下,我们踩过的坑,对时间格式,正常的2016-03-03是几乎没有问题的,可就是有人要输入03/03/16等一系列时间格式
// 请当心读入会出错,如果你也遇到,再说。毕竟总有那么一些人,就是不按常理出牌,谁让我们是程序员,在别人眼里,我们就是来解决
// 变态问题的,^~^
$result = \common\extend\phpexcel\Excel::getInstance()->readSheet($tempFile, 0);
// 针对读完的数组$result 取前8列,排除$number行
$newResult = \common\extend\phpexcel\Excel::getInstance()->handleSheetArray($result, 8, $number);
$now = time();
if (!empty($newResult)) {
$count = count($newResult);
$t_mobiles = [];
foreach ($newResult as $key => $value) {
if (empty($value)) {
continue;
}
$index = 0;
//顺序为姓名-手机号-类别-有效期-区-楼-单元-门牌号-小区-状态为临时数据-创建时间-更新时间-备注-来源物业-操作员
foreach ($value as $k => $val) {
// 各种变态判断,注意这里读到的数据是按表格顺序,索引数组保存的就好
// 同样保存成索引数组,按数据库表字段保存好,这里推荐使用框架的批量插入batchInsert()来操作,速度会好一些。
$data = []; //
}
// 哈哈我们自定义的批量操作,一次处理500条,非常快,就是批量操作不好记录日志,一旦出错,几乎无法查找原因
$total = $this->generate($data);
}
// 其它逻辑
}
return $this->render('');
}
}
5. 总结
简单的导入导出,确实没什么操作,复杂的和大量的导出,需要使用任务日志,命令行等来操作,否则浏览器可能存在超时等原因,影响导出。
2016-03-03 朝阳区定福庄西街