Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文的環(huán)境是參考這篇《Spring Cloud OAuth2 實(shí)現(xiàn)用戶認(rèn)證及單點(diǎn)登錄”》搭建的。
https://www.cnblogs.com/fengzheng/p/11724625.html
當(dāng)前已經(jīng)獲取到了access_token,根據(jù)access_token請(qǐng)求自己的接口時(shí)報(bào)了401,token驗(yàn)證有問(wèn)題。
我自己的接口是:
/**
* 一個(gè) RESTful 方法,只有當(dāng)訪問(wèn)用戶具有 ROLE_ADMIN 權(quán)限時(shí)才能訪問(wèn),否則返回 401 未授權(quán)。
*
* 通過(guò) Authentication 參數(shù)或者 SecurityContextHolder.getContext().getAuthentication() 可以拿到授權(quán)信息進(jìn)行查看。
* @param authentication
* @return
*/
@GetMapping("get")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public Object get(Authentication authentication){
authentication.getCredentials();
OAuth2AuthenticationDetails details=(OAuth2AuthenticationDetails) authentication.getDetails();
String token=details.getTokenValue();
return token;
}
org.springframework.security.oauth2.provider.token.RemoteTokenServices
我們將這個(gè)類復(fù)制出來(lái),在java下創(chuàng)建相同的包名,類。
源碼圖
在源碼中,這里返回的active應(yīng)該得為Boolean類型,但是這個(gè)map的值是個(gè)Object,返回直接返回了個(gè)String類型。
他使用了Boolean和String類型進(jìn)行比較,所以我得到的返回結(jié)果永遠(yuǎn)都是false了,就一直走了這個(gè)異常。(具體他為什么會(huì)這樣來(lái)比較,我真不太懂,歡迎大佬們?cè)谠u(píng)論里指點(diǎn)一下)
修改后的圖
那么我的處理就比較簡(jiǎn)單粗暴了,直接把這個(gè)類提出來(lái),然后把這個(gè)Boolean.TRUE.toString()一下。類型一致,比較就正常了。
再次去請(qǐng)求,現(xiàn)在就沒(méi)有問(wèn)題了,返回成功
本文只是個(gè)人在學(xué)習(xí)過(guò)程中的一次排查處理過(guò)程,如果有更好的見(jiàn)解歡迎大佬們指點(diǎn)。
者:redmed
Web 項(xiàng)目中經(jīng)常會(huì)遇到處理 URL 中 Query 的情況,來(lái)看下下面問(wèn)題你有疑惑嗎?
于是梳理一下關(guān)于 URL Query 的相關(guān)知識(shí)點(diǎn),用來(lái)去偽解惑。
首先介紹下 Query String 的基本概念,這是一切問(wèn)題的開(kāi)始。下面是 wiki[1] 的描述:
A query string is a part of a uniform resource locator[2] (URL) that assigns values to specified parameters.
通常的理解就是 URL 中問(wèn)號(hào)(?)后面的部分,其設(shè)計(jì)最初是用做 HTML form 表單提交時(shí)的傳參。
基本結(jié)構(gòu)
下面我們看下 query 的基本結(jié)構(gòu) field1=value1&field2=value2&field3=value3...
包含了如下標(biāo)準(zhǔn):
補(bǔ)充個(gè)冷知識(shí):除了使用&分割每對(duì)數(shù)據(jù)外,W3C 曾在 1999 年建議所有 Web 服務(wù)器同時(shí)支持分號(hào);分割符:
We recommend that HTTP server implementors, and in particular, CGI implementors support the use of ";" in place of "&" to save authors the trouble of escaping "&" characters in this manner.
但在 2014 年以來(lái),就只建議使用 & 作為分隔符了。也就目前我們用到的方式。
例如:field=a&field=b時(shí),field 的值應(yīng)該是 a、b、['a', 'b']、'a, b' 并無(wú)任何權(quán)威解釋。
關(guān)于處理標(biāo)準(zhǔn)這點(diǎn)實(shí)在令人出乎意料。通常這類情況會(huì)按照數(shù)組的方式處理,即 field 值為 ['a', 'b'],但這僅是不同的框架的決定了如何實(shí)現(xiàn)而已。
關(guān)于這個(gè)問(wèn)題可以前往 stackoverflow[3] 上查看。
數(shù)據(jù)編碼
前面定義好了整體結(jié)構(gòu),接下來(lái)我們看下數(shù)據(jù)是如何在 query 中傳輸?shù)摹?/span>
由于某些字符集(如中文)和在 URL 中有特殊含義的字符(如 空格、%、&、=、?、# 等)無(wú)法直接在 Query String 中使用,因此使用了一種叫做「百分號(hào)編碼[4] Percent-encoding[5]」的方式先將這類特殊字符進(jìn)行編碼后,再進(jìn)行傳輸。
其基本結(jié)構(gòu)就是 % + 2 個(gè) 16 進(jìn)制數(shù)字(一個(gè) Byte 的內(nèi)容),范圍 %00 - %FF。
具體規(guī)則如下:
! # $ & ' ( ) * + , / : ; = ? @ [ ] %21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D
其對(duì)應(yīng)的就是這些字符的 ASCII 編碼的 16 進(jìn)制格式;
注意,如果使用 from 表單 action 方式時(shí),具體編碼會(huì)根據(jù) meta 頭的 charset 的選擇。
當(dāng)然上述只是標(biāo)準(zhǔn),實(shí)踐中 JavaScript 內(nèi)置了使用 UTF-8 編碼的 encodeURI/encodeURIComponent 函數(shù),大大簡(jiǎn)化的編碼過(guò)程。
關(guān)于指定編碼,這里有個(gè)有趣的事情:
在使用百度時(shí),你會(huì)發(fā)現(xiàn) URL 中有個(gè) ie 參數(shù),其實(shí)含義就是 Input Encoding(對(duì),不是 IE 瀏覽器),目的就是指定關(guān)鍵詞 wd 的編碼格式。曾默認(rèn)是 GB2312(因?yàn)楫?dāng)時(shí)很多網(wǎng)站還使用 GB2312 編碼),當(dāng)然現(xiàn)在已經(jīng)默認(rèn)成 UTF-8 。(不過(guò)百度結(jié)果里依然有不少文章還在說(shuō) ie 的默認(rèn)值是 GB2312 )
可以用 https://www.baidu.com/s?wd=%E4%B8%AD&ie=gb2312 和 https://www.baidu.com/s?wd=%E4%B8%AD 來(lái)感受下他們的差異吧~
這一節(jié)我們挑重點(diǎn)地對(duì)比下各類 Query String 的函數(shù)庫(kù),了解老虎老鼠的差異,避免開(kāi)發(fā)時(shí)傻傻分不清楚。
以下僅對(duì)常用 API 的部分用法做演示,更多用法可自行查找。
瑞士軍刀 qs
github[7]
A querystring parsing and stringifying library with some added security.
官方介紹很簡(jiǎn)單:一個(gè)增加了安全性的 Query String 解析和序列化的函數(shù)庫(kù)。
.parse(string, [options])
qs.parse('a=c&b%201=d%26e');
// { a: 'c', 'b 1': 'd&e' }
注意 qs 不會(huì)忽略頭部的 ?,需要自行去掉,否則會(huì)當(dāng)做 field 的一部分,例如:qs.parse('?a=b')會(huì)解析為 { '?a': 'b' }。
qs.parse('foo[bar]=baz');
// { foo: { bar: 'baz' } }
但默認(rèn)子元素最多嵌套 5 層,需要通過(guò) parse(string, [options]) 的 opinion.depth 來(lái)修改。
// defalut
qs.parse('a[b][c][d][e][f][g][h][i]=j');
// {a: {b: {c: {d: {e: {f: {'[g][h][i]': 'j'}}}}}}}
// set depth
qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
// { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }
var delimited=qs.parse('a=b;c=d', { delimiter: ';' });
// { a: 'b', c: 'd' }
這點(diǎn)符合 W3C 對(duì);支持的建議,但大部分情況應(yīng)該不會(huì)用到。
var withArray=qs.parse('a[]=b&a[]=c');
// { a: ['b', 'c'] }
var withArray=qs.parse('a=b&a=c');
// { a: ['b', 'c'] }
同時(shí)也支持為數(shù)組指定索引順序。
var withIndexes=qs.parse('a[1]=c&a[0]=b');
// { a: ['b', 'c'] };
并行支持 allowSparse 獲取抽稀形式的數(shù)組。
var sparseArray=qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
// { a: [, '2', , '5'] };
但默認(rèn)指定的 index 最大值為 20,如果超過(guò)最大值,則按照 object 形式解析。使用 arrayLimit控制最大值。
var withMaxIndex=qs.parse('a[100]=b');
// { a: { '100': 'b' } }
var withArrayLimit=qs.parse('a[1]=b', { arrayLimit: 0 });
// { a: { '1': 'b' } }
.stringify(object, [options])
這里主要介紹下 array 類型的編碼。qs 默認(rèn)會(huì)對(duì) field 和 value 都進(jìn)行編碼,同時(shí)會(huì)使用[]作為數(shù)據(jù)的標(biāo)識(shí)(且默認(rèn)對(duì)[]進(jìn)行百分號(hào)編碼),需指定 encodeValuesOnly: true才僅對(duì) value 編碼。
// defalut
qs.stringify({key: ['a', 'b']});
// key%5B0%5D=a&key%5B1%5D=b
//
qs.stringify({key: ['a', 'b']}, { encodeValuesOnly: true });
// key[0]=a&key[1]=b
去掉[]標(biāo)識(shí),可使用 { indices: false }。
qs.stringify({key: ['a', 'b']}, { indices: false });
// key=a&key=b
支持配置 charset
默認(rèn)使用 UTF-8,內(nèi)置了 ISO-8859-1 模式,也可以支持 encoder 擴(kuò)展。
而接下來(lái)的庫(kù)僅支持 UTF-8 的編碼方式。
簡(jiǎn)潔專注 query-string
github[8]
Parse and stringify URL query strings[1]
For browser usage, this package targets the latest version of Chrome, Firefox, and Safari.
官方名字看起來(lái),依舊是處理 Query String 的。
另外,官方還送上友(wei)情(xian)提示,各位同學(xué)不要看走眼。
Not npm install querystring !!!!!
.parse(string, [options])
不過(guò),頭部的?和#的部分將被忽略,因此可以直接將 location.search 和 location.hash 傳入。
queryString.parse('a=c&b%201=d%26e');
// { a: 'c', 'b 1': 'd&e' }
This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of edge cases[10].
You're much better off just converting the object to a JSON string:
queryString.parse('key=a&key=b');
// { key: ['a', 'b'] };
queryString.parse('key[]=a&key[]=b');
// { 'key[]': ['a', 'b'] };
queryString.parse('key[]=a&key[]=b', { arrayFormat: 'bracket' });
// { key: ['a', 'b'] };
當(dāng)然 query-string 也支持索引的方式標(biāo)記的數(shù)組,{arrayFormat: 'index'}。
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
{foo: ['1', '2', '3']}
.stringify(object, [options])
依然重點(diǎn)介紹 array 類型的編碼,默認(rèn)不使用[]標(biāo)識(shí)。
queryString.stringify({key: ['a', 'b']});
// key=a&key=b
需要[]的話,使用 {arrayFormat: 'bracket'}開(kāi)啟,默認(rèn)[]也不會(huì)被 encode。
queryString.stringify({key: ['a', 'b']}, {arrayFormat: 'bracket'});
// key[]=a&key[]=b
這點(diǎn)和 qs 是相反的,需要特別注意!
歷史產(chǎn)物 querystring
NodeJS 中解析 query 的模塊。
NodeJS 14.x[11] 中明確標(biāo)記為 Legacy,官方推薦 URLSearchaParms 代替。
The querystring API is considered Legacy. New code should use the URLSearchParams[12] API instead.
但在 15.x 以及以后的版本又改為 Stable,但指出這是非標(biāo)準(zhǔn) API。
querystring is more performant than ``[13] but is not a standardized API. Use <URLSearchParams> when performance is not critical or when compatibility with browser code is desirable.
功能類似 query-string,不支持嵌套對(duì)象的解析,這里不再贅述。
血統(tǒng)純正 URL / URLSearchParams
URL 和 URLSearchParams 是 URL API 規(guī)范[14] 中的兩個(gè)標(biāo)準(zhǔn)的接口。其提供了訪問(wèn)、操作 URL 的 API。
其中,URL 定義了像域名、主機(jī)和 IP 地址等概念,URLSearchParams 定義了一些常用的方法來(lái)處理 Query String。我們重點(diǎn)介紹下后者。
URLSearchParams
兩種方式創(chuàng)建 URLSearchParams 對(duì)象,URLSearchParams構(gòu)造函數(shù)會(huì)忽略 search 中的?。
// 1. 通過(guò) URL
const url=new URL('https://abc.com/path/v1?key=a&key=b%26c');
const search1=url.searchParams;
// 2. 直接構(gòu)造
const search2=new URLSearchParams(location.search);
.get(name)
該方法獲取的值會(huì)被自動(dòng) decode,如果 name 不存在返回 null,如果 value 不存在返回空字符串。
const search=new URLSearchParams('key=b%26c&key2');
search.get('key'); // b&c
search.get('key2'); // ''
search.get('key3'); // null
.getAll(name)
需要特別注意,如果有多個(gè)相同的 name,get() 只能獲取第一個(gè)值。獲取全部需要使用 getAll(),該函數(shù)返回?cái)?shù)組(即便只有一個(gè) value)。
const search=new URLSearchParams('key=a&key=b');
search.get('key'); // a
search.getAll('key'); // ['a', 'b']
.set(name, string) / .append(name, string)
向 URLSearchParams 中添加數(shù)據(jù),set() 會(huì)覆蓋原有值。如果需要添加重復(fù)的 name,需要使用 append()。
set() 和 append() 僅支持 string 類型的 value。同時(shí) field 和 value 都會(huì)被 encode,無(wú)需額外處理。
const search=new URLSearchParams();
search.append('key', 'a');
search.append('key', 'b');
search.toString(); // key=a&key=b
.keys()
返回一個(gè) IterableIterator迭代器,可以使用for...of遍歷。需要注意,重復(fù)的 key 會(huì)出現(xiàn)多次
const search=new URLSearchParams('key=a&key=b');
for (const key of search.keys()) {
console.log(key);
}
// key
// key
.toString()
獲取的 Query String,會(huì)被自動(dòng) encode 處理。空格轉(zhuǎn)成+。對(duì)于重復(fù) field,使用了 field=v1&field=v2 的方式。
const search=new URLSearchParams();
search.set('key', '?&=')
search.set('key2', 'a b');
search.toString(); // key=%3F%26%3D&&key2=a+b
兼容
關(guān)于兼容,目前瀏覽器占比基本上沒(méi)有問(wèn)題。實(shí)際開(kāi)發(fā)中遇到 iOS10 以下不兼容的情況,使用 polyfill 即可。
總結(jié)對(duì)比
從上面的總結(jié)來(lái)看,我們發(fā)現(xiàn) qs 和 query-string / URLSearchParams 最大的差異在于對(duì)于多層嵌套對(duì)象(Nested object)的支持與否。
而當(dāng)使用復(fù)雜的 JSON 數(shù)據(jù)結(jié)構(gòu)時(shí),我們通常會(huì)使用JSON.stringify() 方法先將數(shù)據(jù)進(jìn)行序列化(也稱字符串化),將復(fù)雜數(shù)據(jù)轉(zhuǎn)換成基本的字符串?dāng)?shù)據(jù)后,再進(jìn)行傳輸。
因此通常情況下:
expressjs 的 body-parser 中,用戶可以自行選擇使用 qs 還是 querystring;
koajs的koa-body和bodyparser所依賴的 co-body,都選擇了qs。
當(dāng)然了解了他們差異后,選擇哪種方式就要根據(jù)你的實(shí)際情況而定了。
整理資料過(guò)程中,引申出更多有趣的問(wèn)題,也稍作整理。
空格編碼問(wèn)題
還記得前面提到的編碼規(guī)則里, 空格的編碼可以是 + 或者%20,這里描述的就很模糊。
函數(shù)對(duì)比
我們先來(lái)看下上面不同 API 是如何處理的?
對(duì)+和%20的識(shí)別都沒(méi)問(wèn)題(畢竟兼容還是能做到的),但是轉(zhuǎn)換空格URLSearchParams就有不同的邏輯了。至于為什么會(huì)有兩種編碼結(jié)果?
這里要特別說(shuō)明的是URLSearchParams采用了application/x-www-form-urlencoded編碼模式,而這個(gè)編碼采用了一個(gè)非常早期(RFC 1738)的通用百分號(hào)編碼方法——就是將 空格轉(zhuǎn)換為+。至于為什么會(huì)采用這種方式,我猜想是因?yàn)橐紤]到歷史兼容問(wèn)題——生成的 URL 需要被那些舊的僅支持+的程序識(shí)別。
當(dāng)然+已經(jīng)不推薦了,在 RFC 3986[15] 中已推薦使用%20。
特別說(shuō)明
這里特別說(shuō)明下 decodeURIComponent,是無(wú)法解析+為 空格的,因此實(shí)際業(yè)務(wù)中,如果無(wú)法保證傳入空格的編碼方式,還是使用URLSearchParams或者query-string來(lái)解析數(shù)據(jù)吧。
或者做一個(gè)簡(jiǎn)單的兼容處理:
function decodeQueryParam(p) {
return decodeURIComponent(p.replace(/+/g, " "));
}
decodeQueryParam("search+query%20%28correct%29");
// 'search query (correct)'
擴(kuò)展參考
URLSearchParams中 +的問(wèn)題,具體細(xì)節(jié)可參考 whatwg 的描述:
As a URLSearchParams object uses the application/x-www-form-urlencoded format underneath there are some difference with how it encodes certain code points compared to a URL object (including href and search ). This can be especially surprising when using searchParams to operate on a URL[16]’s query[17].
URLSearchParams objects will percent-encode anything in the application/x-www-form-urlencoded percent-encode set, and will encode U+0020 SPACE as U+002B (+).
以及 whatwg 中關(guān)于 application/x-www-form-urlencoded 的描述:
Control names and values are escaped. Space characters are replaced by '+', and then reserved characters are escaped as described in [RFC1738][18], section 2.2: Non-alphanumeric characters are replaced by %HH, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., %0D%0A).
Content-type 中的 x-www-form-urlencoded
當(dāng)我們?cè)?/span>HTTP中使用 MIME 類型為x-www-form-urlencoded格式提交數(shù)據(jù)時(shí),所使用的就是前文所介紹的編碼方式。
只是如果發(fā)送的是 GET 請(qǐng)求,數(shù)據(jù)會(huì)拼接在 Query 中;而發(fā)送 POST 請(qǐng)求則會(huì)將數(shù)據(jù)放置在消息體(body)中,通過(guò)Header中的Content-Type 來(lái)指定 MIME 類型。
當(dāng)然并不是所有的數(shù)據(jù)都適合使用 x-www-form-urlencoded,通常有二進(jìn)制數(shù)據(jù)時(shí),urlencoded使用百分號(hào)%HH和UTF-8的編碼方式,會(huì)大大增加了數(shù)據(jù)的長(zhǎng)度。為了節(jié)省傳輸數(shù)據(jù)的空間,會(huì)選擇form-data代替。
原生 from 表單的編碼
除了上面提到的各類函數(shù)外,原生 html 的 form 表單在提交數(shù)據(jù)時(shí),本身也是可以進(jìn)行編碼的。
<html>
<head>
<meta charset="UTF-8">
<!-- <meta charset="GBK">-->
</head>
<body>
<form action="/search" method="get" enctype="application/x-www-form-urlencoded">
<input type="text" name="name" required>
<input type="submit" value="提交">
</form>
</body>
</html>
當(dāng)點(diǎn)擊提交時(shí), 表單內(nèi)的 input 數(shù)據(jù)會(huì)進(jìn)行百分號(hào)編碼。但要注意的是編碼的格式是按照 meta 中設(shè)定的 charset 進(jìn)行的。例如當(dāng)輸入「中」時(shí),UTF-8 是 name=%E4%B8%AD,GBK 則是 name=%D6%D0。空格 則是+。
encodeURI(Component) 和 escape
前文提到過(guò)encodeURI(encodeURIComponent)使用 UTF-8 編碼,而 escape 是一個(gè)已經(jīng)被廢棄的非標(biāo)準(zhǔn)方式,其采用了 UTF-16 編碼,同時(shí)在碼點(diǎn)小于 255 的使用 %uXX 表示,碼點(diǎn)大于 255 的使用 %uXXXX 的方式。
同時(shí)要注意當(dāng)decodeURI(decodeURIComponent)解析非法的 %HH 格式數(shù)據(jù)時(shí)(如不合規(guī)范的 UTF-8 數(shù)據(jù)、被截?cái)嗟?HH 字符等),會(huì)包拋出URIError異常。
try {
const a=decodeURIComponent("%E0%A4%A");
} catch (e) {
console.error(e);
}
// URIError: malformed URI sequence
因此如果無(wú)法保證數(shù)據(jù)的可用性,記得總是要 try...catch 一下比較保險(xiǎn)。
或者更推薦使用類似`safe-decode-uri-component`[19]的三方庫(kù),來(lái)避免這類麻煩。
至于 UTF-8 的合法格式是什么樣的,這就要涉及更多的編碼知識(shí)了。
[1] https://en.wikipedia.org/wiki/Query_string
[2] https://en.wikipedia.org/wiki/Uniform_resource_locator
[3] https://bytedance.feishu.cn/docx/D6LTd2zgHo2S5NxaFE6cnh9knHf#Oo6UdgOqSog2G2xsp3VcSPdIn6d
[4] https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81
[5] https://en.wikipedia.org/wiki/Percent-encoding
[6] https://datatracker.ietf.org/doc/html/rfc3986
[7] https://github.com/ljharb/qs
[8] https://www.npmjs.com/package/query-string
[9] https://www.baeldung.com/postman-form-data-raw-x-www-form-urlencoded
[10] https://github.com/visionmedia/node-querystring/issues
[11] https://nodejs.org/docs/latest-v14.x/api/querystring.html
[12] https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
[13] https://nodejs.org/dist/latest-v19.x/docs/api/url.html#class-urlsearchparams
[14] https://url.spec.whatwg.org/#api
[15] https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
[16] https://url.spec.whatwg.org/#concept-url
[17] https://url.spec.whatwg.org/#concept-url-query
[18] https://www.w3.org/TR/html4/references.html#ref-RFC1738
[19] https://github.com/jridgewell/safe-decode-uri-component
[20] https://stackoverflow.com/questions/29175465/body-parser-extended-option-qs-vs-querystring/29177740#29177740
[21] https://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2
[22] https://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys
[23] https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
[24] https://url.spec.whatwg.org/#interface-urlsearchparams
[25] https://datatracker.ietf.org/doc/html/rfc1738
[26] https://www.w3.org/International/O-URL-code.html
關(guān)注「字節(jié)前端 ByteFE」公眾號(hào),追更不迷路!
文末彩蛋 >>
碼上掘金正在舉辦第 2 期月賽,在這里邀請(qǐng)大家參與「互動(dòng)抽獎(jiǎng)」活動(dòng),參與互動(dòng)即可抽獎(jiǎng)哦~活動(dòng)玩法請(qǐng)看這里 >>
https://juejin.cn/post/7213184860675571749/
家好,我是站長(zhǎng) polarisxu。
看到標(biāo)題,大家應(yīng)該知曉今天聊的主角是誰(shuí)。是的,它就是 PHP。
PHP 曾經(jīng)很輝煌,現(xiàn)在怎么樣?不做過(guò)多評(píng)價(jià),前幾天好未來(lái)不剛組織了一屆 PHP 大會(huì)嗎?!正因?yàn)樵?jīng)很輝煌,很多現(xiàn)在的 Go 愛(ài)好者曾經(jīng)都是 PHPer,應(yīng)該還有不少還在用著 PHP。我覺(jué)得完全沒(méi)必要非得貶低一門(mén)語(yǔ)言去抬高另外一門(mén)語(yǔ)言,自己喜歡就好。而且掌握多門(mén)語(yǔ)言是自己的優(yōu)勢(shì)。
為什么聊 PHP,因?yàn)槲乙矊?xiě)了好幾年 PHP,而且現(xiàn)在也會(huì)關(guān)注 PHP 的一些動(dòng)態(tài)。PHP 8 發(fā)布差不多半個(gè)月了,有些人可能根本不知曉,還停留在 PHP 5.x。沒(méi)想到吧,一眨眼,PHP 8 都發(fā)布了。
關(guān)于版本的那些事,這里不探討,主要看看 PHP 8 有哪些新特性。另外,本文只會(huì)講述新特性的一些關(guān)鍵點(diǎn),因?yàn)楣俜轿臋n對(duì)它們已經(jīng)有更詳細(xì)的介紹,你應(yīng)該認(rèn)真閱讀官方文檔。
說(shuō)明一點(diǎn),從 PHP 7 開(kāi)始,支持下面這樣的語(yǔ)法:
function sum(int $a, int $b): int {
return $a + $b;
}
是不是越來(lái)越強(qiáng)類型的感覺(jué)?雖然如此,但在非嚴(yán)格類型模式下(strict_types=0,這是默認(rèn)值),你依然可以這么調(diào)用:
sum(1.2, 3);
但因?yàn)楹瘮?shù)參數(shù)接收 int 類型(返回值也是 int 類型),因此上面結(jié)果是 4,而不是 4.2。如果是嚴(yán)格模式下,只允許傳遞 int 類型了。(sum('1.2', 3.0) 結(jié)果也是 4)
如果希望結(jié)果輸出 4.2,同時(shí)又保持類型約束,怎么辦?PHP 不支持方法重載。這就有了 PHP 8 的聯(lián)合類型。
聯(lián)合類型接受多個(gè)不同的類型做為參數(shù)。聲明聯(lián)合類型的語(yǔ)法為 T1|T2|...。
所以,上面代碼可以改為:
function sum(int|float $a, int|float $b): int|float{
return $a + $b;
}
這樣 sum(1.2, 3) 的結(jié)果就是 4.2 了。
一些注意事項(xiàng):
還有其他一些細(xì)節(jié)點(diǎn),詳情請(qǐng)?jiān)L問(wèn)官方文檔查看:https://www.php.net/manual/zh/language.types.declarations.php。
不得不說(shuō),也許越來(lái)越意識(shí)到弱類型的問(wèn)題,PHP 這是在做強(qiáng)類型的事情。然而,不少人要說(shuō)了,搞這么費(fèi)勁、這么復(fù)雜,還不如直接換強(qiáng)類型語(yǔ)言呢?!你覺(jué)得呢?
當(dāng)然,你完全可以忽略聯(lián)合類型,繼續(xù)使用 5.x 的方式寫(xiě) PHP。
這個(gè)特性還是很棒的。了解 Python 的朋友應(yīng)該對(duì)這個(gè)特性很熟悉。這樣一來(lái),PHP 的函數(shù)支持不定參數(shù)、參數(shù)默認(rèn)值、命名參數(shù)等。相對(duì)來(lái)說(shuō),Go 的函數(shù)還是弱很多。
比如 htmlspecialchars 函數(shù)簽名如下:
htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = TRUE ]]] ) : string
PHP 8 之前,如果想要最后一個(gè)參數(shù)傳遞 false,需要這么調(diào)用:
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
而有了命名參數(shù)后(PHP 8),可以這么調(diào)用:
htmlspecialchars($string, double_encode: false);
簡(jiǎn)單清晰。
總結(jié)一下就是:
命名參數(shù)確實(shí)帶來(lái)了不少便利。不過(guò)我覺(jué)得也有一些要注意的點(diǎn):
實(shí)際中我們經(jīng)常通過(guò) state 來(lái)表示各種狀態(tài),比如:0-待審核;1-上線;2-下線;3-刪除。因?yàn)閿?shù)據(jù)庫(kù)中存的數(shù)字,但顯示希望是文字說(shuō)明。這時(shí)一般有兩種做法:
switch ($state) {
case 0:
$stateDesc = '待審核';
break;
case 1:
$stateDesc = '上線';
break;
case 2:
$stateDesc = '下線';
break;
case 3:
$stateDesc = '刪除';
break;
}
echo $stateDesc;
我個(gè)人喜歡通過(guò) map 來(lái)實(shí)現(xiàn):
$stateMap = [
0 => '待審核',
1 => '上線',
2 => '下線',
3 => '刪除',
];
echo $stateMap[$state];
PHP 8 針對(duì)這樣的場(chǎng)景提供了 match 表達(dá)式:
echo match($state) {
0 => '待審核',
1 => '上線',
2 => '下線',
3 => '刪除',
};
可見(jiàn) match 類似于 switch 語(yǔ)句,有如下特點(diǎn):
更多信息查看官方文檔:https://www.php.net/manual/zh/control-structures.match.php。
了解 Swift 之類的語(yǔ)言,應(yīng)該知曉其中的可選型。PHP 8 新增的這個(gè)特性,我覺(jué)得多少有點(diǎn)可選型的意思。
在 PHP 7 中的如下代碼:
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
在 PHP 8 中簡(jiǎn)化為:
$country = $session?->user?->getAddress()?->country;
PHP 8 起構(gòu)造器的參數(shù)可以提升為類的屬性。構(gòu)造器的參數(shù)賦值給類屬性的行為很普遍,否則無(wú)法操作。而構(gòu)造器提升的功能則為這種場(chǎng)景提供了便利。例如下面的代碼:
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
改為 PHP 8 的方式:
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
PHP 8 比較數(shù)字字符串(numeric string)時(shí),會(huì)按數(shù)字進(jìn)行比較。不是數(shù)字字符串時(shí),將數(shù)字轉(zhuǎn)化為字符串,按字符串比較。
這一點(diǎn)要注意,之前這樣的代碼:
0 == 'foobar' // true
現(xiàn)在是 false:
0 == 'foobar' // fals
更多說(shuō)明參見(jiàn)這里:https://wiki.php.net/rfc/string_to_number_comparison。
現(xiàn)在可以用 PHP 原生語(yǔ)法來(lái)使用結(jié)構(gòu)化的元數(shù)據(jù),而非 PHPDoc 聲明。
之前這么寫(xiě):
class PostsController{
/**
* @Route("/api/posts/{id}", methods={"GET"})
*/
public function get($id) { /* ... */ }
}
現(xiàn)在這么寫(xiě):
class PostsController{
#[Route("/api/posts/{id}", methods: ["GET"])]
public function get($id) { /* ... */ }
}
PHP 8 引入了兩個(gè)即時(shí)編譯引擎。Tracing JIT 在兩個(gè)中更有潛力,它在綜合基準(zhǔn)測(cè)試中顯示了三倍的性能, 并在某些長(zhǎng)時(shí)間運(yùn)行的程序中顯示了 1.5-2 倍的性能改進(jìn)。典型的應(yīng)用性能則和 PHP 7.4 不相上下。
官方給了一個(gè)性能測(cè)試:
PHP 8 還有很多其他改動(dòng),在這里有詳細(xì)的說(shuō)明:https://www.php.net/releases/8.0/zh.php。其中新增了 3 個(gè)函數(shù)實(shí)用的函數(shù):str_contains()、str_starts_with() 和 str_ends_with()。(Go 表示第一天就有了)
這里面的新特性,命名參數(shù)我個(gè)人還是比較喜歡。你呢?
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。