CI3升级至CI4后SimpleXMLElement生成XML显示为纯文本问题
CI4中XML无法格式化展示的问题排查与解决
问题描述
将页面从CodeIgniter 3升级到CodeIgniter 4后,页面中用于在新标签页展示XML数据的按钮出现异常:CI4版本的XML内容以纯文本形式显示,但查看页面源码确认内容确实是XML格式,仅URL与CI3版本不同。
相关页面地址:
- CI3 XML页面:https://www.uvm.edu/femc/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview
- CI4 XML页面:https://dev.vmc.w3.uvm.edu/xana/CI4/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview
当前CI4实现代码
控制器代码
public function eml_xmlview($projecttitle = NULL, $datasettitle = NULL) { //Get project and dataset ids from web title, if no match show 404 $pid=$this->common->get_itemid('project', $projecttitle); $did=$this->common->get_itemid('dataset', $datasettitle); if($pid==-1){ show_404(); } $published=($this->common->check_publish('tblProject', 'fldPublish','pkProjectID',$pid) AND $this->common->check_publish('tblDataset', 'fldPublishMetadata','pkDatasetID',$did)); //Check if the person coming to the management section is allowed to manage this particular project $canmanage=$this->dataset_model->can_manage_dataset($pid,$did,'permission_project_datasets'); //Check whether person logged in can view unpublished data //Find out if this project should be hidden - basically, check if the project is published, or the person is admin, or the person can manage it $show=($published OR $canmanage); if(!$show){ show_404(); } $dataset = $this->dataset_model->get_dataset($pid, $did); $project = (ENVIRONMENT=="production") ? $this->project_model->get_projectverbose($pid) : $this->project_model->get_projectverbose($pid, FALSE); $fields = $this->dataset_model->get_fields($did); $version = 1; //Placeholder in case we want to add version to package ids later if (!empty($project)){ $project=array_pop($project); } $emlstart = <<<XML <eml:eml system="vmc" xmlns:eml="eml://ecoinformatics.org/eml-2.1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="eml://ecoinformatics.org/eml-2.1.1 eml.xsd"></eml:eml> XML; $sxe = new SimpleXMLElement($emlstart); $sxe->addAttribute('packageId' , 'vmc.' . $pid . '.' . $did.'.'.$version); $emldata = $sxe->addChild('dataset'); $pbl = $emldata->addChild('publisher'); $mtp = $pbl->addChild('metadataProvider'); $mtp->addChild('organizationName','Forest Ecosystem Monitoring Cooperative'); $mtp->addChild('phone','(802) 656-0683'); $mtp->addChild('electronicMailAddress','femc@uvm.edu'); $mtp->addChild('onlineUrl','www.uvm.edu/femc'); $adr = $mtp->addChild('address'); $adr->addChild('deliveryPoint','705 Spear Street'); $adr->addChild('city','South Burlington'); $adr->addChild('administrativeArea','Vermont'); $adr->addChild('postalCode','05403'); $adr->addChild('country','United States of America'); //Add project organizations $orgsQry = $this->db->query("SELECT * FROM (SELECT * FROM tblOrganization JOIN tblProjectOrganization on tblOrganization.pkOrganizationID = tblProjectOrganization.fkOrganizationID WHERE fkProjectID = $pid) t GROUP BY t.fkOrganizationID, t.enuOrganizationRole ORDER BY enuOrganizationRole, fldStartDate;"); $orgrolesAr = array(); foreach($orgsQry->getResult() as $row) { $orgrolesAr["$row->enuOrganizationRole"][] = "$row->fldName1 $row->fldName2"; } foreach($orgrolesAr as $role=>$orgAr) { $ap=$emldata->addChild('associatedParty'); foreach($orgAr as $index=>$orgName) { $ap->addChild('organizationName',$this->common->sanitizeForXML($orgName)); } $ap->addChild('role',$role); } //Project-level if($project['enuProjectType'] == 'research'){ $prj = $emldata->addChild('researchProject'); } else{ $prj = $emldata->addChild('project'); } $prj->addChild('title', $this->common->sanitizeForXML($project['fldProjectName'])); $prj->addChild('abstract')->addChild('para', $this->common->sanitizeForXML($project['fldProjectDescription'])); $prj->addChild('shortName',$this->common->sanitizeForXML($project['fldProjectName'])); $prs = $prj->addChild('personnel'); if(!empty($project['people'])){ foreach($project['people'] as $person){ if($person['enuRole']=='principal-investigator'){ $prn = $prs->addChild('principalInvestigator'); } else{ $prn = $prs->addChild('contentProvider'); } $prn->addChild('surName',$this->common->sanitizeForXML($person['fldLastName'])); $prn->addChild('givenName',$this->common->sanitizeForXML($person['fldFirstName'])); } } if(!empty($project['tags'])){ $kws = $prj->addChild('keywordSet'); foreach($project['tags'] as $tag){ $kws->addChild('keyword',$this->common->sanitizeForXML($tag['fldTag'])); } } //Dataset-level $dataTable = $emldata->addChild('dataTable'); $dataTable->addChild('title', $this->common->sanitizeForXML($dataset['fldName'])); $cvr = $dataTable->addChild('coverage'); $cvr->addAttribute('scope','document'); $tmp = $cvr->addChild('temporalCoverage'); $tmp->addAttribute('scope','document'); $rng = $tmp->addChild('rangeOfDates'); if($dataset['fldStartDate'] && $dataset['fldStartDate'] != '0000-00-00'){ $rng->addChild('beginDate')->addChild('calendarDate',$dataset['fldStartDate']); } if($dataset['fldEndDate'] && $dataset['fldEndDate'] != '0000-00-00'){ $rng->addChild('endDate')->addChild('calendarDate',$dataset['fldEndDate']); } $dataTable->addChild('description',$this->common->sanitizeForXML($dataset['fldDescription'])); $dataTable->addChild('purpose',$this->common->sanitizeForXML($dataset['fldPurpose'])); $dataTable->addChild('shortName',$this->common->sanitizeForXML($dataset['fldDatabaseTableName'])); $phy = $dataTable->addChild('physical'); $phy->addChild('objectName','VMC.' . $pid . '.' . $did); $dfo = $phy->addChild('dataFormat'); $dfo->addChild('formatName','mySQL'); $phy->addChild('citation',$this->common->sanitizeForXML($dataset['fldDatasetCitation'])); $phy->addChild('distibution')->addChild('online')->addChild('url', site_url('data/archive/project/'.$this->common->sanitizeForXML($project['fldWebTitle']).'/dataset/'.$this->common->sanitizeForXML($dataset['fldWebTitle']))); $atl = $dataTable->addChild('attributeList'); foreach($fields as $field){ $att = $atl->addChild('attribute'); $att->addChild('attributeName',$this->common->sanitizeForXML($field['fldFieldName'])); if($field['fldCaption']){ $att->addChild('attributeLabel',$this->common->sanitizeForXML($field['fldCaption'])); } if($field['fldFieldDescription']){ $att->addChild('attributeDefinition',$this->common->sanitizeForXML($field['fldFieldDescription'])); } if($field['enuDataType']){ $att->addChild('storageType',$this->common->sanitizeForXML($field['enuDataType'])); } if($field['enuMeasurementType']){ $mst = $att->addChild('measurementType'); $typ = $mst->addChild(strtolower($this->common->sanitizeForXML($field['enuMeasurementType']))); if($field['enuUnit']){ $typ->addChild('unit')->addChild('standardUnit',$this->common->sanitizeForXML($field['enuUnit'])); } if($field['fldPrecision']){ $typ->addChild('precision',$this->common->sanitizeForXML($field['fldPrecision'])); } if($field['enuFormatString']){ $typ->addChild('formatString',$this->common->sanitizeForXML($field['enuFormatString'])); } if($field['enuNumberType']){ $typ->addChild('numericDomain')->addChild('numberType',$this->common->sanitizeForXML($field['enuNumberType'])); } if($field['fldMinimum'] || $field['fldMaximum']){ $bdg = $typ->addChild('BoundsGroup'); $bds = $bdg->addChild('bounds'); $bds->addChild('minimum',$field['fldMinimum']); $bds->addChild('maximum',$field['fldMaximum']); } } if($field['fldEnumList'] && $field['fkLookupID']){ $list = $this->dataset_model->get_list($field['fkLookupID']); if(!empty($list)){ $grouplist=array(); list($grouplist['list'],$grouplist['listtype'],$grouplist['listname'])=$list; } switch ($grouplist['listtype']){ case "custom": $enu = $att->addChild('enumeratedDomain'); foreach($grouplist['list'] as $pairing){ $cdd = $enu->addChild('codeDefinition'); $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldItem'])); $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['fldDescription'])); } break; case "species": $enu = $att->addChild('enumeratedDomain'); $speciesCoverage = $dataTable->addChild('coverage'); $tmp = $speciesCoverage->addChild('taxonomicCoverage'); foreach($grouplist['list'] as $pairing){ $cdd = $enu->addChild('codeDefinition'); $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldOriginalCode'])); $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['enuTaxonomicSystem'].' #'.$pairing['fldTaxonomicID'].', '.$pairing['fldCommonName'].' ('.$pairing['fldGenus'].' '.$pairing['fldSpecies'].')')); } break; case "location": $enu = $att->addChild('enumeratedDomain'); foreach($grouplist['list'] as $pairing){ $cdd = $enu->addChild('codeDefinition'); $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldOriginalCode'])); if (!empty($pairing['descLocationType'])){ $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['descLocationType'].' '.$pairing['fldDescription'])); }else{ $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['fldDescription'])); } } break; default: break; } } } //$dataTable->addChild('license',$dataset['enuDataLicense']); $sxe->saveXML(); $data['sxe'] = $sxe->asXML(); $data['dump']=false; echo view('project/dataset/eml_xmlview', $data); }
视图代码
<?php if(!$dump){ header ("Content-Type:text/xml"); } echo $sxe ?>
问题根源
核心问题在于CI4和CI3的响应头处理机制不同:
- CI3中直接在视图里使用
header()设置响应头通常能生效,因为输出控制较为宽松。 - CI4的响应系统由
Response类统一管理,视图加载前可能已经有部分输出(比如框架的初始化内容),此时再用原生header()设置Content-Type会失效,浏览器无法识别内容为XML,只能以纯文本展示。
解决方案
方案1:使用CI4官方Response类输出XML
修改控制器的最后部分,去掉视图加载逻辑,直接用Response类设置响应头并输出XML内容:
// 替换原来的以下代码: // $data['sxe'] = $sxe->asXML(); // $data['dump']=false; // echo view('project/dataset/eml_xmlview', $data); // 改为: $response = service('response'); // 设置正确的XML响应头 $response->setHeader('Content-Type', 'text/xml; charset=utf-8'); // 输出XML内容 $response->setBody($sxe->asXML()); // 返回响应对象 return $response;
同时可以删除对应的视图文件,或者清空视图内容(因为不再需要视图处理输出)。
方案2:检查并消除额外输出干扰
确保所有PHP文件(控制器、模型、公共文件等)的开头<?php标签前没有空格、换行或其他字符,这些额外输出会导致CI4无法正确设置响应头。
方案3:验证响应头
使用浏览器开发者工具(Network标签)查看CI4页面的响应头,确认Content-Type是否为text/xml。如果仍然是text/html,说明还有其他代码覆盖了响应头,需排查全局中间件、过滤器等是否修改了响应设置。
内容的提问来源于stack exchange,提问作者xanabobana
相关产品推荐
相关产品推荐

