When extending the base Walker class I need to extend the walk() method.
However, calling the parent walk() method yields no results.
These are the approaches I have tried:
public function walk($elements, $max_depth) {
parent::walk($elements, $max_depth);
}
public function walk($elements, $max_depth) {
$parent_class=get_parent_class($this);
$args = array($elements, $max_depth);
call_user_func_array(array($parent_class, 'walk'), $args);
}
It appears to me that as soon as I override the walk() things break.
Should this method return some specific value?
Should I call the parent method differently?
Walker::walk will return the string resulting from the walk operation.
What you will get is a text that has been created using the methods Walker::display_element, Walker::start_lvl, Walker::start_el and so on...
What you will get from the parent method is already HTML code probably hard to modify in the right way in a second time, but if you really want to do that:
public function walk($elements, $max_depth) {
$html = parent::walk($elements, $max_depth);
/* Do something with the HTML output */
return $html;
}
As pointed out in a comment by #TheFallen, the class Walker of Wordpress gives back an output
// Extracted from WordPress\wp-includes\class-wp-walker.php
public function walk( $elements, $max_depth ) {
$args = array_slice(func_get_args(), 2);
$output = '';
//invalid parameter or nothing to walk
if ( $max_depth < -1 || empty( $elements ) ) {
return $output;
}
...
So, if you want to extend the class and overwrite the method, you MUST keep the original behaviour, returning the output too. My suggestion:
class Extended_Walker extends Walker {
public function walk( $elements, $max_depth ) {
$output = parent::walk($elements, $max_depth);
// Your code do things with output here...
return $output;
}
}
Related
Hi I am trying to build a class to emulate Gouette as a learning exercise:
https://github.com/FriendsOfPHP/Goutte/blob/master/README.rst
I think I am on the right track by using method chaining which I think they are doing, what I'm not sure of is how they do something like this:
$crawler->filter('h2 > a')->each(function ($node) {
print $node->text()."\n";
});
Would this be some kind of anonymous function?
This is my code so far:
class Bar
{
public $b;
public function __construct($a=null) {
}
public function chain1()
{
echo'chain1';
return $this;
}
public function loop($a)
{
echo'chain2';
return $this;
}
public function chain2()
{
echo'chain2';
return $this;
}
}
$a=array('bob','andy','sue','rob');
$bar1 = new Bar();
$bar1->chain1()->loop($a)->chain2();
I've tried to simplify the code to show just this one aspect of what your after...
class Bar
{
private $list;
public function __construct($a=null) {
$this->list = $a;
}
public function each( callable $fn )
{
foreach ( $this->list as $value ) {
$fn($value);
}
return $this;
}
}
$a=array('bob','andy','sue','rob');
$bar1 = (new Bar($a))->each(function ($value) {
print $value."\n";
});
As you can see, I've created the object with the list you have, and then just called each() with a callable. You can see the function just takes the passed in value and echoes it out.
Then in each() there is a loop across all the items in the list provided in the constructor and calls the closure ($fn($value);) with each value from the list.
The output from this is...
bob
andy
sue
rob
As for the chained calls, the idea is (as you've worked out) is to return an object which will be the start point for the next call. Some use $this (as you do) some systems (like Request commonly does) return a NEW copy of the object passed in. This is commonly linked to the idea of immutable objects. The idea being that you never change the original object, but you create a new object with the changes made in it. Psr7 Http Message, why immutable? gives some more insight into this.
I'm creating a class to manage user submitted articles. Within this class, I'm also using the PHP Simple Dom Parser class. This class allows you to apply a callback function when it outputs HTML, which I use to filter out unwanted elements. Outside of a class, in procedural style, it's implemented by doing this:
<?php
$html = file_get_html($_FILES["repfile"]["tmp_name"]);
function fileFilter($element){
$toDelete = array("img", "script", "object", "iframe");
foreach($toDelete as $el){
if($element->tag==$el){
$element->outertext = "";
}
}
}
$html->set_callback("fileFilter");
$finalContent = (string)$html->find("div", 0)->innertext;
?>
Now what I want to do in my class is something along the lines of
<?php
class Article{
public $blockedElements;
public $html;
function __toString(){
$html->set_callback("Article::htmlFilter");
return (string)$html;
}
public static function htmlFilter($element){
foreach($this->blockedElements as $el){
if($element->tag==$el){
$element->outertext = "";
}
}
}
}
?>
The obvious problem is that you can't use the $this->blockedElements in a static method, so how would I be able to implement this?
By not making htmlFilter static.
Assuming you got an Article instance, you can pass the instance as a callback as well:
function __toString(){
$html->set_callback(array($this, "htmlFilter"));
return (string)$html;
}
If you do that, then htmlFilter will also be called as an instance method (provided you remove the static keyword).
I have written below classes to make certain DOM operations easier. I want the Easy_Dom_Element's functions to be able to accept both a string and an element as input though. To do that I have to access DOMDocument's createElement method. The call to Easy_Dom::toElement works fine, but $this within that method points to the Easy_Dom_Element instead of Easy_Dom itself. I've tried a static call to createElement like so: Easy_Dom::createElement($element) but for some reason that is not allowed.
class Easy_Dom extends DOMDocument{
/*function __construct(){
$this->registerNodeClass('DOMElement', 'Easy_Dom_Element');
}*/
//Gets the first element by tag name
function getElement($tagName){
return $this->getElementsByTagName($tagName)->item(0);
}
//Creates DOMElement from string if needed
function toElement($element){
if(is_string($element))$element = $this->createElement($element);
return $element;
}
}
class Easy_Dom_Element extends DOMElement{
function prependChildEl($element){
$element = Easy_Dom::toElement($element);
$this->insertBefore($element, $this->firstChild);
return $element;
}
function appendChildEl($element){
$element = Easy_Dom::toElement($element);
$this->appendChild($element);
return $element;
}
}
$_testxml = new Easy_Dom('1.0', 'ISO-8859-1');
$_testxml->registerNodeClass('DOMElement', 'Easy_Dom_Element');
//load defaults
$_testxml->load('default.xml');
//test above classes
$test = $_testxml->getElement('general_title');
$test->appendChildEl('test');
echo $test->nodeValue;
echo $_testxml->saveXML();
Just when I was about to give up on this I finally figured it out, it turns out the answer was really simple.
Just reference the DOMElement's DOMDocument using the ownerDocument property like this:
$DOMDocumentFunctionResult = $this->ownerDocument->DOMDocumentFunction();
So in my example:
class Easy_Dom extends DOMDocument{
/*function __construct(){
$this->registerNodeClass('DOMElement', 'Easy_Dom_Element');
}*/
//Gets the first element by tag name
function getElement($tagName){
return $this->getElementsByTagName($tagName)->item(0);
}
//Creates DOMElement from string if needed
function toElement($element){
if(is_string($element))$element = $this->createElement($element);
return $element;
}
}
class Easy_Dom_Element extends DOMElement{
function prependChildEl($element){
$element = $this->ownerDocument->toElement($element);
$this->insertBefore($element, $this->firstChild);
return $element;
}
function appendChildEl($element){
$element = $this->ownerDocument->toElement($element);
$this->appendChild($element);
return $element;
}
}
What version of PHP are you using?
< PHP 5.3 doesn't allow calling of static methods of inherited classes.
See : http://uk3.php.net/lsb
The code sample is an simple example for what i'm working on.
I have 2 classes in php.
class Wrap {
public function wrapA($arg){
return 'A'.$arg.'A';
}
public function wrapB($arg){
return 'B'.$arg.'B';
}
}
class Child extends Wrap {
public $OUT;
public function wrapA($arg){
$this->OUT .= parent::wrapA($arg);
}
public function wrapB($arg){
$this->OUT .= parent::wrapB($arg);
}
public function __destruct(){
echo $this->OUT;
}
}
$X = new Child();
$X->wrapA(
$X->wrapB('CC')
);
The Output here is "BCCBAA". But what I try to achieve is "ABCCBA".
The "Wrap" class must be in this form.
… and if I have the following method-calls:
$X->wrapB( $X->wrapA('1') );
$X->wrapA( $X->wrapB('aa') .$X->wrapA('bbb') .$X->wrapB(
$X->wrapA('cccc') ) );
… i want to have the following output: BA1ABABaaBAbbbABAcccABA
Is there an other way?
I also want the Wrap-Class to work alone (without Child) … this is why the methods have return-value.
But in Child-Class I want to write the return-values in a variable.
THX in advance!
That's because $X->wrapB('CC') doesn't return anything and gets cast to an empty string by the time $X->wrapA() is called, thus A gets wrapped around nothing.
However, because you append BCCB to $X->OUT, by the time you call $X->wrapA(), it appends AA to that, leading to BCCBAA.
After looking at the question again, I feel that it should be solved in another way; this is something to consider:
class Wrap
{
// The wrapping itself can be declared as a static method
protected static function wrapWithChar($arg, $ch)
{
return $ch . $arg . $ch;
}
}
class Child extends Wrap
{
protected $OUT;
// we allow the internal state to be set upon construction
public function __construct($s = '')
{
$this->OUT = $s;
}
// no arguments required here, this gets applied on the internal state
public function wrapA()
{
$this->OUT = self::wrapWithChar($this->OUT, 'A');
// return instance to allow chaining
return $this;
}
public function wrapB()
{
$this->OUT = self::wrapWithChar($this->OUT, 'B');
return $this;
}
public function __toString()
{
return $this->OUT;
}
public function __destruct(){
echo $this->OUT;
}
}
// initialize with 'CC'
$X = new Child('CC');
// wrap B around it; becomes 'BCCB'
$X->wrapB();
// wrap A around that; becomes 'ABCCBA'
$X->wrapA();
// example of chaining
$Y = new Child('ZZ');
// wrap B then A around it; becomes 'ABZZBA'
$Y->wrapB()->wrapA();
Old answer
To make Child appear as something that Wrap can perform on, you could make use of the __toString() magic method (using instanceof would be more explicit, but also a bit more work):
class Child extends Wrap
{
public $OUT;
public function wrapA($arg)
{
$this->OUT = parent::wrapA($arg);
return $this;
}
public function wrapB($arg)
{
$this->OUT = parent::wrapB($arg);
return $this;
}
public function __toString()
{
return $this->OUT;
}
public function __destruct(){
echo $this->OUT;
}
}
Each wrapX() method now returns the instance itself, and __toString() gets called whenever it needs to be wrapped.
The above will generate the correct result.
I added this to my favorites as an interesting puzzle to solve.
And then found that it wasn't that complicated after I woke up and looked at the problem again.
I honestly don't think you should be using subclassing at this point since technically Child is not logically the a child of the Wrap class, it essentially seems to be a guy that wants to store the output of wrap's results.
so.. Here's my modifications that works with your original interface. Hope it's good for you ;).
It makes some very magical use of magic methods.
<?php
class Wrap {
public function wrapA($arg){
return 'A'.$arg.'A';
}
public function wrapB($arg){
return 'B'.$arg.'B';
}
}
class WrapReader{
protected $wrapper;
protected $currentResult;
public function __construct(Wrap $wrapper)
{
$this->wrapper = $wrapper;
}
public function __call($method,$argument)
{
$argument = $argument[0];
if(!method_exists($this->wrapper,$method))
throw new MethodNotFoundException('Method: '.$method.'() does not exist in class: '.get_class($this->wrapper));
$this->currentResult = $this->wrapper->$method($argument);
return $this->currentResult;
}
public function __destruct(){
echo $this;
}
public function __toString()
{
return $this->currentResult;
}
}
class MethodNotFoundException extends Exception{}
The usage:
$reader = new WrapReader(new Wrap());
echo $reader->wrapB( $reader->wrapA('1') );
echo $reader->wrapA( $reader->wrapB('aa') .$reader->wrapA('bbb') .$reader->wrapB( $reader->wrapA('cccc') ) );
echo '<br>';
Outputs BA1ABABaaBAbbbABAccccABA
Which is what you posted in your original question.
Is there any way, in PHP, to call methods from a parent class using the arbitrary-argument call_user_func_array? Essentially, I want to write a little bit of boilerplate code that, while slightly less optimal, will let me invoke the parent to a method arbitrarily like this:
function childFunction($arg1, $arg2, $arg3 = null) {
// I do other things to override the parent here...
$args = func_get_args();
call_user_func_array(array(parent, __FUNCTION__), $args); // how can I do this?
}
Is this an odd hack? Yeah. I will be using this boilerplate in many places, though, where there's likely to be error in transcribing the method args properly, so the tradeoff is for fewer bugs overall.
Try either one of
call_user_func_array(array($this, 'parent::' . __FUNCTION__), $args);
or
call_user_func_array(array('parent', __FUNCTION__), $args);
... depending on your PHP version. Older ones tend to crash slightly, careful :)
You can call any method on a parent class, as long as it is not overloaded closer to the class of the instance. Just use $this->methodName(...)
For slightly more advanced magic, here's a working example of what you seem to want:
Please note that i do not believe this to be a good idea
class MathStuff
{
public function multiply()
{
$total = 1;
$args = func_get_args();
foreach($args as $order => $arg)
{
$total = $total * $arg;
}
return $total;
}
}
class DangerousCode extends MathStuff
{
public function multiply()
{
$args = func_get_args();
$reflector = new ReflectionClass(get_class($this));
$parent = $reflector->getParentClass();
$method = $parent->getMethod('multiply');
return $method->invokeArgs($this, $args);
}
}
$danger = new DangerousCode();
echo $danger->multiply(10, 20, 30, 40);
Basically, this looks up the method MathStuff::multiply in the method lookup table, and executes its code on instance data from a DangerousCode instance.