正文
在团队开发中,有些数据是比较敏感的,比如mysql密码等,这些信息最好能存在团队成员接触不到的服务器上。 但是应用怎么读取这些信息呢,所以有了这个配置中心package:旨在独立与整合应用配置参数,使项目更安全。
这里选用的包是: peachpear/pear-leaf
源码
只有一个类:peachpear\pearLeaf\ConfigService
namespace peachpear\pearLeaf;
/**
* 参数配置服务类
* Class ConfigService
* @package peachpear\pearLeaf
*/
class ConfigService
{
/**
* @var string
* 应用名称
*/
private $appName;
/**
* @var string
* 应用版本号
*/
private $version;
/**
* @var string
* 文件路径
*/
private $filePath = "";
/**
* @var string
* 文件后缀名
*/
private $fileExt = "json";
/**
* @var string
* 文件名称
*/
private $fileName = "";
/**
* @var string
* 完整路径文件名
*/
private $fullName;
/**
* @var array
* 配置参数
*/
private $config = [];
/**
* @var array
*
*/
private $rhKey = [
"mysql" => 1,
"redis" => 1,
"kafka" => 1,
"params" => 1,
"queue" => 1,
"queue_log" => 1,
];
/**
* @var
* 实例
*/
private static $instance;
/**
* 类构建函数
* ConfigService constructor.
* @param $filePath
* @param $fileExt
*/
private function __construct($filePath, $fileExt)
{
$this->setAppName();
$this->setVersion();
$this->setFilePath($filePath);
$this->setFileExtension($fileExt);
$this->setFileName();
$this->setFullName();
}
/**
* 获取实例
* @param string $filePath
* @param string $fileExt
* @return ConfigService
*/
public static function getInstance($filePath = "/data/config/", $fileExt = "json")
{
if (!self::$instance) {
self::$instance = new self($filePath, $fileExt);
}
return self::$instance;
}
/**
* @param null $config
*/
public function loadJson($config = null)
{
if (file_exists($this->getFullName())) {
$jsonData = file_get_contents($this->fullName);
if ($jsonData) {
// 存在数据
$server_config = $this->handleData(json_decode($jsonData, true), $config);
$this->setConfig($server_config);
}
}
}
/**
* 设置配置参数
* @param $config
*/
public function setConfig( $config = [] )
{
$this->config = $config;
}
/**
* 获取配置参数
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* 设置应用名称
*/
public function setAppName()
{
$this->appName = defined("APP_NAME") ? APP_NAME : "demo";
}
/**
* 获取应用名称
* @return string
*/
public function getAppName()
{
return $this->appName;
}
/**
* 设置应用版本号
*/
public function setVersion()
{
$this->version = defined("VERSION") ? VERSION : "*";
}
/**
* 获取应用版本号
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* 设置文件路径
* @param $filePath
*/
public function setFilePath($filePath)
{
if ($filePath) {
$this->filePath = $filePath;
}
}
/**
* 获取文件路径
* @return string
*/
public function getFilePath()
{
return $this->filePath;
}
/**
* 设置文件后缀名
* @param $fileExt
*/
public function setFileExtension($fileExt)
{
$this->fileExt = $fileExt;
}
/**
* 获取文件后缀名
* @return string
*/
public function getFileExtension()
{
return $this->fileExt;
}
/**
* 设置文件名称
*/
public function setFileName()
{
if (!$this->fileName) {
$this->fileName = $this->getAppName() . "." . $this->getFileExtension();
}
}
/**
* 获取文件名称
* @return string
*/
public function getFileName()
{
return $this->fileName;
}
/**
* 设置完整路径文件名
*/
public function setFullName()
{
$this->fullName = $this->getFilePath() . $this->getFileName();
}
/**
* 获取完整路径文件名
* @return string
*/
public function getFullName()
{
return $this->fullName;
}
/**
* @param $config
* @param null $oldConfig
* @return array|null
*/
private function handleData($config, $oldConfig = null)
{
if (!$config) {
return null;
}
// 排序
foreach ($config as $k => $v )
{
foreach ($v as $kk => $vv )
{
uasort($vv, array($this, "versionSort"));
$config[$k][$kk] = array_merge($vv); // array_merge重新索引键名
}
}
$returnConfig = [];
foreach ($config as $key => $value)
{
if ($key == $this->getParamsKey()) {
$key = "params";
}
if (isset($this->rhKey[$key])) {
foreach ($value as $kk => $vv)
{
foreach ($vv as $kkk => $vvv)
{
// 是否有可用的匹配版本配置
if (!$this->matchVersion($vvv)){
continue; // 如果无,跳过本次循环
}
if ($key == "mysql") {
$mysqlKey = $this->getMysqlKey($vvv);
if (!isset($oldConfig["components"][$mysqlKey])) {
continue;
}
if (strpos($kk, "master") !== false) {
$returnConfig["components"][$mysqlKey] = [
"dsn" => $this->getDSN($vvv),
"username" => $vvv["username"],
"password" => $vvv["password"],
];
} elseif (strpos($kk, "slave") !== false) {
$returnConfig["components"][$mysqlKey]["slaves"][] = [
"dsn" => $this->getDSN($vvv),
"username" => $vvv["username"],
"password" => $vvv["password"],
];
}
} elseif ($key == "redis") {
if (!isset($oldConfig["components"][$kk])) {
continue;
}
$returnConfig["components"][$kk] = [
'host' => $vvv["host"],
'port' => $vvv["port"],
"password" => empty($vvv["password"]) ? "" : trim($vvv["password"])
];
if (isset($vvv["database"])) {
$returnConfig["components"][$kk]["database"] = $vvv["database"];
}
if (isset($vvv["keyPrefix"])) {
$returnConfig["components"][$kk]["keyPrefix"] = $vvv["keyPrefix"];
}
} elseif ($key == "kafka") {
if (!isset($oldConfig["components"]["kafkaProducer"])) {
continue;
}
$returnConfig["components"]["kafkaProducer"]["metadata"]["brokerList"] = $this->getKafkaBrokerList($vvv["brokerList"]);
} elseif (strpos($key, "queue") !== false) {
if (!isset($oldConfig["components"][$key])) {
continue;
}
$returnConfig["components"][$key]["credentials"] = [
'host' => $vvv["host"],
'port' => $vvv["port"],
'login' => $vvv["login"],
'password' => $vvv["password"],
];
} elseif ($key == "params") {
if (isset($vvv["version"])) {
unset($vvv["version"]);
}
if (isset($returnConfig["params"])) {
$returnConfig["params"] = $vvv + $returnConfig["params"];
} else {
$returnConfig["params"] = $vvv;
}
}
}
}
}
}
return $returnConfig;
}
/**
* 针对version排序
* @param $a
* @param $b
* @return int
*/
private function versionSort($a, $b)
{
if (!isset($a["version"]) || $a["version"][0] == "*") {
return -1; // $a排到$b前
} else if (!isset($b["version"]) || $b["version"][0] == "*") {
return 1; // $b排到$a前
} else {
return 0; // 维持现状
}
}
/**
* 获取应用params名
*/
private function getParamsKey()
{
return $this->getAppName()."-params";
}
/**
* 匹配版本
* @param array $array
* @return bool
*/
private function matchVersion(array $array)
{
if (!isset($array["version"])) {
return true;
}
if (is_array($array["version"])) {
foreach ($array["version"] as $version)
{
if ($version == $this->getVersion() || $version == "*") {
return true;
}
}
} elseif (is_string($array["version"])) {
if ($array["version"] == $this->getVersion() || $array["version"] == "*") {
return true;
}
}
return false;
}
/**
* @param array $array
* @return string
*/
private function getMysqlKey(array $array)
{
if (empty($array["db"])) {
return "";
}
return $array["db"] ."DB";
}
/**
* 获取数据源名称
* @param array $array
* @return string
*/
private function getDSN(array $array)
{
if (empty($array["host"]) || empty($array["port"]) || empty($array["db"])) {
return "";
}
return "mysql:host=" .$array["host"] .";port=".$array["port"] .";dbname=" .$array["db"];
}
/**
* 获取Kafka的brokerList
* @param array $array
* @return string
*/
private function getKafkaBrokerList(array $array)
{
$return = "";
foreach ($array as $value)
{
$return .= $value["host"].":".$value["port"].",";
}
return rtrim($return, ",");
}
}
使用示例
如果我们在Yii2中使用时,项目入口这里会传入配置参数 $config:
$application = new yii\web\Application($config);
$application->run();
这里的 $config 就是子项目配置和服务器配置中心 配置参数 合并处理后的结果:
$config = yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../common/config/main.php'),
require(__DIR__ . '/../../common/config/main-local.php'),
require(__DIR__ . '/../config/main.php'),
require(__DIR__ . '/../config/main-local.php')
);
// 加载配置中心文件,替换config
if (!empty($config["configService"])) {
$filePath = $config["configService"]["filePath"];
$fileExtension = $config["configService"]["fileExt"];
$configService = peachpear\pearLeaf\ConfigService::getInstance($filePath, $fileExtension);
$configService->loadJson($config); // 敏感参数确认
$config = yii\helpers\ArrayHelper::merge($config, $configService->getConfig()); // 参数合并
unset($config["configService"]);
}
当前子项目的配置中配置了配置中心所在的参数:
defined('APP_NAME') || define('APP_NAME', 'demo'); // 定义项目名称,配置中心也会用到
defined('VERSION') or define('VERSION', '2.0.2'); // 定义项目版本号,如 2.0.2
return [
'components' => [
'cache' => [
'class' => 'common\components\LRedisCache',
'hashKey' => false,
'host' => 'test.redis.demo.com',
'port' => 6379,
'keyPrefix' => 'demoDev.',
],
'demoDB' => [
'class' => '\yii\db\Connection',
'charset' => 'utf8mb4',
'enableQueryCache' => false,
'dsn' => 'mysql:host=192.168.100.2;port=3306;dbname=demo',
'username' => 'demo',
'password' => 'demo_+-*123',
],
'kafkaProducer' => [
"class" => 'common\components\LKafkaProducerQueue',
"metadata" => [
"brokerList" => "192.168.40.122:9092",
],
"requireAck" => 0,
],
'queue' => [
"class" => 'common\components\LRabbitQueue',
'credentials' => [
'host' => 'test.rabbitmq.demo.com',
'port' => '5672',
'login' => 'mqadmin',
'password' => 'demo_+-*123'
]
],
...
],
'params' => [
'ticket' => [
'api_url' => 'http://dev.demo.com',
'api_secret' => 'devf6bcd4341d373cade4e832456b4f7',
],
...
],
// 配置中心所在参数
'configService' => [
'filePath' => '/config/dev/',
'fileExt' => 'json',
]
];
配置中心配置文件/config/dev/demo.json:
{
"demo-params":{
"grab":[
{
"grab":{"domain":"http://test.service.demo.com"},
"version":"2.0.2"
},
{
"grab":{"domain":"http://dev.service.demo.com"},
"version":"*"
}
],
"path":[
{
"path":"http://path.demo.com/",
"version":"*"
}
]
},
"mysql":{
"master":[
{
"db":"peach",
"host":"192.168.18.67",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":"*"
},
{
"db":"pear",
"host":"192.168.18.100",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":["2.0.1","2.0.2"]
},
{
"db":"pear",
"host":"192.168.18.101",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":"*"
}
],
"slaves":[
{
"db":"peach",
"host":"192.168.18.69",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":"*"
},
{
"db":"pear",
"host":"192.168.18.105",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":["2.0.1","2.0.2"]
},
{
"db":"pear",
"host":"192.168.18.106",
"password":"demo_+123*",
"port":3306,
"username":"demo",
"version":["*"]
}
]
},
"redis":{
"cache":[
{
"database":0,
"host":"test.redis.demo.com",
"port":6379
}
]
},
"kafka":{
"master":[
{
"brokerList":[
{
"host":"192.168.40.102",
"port":9092
},
{
"host":"192.168.40.112",
"port":9092
}
]
}
]
},
"queue":{
"master":[
{
"host":"test.rabbitmq.demo.com",
"login":"mqadmin",
"password":"demo_+123*",
"port":5672
}
]
}
}
可以看出,这里是一些比较敏感的数据。 redis多用作cache缓存,所以上面这样赋值。
代码分析
$configService = peachpear\pearLeaf\ConfigService::getInstance($filePath, $fileExtension) :
/**
* 获取实例
* @param string $filePath
* @param string $fileExt
* @return ConfigService
*/
public static function getInstance($filePath = "/data/config/", $fileExt = "json")
{
if (!self::$instance) {
self::$instance = new self($filePath, $fileExt);
}
return self::$instance;
}
这是一个典型的单例模式。
看一下对象构造相关函数:
/**
* 类构建函数
* ConfigService constructor.
* @param $filePath
* @param $fileExt
*/
private function __construct($filePath, $fileExt)
{
$this->setAppName();
$this->setVersion();
$this->setFilePath($filePath);
$this->setFileExtension($fileExt);
$this->setFileName();
$this->setFullName();
}
用上面的示例配置后,配置中心对象的属性为:
private $appName = 'demo';
private $version = '*';
private $filePath = '/config/dev/';
private $fileExt = 'json';
private $fileName = 'demo.json';
private $fullName = '/config/dev/demo.json';
$configService->loadJson($config) :
/**
* @param null $config
*/
public function loadJson($config = null)
{
if (file_exists($this->getFullName())) {
$jsonData = file_get_contents($this->fullName);
if ($jsonData) {
// 存在数据
$server_config = $this->handleData(json_decode($jsonData, true), $config);
$this->setConfig($server_config);
}
}
}
传入的$config是子项目的配置参数,$jsonData是服务器配置中心的数据; setConfig($server_config)是把处理后的子项目会用到的敏感配置参数赋值为本对象的config参数,以备getConfig()获取指定敏感配置参数; 重点我们看一下该配置中心对象是怎么处理取出子项目需要的敏感配置参数的,handleData(json_decode($jsonData, true), $config)。
函数准备
/**
* 针对version排序,通用优先
* @param $a
* @param $b
* @return int
*/
private function versionSort($a, $b)
{
// 如果$a的version没有设置,或者设置为*(字符串可用数组方式访问,键名默认从0开始),则$a排到$b前
if (!isset($a["version"]) || $a["version"][0] == "*") {
return -1;
// 如果$b的version没有设置,或者设置为*(字符串可用数组方式访问,键名默认从0开始),则$b排到$a前
} else if (!isset($b["version"]) || $b["version"][0] == "*") {
return 1;
// 否则维持现状
} else {
return 0;
}
}
/**
* 匹配版本
* @param array $array
* @return bool
*/
private function matchVersion(array $array)
{
// 如果参数中无version字段,则返回真,说明该版本匹配
if (!isset($array["version"])) {
return true;
}
// 参数中version字段可能是数组,如 "version":["2.0.1","2.0.2"]
if (is_array($array["version"])) {
foreach ($array["version"] as $version)
{
// 如果该参数的版本号等于项目版本号,或者可通用,则返回真
if ($version == $this->getVersion() || $version == "*") {
return true;
}
}
// 参数中version字段是字符串
} elseif (is_string($array["version"])) {
// 如果该参数的版本号等于项目版本号,或者可通用,则返回真
if ($array["version"] == $this->getVersion() || $array["version"] == "*") {
return true;
}
}
// 其他,返回假
return false;
}
handleData分析
handleData()方法可以说是确认敏感参数是哪些,如mysql、redis、kafka、params、queue等
/**
* @param $config
* @param null $oldConfig
* @return array|null
*/
private function handleData($config, $oldConfig = null)
{
// 如果服务器配置参数为空,则返回空,相当于无敏感配置参数,默认使用子项目配置
if (!$config) {
return null;
}
/**
* 排序
* $k 可能是 demo-params、mysql、redis 等
*/
foreach ($config as $k => $v )
{
/**
* $kk 可能是 mysql下的 master、 slaves 等
* $vv master的具体多组值
*/
foreach ($v as $kk => $vv )
{
uasort($vv, array($this, "versionSort"));
$config[$k][$kk] = array_merge($vv); // array_merge重新索引键名
}
}
$returnConfig = []; // 返回参数
// 以下循环可以说是对子项目参数进行敏感参数的确认
// $key 可能是 demo-params、mysql、redis 等
foreach ($config as $key => $value)
{
// 如果$key是demo-params,则把$key重命名为params,以与子项目params参数同名
if ($key == $this->getParamsKey()) {
$key = "params";
}
// 如果$key正是那些敏感参数
if (isset($this->rhKey[$key])) {
/**
* $kk 可能是 mysql下的 master、 slaves 等
* $vv master的具体多组值
*/
foreach ($value as $kk => $vv)
{
/**
* $kkk master的具体多组值中的一个的键
* $vvv master的具体多组值中的一个的数组值
*/
foreach ($vv as $kkk => $vvv)
{
// 查看该参数数组是否是可用的版本配置,如果不是则跳过本次赋值,只取出子项目能使用的特定版本配置
if (!$this->matchVersion($vvv)){
continue; // 如果无,跳过本次循环
}
if ($key == "mysql") {
// 获取mysql库名,如 peach 返回 peachDB ,所以这里也要求数据库配置在项目中必须以DB结尾
$mysqlKey = $this->getMysqlKey($vvv);
// 如果子项目配置中无该DB配置,则跳过本次赋值
if (!isset($oldConfig["components"][$mysqlKey])) {
continue;
}
// 如果本次循环是主库,则设定为主库配置
if (strpos($kk, "master") !== false) {
$returnConfig["components"][$mysqlKey] = [
"dsn" => $this->getDSN($vvv), // 组装dsn地址,如返回 mysql:host=192.168.18.67;port=3306;dbname=peach
"username" => $vvv["username"],
"password" => $vvv["password"],
];
// 如果本次循环是从库,则补充为从库配置
} elseif (strpos($kk, "slave") !== false) {
$returnConfig["components"][$mysqlKey]["slaves"][] = [
"dsn" => $this->getDSN($vvv),
"username" => $vvv["username"],
"password" => $vvv["password"],
];
}
// redis配置,重点是组织redis连接信息
} elseif ($key == "redis") {
// 如果子项目配置中无该配置,则跳过本次赋值,如redis未用作cache等
if (!isset($oldConfig["components"][$kk])) {
continue;
}
// 设置返回配置
$returnConfig["components"][$kk] = [
'host' => $vvv["host"],
'port' => $vvv["port"],
"password" => empty($vvv["password"]) ? "" : trim($vvv["password"])
];
// 具体设置redis库id(从0开始)
if (isset($vvv["database"])) {
$returnConfig["components"][$kk]["database"] = $vvv["database"];
}
// 设置前缀,如cache缓存前缀demoDev.
if (isset($vvv["keyPrefix"])) {
$returnConfig["components"][$kk]["keyPrefix"] = $vvv["keyPrefix"];
}
// kafka配置
} elseif ($key == "kafka") {
if (!isset($oldConfig["components"]["kafkaProducer"])) {
continue;
}
// 多个ip、port拼装成一个连接串
$returnConfig["components"]["kafkaProducer"]["metadata"]["brokerList"] = $this->getKafkaBrokerList($vvv["brokerList"]);
// queue队列配置
} elseif (strpos($key, "queue") !== false) {
if (!isset($oldConfig["components"][$key])) {
continue;
}
$returnConfig["components"][$key]["credentials"] = [
'host' => $vvv["host"],
'port' => $vvv["port"],
'login' => $vvv["login"],
'password' => $vvv["password"],
];
// params配置
} elseif ($key == "params") {
// 如果params参数中version定义了,取消定义,以备下面赋值中不带上version这个参数
if (isset($vvv["version"])) {
unset($vvv["version"]);
}
if (isset($returnConfig["params"])) {
// 数组可以使用 + 合并,键名相同,取前面的
$returnConfig["params"] = $vvv + $returnConfig["params"];
} else {
$returnConfig["params"] = $vvv;
}
}
}
}
}
}
return $returnConfig;
}
通过分析这段代码,我们可以发现:服务器配置中心文件参数要求组织为3层,k -> kk -> kkk ; v -> vv -> vvv 。 k 是敏感参数键名; kk 是 一些子级配置名,如 MySQL的 master、slaves,redis用作cache等; kkk 没有自定义值,是自增键名,如0、1、2等。 vvv 是 具体的一组配置,含有version参数。
服务器配置中心文件参数经过处理后,如上面的示例demo文件经处理后,形成的参数可能是:
[
'components' => [
'peachDB' => [
'dsn' => 'mysql:host=192.168.18.67;port=3306;dbname=peach',
'username' => 'demo',
'password' => 'demo_+-*123',
'slaves' => [
[
'dsn' => 'mysql:host=192.168.18.69;port=3306;dbname=peach',
'username' => 'demo',
'password' => 'demo_+-*123'
],
]
],
'pearDB' => [
'dsn' => 'mysql:host=192.168.18.101;port=3306;dbname=pear',
'username' => 'demo',
'password' => 'demo_+-*123',
'slaves' => [
[
'dsn' => 'mysql:host=192.168.18.105;port=3306;dbname=pear',
'username' => 'demo',
'password' => 'demo_+-*123'
],
[
'dsn' => 'mysql:host=192.168.18.106;port=3306;dbname=pear',
'username' => 'demo',
'password' => 'demo_+-*123'
]
]
],
'cache' => [
'database' => 0,
'host' => 'test.redis.demo.com',
'port' => 6379
],
'kafkaProducer' => [
'metadata' => [
'brokerList' => '192.168.40.102:9092,192.168.40.112:9092'
],
],
'queue' => [
'credentials' => [
'host' => 'test.rabbitmq.demo.com',
'port' => 5672,
'login' => 'mqadmin',
'password' => 'demo_+123*'
]
]
],
'params' => [
'grab' => [
'domain' => 'http://dev.service.demo.com'
],
'path' => 'http://path.demo.com/'
],
];
然后与子项目配置函数合并,键名相同的参数,服务器配置中心参数覆盖子项目配置。
uasort
uasort — 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
uasort ( array &$array , callable $value_compare_func ) : bool
示例:
// Comparison function,从小到大排序
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
// Array to be sorted
$array = array('a' => 4, 'b' => 8, 'c' => -1, 'd' => -9, 'e' => 2, 'f' => 5, 'g' => 3, 'h' => -4);
// Sort and print the resulting array
uasort($array, 'cmp');
print_r($array);
cmp($a, $b)的$a, $b参数是自动取自数组中的两个值。
看一下结果:
Array
(
[d] => -9
[h] => -4
[c] => -1
[e] => 2
[g] => 3
[a] => 4
[f] => 5
[b] => 8
)
数组合并运算
数组合并可以使用 + 、 array_merge() 、 array_merge_recursive()
+ 两个数组合并,如果键名相同取前面的:
$a = [1, 2, 'a'];
$b = [2, 3, 'b'];
$c = $a + $b;
print_r($c);
Array
(
[0] => 1
[1] => 2
[2] => a
)
数字键名:
$a = [50 => 1, 51 => 2, 52 => 'a'];
$b = [51 => 3, 52 => 4, 53 => 'b'];
$c = $a + $b;
print_r($c);
Array
(
[50] => 1
[51] => 2
[52] => a
[53] => b
)
字符键名:
$a = [50 => 1, 51 => 2, 'a' => 'a', 'b' => 'b'];
$b = [51 => 3, 52 => 4, 'b' => 'c', 'c' => 'd'];
$c = $a + $b;
print_r($c);
Array
(
[50] => 1
[51] => 2
[a] => a
[b] => b
[52] => 4
[c] => d
)
array_merge(array1,array2,array3…) 把一个或多个数组合并为一个数组, 如果两个或更多个数组元素有相同的键名,则最后的元素会覆盖其他元素
$a = [1, 2, 'a'];
$b = [2, 3, 'b'];
$c = array_merge($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[2] => a
[3] => 2
[4] => 3
[5] => b
)
数字键名,完整合并,合并后键名从0开始:
$a = [50 => 1, 51 => 2, 52 => 'a'];
$b = [51 => 2, 52 => 3, 53 => 'b'];
$c = array_merge($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[2] => a
[3] => 2
[4] => 3
[5] => b
)
字符键名,如果两个或更多个数组元素有相同的键名,则最后的元素会覆盖前面元素,里面整数键名还是从0开始:
$a = [50 => 1, 51 => 2, 'a' => 'a', 'b' => 'b'];
$b = [51 => 3, 52 => 4, 'b' => 'c', 'c' => 'd'];
$c = array_merge($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[a] => a
[b] => c
[2] => 3
[3] => 4
[c] => d
)
array_merge_recursive(array1,array2,array3…) 不会进行键名覆盖,而是将多个相同键名的值递归组成一个数组
$a = [1, 2, 'a'];
$b = [2, 3, 'b'];
$c = array_merge_recursive($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[2] => a
[3] => 2
[4] => 3
[5] => b
)
数字键名,完整合并,合并后键名从0开始:
$a = [50 => 1, 51 => 2, 52 => 'a'];
$b = [51 => 2, 52 => 3, 53 => 'b'];
$c = array_merge_recursive($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[2] => a
[3] => 2
[4] => 3
[5] => b
)
字符键名,如果两个或更多个数组元素有相同的键名,则递归组成一个数组:
$a = [50 => 1, 51 => 2, 'a' => 'a', 'b' => 'b'];
$b = [51 => 3, 52 => 4, 'b' => 'c', 'c' => 'd'];
$c = array_merge_recursive($a, $b);
print_r($c);
Array
(
[0] => 1
[1] => 2
[a] => a
[b] => Array
(
[0] => b
[1] => c
)
[2] => 3
[3] => 4
[c] => d
)
参考资料
PHP 手册 函数参考 变量与类型相关扩展 数组 数组 函数 uasort https://www.php.net/uasort