I'm trying to debug a large and complex DOMDocument object in php. Ideally it'd be nice if I could get DOMDocument to output in a array-like format.
DoMDocument:
$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
var_dump($dom); //or something equivalent
This outputs
DOMDocument Object ( )
whereas I'd like it to output
DOMDocument:
html
=>body
==>p
===>Hello World
Or something like that. Why is there no handy debug or output for this?!?
This answer is a little late probably, but I liked your question!
PHP has nothing build-in directly to solve your problem, so there is not XML dump or something.
However, PHP has the RecursiveTreeIteratorDocs that comes pretty close to your output:
\-<html>
\-<body>
\-<p>
\-Hello World
(it will look better if your X(HT)ML structure looks more complicated.)
It's used quite simple (as most iterators) with a foreach:
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
echo $value . "\n";
}
(You can wrap this inside a function, so you only need to call the function)
Even this looks simple, there's one caveat: it needs a RecursiveIterator over the DOMDocument tree. As PHP can not guess what you need, it needs to be wrapped into code. As written, I found the question interesting (and obviously you have not asked for XML output), so I wrote some little code that offers the recursive iterator needed. So here we go.
First of all you might not be familiar with iterators in PHP. That's no deal to make use of the code I'll show as I'll do it backwards, however, whenever you consider to run some code on your own, consider whether or not you can make use of the iterator capabilities PHP has to offer. I write that because it helps to solve common problems and to make components that are not really related with each other to work with each other. For example, the RecursiveTreeIteratorDocs is built-in, and it will work with anything you feed it with (and you can even configure it). However it needs a RecursiveIterator to operate upon.
So let's give it a RecursiveIterator that offers <tag> for DOMNodes that are tags (elements) and just the text if they are textnodes:
class DOMRecursiveDecoratorStringAsCurrent extends RecursiveIteratorDecoratorStub
{
public function current()
{
$node = parent::current();
$nodeType = $node->nodeType;
switch($nodeType)
{
case XML_ELEMENT_NODE:
return "<$node->tagName>";
case XML_TEXT_NODE:
return $node->nodeValue;
default:
return sprintf('(%d) %s', $nodeType, $node->nodeValue);
}
}
}
This DOMRecursiveDecoratorStringAsCurrent class (the name is exemplary only) makes use of some abstract code in RecursiveIteratorDecoratorStub. The important part however is the ::current function which just returns the tagName of a DOMNode in bracketsWikipedia (<>) and the text of textnodes as-is. That's what your output needs, so that's everything needed to code.
Actually this does not work until you have the abstract code as well, but to visualize the code how it's used (the most interesting part), let's view it:
$iterator = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
echo $value . "\n";
}
As it's done backwards, for the moment we have the output specified based on which DOMNode is to be displayed by the RecursiveTreeIterator. Fine so far, easy to get. But the missing meat it is inside the abstract code and how to create a RecursiveIterator over all nodes inside a DOMElement. Just preview the whole code how it is invoked (as written before, you can put this into a function to make it easily accessible within your code for debugging purposes. Probably a function called xmltree_dump):
$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
$iterator = new DOMRecursiveIterator($dom->documentElement);
$iterator = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
echo $value . "\n";
}
So what do we got here in addition to the code already covered? First there is a DOMRecursiveIterator - and that's it. The rest of the code is standard DOMDocument code.
So let's write about DOMRecursiveIterator. It's the needed RecursiveIterator that's finally needed within the RecursiveTreeIterator. It get's decorated so that the dump of the tree actually prints tagnames in brackets and text as-is.
Probably it's worth to share the code of it now:
class DOMRecursiveIterator extends DOMIterator implements RecursiveIterator
{
public function hasChildren()
{
return $this->current()->hasChildNodes();
}
public function getChildren()
{
$children = $this->current()->childNodes;
return new self($children);
}
}
It's a pretty short class with only two functions. I'm cheating here as this class also extends from another class. But as written, this is backwards, so this class actually takes care of the recursion: hasChildren and getChildren. Obviously even those two functions don't have much code, they are just mapping the "question" (hasChildren? getChildren?) onto a standard DOMNode. If a node has children, well, say yes or just return them (and this is an iterator, return them in form of an iterator, hence the new self()).
So as this is pretty short, after choking it, just continue with the parent class DOMIterator (the implements RecursiveIteratorDocs is just to make it working):
class DOMIterator extends IteratorDecoratorStub
{
public function __construct($nodeOrNodes)
{
if ($nodeOrNodes instanceof DOMNode)
{
$nodeOrNodes = array($nodeOrNodes);
}
elseif ($nodeOrNodes instanceof DOMNodeList)
{
$nodeOrNodes = new IteratorIterator($nodeOrNodes);
}
if (is_array($nodeOrNodes))
{
$nodeOrNodes = new ArrayIterator($nodeOrNodes);
}
if (! $nodeOrNodes instanceof Iterator)
{
throw new InvalidArgumentException('Not an array, DOMNode or DOMNodeList given.');
}
parent::__construct($nodeOrNodes);
}
}
This is the base iterator for DOMPHP, it just takes a DOMNode or a DOMNodeList to iterate over. This sounds a bit superfluous maybe, as DOM supports this sort-of with DOMNodeList already, but it does not support a RecursiveIterator and we already know that we need one for RecursiveTreeIterator for the output. So in it's constructor an Iterator is created and passed on to the parent class, which again is abstract code. Sure I'll reveal this code in just a minute. As this is backwards, let's review what's been done so far:
RecursiveTreeIterator for the tree-like output.
DOMRecursiveDecoratorStringAsCurrent for the visualization of a DOMNode in the tree
DOMRecursiveIterator and DOMIterator to iterate recursively over all nodes in a DOMDocument.
This in terms of definition as all that's needed, however the code that I called abstract is still missing. It's just some sort of simple proxy code, it delegates the same method down to another object. A related pattern is called Decorator. However, this is just the code, first the Iterator and then it's RecursiveIterator friend:
abstract class IteratorDecoratorStub implements OuterIterator
{
private $iterator;
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
public function getInnerIterator()
{
return $this->iterator;
}
public function rewind()
{
$this->iterator->rewind();
}
public function valid()
{
return $this->iterator->valid();
}
public function current()
{
return $this->iterator->current();
}
public function key()
{
return $this->iterator->key();
}
public function next()
{
$this->iterator->next();
}
}
abstract class RecursiveIteratorDecoratorStub extends IteratorDecoratorStub implements RecursiveIterator
{
public function __construct(RecursiveIterator $iterator)
{
parent::__construct($iterator);
}
public function hasChildren()
{
return $this->getInnerIterator()->hasChildren();
}
public function getChildren()
{
return new static($this->getInnerIterator()->getChildren());
}
}
That's nothing very magically, it's just well delegating the method calls to it's inherited object $iterator. It looks like repeating and well iterators are about repetition. I put this into abstract classes so I only need to write this very simple code once. So at least I myself don't need to repeat myself.
These two abstract classes are used by other classes which have been already discussed earlier. Because they are so simple, I left it until here.
Well, much to read until here but the good part is, that's it.
In short: PHP does not have this build in, but you can write this on your own quite simple and re-useable. As written earlier, it's a good idea to wrap this into a function called xmltree_dump so it can be easily called for debugging purposes:
function xmltree_dump(DOMNode $node)
{
$iterator = new DOMRecursiveIterator($node);
$decorated = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($decorated);
foreach($tree as $key => $value)
{
echo $value . "\n";
}
}
Usage:
$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
xmltree_dump($dom->documentElement);
the only thing needed is to have all the class definitions used included / required. You can put them in one file and use require_once or integrate them with an autoloader that you're probably using. Full code at once.
If you need to edit the way of output, you can edit DOMRecursiveDecoratorStringAsCurrent or change the configuration of RecursiveTreeIterator inside xmltree_dump. Hope this is helpful (even quite lengthy, backwards is pretty in-direct).
http://usphp.com/manual/en/function.dom-domdocument-savexml.php
$dom->formatOutput = true;
echo $dom->saveXML();
for a dom node, just use the following:
print_r(simplexml_import_dom($entry)->asXML());
Though I haven't tried it myself, check out Zend_Dom, part of the Zend Framework. Documentation and examples for most of the Zend Framework components is really thorough.
I just used DOMDocument::save. It's lame that it has to write to a file, but whatever.
You can cheat and use JSON to inspect the structure by converting it to an array.
print_r(json_decode(json_encode($node), true));
Related
Hi how do i create a class that works like this?
$shop = new shop();
$shop->cart(function ($data){
//$data
})->coupon('hello world!');
$shop->getCoupon(); //hello world!
so how do i do this? i have played around with examples from Calling a function within a Class method?
i even took parts of the original title sorry original poster.
Your question is a bit vague, but I think what you are talking about is a Fluent Interface. The idea behind them is to enable you to call multiple methods on a single instance, by having each method return the instance. It's commonly used for setters on classes, and enables you to write code like:
$foo = new Foo();
$foo
->setThisThing()
->setAnotherThing()
->setThingToParameter($parameter)
...;
rather than
$foo->setThisThing();
$foo->setAnotherThing();
...
Whether you find this better or worse is a matter of taste, but Fluent interfaces do come some drawbacks
In your case, the shop class might look like:
<?php
class shop
{
private $couponText;
public function cart($function) {
// Do something with $function here
return $this;
}
public function coupon($couponText) {
$this->couponText = $couponText;
return $this;
}
public function getCoupon() {
return $this->couponText;
}
}
The key parts are the return $this; lines - they allow you to chain subsequent method calls onto each other, as in your example.
See https://eval.in/851708 for an example.
Sorry for a bit misleading title but I don't quite know how to ask that better.
I have a directory with classes doing same job but with different implementation. They all look like this:
class someClass {
private static $variable=12345;
public static function someTask($keyword){
...
return array();
}
private function pf() {...}
}
The methods are taking same arguments, and returning array of the same structure.
I'd like to create one class to be able to call selected classes form that folder (and maybe each of them) combine their result, sort it by some criteria end return.
I thought of strategy pattern, but as far as I know it goes like :
$obj = new Context();
$obj->setStrategy(new ConcreteStrategy1);
$obj->getStrategy()->task(); // => „Strategy 1”
$obj->setStrategy(new ConcreteStrategy2);
$obj->getStrategy()->task(); // => „Strategy 2”
Each time I want to call another class I have to change it manually. That will leave me using foreach on some array of classes (strategies) names. Is that the right way to go? Also please help me find better title for that question because that one is misleading.
Strategy pattern it's just an algorithm that we extracted and put into some method. Then we pass the algorithm we need into context and use it there.
If you want to have one class that processes different strategies and combine their result I think Visitor pattern will be better.
I come up with a working solution
class SearchEngine {
public $instances=array();
public function __construct() {
$inputSites = $this->importClasses(); //import all classes in directory
foreach ($inputSites as $site) {
$classname = 'API\\' . $site . "Api";
$startegyInstance = new $classname();
array_push($this->instances,$startegyInstance);
}
}
public function searchByTitle($keyword) {
$results = array();
for ($i=0; $i<sizeof($this->instances); $i++){
//this searchByTitle is not recursion
$next = $this->instances[$i]->searchByTitle($keyword);
$results=array_merge($results,$next);
}
return $results;
}
private function importClasses($classesToImport){
foreach ($classesToImport as $class) {
$imp="API/".$class."Api.php";
require_once ($imp);
}
}
}
(I cut less significant details)
In classes directory I have interface and classes.
This solution works. I have not enough experience to judge it optimal or good.
I have a class called Rule and I'm about to create a RuleContainer class that's actually an array of Rule objects.
I wonder if there is an alternative of creating a new class. Is there any (modern) way to approach this problem? That is, something like using SPL to define an array that only allows adding objects of a specific class.
If not, which interface should I implement in my RuleContainer class?
The most suitable class for your task would be SplObjectStorage, but it doesn't allow for class typehint.
I think, you could do as follow:
class RuleContainer extends SplObjectStorage
{
function attach(Rule $rule)
{
parent::attach($rule);
}
function detach(Rule $rule)
{
parent::detach($rule);
}
}
and so on. You can read for SplObjectStorage interface on php.net and decide, what will you use and what needs overriding.
In your case, I would implement the Iterator interface in the RuleContainer, as I've done several times when I needed a sort of Collection<T> as we know it from other (typed) languages. And in the add(Rule $item) or addItem(Rule $item) method I'd make sure with the type definition of the argument (or using instanceof) that the item to be added is of type Rule.
Depending on the usage patterns for your container class, you need to implement one or more of these interfaces:
Iterator - to use it as foreach($container as $key => $value);
Countable - for count($container);
ArrayAccess - for $container[$key] (set it, get it, check if it isset(), unset() it);
Usage of PHP array-routines interfaces
You may achieve your goal with, for example, ArrayAccess implementation. Together with Iterator it will look like:
class ArrayStorage implements Iterator, ArrayAccess
{
private $holder = [];
private $instanceName;
public function __construct($instanceName)
{
if (!class_exists($instanceName)) {
throw new \Exception('Class '.$instanceName.' was not found');
}
$this->instanceName = $instanceName;
}
public function rewind()
{
reset($this->holder);
}
public function current()
{
return current($this->holder);
}
public function key()
{
return key($this->holder);
}
public function next()
{
next($this->holder);
}
public function valid()
{
return false !== $this->current();
}
public function offsetSet($offset, $value)
{
if (!($value instanceof $this->instanceName)) {
throw new \Exception('Storage allows only '.$this->instanceName.' instances');
}
if (is_null($offset)) {
$this->holder[] = $value;
} else {
$this->holder[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->holder[$offset]);
}
public function offsetUnset($offset)
{
unset($this->holder[$offset]);
}
public function offsetGet($offset)
{
return isset($this->holder[$offset]) ? $this->holder[$offset] : null;
}
}
Procs
So - yes, you are doing instanceof check explicitly, but end user of your class doesn't know about that. It will only be possible to operate on valid instances in context of this storage (you can check this fiddle for usage sample). Concept is like:
$storage = new ArrayStorage('Foo'); //define what we will accept
$storage[] = new Foo; //fine, [] array-writing
$storage['baz'] = new Foo; //fine, key set
foreach ($storage as $key => $value) {
echo($key. ' => '.PHP_EOL.var_export($value, 1).PHP_EOL);
}
//invalid, will not pass. Either throw exception or just ignore:
$storage['bee'] = new Bar;
End fail-check behavior is up to you, but, my opinion, throwing exception is the best choice here as they are catchable, thus, end user may decide what to do in this case. Further option may be to add Countable to the storage, but it won't change generic idea.
And cons
Downside - no, you will not be able to "typehint" it somehow. While it is useful, in doc blocks you still will need to show what kind of entity are you accepting. In terms of general language features, there is arrayof RFC, by Joe Watkins, which was proposed for PHP version 5.6, but, unfortunately, failed. May be it will be reconsidered in next versions releases.
You can make RuleContainer yourself (as you say) and do all sorts of cleverness to manually enforce it but you live in the real world I live in the real world and you simply don't need a container object for this, just use an array.
If your problem is simply one of enforcement of the subject object type a lá List<className>() you can't do this in PHP and to be honest it's of debatable use in the languages where it is found (I know I will get down voted for saying this, but I will still be right) //excepting it helps further clarify the purpose of the list// In all honesty my 20+ years of programming across almost all the languages there is (except machine code perl and fortran), I can tell you such constructs and simply not worth the human overhead and themselves can include indirect unintended burdens way over their actual worth:
An easy compromise is: no laziness, start naming the array more than something like tmpList and if you are absolultey determined implment a simple test to http://php.net/manual/en/function.get-class.php at the start of the forloops you surely eventually use
So, i would like to implement something like this:
class Collection{
private $array;
public function add($object){
array_push($this->array, $object);
}
public function array(){
return $this->array;
}
}
class JustaClass{
public $myCollection;
public function __construct(){
$this->myCollection = new Collection();
}
}
$justAnObject = new JustaClass();
$justAnObject->myCollection->add(new SomeObject());
this works just fine, but i would like to work with it like i do in .Net, ie, when i want to refer to the collection Object, i would like to do it directly, like:
foreach($justAnObject->myCollection as $collectionItem)
and not like
foreach($justAnObject->myCollection->array() as $collectionItem)
Is there any way I can do this? maybe a magic method, or implementing an Iiterator-like interface?
thanks
Actually, this is what SplObjectStorage does, so no need to code anything:
The SplObjectStorage class provides a map from objects to data or, by ignoring data, an object Set. This dual purpose can be useful in many cases involving the need to uniquely identify objects.
It implements Countable, Iterator and ArrayAccess, so you can foreach it, access it with [] and use count on it. Like the description says it's a Set, so it contains no duplicate elements.
If you want to allow for duplicate elements, you can simply use ArrayIterator or ArrayObject. You can find additional Data Structures similar to the various .NET collections in
http://php.net/manual/en/spl.datastructures.php
IMO, there is no point in writing a custom class unless you also need to customize behavior of any of the options mentioned above.
Let your Collection class implement the Iterator or IteratorAggregate interfaces. There's also an ArrayIterator class, so it's really as easy as just returning an instance of that class:
class Collection implements IteratorAggregate {
private $array;
public function add($object){
array_push($this->array, $object);
}
/* required by IteratorAggregate */
public function getIterator() {
return new ArrayIterator($this->array);
}
}
You can then use your class in the following way:
$c = new Collection();
$c->add(1);
$c->add(2);
$c->add('we can even add strings');
foreach($c as $v) {
doSomething($v);
}
I'm looking to create an array or list with elements of a certain type (eg objects the implement a certain interface). I know I can create an object that does the same thing implementing Traversable and Iterator, or override ArrayObject. But maybe there's another way I have missed.
Do you mean something like:
$array=Array();
foreach ($itemsToAdd as $item) {
if ($item instanceof NameOfwantedInterface) {
Array_push($array,$item);
}
}
If you don't, them I'm sorry - it's just that your question isn't too clear.
I would write a custom class that extended ArrayObject and threw an exception if you tried to assign a variable that wasn't the correct type, there's really no better way to do it that I can think of.
PHP as a lanugage is very flexible in terms of type handling and type conversion. You will probably have to put a manual check in if you want any kind of strong type checking, a simple if statement will do.
The array object is designed to be especially flexible (lazy key assignment, automatic increment, string or integer keys, etc.) so you should probably use a custom object of your own.
You could use type hinting:
<?php
interface Shape
{
function draw();
}
class MyArray
{
private $array = array();
function addValue(Shape $shape) //The hinting happens here
{
$array[] = $shape;
}
}
?>
This example is not perfect, but you'll get the idea.
Basically, you are going to want to do a function that checks if the variable you are inserting into the array is an object.
function add($var)
{
if(is_object($var))
{
$this->array[] = $var;
}
}
If you want to make sure it has a specific class name, you would do something like this (for PHP5):
function add(className $var)
{
$this->array[] = $var;
}
or this for previous PHP versions:
function add($var)
{
if($var instanceOf className)
{
$this->array[] = $var
}
}
You might want to look into array_filter() to do this without building an object.
Looking at that page, I've found that you can use array_filter with common functions like is_object. So doing something like this:
$this->array = array_filter($this->array ,'is_object');
Would filter the array to contain only objects.