Использование SimpleXMLElement для получения данных
«Название статьи некорректно отображает ее суть, так как описанное ниже затрагивает в равной степени и SimpleXMLElement и расширение SOAP и библиотеку highcharts. Причем разбор последней занял большую часть времени при подготовке статьи. Но если отображать все, то название будет слишком длинным, по этому, с Вашего позволения, оставлю все как есть..»
И сразу спойлер. Вот то, ради чего все ниженаписанное:
01изучение wdsl сервиса
Для демонстрации воспользуемся открытым Web-сервисом Центрального банка Российской Федерации:
Начнем с просмотра веб-сервиса для получения информации о том, какие методы предоставляет сервис, какие параметры необходимо передать в качестве входных значений того или иного метода и что ожидать в качестве ответа.
Для примера возьмем метод GetCursOnDateXML - Получение ежедневных курсов валют (как XMLDocument). Смотрим входные параметры и тип ответа сервера:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<wsdl:documentation>
Получение ежедневных курсов валют (как XMLDocument)
</wsdl:documentation>
<wsdl:input message="tns:GetCursOnDateXMLSoapIn"/><wsdl:output message="tns:GetCursOnDateXMLSoapOut"/>
</wsdl:operation>
...
<s:element name="GetCursOnDateXML">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="On_date" type="s:dateTime"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetCursOnDateXMLResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetCursOnDateXMLResult">
<s:complexType mixed="true">
<s:sequence>
<s:any/>
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
Из вышеприведенного фрагмента видим, что сервер желает получить параметр 'name="On_date" type="s:dateTime"' - ассоциативный массив с ключом 'On_date' и значением 's:dateTime' (на сайте w3schools.com утверждают, что это тип данных, использующийся для указания даты и времени в формате 'YYYY-MM-DDThh:mm:ss'), а ответ передаст нам как свойство GetCursOnDateXMLResponse->any. Больше нам ничего и не нужно.
02создание SOAP клиента
На очереди создание SOAP клиента (предварительно необходимо проверить подключение модуля SOAP). В конструктор класса SoapClient необходимо передать URL WSDL-документа и получить объект для работы с нужным веб-сервисом.
Посылаем запрос и принимаем ответ:
2
3
$result=$client->GetCursOnDate(['On_date'=>$On_date]);// Soap-запрос
$any=$result->GetCursOnDateResult->any;// получение ответа
Смотрим, что скрывается за свойством, переданным в качестве ответа:
2
3
var_dump($any);
echo "</pre>";
Мы получили строку с xml-документом. Используем страшное колдунство, и преобразуем полученный ответ в объект класса SimpleXMLElement. Таким образом все элементы xml-документа становятся свойствами объекта.
2
3
4
5
echo "<pre>";
var_dump($obj);
echo "</pre>";
обратимся же к свойствам объекта нашего и да получим желаемое:
02
03
04
05
06
07
08
09
10
11
12
13
14
echo "По данным Центробанка РФ сегодня, "
. date('d.m.Y')
. ", в "
. date('H:i')
. $obj->ValuteData->ValuteCursOnDate[1]->Vname
. " оценивается как "
. $obj->ValuteData->ValuteCursOnDate[1]->Vnom
. " "
. $obj->ValuteData->ValuteCursOnDate[1]->VchCode
. " : "
. $obj->ValuteData->ValuteCursOnDate[1]->Vcurs
. " RUB.<br>";
03Выборка необходимой валюты
Так как количество валют, по которому сервис выдает информацию, непостоянно (проверено!), привязываться к индексу ValuteCursOnDate бесполезно и даже чревато. Поэтому добавим цикл с условием:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
$countVal=$obj->ValuteData->ValuteCursOnDate->count();
for($j=1;$j<$countVal;$j++){
if($obj->ValuteData->ValuteCursOnDate[$j]->VchCode==$needCode){
echo "По данным Центробанка РФ сегодня, "
. date('d.m.Y')
. ", в "
. date('H:i')
. " "
. $obj->ValuteData->ValuteCursOnDate[$j]->Vname
. " оценивается как "
. $obj->ValuteData->ValuteCursOnDate[$j]->Vnom
. " "
. $obj->ValuteData->ValuteCursOnDate[$j]->VchCode
. " : "
. $obj->ValuteData->ValuteCursOnDate[$j]->Vcurs
. " RUB.<br>";
}
}
04Построение графика
Для построения графика нам потребуются на определенный период времени для нескольких валют. Введем также сохранение полученных данных в файл с проверкой последней даты получения (для уменьшения количества запросов), учтем номинал котировок, автоматическое обновление страницы (только при обнаружении неполных данных) и вишенкой на торте отрисуем полученные данные с помощью библиотеки highcharts. Вот, собственно, и окончательная версия скрипта:
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// --------------- $php_code ---------------
// ===============================================================
// config
$res_mess='';
define("PATH_JS", "/src/highstock/js");
define("PATH_JSON", "/src/highstock/json");
$arr_curr=array('USD','EUR','UAH','AMD',);// необходимые валюты
$arr_mrrf7D=array('mrrf',);// Международные резервы Российской Федерации, еженедельные значения
define("START_TS", 709722060);// временная метка начала графиков /за три дня до начала данных по USD/
$q=3;// количество запросов за одно обращение к скрипту
$refresh_content='<meta http-equiv="refresh" content="5; URL=' . $_SERVER['SCRIPT_URI'] . '">';
// /config
// ===============================================================
// ===============================================================
// получение временной метки уже имеющихся данных
$arr_i=array();
foreach($arr_curr as $key=>$curr){
$path=DIR_PATH_JSON."/all_$curr.json";
if(file_exists($path)){
$file = file_get_contents($path);
$arr_json = json_decode($file,TRUE);
// $res_mess.=__line__ . print_r($arr_json,true)."<br>\n";
$arr_i[]=$arr_json[count($arr_json)-1][0];// последняя временная метка
}
}
// $res_mess.=__line__ . print_r($arr_i,true)."<br>\n";
if(!empty($arr_i)){$i=max($arr_i);// надо бы проверку..
}else{$i=START_TS;}
// $res_mess.= __line__ . " \$i='$i';<br>\n";
unset($key,$curr,$path);
// /получение временной метки имеющихся данных
// ===============================================================
// ===============================================================
// получение данных с сервера (Soap-запрос) и их обработка
$client=new SoapClient("https://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL");// создание SOAP клиента
// print_r($client->__getFunctions());// получение списка доступных функций
$date = new DateTime();
$date->setDate(date('Y',$i), date('m',$i), date('d',$i));
$no_data=true;
for($n=1;$n<=$q;$n++){ // посылаем на сервер $q запросов
// инкрементируем временную метку перед следующей итерацией
$date->add(new DateInterval('P1D'));//'P1M''P12M''P10D'
$i=mktime(13, 01, 0, $date->format('m'), $date->format('d'), $date->format('Y'));// Официальные курсы иностранных валют устанавливаются приказом Банка России каждый рабочий день обычно не позднее 13 часов по московскому времени
// $res_mess.= "<br>\n";
// $res_mess.= __line__ . " \$n='$n'; \$i='$i';<br>\n";
if($i<=time()){
// создаем ежедневный массив для каждой валюты с пустыми значениями
foreach($arr_curr as $curr){${'arr_d_'.$curr}=array($i,null,null);}
$On_date=date('Y-m-d\TH:i:s',$i);
// $res_mess.= __line__ . " \$On_date='$On_date';<br>\n";
// echo $res_mess;exit;
$alert="<div class='mess'>Идет получение данных: " . date('Y.m.d',$i) . ".</div></span>";
$meta_tag_from_tpl=$refresh_content;
$result=$client->GetCursOnDate(['On_date'=>$On_date]);// Soap-запрос
$any=$result->GetCursOnDateResult->any;// получение ответа
$obj = new SimpleXMLElement($any);// преобразование ответа в объект класса SimpleXMLElement
// $res_mess.=__line__ . print_r($obj,true)."<br>\n";
// <ValuteCursOnDate diffgr:id="ValuteCursOnDate1" msdata:rowOrder="0">
// <Vname>Австралийский доллар</Vname>
// <Vnom>1</Vnom>
// <Vcurs>47.6580</Vcurs>
// <Vcode>36</Vcode>
// <VchCode>AUD</VchCode>
// </ValuteCursOnDate>
if(!empty($obj->ValuteData->ValuteCursOnDate)){
$countVal=$obj->ValuteData->ValuteCursOnDate->count();
for($j=1;$j<$countVal;$j++){// перебираем все валюты
foreach($arr_curr as $curr){
if($obj->ValuteData->ValuteCursOnDate[$j]->VchCode==$curr){
// для каждой валюты из списка создаем массив вида $arr_d_USD=array(845370050=>array(Vnom,Vcurs))
${'Vnom_'.$curr}=$obj->ValuteData->ValuteCursOnDate[$j]->Vnom->__toString()*1;
${'Vcurs_'.$curr}=$obj->ValuteData->ValuteCursOnDate[$j]->Vcurs->__toString()*1;
${'arr_d_'.$curr}=array($i,${'Vnom_'.$curr},${'Vcurs_'.$curr});
}
}
}
}else{
// $res_mess.= __line__ . " Err \$obj->ValuteData->ValuteCursOnDate is empty;<br>\n";
}
foreach($arr_curr as $key=>$curr){// запись данных ежедневного массива в общий массив валюты
${'arr'.$curr}[]=${'arr_d_'.$curr};
}
}else{// $i>time(); - превышено значение временной метки
// $res_mess.= __line__ . " $i>" . time() . "; break.<br>\n";
$alert='';
$meta_tag_from_tpl='';
break;
}
if($no_data==true and $i<=time()){
// $res_mess.= __line__ . ": $n в рассматриваемый момент времени ($i) нет данных о $curr;<br>\n";
}
}// посылаем на сервер $q запросов
// /получение данных с сервера (Soap-запрос) и их обработка
// ===============================================================
// ===============================================================
// перезапись файлов all_$curr.json
foreach($arr_curr as $key=>$curr){
if(!empty(${'arr'.$curr})){
$path=DIR_PATH_JSON."/all_$curr.json";
// $res_mess.= __line__ . " \$path='$path';<br>\n";
// $res_mess.=__line__ . print_r(${'arr'.$curr},true)."<br>\n";
// добавление данных к существующим
if(file_exists($path)){
$file = file_get_contents($path);
$arr_json = json_decode($file,TRUE);
${'arr'.$curr}=array_merge($arr_json,${'arr'.$curr});// сливаем массивы
}unset($file,$arr_json);
${'arr'.$curr}=json_encode(${'arr'.$curr});
if(file_put_contents($path,${'arr'.$curr})){
// $res_mess.= __line__ . " file_put_contents($path);<br>\n";
}else{
$res_mess.= __line__ . " ERR not file_put_contents($path);<br>\n";
}
unset(${'arr'.$curr});
}unset($curr);
}
// /перезапись файлов all_$curr.json
// ===============================================================
// ===============================================================
// формирование файлов JSON для интеграции в графики highcharts
// здесь только перезапись!
$names='';
foreach($arr_curr as $key=>$curr){
$path=DIR_PATH_JSON."/all_$curr.json";
if(file_exists($path)){
$arr_json = json_decode(file_get_contents($path),TRUE);
// для удобства просмотра графика воспользуемся номиналом Vnom последнего значения
$lastVnom=$arr_json[count($arr_json)-1][1];// последний номинал валюты
foreach($arr_json as $key_=>$val){
$i=$arr_json[$key_][0];// временная метка
$Vnom=$arr_json[$key_][1];// номинал валюты
$Vcurs=$arr_json[$key_][2];// курс валюты
if(!empty($Vcurs) and !empty($Vnom)){
$curs=$Vcurs/$Vnom;// стоимость единицы валюты
$curs=$curs*$lastVnom;// стоимость номинала валюты
}else{
$curs=null;
}
$arr[]=array($i*1000,$curs);
}unset($key_,$arr_json);
// создаем файл с указанием номинала $lastVnom в названии
$fn="$lastVnom$curr";
$path=DIR_PATH_JSON."/$fn.json";
$arr=json_encode($arr);
if(file_put_contents($path,$arr)){
// $res_mess.= __line__ . " file_put_contents($path);<br>\n";
$names.="'$fn', ";
}else{
$res_mess.= __line__ . " ERR not file_put_contents($path);<br>\n";
}
unset($arr);
}unset($key,$curr);
}
// /формирование файлов JSON для интеграции в графики highcharts
// ===============================================================
// ===============================================================
// метод mrrf7D Международные резервы Российской Федерации, еженедельные значения
$arrSOAP=array(
'fromDate'=>date('Y-m-d\TH:i:s', 0),
'ToDate'=>date('Y-m-d\TH:i:s', time()),
);
$result=$client->mrrf7DXML($arrSOAP);// Soap-запрос mrrf
$any=$result->mrrf7DXMLResult->any;// получение ответа
$obj = new SimpleXMLElement($any);
if(!empty($children=$obj)){
$countVal=$obj->count();
for($j=0;$j<$countVal;$j++){
$D0=$obj->mr[$j]->D0;
$i=strtotime($D0);
$val=$obj->mr[$j]->val;
$arr_mrrf7DXML[]=array($i*1000,($val->__toString())*1);
}
}
// обновление данных в файле JSON.
if(!empty($arr_mrrf7DXML)){// массив полученных данных существует и не пуст
$path=DIR_PATH_JSON.'/mrrf7DXML.json';
file_put_contents($path,json_encode($arr_mrrf7DXML)); // Перекодировать в формат и записать в файл.
}
// /метод mrrf7D Международные резервы Российской Федерации, еженедельные значения
// ===============================================================;
// $res_mess.= __line__ . " end;<br>\n";
В html-блок страницы добавим разметку для отображения графиков:
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<div class='mess'><?php echo $res_mess;?></div>
<div class='mess'><?php echo $alert;?></div>
<!-- main_content -->
<script src="<?php echo LINK_PATH_JS;?>/jquery-3.1.1.min.js"></script>
<script src="<?php echo LINK_PATH_JS;?>/highstock.js"></script>
<script src="<?php echo LINK_PATH_JS;?>/exporting.js"></script>
<script src="<?php echo LINK_PATH_JS;?>/export-data.js"></script>
<div id='container1' style='height: 600px; min-width: 310px'></div>
<div id='container2' style='height: 400px; min-width: 310px'></div>
<!--[if lt IE 9]>
<script src="https://code.highcharts.com/modules/oldie.js"></script>
<![endif]-->
<?php //https://www.highcharts.com/stock/demo/compare?>
<script type="text/javascript">
jQuery.noConflict();
var example = 'compare',
theme = 'default';
(function($) { // encapsulate jQuery
var seriesOptions = [],
seriesCounter = 0,
// names = ['MSFT', 'AAPL', 'GOOG' ,'USD'];
names = [<?php echo $names;?>];
/**
* Create the chart when all data is loaded
* @returns {undefined}
*/
function createChart() {
Highcharts.stockChart('container1', {
title: {
text: 'График динамики курсов валют'
},
subtitle: {
text: 'Источник <a href="https://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL">cbr.ru</a>'
},
rangeSelector: {
selected: 4
},
// yAxis: {
// labels: {
// formatter: function() {
// return (this.value > 0 ? ' + ' : '') + this.value + '%';
// }
// },
// plotLines: [{
// value: 0,
// width: 2,
// color: 'silver'
// }]
// },
xAxis: {
gridLineWidth: 1
},
yAxis: {
title: {
text: 'value'
},
type: 'linear',
labels: {
formatter: function() {
// return (this.value > 0 ? ' + ' : '') + this.value + '%';
return this.value;
}
},
plotLines: [{
value: 50,
width: 1,
color: '#5290a4'
},{
value: 100,
width: 1,
color: '#5290a4'
}]
},
plotOptions: {
series: {
//compare: 'percent',
showInNavigator: true
}
},
// tooltip: {
// pointFormat:
// '<div style="color:{series.color}">{series.name}</div>:
// <b>{point.y}</b> ({point.change}%)<br/>',
// valueDecimals: 2,
// split: true
// },
tooltip: {
pointFormat: '<div style="color:{series.color}">{series.name} : {point.y}RUB</div>',
valueDecimals: 5,
split: true
},
legend: {
align: 'center',
verticalAlign: 'bottom',
},
series: seriesOptions
});
}
$.each(names, function(i, name) {
// $.getJSON('https://www.highcharts.com/samples/data/'
// + name.toLowerCase()
// + '-c.json', function (data) {
$.getJSON('<?php echo LINK_PATH_JSON."/";?>' + name + '.json', function(data) {
seriesOptions[i] = {
name: name,
data: data
};
// As we're loading the data asynchronously, we don't know what order it will arrive. So
// we keep a counter and create the chart when all the data is loaded.
seriesCounter += 1;
if (seriesCounter === names.length) {
createChart();
}
});
});
})(jQuery);
</script>
<script type="text/javascript">
jQuery.noConflict();
var example = 'line-time-series',
theme = 'default';
// https://www.highcharts.com/demo/line-time-series
(function($) { // encapsulate jQuery
$.getJSON('<?php echo LINK_PATH_JSON;?>/mrrf7DXML.json',
function(data) {
Highcharts.chart('container2', {
chart: {
zoomType: 'x'
},
title: {
text: 'График еженедельных значений международных резервов Российской Федерации'
},
subtitle: {
text: document.ontouchstart === undefined ?
'Источник <a href="https://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL">cbr.ru</a>
Click and drag in the plot area to zoom in' : 'Pinch the chart to zoom in'
},
xAxis: {
type: 'datetime'
},
yAxis: {
title: {
text: 'млрд долл. США'
}
},
legend: {
enabled: false
},
plotOptions: {
area: {
fillColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 1
},
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
},
marker: {
radius: 2
},
lineWidth: 1,
states: {
hover: {
lineWidth: 1
}
},
threshold: null
}
},
series: [{
type: 'area',
name: 'млрд USD',
data: data
}]
});
}
);
})(jQuery);
// yakoffka
Highcharts.setOptions({
lang: {
months: [
'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'
],
shortMonths: [
'янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сент', 'окт', 'нояб', 'дек',
],
weekdays:[
'воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота',
],
shortWeekdays:[
'вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб',
],
}
});
</script>
Да, кстати, для автообновления страницы по необходимости в голову воткнем вот это:
2
3
4
5
...
<?php if(!empty($meta_tag_from_tpl)){echo $meta_tag_from_tpl;}else{echo "<!-- empty -->";}?>
...
</head>
Капустин Яков (2018.10.08 00:38)