Проблема
Проблема с этим кодом находится в самой первой строке:
$xmlTest = new SimpleXMLElement('<Test/>', 0, false, 'ws', true);
Прежде чем делать что-либо еще, давайте выведем это в виде XML:
echo $xmlTest->asXML();
<?xml version="1.0"?>
<Test/>
Это имеет смысл, мы получили то, что вкладываем.
Руководство довольно расплывчато в том, что делает аргумент $ns
, но в этом случае оно не делает ничего полезного.Он устанавливает контекст для чтения XML, так что ->foo
относится к <ws:foo>
, а ['bar']
относится к ws:bar="..."
.Он ничего не делает для изменения структуры самого XML.
Установка корневого пространства имен
Чтобы установить пространство имен в корневом элементе, мы просто должны включить его в нашу строку, определяющуюкорневой элемент:
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/" />');
echo $xmlTest->asXML();
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/"/>
Пока все хорошо ...
Настройка пространств имен для дочерних элементов
Далее, давайте рассмотрим, что на самом деле выводит код в вопросе (Я добавил несколько пробелов, чтобы сделать его более читабельным):
<?xml version="1.0"?>
<Test>
<ws:somename2 xmlns:ws="http://microsoft.com/wsdl/types/">somevalue2</ws:somename2>
<ws:make xmlns:ws="ws">
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</Test>
ОК, поэтому этот документ содержит два объявления пространства имен:
Что произойдет, если мы добавим наш исправленный корневой элемент?
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/">
<ws:somename2>somevalue2</ws:somename2>
<ws:make xmlns:ws="ws">
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</ws:Test>
Классно, поэтому элемент somename2
теперь наследует свое определение пространства имен от корневого элемента и не объявляет его повторно.Но что не так с make
и model
с?Давайте сравним:
$xmlTest->addChild("ws:somename2", "somevalue2", 'http://microsoft.com/wsdl/types/');
$make = $xmlTest->addChild('ws:make', null, 'ws');
Этот третий аргумент должен быть URI пространства имен , а не просто префиксом.Поэтому, когда мы задали его как 'ws'
, SimpleXML предположил, что мы хотим объявить фактический URI пространства имен как ws
, поэтому добавил для этого атрибут xmlns
.
Что мы на самом деле хотели, так это все элементычтобы быть в одном и том же пространстве имен:
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/" />');
$xmlTest->addChild("ws:somename2", "somevalue2", 'http://microsoft.com/wsdl/types/');
$make = $xmlTest->addChild('ws:make', null, 'http://microsoft.com/wsdl/types/');
#$make->addAttribute('name','Ford', 'http://microsoft.com/wsdl/types/');
$make->addChild('ws:model', 'foo', 'http://microsoft.com/wsdl/types/');
$make->addChild('ws:model', 'bar', 'http://microsoft.com/wsdl/types/');
echo $xmlTest->asXML();
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/">
<ws:somename2>somevalue2</ws:somename2>
<ws:make>
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</ws:Test>
Отлично, мы получили желаемый результат!
Убираем
Но этот код выглядит довольно некрасиво, почему мынужно повторять URI везде?Что касается SimpleXML, выбор невелик: один и тот же префикс может означать разные вещи в разных частях документа, поэтому мы должны сказать ему, что мы хотим.
Что мы можем do приводит в порядок наш код, используя переменную или константу для URI пространства имен, вместо того, чтобы записывать его полностью каждый раз:
define('XMLNS_WS', 'http://microsoft.com/wsdl/types/');
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="' . XMLNS_WS . '" />');
$xmlTest->addChild("ws:somename2", "somevalue2", XMLNS_WS);
$make = $xmlTest->addChild('ws:make', null, XMLNS_WS);
#$make->addAttribute('name','Ford', XMLNS_WS);
$make->addChild('ws:model', 'foo', XMLNS_WS);
$make->addChild('ws:model', 'bar', XMLNS_WS);
Здесь нет ничего особенного в имени XMLNS_WS
, ив равной степени это может быть переменная, константа класса или константа пространства имен.Код работает точно так же, на глаз немного проще.