For a current project it would be great to be able to extend the class SimpleXML to access the magic of the SimpleXML traversable. For a reason beyond me the constructor is final. How would I go about extending the class anyway.
What I've done sofar is refrain from constructor on a subclass, instead create a static method to return an XML string the can be used in the caller to construct an object. Not pretty, as it burdens the caller with a pattern that should be hidden, but it is working. Is there a more elegant way to circumvent the obstructing design choice of having a final constructor?
namespace acme.com;
class SubXML extends \SimpleXML{
static public createXML() {
// here we return a magnificent XML string
// probably from a DB or a REST interface
}
}
$o = new \SubXML( \SubXML::createXML() );
The constructor on SimpleXMLElement is not just initialising a PHP object, but making a call into an external library that initialises some internal memory structures which are then reused as you traverse the XML document or fragment.
One supported way of extending the class is to pass the name of a class as second argument to simplexml_load_string or simplexml_load_file, which will be used for all elements and attributes as you traverse. For instance:
class MyXML extends SimpleXMLElement {
public function hello() { echo "Hello ", $this->getName(); }
}
$sx = simplexml_load_string('<foo><bar><baz>42</baz></bar></foo>', 'MyXML');
$sx->bar->baz->hello();
It's not clear what would happen if MyXML
had a custom constructor in this case: should it be called for each child element? With what arguments?
Leaving that aside, your actual requirement seems to be to have a factory of some sort that takes data from some source (a database, an API) and creates some sort of object. Your static method seems like a sensible direction, but can return the object directly rather than a string:
class SubXML extends SimpleXMLElement {
public static function createXMLfromDB($db) {
$xmlString = $db->query('Select XML from SpecialTable');
return simplexml_load_string($xmlString, static::class);
}
}
However, I would argue inheritance isn't a particularly good way of representing this in the first place - the resulting object probably doesn't need to know where its data comes from, it needs to be able to represent that data. So your factory can be a completely different class, managing its dependencies in a more test-friendly way:
class MyXMLFromDBFactory {
private MyDBWrapper $db;
public function __construct(MyDBWrapper $db) {
$this->db = $db;
}
public function createXML() {
$xmlString = $db->query('Select XML from SpecialTable');
return simplexml_load_string($xmlString);
}
}
Then you can have a separate MyXMLFromAPIFactory
, MyXMLFromFileOnAmazonS3Factory
, etc. If you want to, these can all return the same extended SimpleXMLElement
class, or even take a class name as a parameter and pass it through to simplexml_load_string
.