PHP - 使用点表示法

时间:2016-08-19 18:53:58

标签: php arrays

我正在构建REST API,所有数据都以JSON的形式返回。每个请求都通过一个函数进行汇总,该函数负责设置HTTP状态代码,返回消息或数据,设置标题等。我还允许用户添加?fields=参数,以指定他们想要返回的字段(例如?fields=id,hostnames,ip_addresses),如果参数不存在,它们当然会返回所有数据。执行此操作的函数也是所提及的函数的一部分,用于设置标题/数据/消息等。我希望能够做的是允许用户使用点表示法指定字段名称,以便他们可以指定某些字段除了顶级字段。例如,我有一个这样的结构:

{
    "id": "8a2b449111b449409c465c66254c6fcc",
    "hostnames": [
        "webapp1-sfo",
        "webapp1-sfo.example.com"
    ],
    "ip_addresses": [
        "12.26.16.10",
        "ee80::ae56:2dff:fd89:7868"
    ],
    "environment": "Production",
    "data_center": "sfo",
    "business_unit": "Operations",
    "hardware_type": "Server",
    "currently_in_maintenance": false,
    "history": [
        {
            "id": 58,
            "time_start_utc": "2013-01-27 00:40:00",
            "time_end_utc": "2013-01-27 01:45:00",
            "ticket_number": "CHG123456",
            "reason": "January production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/58"
                }
            ]
        },
        {
            "id": 104,
            "time_start_utc": "2013-02-25 14:36:00",
            "time_end_utc": "2013-02-25 18:36:00",
            "ticket_number": "CHG456789",
            "reason": "February production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/104"
                }
            ]
        },
        {
            "id": 143,
            "time_start_utc": "2013-03-17 00:30:00",
            "time_end_utc": "2013-03-17 01:55:00",
            "ticket_number": "CHG789123",
            "reason": "March production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/143"
                }
            ]
        }
    ]
}

使用此功能,我可以提取顶级字段(其中$mData是上面的数据结构,$sParams是用户请求的字段串):

private function removeFields($mData, $sParams){
    $clone = $mData;  // Clone the original data
    $fields = explode(',', $sParams);

    // Remove fields not requested by the user

    foreach($mData as $key => $value){
        if(!in_array((string)$key, $fields)){
            unset($mData[$key]);
        }
    }

    // If no fields remain, restore the original data
    // Chances are the user made a typo in the fields list

    if(count($mData) == 0){
        $mData = $clone;
    }

    return $mData;
}

注意:$sParams以字符串形式出现,是用户提供的内容(以逗号分隔的字段列表)。

所以?fields=hostnames,history会返回:

{
    "hostnames": [
        "webapp1-sfo",
        "webapp1-sfo.example.com",
    ],
    "history": [
        {
            "id": 58,
            "time_start_utc": "2013-01-27 00:40:00",
            "time_end_utc": "2013-01-27 01:45:00",
            "ticket_number": "CHG123456",
            "reason": "January production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/58"
                }
            ]
        },
        {
            "id": 104,
            "time_start_utc": "2013-02-25 14:36:00",
            "time_end_utc": "2013-02-25 18:36:00",
            "ticket_number": "CHG456789",
            "reason": "February production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/104"
                }
            ]
        },
        {
            "id": 143,
            "time_start_utc": "2013-03-17 00:30:00",
            "time_end_utc": "2013-03-17 01:55:00",
            "ticket_number": "CHG789123",
            "reason": "March production maintenance",
            "links": [
                {
                    "rel": "self",
                    "link": "https://localhost/api/v1/maintenances/143"
                }
            ]
        }
    ]
}

但是,如果我想从ticket_number返回history字段,我希望用户能够?fields=history.ticket_number,或者如果他们想要票号和链接他们可以做这个:?fields=history.ticket_number,history.links.link ...会返回:

{
    "history": [
        {
            "ticket_number": "CHG123456",
            "links": [
                {
                    "link": "https://localhost/api/v1/maintenances/58"
                }
            ]
        },
        {
            "ticket_number": "CHG456789",
            "links": [
                {
                    "link": "https://localhost/api/v1/maintenances/104"
                }
            ]
        },
        {
            "ticket_number": "CHG789123",
            "links": [
                {
                    "link": "https://localhost/api/v1/maintenances/143"
                }
            ]
        }
    ]
}

我尝试了许多不同的数组访问方法,用于堆栈溢出中的点符号,但是当history的值是数字数组时它们都会中断...所以例如,使用我在网上找到的方法到目前为止,我需要做这样的事情来实现上面相同的输出(这显然不是很好......特别是当你有数百条记录时)。

?fields=history.0.ticket_number,history.0.links.0.link,history.1.ticket_number,history.1.links.0.link,history.2.ticket_number,history.2.links.0.link,

我也在寻找动态和递归的东西,因为每个API端点返回不同的数据结构(例如,当请求集合时,它返回一个填充了关联数组的数字数组。或者在json中,一个数组对象......以及其中一些对象可能有数组(数字或关联)。)

提前致谢

P.S。 - 我真的不在乎代码是否创建了包含所请求数据的新数据数组,或者直接操作原始数据(就像我在removeFields()函数中那样)。

更新:我已经创建了一个PHPFiddle,希望能够显示我遇到过的问题。 http://phpfiddle.org/main/code/tw1i-qu7s

3 个答案:

答案 0 :(得分:2)

感谢您的提示和帮助。我实际上今天早上提出了一个解决方案,它似乎适用于我迄今为止测试的每个案例。它可能不是超级优雅,但适用于我需要的东西。我基本上使用点符号将数组展平为扁平数组中的键。然后我获取每个请求的字段并构建一个正则表达式(基本上用一个可选的.[digit].替换任何“。”以捕获数字索引),然后使用正则表达式测试每个字段名称,保留匹配的字段名称。最后,我将数组重新扩展为多维数组。

扁平阵列变为:

Array
(
    [id] => 8a2b449111b449409c465c66254c6fcc
    [hostnames.0] => webapp1-sfo
    [hostnames.1] => webapp1-sfo.example.com
    [ip_addresses.0] => 12.26.16.10
    [ip_addresses.1] => ee80::ae56:2dff:fd89:7868
    [environment] => Production
    [data_center] => sfo
    [business_unit] => Operations
    [hardware_type] => Server
    [currently_in_maintenance] => 
    [history.0.id] => 58
    [history.0.time_start_utc] => 2013-01-27 00:40:00
    [history.0.time_end_utc] => 2013-01-27 01:45:00
    [history.0.ticket_number] => CHG123456
    [history.0.reason] => January production maintenance
    [history.0.links.0.rel] => self
    [history.0.links.0.link] => https://localhost/api/v1/maintenances/58
    [history.1.id] => 104
    [history.1.time_start_utc] => 2013-02-25 14:36:00
    [history.1.time_end_utc] => 2013-02-25 18:36:00
    [history.1.ticket_number] => CHG456789
    [history.1.reason] => February production maintenance
    [history.1.links.0.rel] => self
    [history.1.links.0.link] => https://localhost/api/v1/maintenances/104
    [history.2.id] => 143
    [history.2.time_start_utc] => 2013-03-17 00:30:00
    [history.2.time_end_utc] => 2013-03-17 01:55:00
    [history.2.ticket_number] => CHG789123
    [history.2.reason] => March production maintenance
    [history.2.links.0.rel] => self
    [history.2.links.0.link] => https://localhost/api/v1/maintenances/143
)

以下是展平和展开数组的两个函数:

function flattenArray($aArrayToFlatten, $sSeparator = '.', $sParentKey = NULL){
    if(!is_array($aArrayToFlatten)){
        return $aArrayToFlatten;
    }
    $_flattened = array();

    // Rewrite keys

    foreach($aArrayToFlatten as $key => $value){
        if($sParentKey !== NULL){
            $key = $sParentKey . $sSeparator . $key;
        }
        $_flattened[$key] = flattenArray($value, $sSeparator, $key);
    }

    // Flatten

    $flattened = array();
    foreach($_flattened as $key => $value){
        if(is_array($value)){
            $flattened = array_merge($flattened, $value);
        }else{
            $flattened[$key] = $value;
        }
    }

    return $flattened;
}

function expandArray($aFlattenedArray, $sSeparator = '.'){
    $result = array();
    foreach($aFlattenedArray as $key => $val){
        $keyParts = explode($sSeparator, $key);
        $currentArray = &$result;
        for($i = 0; $i < count($keyParts) - 1; $i++){
            if(!isset($currentArray[$keyParts[$i]])){
                $currentArray[$keyParts[$i]] = array();
            }
            $currentArray = &$currentArray[$keyParts[$i]];
        }
        $currentArray[$keyParts[count($keyParts)-1]] = $val;
    }

    return $result;
}

示例:

$mData = json_decode('{ "id": "8a2b449111b449409c465c66254c6fcc", "hostnames": [ "webapp1-sfo", "webapp1-sfo.example.com" ], "ip_addresses": [ "12.26.16.10", "ee80::ae56:2dff:fd89:7868" ], "environment": "Production", "data_center": "sfo", "business_unit": "Operations", "hardware_type": "Server", "currently_in_maintenance": false, "history": [ { "id": 58, "time_start_utc": "2013-01-27 00:40:00", "time_end_utc": "2013-01-27 01:45:00", "ticket_number": "CHG123456", "reason": "January production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/58" } ] }, { "id": 104, "time_start_utc": "2013-02-25 14:36:00", "time_end_utc": "2013-02-25 18:36:00", "ticket_number": "CHG456789", "reason": "February production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/104" } ] }, { "id": 143, "time_start_utc": "2013-03-17 00:30:00", "time_end_utc": "2013-03-17 01:55:00", "ticket_number": "CHG789123", "reason": "March production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/143" } ] } ] }', TRUE);

print_r($mData);   // Original Data

$fields = array("id", "hostnames", "history.id", "history.links.link");
$regexFields = array();

// Build regular expressions for each of the requested fields

foreach($fields as $dotNotatedFieldName){

    // Requested field has a dot in it -- it's not a top-level field
    // It may be part of a collection (0.fieldname.levelTwo, 1.fieldName.levelTwo,...) or be a collection (fieldName.0.levelTwo, fieldName.1.levelTwo, ...)

    if(preg_match('/\./', $dotNotatedFieldName)){
        $regexFields[] = "^\d*\.?" . str_replace(".", "\.\d*\.?", $dotNotatedFieldName);

    // Requested field does not have a dot in it -- it's a top-level field
    // It may be part of a collection (0.fieldname, 1.fieldName,...) or be a collection (fieldName.0, fieldName.1, ...)

    }else{
        $regexFields[] = "^\d*\.?" . $dotNotatedFieldName . "\.?\d*";
    }
}

// Flatten the array

$flattened = flattenArray($mData);

// Test each flattened key against each regular expression and remove those that don't match

foreach($flattened as $key => $value){
    $matchFound = FALSE;

    foreach($regexFields as $regex){
        if(preg_match('/' . $regex . '/', $key)){
            $matchFound = TRUE;
            break;
        }
    }

    if($matchFound === FALSE){
        unset($flattened[$key]);
    }

}

// Expand the array

$mData = expandArray($flattened);

print_r(json_encode($mData));  // New Data

哪个输出以下JSON:

{
   "id": "8a2b449111b449409c465c66254c6fcc",
   "hostnames": [
      "webapp1-sfo",
      "webapp1-sfo.example.com"
   ],
   "history": [
      {
         "id": 58,
         "links": [
            {
               "link": "https://localhost/api/v1/maintenances/58"
            }
         ]
      },
      {
         "id": 104,
         "links": [
            {
               "link": "https://localhost/api/v1/maintenances/104"
            }
         ]
      },
      {
         "id": 143,
         "links": [
            {
               "link": "https://localhost/api/v1/maintenances/143"
            }
         ]
      }
   ]
}

答案 1 :(得分:0)

不是从克隆中删除字段,而是在字段上循环,然后将每个字段都放入克隆中

你需要一个函数让点符号遍历你的json。像这个https://selvinortiz.com/blog/traversing-arrays-using-dot-notation

private function buildResponse($mData, $sParams){
    $fields = explode(',', $sParams['fields']);
    $response = $mData;

     if (count($fields > 0)){
       $response = [];
       foreach($fields as $value){
         if (getDotValue($mData, $value)){
           $response[$value] = $mData.getDotValue($value);
         }
       }
     }

     return json_encode($response);
}

代码未经过测试,但您可以理解我的观点。

答案 2 :(得分:0)

运行支票

if (isset($field=>$nextfield)) { //if next field level is named
  //do magic here
} else if (isset($field[0])) { //if next field level is indexed
  for ($i=0; $i < count($field); $i++) { 
    //catch 'em all
  }
}

例如,history.id会抓住if上的history,编号索引上的else if,然后是if id }。

希望有所帮助。