Passed
Push — master ( 77f770...403ea5 )
by ma
02:28
created

TWorkSheet::excelSetCellValue()   D

Complexity

Conditions 19
Paths 128

Size

Total Lines 60
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 19
eloc 42
c 2
b 0
f 0
nc 128
nop 1
dl 0
loc 60
rs 4.2833

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @name: TWorklSheet
4
 * @author: JiaMeng <[email protected]>
5
 * @file: Export.php
6
 * @Date: 2024/03/04 10:15
7
 */
8
namespace tinymeng\spreadsheet\Excel;
9
10
use PhpOffice\PhpSpreadsheet\Cell\DataType;
11
use tinymeng\spreadsheet\Util\ConstCode;
12
use tinymeng\tools\exception\StatusCode;
13
use tinymeng\tools\exception\TinymengException;
14
15
trait TWorkSheet{
16
17
    /**
18
     * sheet名称
19
     * @var
20
     */
21
    private $sheetName;
22
    /**
23
     * 查询数据
24
     * @var
25
     */
26
    private $data;
27
28
    /**
29
     * 定义默认列数
30
     * @var int
31
     */
32
    private $_col = 0;
33
    /**
34
     * 定义当前行数
35
     * @var int
36
     */
37
    private $_row = 1;
38
    /**
39
     * 定义所有字段
40
     * @var array
41
     */
42
    private $field = [];
43
44
    /**
45
     * 文件信息
46
     * @var array
47
     */
48
    private $fileTitle=[];
49
50
51
    /**
52
     * 左侧分组字段
53
     * @var array
54
     */
55
    private $group_left = [];
56
57
58
    /**
59
     * 获取sheet表格数目
60
     * @var
61
     */
62
    private $sheetCount = 1;
63
64
    /**
65
     * 字段映射方式
66
     * @var int
67
     */
68
    private $fieldMappingMethod = ConstCode::FIELD_MAPPING_METHOD_FIELD_CORRESPONDING_NAME;
69
70
    /**
71
     * 需要自动合并的字段
72
     * @var array
73
     */
74
    private $mergeColumns = [];
75
76
    /**
77
     * 小计行样式
78
     * @var array
79
     */
80
    private $subtotalStyle = [];
81
    /**
82
     * 全表样式
83
     * @var array
84
     */
85
    private $sheetStyle = [];
86
87
    /**
88
     * 用户自定义表格操作回调
89
     * @var callable|null
90
     */
91
    private $complexFormatCallback = null;
92
    /**
93
     * @var array
94
     */
95
    private $titleConfig = [];
96
97
    /**
98
     * @param $data
99
     * @return $this
100
     */
101
    public function setData($data){
102
        $this->data = $data;
103
        return $this;
104
    }
105
106
    /**
107
     * @param $data
108
     * @return $this
109
     */
110
    public function getData(){
111
        return $this->data;
112
    }
113
114
115
    /**
116
     * @return void
117
     * @throws \PhpOffice\PhpSpreadsheet\Exception
118
     */
119
    public function initWorkSheet()
120
    {
121
        $this->_col = 0;
122
        $this->_row = 1;
123
        $this->fileTitle = [];
124
        $this->data = [];
125
        $this->field = [];
126
        if($this->freezePane) $this->workSheet->freezePane($this->freezePane); //冻结窗格
127
    }
128
129
    /**
130
     * @param $fileTitle
131
     * @param $data
132
     * @return $this
133
     * @throws TinymengException
134
     * @throws \PhpOffice\PhpSpreadsheet\Exception
135
     */
136
    public function setWorkSheetData($titleConfig,$data)
137
    {
138
        $this->titleConfig = $titleConfig;
139
        if(isset($titleConfig['title_row']) || isset($titleConfig['group_left'])){
140
            /**
141
             * $titleConfig = [
142
             *       'title_row'=>1,
143
             *       'group_left'=>[],
144
             *       'title'=>[
145
             *           '姓名'=>'name'
146
             *       ],
147
             *  ];
148
             */
149
            $this->title_row = $titleConfig['title_row']??1;
0 ignored issues
show
Bug Best Practice introduced by
The property title_row does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
150
            $this->group_left = $titleConfig['group_left']??[];
151
            $titleData = $titleConfig['title']??[];
152
            // 新增:读取mergeColumns配置
153
            if (isset($titleConfig['mergeColumns'])) {
154
                $this->mergeColumns = $titleConfig['mergeColumns'];
155
            }
156
        }else{
157
            /**
158
             *  $titleConfig = [
159
             *       '姓名'=>'name',
160
             *  ];
161
             */
162
            $titleData = $titleConfig;
163
        }
164
        // 根据字段映射方式处理 title
165
        if ($this->fieldMappingMethod === ConstCode::FIELD_MAPPING_METHOD_FIELD_CORRESPONDING_NAME) {
166
            $this->fileTitle = array_flip($titleData);// 字段对应名称方式 - 需要将键值对调
167
        }else{
168
            $this->fileTitle = $titleData;// 名称对应字段方式 - 保持原样
169
        }
170
        $this->data = $data;
171
172
        /** 设置第一行格式 */
173
        if(!empty($this->mainTitle)){
174
            $this->excelHeader();
175
            $this->_row ++;//当前行数
176
        }
177
178
        /** 设置表头 **/
179
        $this->excelTitle();
180
181
        /** 获取列表里所有字段 **/
182
        foreach ($this->fileTitle as $key => $val){
183
            if(is_array($val)){
184
                foreach ($val as $k => $v){
185
                    $this->field[] = $v;
186
                }
187
            }else{
188
                $this->field[] = $val;
189
            }
190
        }
191
        /** 查询结果赋值 **/
192
        if(!empty($this->data)){
193
            $this->excelSetValue();
194
        }
195
        // 新增:应用全表样式
196
        $this->applySheetStyle();
197
        // 新增:调用自定义表格操作回调
198
        if (is_callable($this->complexFormatCallback)) {
199
            call_user_func($this->complexFormatCallback, $this->workSheet);
0 ignored issues
show
Bug introduced by
It seems like $this->complexFormatCallback can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
            call_user_func(/** @scrutinizer ignore-type */ $this->complexFormatCallback, $this->workSheet);
Loading history...
200
        }
201
        return $this;
202
    }
203
204
    /**
205
     * excelSetValue
206
     * @author: Tinymeng <[email protected]>
207
     * @time: 2022/2/22 11:43
208
     */
209
    public function excelSetValue(){
210
        if(!empty($this->titleConfig['data_start_row'])){
211
            $this->_row = $this->titleConfig['data_start_row'];
212
        }
213
214
        if(empty($this->group_left)){ //判断左侧是否分组
215
            $rowStart = $this->_row;
216
            foreach ($this->data as $key => $val){
217
                $this->excelSetCellValue($val);
218
            }
219
            // 新增:处理mergeColumns自动合并
220
            if (!empty($this->mergeColumns)) {
221
                $this->autoMergeColumns($rowStart, $this->_row - 1);
222
            }
223
        }else{   //根据设置分组字段进行分组
224
            /** 数据分组 **/
225
            $data = [];
226
            $group_left_count = count($this->group_left);
227
            if($group_left_count == 1){
228
                foreach ($this->data as $k => $v){
229
                    $data[$v[$this->group_left[0]]][] = $v;
230
                }
231
                foreach ($data as $k =>$v){
232
                    $data[$k] = [
233
                        'data' => $v,
234
                        'count' => count($v)
235
                    ];
236
                }
237
                $this->excelGroupLeft($data, $group_left_count);
238
            }elseif($group_left_count == 2){
239
                foreach ($this->data as $v) {
240
                    $data[$v[$this->group_left[0]]][$v[$this->group_left[1]]][] = $v;
241
                }
242
                $this->data = $this->arrayCount($data);
243
                $this->excelGroupLeft($this->data, $group_left_count);
244
            }else{
245
                throw new TinymengException(StatusCode::COMMON_PARAM_INVALID,
246
                    '左侧分组过多,导出失败!'
247
                );
248
            }
249
        }
250
    }
251
252
    /**
253
     * @return void
254
     * @throws \PhpOffice\PhpSpreadsheet\Exception
255
     */
256
    public function excelHeader(){
257
        $row = 1;
258
        if(!empty($this->mainTitle)){
259
            $this->workSheet->setCellValue('A'.$row, $this->mainTitle);
260
        }
261
262
        // 计算实际的标题列数
263
        $titleCount = 0;
264
        foreach ($this->fileTitle as $val) {
265
            if (is_array($val)) {
266
                $titleCount += count($val); // 如果是数组,加上子项的数量
267
            } else {
268
                $titleCount++; // 如果是单个标题,加1
269
            }
270
        }
271
272
        // 使用实际的标题列数来合并单元格
273
        $this->workSheet->mergeCells('A'.$row.':'.$this->cellName($titleCount-1).$row);
0 ignored issues
show
Bug introduced by
It seems like cellName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
        $this->workSheet->mergeCells('A'.$row.':'.$this->/** @scrutinizer ignore-call */ cellName($titleCount-1).$row);
Loading history...
274
    }
275
276
    /**
277
     * @return void
278
     * @throws \PhpOffice\PhpSpreadsheet\Exception
279
     */
280
    private function excelTitle(){
281
        if(isset($this->titleConfig['title_show']) && $this->titleConfig['title_show']===false){
282
            return;
283
        }
284
        if(!empty($this->titleConfig['title_start_row'])){
285
            $this->_row = $this->titleConfig['title_start_row'];
286
        }
287
288
        $_merge = $this->cellName($this->_col);
289
        foreach ($this->fileTitle as $key => $val) {
290
            if(!empty($this->titleHeight)) {
291
                $this->workSheet->getRowDimension($this->_col)->setRowHeight($this->titleHeight);//行高度
292
            }
293
            $rowName = $this->cellName($this->_col);
294
            $this->workSheet->getStyle($rowName . $this->_row)->getAlignment()->setWrapText(true);//自动换行
295
            if (is_array($val)) {
296
                $num = 1;
297
                $_cols = $this->_col;
298
                foreach ($val as $k => $v) {
299
                    $this->workSheet->setCellValue($this->cellName($_cols) . ($this->_row+1), $k);
300
                    if(!empty($this->titleWidth)) {
301
                        $this->workSheet->getColumnDimension($this->cellName($_cols))->setWidth($this->titleWidth); //列宽度
302
                    }else{
303
                        $this->workSheet->getColumnDimension($this->cellName($_cols))->setAutoSize(true); //自动计算宽度
304
                    }
305
                    if ($num < count($val)) {
306
                        $this->_col++;
307
                        $num++;
308
                    }
309
                    $_cols++;
310
                }
311
                $this->workSheet->mergeCells($_merge . $this->_row.':' . $this->cellName($this->_col) .$this->_row);
312
                $this->workSheet->setCellValue($_merge . $this->_row, $key);//设置值
313
            } else {
314
                if ($this->title_row != 1) {
315
                    $this->workSheet->mergeCells($rowName . $this->_row.':' . $rowName . ($this->_row + $this->title_row - 1));
316
                }
317
                $this->workSheet->setCellValue($rowName . $this->_row, $key);//设置值
318
                if(!empty($this->titleWidth)){
319
                    $this->workSheet->getColumnDimension($rowName)->setWidth($this->titleWidth); //列宽度
320
                }else{
321
                    $this->workSheet->getColumnDimension($rowName)->setAutoSize(true); //自动计算宽度
322
                }
323
            }
324
            $this->_col++;
325
            $_merge = $this->cellName($this->_col);
326
        }
327
        $this->_row += $this->title_row;//当前行数
328
    }
329
330
    /**
331
     * excel单元格赋值
332
     * @author tinymeng
333
     * @param array $val 数据
334
     */
335
    private function excelSetCellValue($val)
336
    {
337
        //设置单元格行高
338
        if(!empty($this->height)){
339
            $this->workSheet->getRowDimension($this->_row)->setRowHeight($this->height);
340
        }
341
        $_lie = 0;
342
        foreach ($this->field as $v){
343
            $rowName = $this->cellName($_lie);
344
345
            if(strpos($v,'.') !== false){
346
                $v = explode('.',$v);
347
                $content = $val;
348
                for ($i=0;$i<count($v);$i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
349
                    $content = $content[$v[$i]]??'';
350
                }
351
            }elseif($v == '_id'){
352
                $content = $this->_row-$this->title_row;//自增序号列
353
            }else{
354
                $content = ($val[$v]??'');
355
            }
356
            if(is_array($content) && isset($content['type']) && isset($content['content'])){
357
                if($content['type'] == 'image'){
358
                    $path = $this->verifyFile($content['content']);
0 ignored issues
show
Bug introduced by
It seems like verifyFile() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

358
                    /** @scrutinizer ignore-call */ 
359
                    $path = $this->verifyFile($content['content']);
Loading history...
359
                    $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
360
                    $drawing->setPath($path);
361
                    if(!empty($content['height'])) {
362
                        $drawing->setHeight($content['height']);
363
                    }
364
                    if(!empty($content['width'])) {
365
                        $drawing->setWidth($content['width']);//只设置高,宽会自适应,如果设置宽后,高则失效
366
                    }
367
                    if(!empty($content['offsetX'])) {
368
                        $drawing->setOffsetX($content['offsetX']);//设置X方向偏移量
369
                    }
370
                    if(!empty($content['offsetY'])) {
371
                        $drawing->setOffsetY($content['offsetY']);//设置Y方向偏移量
372
                    }
373
374
                    $drawing->setCoordinates($rowName.$this->_row);
375
                    $drawing->setWorksheet($this->workSheet);
376
                }
377
            }elseif(is_array($content) && isset($content['formula'])){
378
                // 新增:支持 ['formula' => '公式'] 写法
379
                $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content['formula'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA);
380
            }else {
381
                $content = $this->formatValue($content);//格式化数据
0 ignored issues
show
Bug introduced by
It seems like formatValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

381
                /** @scrutinizer ignore-call */ 
382
                $content = $this->formatValue($content);//格式化数据
Loading history...
382
                if (is_numeric($content)){
383
                    if($this->autoDataType && strlen($content)<11){
384
                        $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_NUMERIC);
385
                    }else{
386
                        $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2);
387
                    }
388
                }else{
389
                    $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2);
390
                }
391
            }
392
            $_lie ++;
393
        }
394
        $this->_row ++;
395
    }
396
397
    /**
398
     * 单元格合并并赋值
399
     * @param array $data 数据
400
     * @param $group_left_count
401
     * @author tinymeng
402
     */
403
    private function excelGroupLeft(array $data, $group_left_count)
404
    {
405
        // 获取分组字段在field中的实际位置
406
        $group_field_positions = [];
407
        foreach($this->group_left as $group_field){
408
            $position = array_search($group_field, $this->field);
409
            if($position !== false){
410
                $group_field_positions[] = $position;
411
            }
412
        }
413
414
        if(empty($group_field_positions)){
415
            throw new TinymengException(StatusCode::COMMON_PARAM_INVALID, '分组字段未在标题中定义');
416
        }
417
418
        $group_start = $this->_row;
419
        foreach ($data as $key => $val){
420
            // 第一级分组的合并单元格
421
            $rowName = $this->cellName($group_field_positions[0]); // 使用第一个分组字段的实际位置
422
            $coordinate = $rowName.$this->_row.':'.$rowName.($this->_row+$val['count']-1);
423
            $this->workSheet->mergeCells($coordinate);
424
            $this->workSheet->setCellValue($rowName.$this->_row, $key);
425
426
            // 新增:合并mergeColumns指定的其它列
427
            if (!empty($this->mergeColumns)) {
428
                foreach ($this->mergeColumns as $field) {
429
                    // 跳过分组字段本身
430
                    if (in_array($field, $this->group_left)) continue;
431
                    $colIdx = array_search($field, $this->field);
432
                    if ($colIdx !== false) {
433
                        $colLetter = $this->cellName($colIdx);
434
                        $this->workSheet->mergeCells($colLetter.$this->_row.':'.$colLetter.($this->_row+$val['count']-1));
435
                        // 取本组第一个数据的值
436
                        $this->workSheet->setCellValue($colLetter.$this->_row, $val['data'][0][$field] ?? '');
437
                    }
438
                }
439
            }
440
            
441
            if($group_left_count == 1){
442
                foreach ($val['data'] as $dataRow){
443
                    $this->excelSetCellValue($dataRow);
444
                }
445
            }else{
446
                $sub_group_start = $this->_row;
447
                $rowName = $this->cellName($group_field_positions[1]); // 使用第二个分组字段的实际位置
448
                
449
                foreach ($val['data'] as $k => $v){
450
                    $coordinate = $rowName.$sub_group_start.':'.$rowName.($sub_group_start+$v['count']-1);
451
                    $this->workSheet->mergeCells($coordinate);
452
                    $this->workSheet->setCellValue($rowName.$sub_group_start, $k);
453
                    
454
                    foreach ($v['data'] as $data){
455
                        $this->excelSetCellValue($data);
456
                    }
457
                    
458
                    $sub_group_start = $sub_group_start + $v['count'];
459
                }
460
            }
461
            
462
            $this->_row = $group_start + $val['count'];
463
            $group_start = $this->_row;
464
        }
465
    }
466
467
    /**
468
     * 二位数组获取每一级别数量
469
     * @author tinymeng
470
     * @param array $data 二维数组原始数据
471
     * @return array
472
     */
473
    private function arrayCount($data=[])
474
    {
475
        foreach ($data as $key => $val){
476
            $num = 0;
477
            foreach ($val as $k => $v){
478
                $sub_num = count($v);
479
                $num = $num+$sub_num;
480
                $val[$k] = [
481
                    'count' => $sub_num,
482
                    'data' => $v
483
                ];
484
            }
485
            $data[$key] = [
486
                'count' => $num,
487
                'data' => $val
488
            ];
489
        }
490
        return $data;
491
    }
492
493
    /**
494
     * 自动合并指定字段相同值的单元格
495
     * @param int $rowStart 数据起始行
496
     * @param int $rowEnd 数据结束行
497
     */
498
    private function autoMergeColumns($rowStart, $rowEnd)
499
    {
500
        if ($rowEnd <= $rowStart) return;
501
        foreach ($this->mergeColumns as $fieldName) {
502
            $colIdx = array_search($fieldName, $this->field);
503
            if ($colIdx === false) continue;
504
            $colLetter = $this->cellName($colIdx);
505
            $lastValue = null;
506
            $mergeStart = $rowStart;
507
            for ($row = $rowStart; $row <= $rowEnd; $row++) {
508
                $cellValue = $this->workSheet->getCell($colLetter . $row)->getValue();
509
                if ($lastValue !== null && $cellValue !== $lastValue) {
510
                    if ($row - $mergeStart > 1) {
511
                        $this->workSheet->mergeCells($colLetter . $mergeStart . ':' . $colLetter . ($row - 1));
512
                    }
513
                    $mergeStart = $row;
514
                }
515
                $lastValue = $cellValue;
516
            }
517
            // 处理最后一组
518
            if ($rowEnd - $mergeStart + 1 > 1) {
519
                $this->workSheet->mergeCells($colLetter . $mergeStart . ':' . $colLetter . $rowEnd);
520
            }
521
        }
522
    }
523
524
    /**
525
     * 应用全表样式
526
     */
527
    private function applySheetStyle()
528
    {
529
        if (empty($this->sheetStyle)) return;
530
        // 计算数据区范围
531
        $startCol = 'A';
532
        $endCol = $this->cellName(count($this->field) - 1);
533
        $startRow = 1;
534
        $endRow = $this->_row - 1;
535
        $cellRange = $startCol . $startRow . ':' . $endCol . $endRow;
536
        $this->workSheet->getStyle($cellRange)->applyFromArray($this->sheetStyle);
537
    }
538
539
    /**
540
     * 设置自定义表格操作回调
541
     * @param callable $fn
542
     * @return $this
543
     */
544
    public function complexFormat(callable $fn) {
545
        $this->complexFormatCallback = $fn;
546
        return $this;
547
    }
548
549
550
}
551