引言:經(jīng)常在一些社區(qū)上看到莫名的一句話
PHP是世界上最好的語言
呼畸,在語言的爭論上里初,確實是大有華山論劍
的門派之爭锅睛。本文將通過編程語言的范式
角度疚膊,來了解整個編程語言的發(fā)展史义辕,同時更清晰的認知編程范式
,以達到知己知彼寓盗,百戰(zhàn)不殆
??編程語言發(fā)展到今天终息,出現(xiàn)了好多不同的代碼編寫方式,但不同的方式解決的都是同一個問題贞让,那就是如何寫出更為通用周崭、更具可重用性的代碼或模塊。
一喳张、從C語言談起
??C語言的歷史悠久续镇,自其問世以來,其影響了太多太多的編程語言销部,到現(xiàn)在還一直被廣泛使用摸航,不得不佩服它的生命力。但是舅桩,我們也要清楚的知道酱虎,大多數(shù)的使用C語言來做內(nèi)核的編程語言其實都是在改善C語言帶來的問題+時代發(fā)展帶來的變化。
下面來簡單回顧下C語言的特性:
- C 語言是一個靜態(tài)弱類型語言擂涛,在使用變量時需要聲明變量類型读串,但是類型間可以有隱式轉(zhuǎn)換;
- 不同的變量類型可以用結(jié)構(gòu)體(struct)組合在一起撒妈,以此來聲明新的數(shù)據(jù)類型恢暖;
- C 語言可以用 typedef 關(guān)鍵字來定義類型的別名,以此來達到變量類型的抽象狰右;
- C 語言是一個有結(jié)構(gòu)化程序設(shè)計杰捂、具有變量作用域以及遞歸功能的過程式語言;
- 通過指針棋蚌,C 語言可以容易地對內(nèi)存進行低級控制嫁佳,然而這加大了編程復(fù)雜度;
- 編譯預(yù)處理讓 C 語言的編譯更具有彈性谷暮,比如跨平臺蒿往。
然而,在代碼組織和功能編程上坷备,C語言的上述熄浓,卻不那么美妙了,eq:
//一個簡單的交換兩個變量的函數(shù)
void changeVar(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
可以想一想省撑,這里為什么要用指針呢赌蔑?因為如果不用指針的話,只是傳進來的行參竟秫,即函數(shù)的形參是調(diào)用實參的一個拷貝娃惯,函數(shù)閉包里對形參的修改無法影響實參的結(jié)果。
然而肥败,這個函數(shù)最大的問題是int tmp = *x
決定了這個函數(shù)只能給int值使用趾浅,但是還有好多類型也等著能被調(diào)用呢,eq:double馒稍、float皿哨、string等,這就是個靜態(tài)語言最糟糕的問題纽谒。
當(dāng)然证膨,這個時候大家都會想到類型轉(zhuǎn)換,然而對于C語言的類型轉(zhuǎn)換鼓黔,是會出現(xiàn)很多問題的央勒。
- 比如:一個 double a[10] 的數(shù)組,a[2] 意味著 a + sizeof(double) * 2澳化。如果你把 a 強轉(zhuǎn)成 int崔步,那么 a[2] 就意味著 a + sizeof(int) * 2。我們知道 sizeof(double) 是 8缎谷,而 sizeof(int) 是 4井濒。于是訪問到了不同的地址和內(nèi)存空間,這就導(dǎo)致程序出現(xiàn)嚴重的問題列林。下面這種使用臨時交換數(shù)據(jù)的buffer拷貝方案可以去掉類型轉(zhuǎn)換時導(dǎo)致的地址變換:
//加入泛型變量的交換兩個變量的函數(shù)
void changeVar(void* x, void* y, size_t size)
{
char tmp[size]; //交換數(shù)據(jù)時需要用的 buffer
memcpy(tmp, y, size);
memcpy(y, x, size);
memcpy(x, tmp, size);
/**
* 1.函數(shù)接口中增加了一個size參數(shù)眼虱,用了 void* 后,類型被“抽象”掉了
席纽,編譯器不能通過類型得到類型的長度了捏悬,所以,需要我們手動地加上一個類型長度的標(biāo)識润梯。
2.函數(shù)的實現(xiàn)中使用了memcpy()函數(shù)过牙,因為類型被“抽象”掉了,所以不能用賦值表達式了纺铭,很有可能傳進來的參數(shù)類型還是一個結(jié)構(gòu)體寇钉,因此,為了要交換這些復(fù)雜類型的值舶赔,我們只能使用內(nèi)存復(fù)制的方法了扫倡。
*/
}
除了上面使用void* 來做泛型,在C語言中,還可以用宏定義來做泛型撵溃,不過卻會帶來宏的字符串替換疚鲤,導(dǎo)致代碼膨脹,導(dǎo)致編譯出的執(zhí)行文件相對較大缘挑,此處感興趣的可以去深入學(xué)習(xí)一下集歇。
二、泛型編程
-
泛型
:即是指具有在多種數(shù)據(jù)類型上皆可操作的含義语淘,與模板有些相似诲宇。
1.舉例:- C語言版本
long sum(int *a, size_t size) {
long result = 0; for(int i=0; i<size; i++) { result += a[i]; } return result;
}
- C++版本
template<typename T, typename Iter>
T sum(Iter pStart, Iter pEnd) {
T result = 0;
for(Iter p=pStart; p!=pEnd; p++) {
result += *p;
}
return result;
}
// 那個0假設(shè)了類型是int;
// 那個T假設(shè)了 Iter 中出來的類型是T惶翻。如果類型不一樣姑蓝,那么就會導(dǎo)致轉(zhuǎn)類型的問題。
- php版本
public function sum($a, $b) {return intval($a)+intval($b);}
- js版本
function sum($x, $y) {return parseInt($x)+parseInt($y);}
-
泛型編程的重要設(shè)計模式-迭代器
:
- 首先吕粗,一個迭代器需要和一個容器在一起纺荧,因為里面是對這個容器的具體的代碼實現(xiàn)。
- 它需要重載一些操作符溯泣,比如:取值操作*虐秋、成員操作->、比較操作==和!=垃沦,還有遍歷操作++客给,等等。
3.然后肢簿,還要typedef一些類型靶剑,比如value_type,告訴我們?nèi)萜鲀?nèi)的數(shù)據(jù)的實際類型是什么樣子池充。 - 還有一些桩引,如begin()和end()的基本操作。日志的記錄等收夸。
三坑匠、類型系統(tǒng)和泛型的本質(zhì)
0. 函數(shù)式編程
對于函數(shù)式編程來說,它只關(guān)心定義輸入數(shù)據(jù)和輸出數(shù)據(jù)相關(guān)的關(guān)系卧惜,數(shù)學(xué)表達式里面其實是在做一種映射(mapping)厘灼,輸入的數(shù)據(jù)和輸出的數(shù)據(jù)關(guān)系是什么樣的,是用函數(shù)來定義的咽瓷。
特征:
- stateless:函數(shù)不維護任何狀態(tài)设凹。函數(shù)式編程的核心精神是 stateless,簡而言之就是它不能存在狀態(tài)茅姜,打個比方闪朱,你給我數(shù)據(jù)我處理完扔出來。里面的數(shù)據(jù)是不變的。
- immutable:輸入數(shù)據(jù)是不能動的奋姿,動了輸入數(shù)據(jù)就有危險锄开,所以要返回新的數(shù)據(jù)集。
優(yōu)勢: - 沒有狀態(tài)就沒有傷害胀蛮。
- 并行執(zhí)行無傷害院刁。
- Copy-Paste 重構(gòu)代碼無傷害糯钙。
- 函數(shù)的執(zhí)行沒有順序上的問題粪狼。
- 確定性。所謂確定性任岸,就是像在數(shù)學(xué)中那樣再榄,f(x) = y 這個函數(shù)無論在什么場景下,都會得到同樣的結(jié)果享潜,而不是像程序中的很多函數(shù)那樣困鸥。同一個參數(shù),在不同的場景下會計算出不同的結(jié)果剑按,這個我們稱之為函數(shù)的確定性疾就。所謂不同的場景,就是我們的函數(shù)會根據(jù)運行中的狀態(tài)信息的不同而發(fā)生變化艺蝴。
1.基于原型的編程范式
講到原型猬腰,相信大家的第一映像一定是JavaScript吧,來個小問題猜敢,大家有誰知道js的國際全稱叫什么嗎姑荷?
- 每個對象都有一個 proto 的屬性
- 來看下原型鏈:
var a = {
x: 10,
calculate: function (z) {
return this.x + this.y + z;
}
};
var b = {
y: 20,
__proto__: a
};
var c = {
y: 30,
__proto__: a
};
// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80
- 再來看看
// 一種構(gòu)造函數(shù)寫法
function Foo(y) {
this.y = y;
}
// 修改 Foo 的 prototype,加入一個成員變量 x
Foo.prototype.x = 10;
// 修改 Foo 的 prototype缩擂,加入一個成員函數(shù) calculate
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
};
// 現(xiàn)在鼠冕,我們用 Foo 這個原型來創(chuàng)建 b 和 c
var b = new Foo(20);
var c = new Foo(30);
// 調(diào)用原型中的方法,可以得到正確的值
b.calculate(30); // 60
c.calculate(40); // 80
再來重溫一下上面的原型講解實例
function Person(){}
var p = new Person();
Person.prototype.name = "Hao Chen";
Person.prototype.sayHello = function(){
console.log("Hi, I am " + this.name);
}
console.log(p.name); // "Hao Chen"
p.sayHello(); // "Hi, I am Hao Chen"
在上面這個例子中:
- 我們先生成了一個空的函數(shù)對象 Person()胯盯;
- 然后將這個空的函數(shù)對象 new 出另一個對象懈费,存在 p 中;
- 這時再改變 Person.prototype博脑,讓其有一個 name 的屬性和一個 sayHello() 的方法憎乙;
- 我們發(fā)現(xiàn),另外那個 p 的對象也跟著一起改變了趋厉。
注意一下:
- 當(dāng)創(chuàng)建 function Person(){} 時寨闹,Person.proto 指向 Function.prototype;
- 當(dāng)創(chuàng)建 var p = new Person() 時,p.proto 指向 Person.prototype;
- 當(dāng)修改了 Person.prototype 的內(nèi)容后君账,p.proto 的內(nèi)容也就被改變了繁堡。
小結(jié):
通過上述可以看到,這種玩法就是一種委托的方式。在使用委托的基于原型的語言中椭蹄,運行時語言可以“僅僅通過序列的指針找到匹配”這樣的方式來定位屬性或者尋找正確的數(shù)據(jù)闻牡。所有這些創(chuàng)建行為、共享的行為需要的是委托指針绳矩。
不像是基于類的面向?qū)ο笳Z言中類和接口的關(guān)系罩润,原型和它的分支之間的關(guān)系并不要求子對象有相似的內(nèi)存結(jié)構(gòu),因為如此翼馆,子對象可以繼續(xù)修改而無需像基于類的系統(tǒng)那樣整理結(jié)構(gòu)割以。還有一個要提到的地方是,不僅僅是數(shù)據(jù)应媚,方法也能被修改严沥。因為這個原因,大多數(shù)基于原型的語言把數(shù)據(jù)和方法提作“slots”中姜。
這種在對象里面直接修改的玩法消玄,雖然這個特性可以帶來運行時的靈活性,我們可以在運行時修改一個 prototype丢胚,給它增加甚至刪除屬性和方法翩瓜。但是其帶來了執(zhí)行的不確定性,也有安全性的問題携龟,而代碼還變得不可預(yù)測兔跌,這有點黑科技的味道了。因為這些不像靜態(tài)類型系統(tǒng)骨宠,沒有一個不可變的契約對代碼的確定性有保證浮定,所以,需要使用者來自己保證层亿。
2. 面向?qū)ο笈cgo語言的委托模式
OOP三大特性:封裝桦卒、繼承和多態(tài)。
OOD原則/SOLID原則:
- OCP-Open/close principle:開/閉原則;Open for extension;Closed for modification;
比如加上一些策略模式匿又、適配器模式方灾、觀察者模式等
- DIP-Dependency Inversion Principle:依賴倒置原則
高層模塊不能依賴低層模塊,而是大家都依賴于抽象碌更;
抽象不能依賴實現(xiàn)裕偿,而是實現(xiàn)依賴抽象。
DIP 倒置了:模塊或包的依賴關(guān)系
開發(fā)順序和職責(zé)
- SRP-Single Responsibility Principle:單一職責(zé)原則痛单,又被稱為“內(nèi)聚性原則(Cohesion)”嘿棘,意為:
一個模塊的組成元素之間的功能相關(guān)性。
一個類旭绒,只能有一個引起它的變化的原因鸟妙。
一個職責(zé)是一個變化的原因焦人。
違反的后果:
- 脆弱性:switch case/if else 非常脆弱;對系統(tǒng)的改動會導(dǎo)致系統(tǒng)中和改動的地方無關(guān)的許多地方出現(xiàn)問題重父。出現(xiàn)新問題的地方與改動的地方?jīng)]有概念上的關(guān)聯(lián)花椭。要修正這些問題又會引出更多的問題,從而使開發(fā)團隊就像一只不停追逐自己尾巴的狗一樣房午。
- 不可移植性
設(shè)計臭味:糟糕的代碼有哪些特點矿辽?
- 僵硬-不易改變
僵化性(rigidity):很難對系統(tǒng)改動,因為每個改動都會迫使許多對系統(tǒng)其他部分的改動郭厌。如果單一的改動會導(dǎo)致依賴關(guān)系的模塊中的連鎖改動袋倔,那么設(shè)計就是僵化的,必須要改動的
模塊越多沪曙,設(shè)計就越僵化奕污。
- 脆弱-只想改變A萎羔,結(jié)果B被意外破壞液走。
牢固性(Immobility): 很難解開系統(tǒng)的糾結(jié),使之成為一些可在其他系統(tǒng)中重用的組件贾陷。設(shè)計中包含了對其他系統(tǒng)有用的部分缘眶,而把這些部分從系統(tǒng)中分離出來所需的努力和風(fēng)險是巨大的。
- 導(dǎo)致誤用的陷阱-做錯誤的事比做正確的事更容易髓废,引誘程序員破壞原有的設(shè)計巷懈。
粘滯性(Viscosity): 做正確的事情比做錯誤的事情要困難。
- 面臨一個改動的時候慌洪,開發(fā)人員常常會發(fā)現(xiàn)會有多種改動的方法顶燕。有的方法會保持系統(tǒng)原來的設(shè)計,而另外一些則會破壞設(shè)計冈爹,當(dāng)那些可以保持系統(tǒng)設(shè)計的方法比那些破壞設(shè)計的方法跟難應(yīng)用是涌攻,就表明設(shè)計具有高的粘滯性,作錯誤的事情就很容易频伤。
- 晦澀-代碼難以理解恳谎,定義的變量名和注釋都不能夠見名知意。
晦澀性(Opacity):很難閱讀憋肖、理解因痛。沒有很好的表現(xiàn)出意圖。
- 代碼可以用清晰岸更、富有表現(xiàn)力的方式編寫鸵膏,也可以用晦澀、費解的方式編寫怎炊。一般說來谭企,隨著時間的推移用僧,代碼會變得越來越晦澀。
不必要的復(fù)雜性(Needless Complexity):設(shè)計中包含有不具任何直接好處的基礎(chǔ)結(jié)構(gòu)赞咙。
如果設(shè)計中包含有當(dāng)前沒有用的組成部分责循,他就含有不必要的復(fù)雜性。當(dāng)開發(fā)人員預(yù)測需求的變化攀操,并在軟件中放置了處理那些潛在變化的代碼時院仿,常常會出現(xiàn)這種情況。
- 過度設(shè)計速和、copy-paste代碼
不必要的重復(fù)(Needless Repetition):設(shè)計中包含有重復(fù)的結(jié)構(gòu)歹垫,而該重復(fù)的結(jié)構(gòu)本可以使用單一的抽象進行統(tǒng)一。
- 當(dāng) copy颠放,cut排惨,paste 編程的時候,這種情況就會發(fā)生碰凶。
面向?qū)ο髢?yōu)點:
- 能和真實的世界交相輝映暮芭,符合人的直覺。
- 面向?qū)ο蠛蛿?shù)據(jù)庫模型設(shè)計類型欲低,更多地關(guān)注對象間的模型設(shè)計辕宏。
- 強調(diào)于“名詞”而不是“動詞”,更多地關(guān)注對象和對象間的接口砾莱。
- 根據(jù)業(yè)務(wù)的特征形成一個個高內(nèi)聚的對象瑞筐,有效地分離了抽象和具體實現(xiàn),增強了可重用性和可擴展性腊瑟。
- 擁有大量非常優(yōu)秀的設(shè)計原則和設(shè)計模式聚假。
面向?qū)ο笕秉c:
- 代碼都需要附著在一個類上,從一側(cè)面上說闰非,其鼓勵了類型膘格。
- 代碼需要通過對象來達到抽象的效果,導(dǎo)致了相當(dāng)厚重的“代碼粘合層”河胎。
- 因為太多的封裝以及對狀態(tài)的鼓勵闯袒,導(dǎo)致了大量不透明并在并發(fā)下出現(xiàn)很多問題。
go語言的委托模式:
type Widget struct {
X, Y int
}
type Label struct {
Widget // Embedding (delegation)
Text string // Aggregation
X int // Override
}
func (label Label) Paint() {
// [0xc4200141e0] - Label.Paint("State")
fmt.Printf("[%p] - Label.Paint(%q)\n",
&label, label.Text)
}
由上面可知:
- 我們聲明了一個 Widget游岳,其有 X和Y政敢;
- 然后用它來聲明一個 Label,直接把 Widget 委托進去胚迫;
- 然后再給 Label 聲明并實現(xiàn)了一個 Paint() 方法喷户。
label := Label{Widget{10, 10}, "State", 100}
// X=100, Y=10, Text=State, Widget.X=10
fmt.Printf("X=%d, Y=%d, Text=%s Widget.X=%d\n",
label.X, label.Y, label.Text,
label.Widget.X)
fmt.Println()
// {Widget:{X:10 Y:10} Text:State X:100}
// {{10 10} State 100}
fmt.Printf("%+v\n%v\n", label, label)
label.Paint()
我們可以看到,如果有成員變量重名访锻,則需要手動地解決沖突褪尝。
3. 來看看我們?nèi)粘R欢纬绦虼a的抽象解構(gòu)(見draw圖)闹获,logic + control
分離業(yè)務(wù)邏輯+控制邏輯,業(yè)務(wù)邏輯又可分為:校驗業(yè)務(wù)類型河哑,數(shù)據(jù)處理業(yè)務(wù)類型避诽;數(shù)據(jù)處理業(yè)務(wù)類型又可分為:格式化組裝數(shù)據(jù)處理類型、struct結(jié)構(gòu)體枚舉預(yù)處理CURD
-
用過的經(jīng)驗璃谨,以編程方式類型區(qū)分:
- 面向?qū)ο螅何猩陈⒉呗浴蚪蛹淹獭⑿揎椆俺oC/DIP、MVC
- 函數(shù)式編程:修飾底扳、管道铸抑、拼裝
Logic 部分才是真正有意義的(What)
Control 部分只是影響 Logic 部分的效率(How)
一個是在解決數(shù)據(jù)和算法,一個是在解決邏輯和控制衷模。
4. 邏輯編程范式
四鹊汛、泛型思想日常編碼中的引用
<?php
namespace YfSdk\Enum;
use YfSdk\Enum\base\EnumBase;
class QueueResultLogStatus extends EnumBase
{
/**
* 入列
*/
const INIT = 0;
/**
* 處理中
*/
const HANDLING = 1;
/**
* 成功
*/
const SUCCESS = 2;
/**
* 失敗
*/
const FAIL = 3;
/**
* 忽略處理
*/
const IGNORE = 4;
/**
* 傳入的隊列狀態(tài)異常
*/
const UNUSUAL = 8;
public $key;
public $name;
public $remark;
private static $logObj;
/**
* 枚舉注釋
*
* @return array
*/
public static function comment()
{
return [
'消息入列' => self::INIT,
'消息出列' => self::HANDLING,
'處理失敗' => self::FAIL,
'處理成功' => self::SUCCESS,
'忽略處理' => self::IGNORE
];
}
/**
* 所有值
*
* @return array
*/
public static function all()
{
return array_values(static::comment());
}
/**
* list
*
* @return mixed
*/
public static function allObj()
{
if (static::$logObj == null) {
static::$logObj = [
static::INIT => static::create(static::INIT, '消息入列', ''),
static::HANDLING => static::create(static::HANDLING, '消息出列', ''),
static::FAIL => static::create(static::FAIL, '處理失敗', ''),
static::SUCCESS => static::create(static::SUCCESS, '處理成功', ''),
static::IGNORE => static::create(static::IGNORE, '忽略處理', ''),
static::UNUSUAL => static::create(static::UNUSUAL, '傳入的隊列狀態(tài)異常', '傳入的隊列狀態(tài)異常')
];
}
return static::$logObj;
}
/**
* 查詢
*
* @param $key
* @return false|static
*/
public static function find($key)
{
return isset(static::all()[$key]) ? static::all()[$key] : false;
}
/**
* 創(chuàng)建struct
*
* @param $key
* @param $name
* @param $remark
* @return $this
*/
public function create($key, $name, $remark)
{
$obj = new static();
$obj->key = $key;
$obj->name = $name;
$obj->remark = $remark;
return $obj;
}
}
// 調(diào)用代碼-原始
public function updateLogStatusById($tenantCode, $logId, $status, $message = '')
{
if (!is_scalar($message)) {
$message = json_encode($message, JSON_UNESCAPED_UNICODE);
}
switch ($status) {
case '消息出列':
$this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '消息出列', 'message' => 'yf_queue_plus_iLogId:' . $this->getILogId().'message:' . $message]);
break;
case '忽略處理':
$this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '忽略處理', 'message' => $message]);
break;
case '處理失敗':
$this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '處理失敗', 'message' => $message]);
break;
case '處理成功':
$this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '處理成功', 'message' => $message]);
break;
}
}
// 調(diào)用代碼-改造后
public function updateQueueStatue($tenantCode, $logId, $status, $message = '')
{
if (!is_scalar($message)) {
$message = json_encode($message, JSON_UNESCAPED_UNICODE);
}
if (!in_array($status, QueueResultLogStatus::comment())) {
$statusName = QueueResultLogStatus::find($status)->name;
} else {
$statusName = QueueResultLogStatus::find(QueueResultLogStatus::UNUSUAL)->name;
}
if ($status == QueueResultLogStatus::HANDLING) {
$message = 'yf_queue_plus_iLogId:' . $this->getILogId().'message:' . $message;
}
$updateRow = ['status' => $statusName, 'message' => $message];
$this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, $updateRow);
}
寫在最后,留個小作業(yè)算芯,下面這段代碼請?zhí)岢瞿愕膬?yōu)化思路:
private function checkClassProblemSheet($sheet, $highestColumn, $highestRow)
{
try {
// 不合法的行
$failRow = [];
//返回合法數(shù)據(jù)
$legalImportRows = [];
// 檢查項為空的行
$nullClassRow = [];
// 檢查項名稱包含特殊字符的行
$illegalCharacterRow = [];
// 檢查項不存在的行
$notExistClassRow = [];
// 問題描述為空的行
$nullClassProblemRow = [];
// 問題描述超過特定字符
$exceedMaxLengthClassProblemRow = [];
// 問題描述重復(fù)的行
$classProblemRepeatRow = [];
// 非末級檢查項的行
$notEndClassRow = [];
// 裝修標(biāo)準(zhǔn)為空的行
$nullFitmentStandardRow = [];
// 裝修標(biāo)準(zhǔn)不合法的行
$illegalFitmentStandardRow = [];
// 裝修標(biāo)準(zhǔn)超過檢查項的裝修標(biāo)準(zhǔn)
$exceedClassFitmentStandardRow = [];
// 內(nèi)驗期整改時限為空的行
$nullInternalRepairRow = [];
// 內(nèi)驗期整改時限不合法的行
$illegalInternalRepairRow = [];
// 交付期整改時限為空的行
$nullDeliveryRepairRow = [];
// 交付期整改時限不合法的行
$illegalDeliveryRepairRow = [];
// 補充說明是否必填為空的行
$nullIsRemarkRequiredRow = [];
// 補充說明是否必填不合法的行
$illegalIsRemarkRequiredRow = [];
// 字段到Excel列的轉(zhuǎn)換
$fieldToColumnMapping = $this->getImportFieldMapping($sheet, $highestColumn, $this->_importClassProblemFields);
// 提示不存在的列
$this->checkNotExistColumn($fieldToColumnMapping, $sheet->getTitle());
for ($row = 3; $row <= $highestRow; $row++) {
$checkName1 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['一級檢查項名稱'], $row)->getValue();
$checkName1 = !empty($checkName1) ? trim($checkName1) : '';
$checkName2 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['二級檢查項名稱'], $row)->getValue();
$checkName2 = !empty($checkName2) ? trim($checkName2) : '';
$checkName3 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['三級檢查項名稱'], $row)->getValue();
$checkName3 = !empty($checkName3) ? trim($checkName3) : '';
$checkName4 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['四級檢查項名稱'], $row)->getValue();
$checkName4 = !empty($checkName4) ? trim($checkName4) : '';
$checkName5 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['五級檢查項名稱'], $row)->getValue();
$checkName5 = !empty($checkName5) ? trim($checkName5) : '';
$classProblem = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['問題描述'], $row)->getValue();
$classProblem = !empty($classProblem) ? trim($classProblem) : '';
$fitRange = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['適用裝修標(biāo)準(zhǔn)'], $row)->getValue();
$internalRepairDeadline = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['內(nèi)驗期整改時限'], $row)->getValue();
$deliveryRepairDeadline = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['交付期整改時限'], $row)->getValue();
$isRemarkRequired = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['補充說明是否必填'], $row)->getValue();
if (empty($checkName1) && empty($checkName2) && empty($checkName3) && empty($checkName4) && empty($checkName5) && empty($classProblem) && empty($fitRange) && empty($internalRepairDeadline) && empty($deliveryRepairDeadline) && empty($isRemarkRequired)) {
// 忽略空行
continue;
}
if ((empty($checkName1) && empty($checkName2) && empty($checkName3) && empty($checkName4) && empty($checkName5))
|| ((!empty($checkName2) && empty($checkName1)) || (!empty($checkName3) && empty($checkName2)) || (!empty($checkName4) && empty($checkName3)) || (!empty($checkName5) && empty($checkName4)))) {
// 檢查項為空(包含該級檢查項不為空但是上級檢查項為空的情況)
$nullClassRow[] = $row;
}
if (strpos($checkName1, '-') !== false || strpos($checkName2, '-') !== false || strpos($checkName3, '-') !== false || strpos($checkName4, '-') !== false || strpos($checkName5, '-') !== false) {
// 檢查項名稱有特殊字符
$illegalCharacterRow[] = $row;
}
$strCheckName = $checkName1;
if (!empty($checkName2)) {
$strCheckName .= '-' . $checkName2;
}
if (!empty($checkName3)) {
$strCheckName .= '-' . $checkName3;
}
if (!empty($checkName4)) {
$strCheckName .= '-' . $checkName4;
}
if (!empty($checkName5)) {
$strCheckName .= '-' . $checkName5;
}
if (!empty($strCheckName) && !in_array($strCheckName, $this->_checkName) && !in_array($row, $illegalCharacterRow)) {
// 問題描述中的檢查項不存在
$notExistClassRow[] = $row;
}
if (!empty($strCheckName) && $this->isNotEndClass($strCheckName)) {
// 問題描述對應(yīng)的檢查項非末級檢查項
$notEndClassRow[] = $row;
}
if (empty($classProblem)) {
// 問題描述為空
$nullClassProblemRow[] = $row;
}
if (!empty($classProblem) && mb_strlen($classProblem, 'utf-8') > 30) {
// 問題描述長度超過特定字符
$exceedMaxLengthClassProblemRow[] = $row;
}
$importData = [
'check_name1' => $checkName1,
'check_name2' => $checkName2,
'check_name3' => $checkName3,
'check_name4' => $checkName4,
'check_name5' => $checkName5,
'class_problem' => $classProblem
];
$repeatRow = $this->getRepeatClassProblemRow($legalImportRows, $importData);
if ($repeatRow) {
if (!in_array($repeatRow, $classProblemRepeatRow)) {
// 記錄重復(fù)的行數(shù)
$classProblemRepeatRow[] = $repeatRow;
}
// 問題描述重復(fù)
$classProblemRepeatRow[] = $row;
}
if (empty($fitRange)) {
// 適用裝修標(biāo)準(zhǔn)為空
$nullFitmentStandardRow[] = $row;
}
if (!empty($fitRange) && !in_array($fitRange, ['毛坯', '精裝', '毛坯&精裝'])) {
// 裝修標(biāo)準(zhǔn)不合法
$illegalFitmentStandardRow[] = $row;
}
if (!empty($fitRange) && !empty($this->_classRange[$strCheckName])) {
// 問題描述的適用裝修標(biāo)準(zhǔn)不超過檢查項的適用裝修標(biāo)準(zhǔn)
$checkItemFitRange = $this->_classRange[$strCheckName];
if (($checkItemFitRange == '精裝' && strpos($fitRange, '毛坯') !== false) || ($checkItemFitRange == '毛坯' && strpos($fitRange, '精裝') !== false)) {
$exceedClassFitmentStandardRow[] = $row;
}
}
if ($internalRepairDeadline == '') {
// 內(nèi)驗整改期限為空
$nullInternalRepairRow[] = $row;
} elseif (!preg_match('/^[1-9][0-9]*$/', $internalRepairDeadline)) {
// 內(nèi)驗整改期限不合法
$illegalInternalRepairRow[] = $row;
}
if ($deliveryRepairDeadline == '') {
// 交付整改期限為空
$nullDeliveryRepairRow[] = $row;
} elseif (!preg_match('/^[1-9][0-9]*$/', $deliveryRepairDeadline)) {
// 交付整改期限不合法
$illegalDeliveryRepairRow[] = $row;
}
if (empty($isRemarkRequired)) {
// 補充說明是否必填為空
$nullIsRemarkRequiredRow[] = $row;
}
if (!empty($isRemarkRequired) && !in_array($isRemarkRequired, ['是', '否'])) {
// 補充說明是否必填不合法
$illegalIsRemarkRequiredRow[] = $row;
}
$legalImportRows[$row] = [
'check_name1' => $checkName1,
'check_name2' => $checkName2,
'check_name3' => $checkName3,
'check_name4' => $checkName4,
'check_name5' => $checkName5,
'full_name' => $strCheckName,
'class_problem' => $classProblem,
'is_for_rough' => strpos($fitRange, '毛坯') !== false ? 1 : 0,
'is_for_decorated' => strpos($fitRange, '精裝') !== false ? 1 : 0,
'internal_repair_deadline' => $internalRepairDeadline,
'delivery_repair_deadline' => $deliveryRepairDeadline,
'is_remark_required' => $isRemarkRequired == '是' ? 1 : 0
];
}
if (!empty($nullClassRow)) {
$failRow['檢查項為空'] = $nullClassRow;
}
if (!empty($illegalCharacterRow)) {
$failRow['檢查項名稱包含非法字符'] = $illegalCharacterRow;
}
if (!empty($notExistClassRow)) {
$failRow['檢查項不存在'] = $notExistClassRow;
}
if (!empty($notEndClassRow)) {
$failRow['非末級檢查項不能添加問題描述'] = $notEndClassRow;
}
if (!empty($nullClassProblemRow)) {
$failRow['問題描述為空'] = $nullClassProblemRow;
}
if (!empty($exceedMaxLengthClassProblemRow)) {
$failRow['問題描述超過30個字'] = $exceedMaxLengthClassProblemRow;
}
if (!empty($classProblemRepeatRow)) {
$failRow['檢查項+問題描述重名'] = $classProblemRepeatRow;
}
if (!empty($nullFitmentStandardRow)) {
$failRow['適用裝修標(biāo)準(zhǔn)為空'] = $nullFitmentStandardRow;
}
if (!empty($illegalFitmentStandardRow)) {
$failRow['適用裝修標(biāo)準(zhǔn)不合法'] = $illegalFitmentStandardRow;
}
if (!empty($exceedClassFitmentStandardRow)) {
$failRow['問題描述的適用裝修標(biāo)準(zhǔn)與檢查項的不匹配'] = $exceedClassFitmentStandardRow;
}
if (!empty($nullInternalRepairRow)) {
$failRow['內(nèi)驗期整改時限為空'] = $nullInternalRepairRow;
}
if (!empty($illegalInternalRepairRow)) {
$failRow['內(nèi)驗期整改時限不合法'] = $illegalInternalRepairRow;
}
if (!empty($nullDeliveryRepairRow)) {
$failRow['交付期整改時限為空'] = $nullDeliveryRepairRow;
}
if (!empty($illegalDeliveryRepairRow)) {
$failRow['交付期整改時限不合法'] = $illegalDeliveryRepairRow;
}
if (!empty($nullIsRemarkRequiredRow)) {
$failRow['補充說明是否必填為空'] = $nullIsRemarkRequiredRow;
}
if (!empty($illegalIsRemarkRequiredRow)) {
$failRow['補充說明是否必填不合法'] = $illegalIsRemarkRequiredRow;
}
return ['failRow' => $failRow, 'legalImportRows' => $legalImportRows];
} catch (InfoException $iex) {
throw $iex;
}
}