I'm using a single XML file to store configuration settings for multiple objects. For example:
<config>
<database>
<user>myuser</user>
</database>
<application>
<component1>mycomponent</component1>
</application>
<infrastructure>
<hostname1>server1</hostname1>
</infrastructure>
</config>
I want each of my objects to access different aspects of the xml; for instance the "database" class would access the database element.
What method could be used to quickly access this configuration across my objects without reproducing code?
You could create a Config object which reads the configuration from the XML file, then use a Configurable trait on the classes you want to be "automatically" configurable from the XML (see example below).
config.xml:
<config>
<database>
<user>myuser</user>
</database>
<application>
<component1>mycomponent</component1>
</application>
<infrastructure>
<hostname1>server1</hostname1>
</infrastructure>
</config>
PHP code:
class Config
{
/**
* Full XML configuration
*
* #var SimpleXMLElement
*/
private static $xml;
/**
* Parse XML configuration file into XML object (once)
*/
public function __construct()
{
if (!self::$xml) {
$xmlSource = file_get_contents('config.xml');
self::$xml = new SimpleXMLElement($xmlSource);
}
}
/**
* Returns the first node matching the specified xpath
*
* #param $xpath
*
* #return SimpleXMLElement
*/
public function getFirstByXpath($xpath)
{
// Return the first matching configuration
return self::$xml->xpath($xpath)[0];
}
}
trait Configurable
{
/**
* Class configuration XML element
*
* #var SimpleXMLElement
*/
private $configuration;
/**
* Get the class configuration XML element
*
* #return SimpleXMLElement
*/
function getConfiguration()
{
if (!$this->configuration) {
$tag = strtolower(get_class($this));
$xpath = '/config/' . $tag;
$this->configuration = (new Config())->getFirstByXpath($xpath);
}
return $this->configuration;
}
}
class application
{
use configurable;
}
class database
{
use configurable;
}
$app = new application();
echo "App component 1: ", $app->getConfiguration()->component1, PHP_EOL;
$db = new database();
echo "DB user: ", $db->getConfiguration()->user, PHP_EOL;
Output:
App component 1: mycomponent
DB user: myuser
Related
i want to override AbstractBlock class, i tried with my custom module but it not working
di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Framework\View\Element\AbstractBlock" type="YourCompany\YourModule\Framework\View\Element\AbstractBlock" />
</config>
AbstractBlock.php
<?php
namespace YourCompany\YourModule\Framework\View\Element;
abstract class AbstractBlock extends \Magento\Framework\View\Element\AbstractBlock {
/**
* Retrieve child block HTML
*
* #param string $alias
* #param boolean $useCache
* #return string
*/
public function getChildHtml($alias = '', $useCache = true)
{
die("here");
}
}
You cannot replace a class in the middle of the hierarchy.
Meaning, exchanging an existing parent (abstract) class by another abstract class.
May be you can show how are you using the original class (I guess in a constructor) and that would help a little bit to give a better answer.
I'm trying to register a namespace , but everytime I use th returned value from xpath , I have to register the same namespace again and again.
<?php
$xml= <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<extension>
<xyz:form xmlns:xyz="urn:company">
<xyz:formErrorData>
<xyz:field name="field">
<xyz:error>REQUIRED</xyz:error>
<xyz:value>username</xyz:value>
</xyz:field>
</xyz:formErrorData>
</xyz:form>
</extension>
</response>
</epp>
XML;
The parser :
$xmlObject = simplexml_load_string(trim($xml), NULL, NULL);
$xmlObject->registerXPathNamespace('ns','urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach($fields as $field){
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix in
//$errors = $field->xpath("//ns:error");
// I have to register the same namespace again so it works
$field->registerXPathNamespace('ns','urn:company');
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
?>
Notice that I had to register the namespace again inside the loop, if I did not I will get the following error :
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix
in...
Do you have any idea how to keep the registered namespaces in the returned simplexml objects from xpath function.
Yes you're right for your example, not registering the xpath namespace again would create a warning like the following then followed by another warning leading to an empty result:
Warning: SimpleXMLElement::xpath(): Undefined namespace prefix
Warning: SimpleXMLElement::xpath(): xmlXPathEval: evaluation failed
The explanations given in the comments aren't too far off, however they do not offer a good explanation that could help to answer your question.
First of all the documentation is not correct. It's technically not only for the next ::xpath() invocation:
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
This does not give the warning despite it's not only the next, but another further three calls. So the description from the comment is perhaps more fitting that this is related to the object.
One solution would be to extend from SimpleXMLElement and interfere with the namespace registration so that when the xpath query is executed, all result elements could get the namespace prefix registered as well. But that would be much work and won't work when you would access further children of a result.
Additionally you can't assign arrays or objects to store the data within a SimpleXMLElement it would always create new element nodes and then error that objects / arrays are not supported.
One way to circumvent that is to store not inside the SimpleXMLElement but inside the DOM which is accessible via dom_import_simplexml.
So, if you create a DOMXpath you can register namespaces with it. And if you store the instance inside the owner document, you can access the xpath object from any SimpleXMLElement via:
dom_import_simplexml($xml)->ownerDocument-> /** your named field here **/
For this to work, a circular reference is needed. I outlined this in The SimpleXMLElement Magic Wonder World in PHP and an encapsulated variant with easy access could look like:
/**
* Class SimpleXpath
*
* DOMXpath wrapper for SimpleXMLElement
*
* Allows assignment of one DOMXPath instance to the document of a SimpleXMLElement so that all nodes of that
* SimpleXMLElement have access to it.
*
* #link
*/
class SimpleXpath
{
/**
* #var DOMXPath
*/
private $xpath;
/**
* #var SimpleXMLElement
*/
private $xml;
...
/**
* #param SimpleXMLElement $xml
*/
public function __construct(SimpleXMLElement $xml)
{
$doc = dom_import_simplexml($xml)->ownerDocument;
if (!isset($doc->xpath)) {
$doc->xpath = new DOMXPath($doc);
$doc->circref = $doc;
}
$this->xpath = $doc->xpath;
$this->xml = $xml;
}
...
This class constructor takes care that the DOMXPath instance is available and sets the private properties according to the SimpleXMLElement passed in the ctor.
A static creator function allows easy access later:
/**
* #param SimpleXMLElement $xml
*
* #return SimpleXpath
*/
public static function of(SimpleXMLElement $xml)
{
$self = new self($xml);
return $self;
}
The SimpleXpath now always has the xpath object and the simplexml object when instantiated. So it only needs to wrap all the methods DOMXpath has and convert returned nodes back to simplexml to have this compatible. Here is an example on how to convert a DOMNodeList to an array of SimpleXMLElements of the original class which is the behavior of any SimpleXMLElement::xpath() call:
...
/**
* Evaluates the given XPath expression
*
* #param string $expression The XPath expression to execute.
* #param DOMNode $contextnode [optional] <The optional contextnode
*
* #return array
*/
public function query($expression, SimpleXMLElement $contextnode = null)
{
return $this->back($this->xpath->query($expression, dom_import_simplexml($contextnode)));
}
/**
* back to SimpleXML (if applicable)
*
* #param $mixed
*
* #return array
*/
public function back($mixed)
{
if (!$mixed instanceof DOMNodeList) {
return $mixed; // technically not possible with std. SimpleXMLElement
}
$result = [];
$class = get_class($this->xml);
foreach ($mixed as $node) {
$result[] = simplexml_import_dom($node, $class);
}
return $result;
}
...
It's more straight forward for the actual registering of xpath namespaces because it works 1:1:
...
/**
* Registers the namespace with the DOMXPath object
*
* #param string $prefix The prefix.
* #param string $namespaceURI The URI of the namespace.
*
* #return bool true on success or false on failure.
*/
public function registerNamespace($prefix, $namespaceURI)
{
return $this->xpath->registerNamespace($prefix, $namespaceURI);
}
...
With these implementations in the chest, all what is left is to extend from SimpleXMLElement and wire it with the newly created SimpleXpath class:
/**
* Class SimpleXpathXMLElement
*/
class SimpleXpathXMLElement extends SimpleXMLElement
{
/**
* Creates a prefix/ns context for the next XPath query
*
* #param string $prefix The namespace prefix to use in the XPath query for the namespace given in ns.
* #param string $ns The namespace to use for the XPath query. This must match a namespace in use by the XML
* document or the XPath query using prefix will not return any results.
*
* #return bool TRUE on success or FALSE on failure.
*/
public function registerXPathNamespace($prefix, $ns)
{
return SimpleXpath::of($this)->registerNamespace($prefix, $ns);
}
/**
* Runs XPath query on XML data
*
* #param string $path An XPath path
*
* #return SimpleXMLElement[] an array of SimpleXMLElement objects or FALSE in case of an error.
*/
public function xpath($path)
{
return SimpleXpath::of($this)->query($path, $this);
}
}
With this modification under the hood, it works transparently with your example if you use that sub-class:
/** #var SimpleXpathXMLElement $xmlObject */
$xmlObject = simplexml_load_string($buffer, 'SimpleXpathXMLElement');
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach ($fields as $field) {
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
This example then runs error free, see here: https://eval.in/398767
The full code is in a gist, too: https://gist.github.com/hakre/1d9e555ac1ebb1fc4ea8
Writing group of parsers that rely on one abstract class which implements shared methods and asks to implement addition method which contains per parser logic.
Abstract parser code:
<?
abstract class AbstractParser {
/*
* The only abstract method to implement. It contains unique logic of each feed passed to the function
*/
public abstract function parse($xmlObject);
/**
* #param $feed string
* #return SimpleXMLElement
* #throws Exception
*/
public function getFeedXml($feed) {
$xml = simplexml_load_file($feed);
return $xml;
}
/**
* #return array
*/
public function getParsedData() {
return $this->data;
}
/**
* #param SimpleXMLElement
* #return Array
*/
public function getAttributes($object) {
// implementation here
}
}
Concrete Parser class:
<?php
class FormulaDrivers extends AbstractParser {
private $data;
/**
* #param SimpleXMLElement object
* #return void
*/
public function parse($xmlObject) {
if (!$xmlObject) {
throw new \Exception('Unable to load remote XML feed');
}
foreach($xmlObject->drivers as $driver) {
$driverDetails = $this->getAttributes($driver);
var_dump($driver);
}
}
}
Instantiation:
$parser = new FormulaDrivers();
$parser->parse( $parser->getFeedXml('http://api.xmlfeeds.com/formula_drivers.xml') );
As you can see, I pass the result of getFeedXml method to parse method, basically delegating the validation of result of getFeedXml to parse method.
How can I avoid it, make sure it returns correct XML object before I pass it to parse method?
Increasing instantiation process and amount of called methods leads to the need of some factory method...
Anyway, how would you fix this small issue?
Thanks!
Make parse protected, so that only parse_xml_file calls it:
abstract class AbstractParser {
/*
* The only abstract method to implement. It contains unique logic of each feed passed to the function
*/
protected abstract function parse($xmlObject);
/**
* #param $feed string
* #return [whatever .parse returns]
* #throws Exception
*/
public function parseFile($feed) {
$xml = simplexml_load_file($feed);
if (!$xml) {
throw new \Exception('Unable to load remote XML feed');
}
return $this->parse($xml);
}
/**
* #return array
*/
public function getParsedData() {
return $this->data;
}
/**
* #param SimpleXMLElement
* #return Array
*/
public function getAttributes($object) {
// implementation here
}
}
$parser->parseFile('http://api.xmlfeeds.com/formula_drivers.xml');
I collect some data in xml format through an API and would like to deserialize it in an objects list.
I'm using Symfony2 and find out JMSSerializerBundle but I do not really know how to use it.
I know that Sf2 allows to serialize/deserialize object to/from array, but I'm looking for something more specific.
For example, for this class :
class Screenshot
{
/**
* #var integer $id
*/
private $id;
/**
* #var string $url_screenshot
*/
private $url_screenshot;
public function __construct($id, $url_screenshot) {
$this->id = $id;
$this->url_screenshot = $url_screenshot;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set url_screenshot
*
* #param string $urlScreenshot
*/
public function setUrlScreenshot($urlScreenshot)
{
$this->url_screenshot = $urlScreenshot;
}
/**
* Get url_screenshot
*
* #return string
*/
public function getUrlScreenshot()
{
return $this->url_screenshot;
}
/**
* Serializes the Screenshot object.
*
* #return string
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->url_screenshot
));
}
/**
* Unserializes the Screenshot object.
*
* #param string $serialized
*/
public function unserialize($serialized)
{
list(
$this->id,
$this->url_screenshot
) = unserialize($serialized);
}
public function __toString() {
return "id: ".$this->id
."screenshot: ".$this->url_screenshot;
}
}
I would like serializing/deserializing to/from this kind of xml :
<?xml version="1.0" encoding="UTF-8" ?>
<screenshots>
<screenshot>
<id>1</id>
<url_screenshot>screenshot_url1</url_screenshot>
</screenshot>
<screenshot>
<id>2</id>
<url_screenshot>screenshot_url2</url_screenshot>
</screenshot>
<screenshot>
<id>3</id>
<url_screenshot>screenshot_url3</url_screenshot>
</screenshot>
</screenshots>
I really want to use something integrated/to integrate in Sf2 (something "smooth") and prefer avoiding any homemade xml parsers.
Due to the nature of XML, the exact thing you want, is not possible. You would always need something to translate object -> xml and xml -> object.
My suggestion to you would be a class that works as collection and takes care of it for you, holding the list of objects as property, which can be created from an xml input and create xml output from existing objects.
An alternativ (if you don't really need to have it as xml anymore) would be to simply serialize the objects and store them that way, or searialize an array (or collection object) if you want them all at once. The plain serialize() and unserialize() functions from PHP will do the trick there. Since it's data only, you don't even need the methods serialize and unserialize in your class.
Update: If it's only take the XML into an object, then simplexml already has you covered:
http://www.php.net/manual/en/function.simplexml-load-string.php
The second parameter is class name.
Quote: You may use this optional parameter so that simplexml_load_string() will return an object of the specified class. That class should extend the SimpleXMLElement class.
If only this is your goal, then simplexml does it already.
Update 2: I've read some more into the bundle. It does NOT do what you want. It takes an object and the serializes it into XML/YAML, and then of course reverses that process again from those serialized states. It cannot take some random XML file and turn that into a perfect object for you.
registerNodeClass is great for extending the various DOMNode-based DOM classes in PHP, but I need to go one level deeper.
I've created an extDOMElement that extends DOMElement. This works great with registerNodeClass, but I would like to have something that works more like this:
registerNodeClass("DOMElement->nodeName='XYZ'", 'extDOMXYZElement')
Consider the following XML document, animals.xml:
<animals>
<dog name="fido" />
<dog name="lucky" />
<cat name="scratchy" />
<horse name="flicka" />
</animals>
Consider the following code:
extDomDocument extends DOMDocument {
public function processAnimals() {
$animals = $this->documentElement->childNodes;
foreach($animals as $animal) {
$animal->process();
}
}
}
extDOMElement extends DOMElement {
public function process() {
if ($this->nodeName=='dog'){
$this->bark();
} elseif ($this->nodeName=='cat'){
$this->meow();
} elseif ($this->nodeName=='horse'){
$this->whinny();
}
this->setAttribute('processed','true');
}
private function bark () {
echo "$this->getAttribute('name') the $this->nodeName barks!";
}
private function meow() {
echo "$this->getAttribute('name') the $this->nodeName meows!";
}
private function whinny() {
echo "$this->getAttribute('name') the $this->nodeName whinnies!";
}
}
$doc = new extDOMDocument();
$doc->registerNodeClass('DOMElement', 'extDOMElement');
$doc->loadXMLFile('animals.xml');
$doc->processAnimals();
$doc->saveXMLFile('animals_processed_' . Now() . '.xml');
Output:
fido the dog barks!
lucky the dog barks!
scratchy the cat meows!
flicka the horse whinnies!
I don't want to have to put bark(), meow() and whinny() into extDOMElement - I want to put them into extDOMDogElement, extDOMCatElement and extDOMHorseElement, respectively.
I've looked at the Decorator and Strategy patterns here, but I'm not exactly sure how to proceed. The current setup works OK, but I'd prefer to have shared properties and methods in extDOMElement with separate classes for each ElementName, so that I can separate methods and properties specific to each Element out of the main classes.
I had the same problem. My solution was to write an own parser based on the XMLReader extension. The resulting AdvancedParser class works very well for me. A separate element class can be registered for each element name. By extending the AdvancedParser class and overwriting the getClassForElement() method it's also possible to dynamically calculate the name of the desired class based on the element name.
/**
* Specialized Xml parser with element based class registry.
*
* This class uses the XMLReader extension for document parsing and creates
* a DOM tree with individual DOMElement subclasses for each element type.
*
* #author Andreas Traber < a.traber (at) rivo-systems (dot) com >
*
* #since April 21, 2012
* #package XML
*/
class AdvancedParser
{
/**
* Map with registered classes.
* #var array
*/
protected $_elementClasses = array();
/**
* Default class for unknown elements.
* #var string
*/
protected $_defaultElementClass = 'DOMElement';
/**
* The reader for Xml parsing.
* #var XMLReader
*/
protected $_reader;
/**
* The document object.
* #var DOMDocument
*/
protected $_document;
/**
* The current parsing element.
* #var DOMElement
*/
protected $_currentElement;
/**
* Gets the fallback class for unknown elements.
*
* #return string
*/
public function getDefaultElementClass()
{
return $this->_defaultElementClass;
}
/**
* Sets the fallback class for unknown elements.
*
* #param string $class
* #return void
* #throws Exception $class is not a subclass of DOMElement.
*/
public function setDefaultElementClass($class)
{
switch (true) {
case $class === null:
$this->_defaultElementClass = 'DOMElement';
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_defaultElementClass = $class;
}
}
/**
* Registers the class for a specified element name.
*
* #param string $elementName.
* #param string $class.
* #return void
* #throws Exception $class is not a subclass of DOMElement.
*/
public function registerElementClass($elementName, $class)
{
switch (true) {
case $class === null:
unset($this->_elementClasses[$elementName]);
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_elementClasses[$elementName] = $class;
}
}
/**
* Gets the class for a given element name.
*
* #param string $elementName
* #return string
*/
public function getClassForElement($elementName)
{
return $this->_elementClasses[$elementName]
? $this->_elementClasses[$elementName]
: $this->_defaultElementClass;
}
/**
* Parse Xml Data from string.
*
* #see XMLReader::XML()
*
* #param string $source String containing the XML to be parsed.
* #param string $encoding The document encoding or NULL.
* #param string $options A bitmask of the LIBXML_* constants.
* #return DOMDocument The created DOM tree.
*/
public function parseString($source, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->XML($source, $encoding, $options);
return $this->_parse();
}
/**
* Parse Xml Data from file.
*
* #see XMLReader::open()
*
* #param string $uri URI pointing to the document.
* #param string $encoding The document encoding or NULL.
* #param string $options A bitmask of the LIBXML_* constants.
* #return DOMDocument The created DOM tree.
*/
public function parseFile($uri, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->open($uri, $encoding, $options);
return $this->_parse();
}
/**
* The parser.
*
* #return DOMDocument The created DOM tree.
*/
protected function _parse()
{
$this->_document = new DOMDocument('1.0', 'utf-8');
$this->_document->_elements = array(); // keep references to elements
$this->_currentElement = $this->_document;
while ($this->_reader->read()) {
switch ($this->_reader->nodeType) {
case XMLReader::ELEMENT:
$this->_reader->isEmptyElement
? $this->_addElement()
: $this->_currentElement = $this->_addElement();
break;
case XMLReader::END_ELEMENT:
$this->_currentElement = $this->_currentElement->parentNode;
break;
case XMLReader::CDATA:
$this->_currentElement->appendChild(
$this->_document->createCDATASection($this->_reader->value)
);
break;
case XMLReader::TEXT:
case XMLReader::SIGNIFICANT_WHITESPACE:
$this->_currentElement->appendChild(
$this->_document->createTextNode($this->_reader->value)
);
break;
case XMLReader::COMMENT:
$this->_currentElement->appendChild(
$this->_document->createComment($this->_reader->value)
);
break;
}
}
$this->_reader->close();
return $this->_document;
}
/**
* Adds the current element into the DOM tree.
*
* #return DOMElement The added element.
*/
protected function _addElement()
{
$element = $this->_createElement();
// It's important to keep a reference to each element.
// Elements without any reference were destroyed by the
// garbage collection and loses their type.
$this->_document->_elements[] = $element;
$this->_currentElement->appendChild($element);
$this->_addAttributes($element);
return $element;
}
/**
* Creates a new element.
*
* #return DOMElement The created element.
*/
protected function _createElement()
{
$class = $this->getClassForElement($this->_reader->localName);
return new $class(
$this->_reader->name,
$this->_reader->value,
$this->_reader->namespaceURI
);
}
/**
* Adds the current attributes to an $element.
*
* #param DOMElement $element
* #return void
*/
protected function _addAttributes(DOMElement $element)
{
while ($this->_reader->moveToNextAttribute()) {
$this->_reader->prefix && ($uri = $this->_reader->lookupNamespace($this->_reader->prefix))
? $element->setAttributeNS($uri, $this->_reader->name, $this->_reader->value)
: $element->setAttribute($this->_reader->name, $this->_reader->value);
}
}
}
I can't really pin point it but what you're trying has a certain smell, like Gordon already pointed out.
Anyway... you could use __call() to expose different methods on your extDOMElement object depending on the actual node (type/contents/...). For that purpose your extDOMElement object could store an helper object which is instantiated according to the "type" of the element and then delegate method calls to this helper object. Personally I don't like that too much as it doesn't exactly make documentation, testing and debugging any easier. If that sounds feasible to you I can write down a self-contained example.
This certainly needs comments/documentation ...work in progress since I don't have the time right now...
<?php
$doc = new MyDOMDocument('1.0', 'iso-8859-1');
$doc->loadxml('<animals>
<Foo name="fido" />
<Bar name="lucky" />
<Foo name="scratchy" />
<Ham name="flicka" />
<Egg name="donald" />
</animals>');
$xpath = new DOMXPath($doc);
foreach( $xpath->query('//Foo') as $e ) {
echo $e->name(), ': ', $e->foo(), "\n";
}
echo "----\n";
foreach( $xpath->query('//Bar') as $e ) {
echo $e->name(), ': ', $e->bar(), "\n";
}
echo "====\n";
echo $doc->savexml();
class MyDOMElement extends DOMElement {
protected $helper;
public function getHelper() {
// lazy loading and caching the helper object
// since lookup/instantiation can be costly
if ( is_null($this->helper) ) {
$this->helper = $this->resolveHelper();
}
return $this->helper;
}
public function isType($t) {
return $this->getHelper() instanceof $t;
}
public function __call($name, $args) {
$helper = $this->getHelper();
if ( !method_exists($helper, $name) ) {
var_dump($name, $args, $helper);
throw new Exception('yaddayadda');
}
return call_user_func_array( array($this->helper, $name), $args);
}
public function releaseHelper() {
// you might want to consider something like this
// to help php with the circular references
// ...or maybe not, haven't tested the impact circual references have on php's gc
$this->helper = null;
}
protected function resolveHelper() {
// this is hardcored only for brevity's sake
// add any kind of lookup/factory/... you like
$rv = null;
switch( $this->tagName ) {
case 'Foo':
case 'Bar':
$cn = "DOMHelper".$this->tagName;
return new $cn($this);
default:
return new DOMHelper($this);
break;
}
}
}
class MyDOMDocument extends DOMDocument {
public function __construct($version=null,$encoding=null) {
parent::__construct($version,$encoding);
$this->registerNodeClass('DOMElement', 'MyDOMElement');
}
}
class DOMHelper {
protected $node;
public function __construct(DOMNode $node) {
$this->node = $node;
}
public function name() { return $this->node->getAttribute("name"); }
}
class DOMHelperFoo extends DOMHelper {
public function foo() {
echo 'foo';
$this->node->appendChild( $this->node->ownerDocument->createElement('action', 'something'));
}
}
class DOMHelperBar extends DOMHelper {
public function bar() {
echo 'bar';
$this->node->setAttribute('done', '1');
}
}
prints
fido: foo
scratchy: foo
----
lucky: bar
====
<?xml version="1.0"?>
<animals>
<Foo name="fido"><action>something</action></Foo>
<Bar name="lucky" done="1"/>
<Foo name="scratchy"><action>something</action></Foo>
<Ham name="flicka"/>
<Egg name="donald"/>
</animals>
EDIT for the code you show, wouldn't it be easier not to extend DOMElement at all? Just pass in the regular DOMElements to your processing Strategies, e.g.
class AnimalProcessor
{
public function processAnimals(DOMDocument $dom) {
foreach($dom->documentElement->childNodes as $animal) {
$strategy = $animal->tagName . 'Strategy';
$strategy = new $strategy($animal);
$strategy->process();
}
}
}
$dom = new DOMDocument;
$dom->load('animals.xml');
$processor = new AnimalProcessor;
$processor->processAnimals($dom);
Original answer before question update
Not sure if this is what you are looking for, but if you want specialized DOMElements, you can simply create them and use them directly, e.g. bypassing createElement, so you dont have to registerNodeClass at all.
class DogElement extends DOMElement
{
public function __construct($value)
{
parent::__construct('dog', $value);
}
}
class CatElement extends DOMElement
{
public function __construct($value)
{
parent::__construct('cat', $value);
}
}
$dom = new DOMDocument;
$dom->loadXML('<animals/>');
$dom->documentElement->appendChild(new DogElement('Sparky'));
$dom->documentElement->appendChild(new CatElement('Tinky'));
echo $dom->saveXml();
I don't think you can easily use registerNodeClass to instantiate elements based on the tagname or influence parsing that much. But you can override DOMDocument's createElement class, e.g.
class MyDOMDocument extends DOMDocument
{
public function createElement($nodeType, $value = NULL)
{
$nodeType = $nodeType . 'Element';
return new $nodeType($value);
}
}
$dom = new MyDOMDocument;
$dom->loadXML('<animals/>');
var_dump( $dom->createElement('Dog', 'Sparky') ); // DogElement
var_dump( $dom->createElement('Cat', 'Tinky') ); // CatElement
echo $dom->saveXml();