Link to home
Start Free TrialLog in
Avatar of rgb192
rgb192Flag for United States of America

asked on

calling the parent instead of child class

<?php
abstract class ParamHandler {
    protected $source;
    protected $params = array();

    function __construct( $source ) {
        $this->source = $source;
    }

    function addParam( $key, $val ) {
        $this->params[$key] = $val;
    }

    function getAllParams() {
        return $this->params;
    }

    protected function openSource( $flag ) {
        $fh = @fopen( $this->source, $flag );
        if ( empty( $fh ) ) {
            throw new Exception( "could not open: $this->source!" );
        }
        return $fh;
    }

    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        return new TextParamHandler( $filename );
    }

    abstract function write();
    abstract function read();
}

class XmlParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        fputs( $fh, "<params>\n" );
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "\t<param>\n" );
            fputs( $fh, "\t\t<key>$key</key>\n" );
            fputs( $fh, "\t\t<val>$val</val>\n" );
            fputs( $fh, "\t</param>\n" );
        }
        fputs( $fh, "</params>\n" );
        fclose( $fh );
        return true;
    }

    function read() {
        $el = @simplexml_load_file( $this->source ); 
        if ( empty( $el ) ) { 
            throw new Exception( "could not parse $this->source" );
        } 
        foreach ( $el->param as $param ) {
            $this->params["$param->key"] = "$param->val";
        }
        return true;
    } 

}

class TextParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "$key:$val\n" );
        }
        fclose( $fh );
        return true;
    }

    function read() {
        $lines = file( $this->source );
        foreach ( $lines as $line ) {
            $line = trim( $line );
            list( $key, $val ) = explode( ':', $line );
            $this->params[$key]=$val;
        }
        return true;
    } 
}

class extendingParam extends ParamHandler{
      function getAllParams() {
        return $this->params;
    }
    function read(){
      parent::read();
    }
    function write(){
      parent::write();
    }
}

$file = "./texttest-fake.xmlfake"; 
//$file = "./texttest.txt"; 
$test = ParamHandler::getInstance( $file );
$test->addParam("key1", "val1" );
$test->addParam("key2", "val2" );
$test->addParam("key3", "val3" );
$test->write();

$test2 = ParamHandler::getInstance( $file );
$test2->read();

$arr2 = $test2->getAllParams();
print_r( $arr2 );

$test3=extendingParam::getInstance($file);
$test3->read();

$arr3= $test3->getAllParams();
print_r($arr3);

Open in new window



this code was originally from php book matt zandstra

I added

class extendingParam extends ParamHandler{

$test3=extendingParam::getInstance($file);
$test3->read();

$arr3= $test3->getAllParams();
print_r($arr3);

why does the code use
paramHandler::getAllParams
instead of
extendingParam::getAllParams
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

I'm not 100% certain on this, because I -think- some types of object behavior has changed across different versions of PHP, but here is an educated guess:

Because extendingParam doesn't have its own static getInstance() method, it's going back to the parent to make the call, and so it's getting an instance of the parent instead of extendingParam.

I've noticed some funny behavior when it comes to inheriting static methods and properties. Unless the child specifically implements its own method of that same name, PHP will go back to the parent for the method/property value.
I don't know -- I don't see any reason in the code.  They seem to be the same thing.

ParamHandler::getAllParams() {
        return $this->params;
}

extendingParam extends ParamHandler{
    function getAllParams() {
        return $this->params;
    }
}
... when it comes to inheriting [strike]static[/strike] methods and properties. Unless the child specifically implements its own method of that same name, PHP will go back to the parent for the method/property value.
Wouldn't that be true for all methods and properties up the inheritance chain?
how can you tell it is calling the Parent function when they both return the same thing?
Because you are using getInstance to get an Instance to the object and if you look at the code
    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        return new TextParamHandler( $filename );
    }

Open in new window

getInstance only returns an instance of XmlParamHandler or TextParamHandler - nowhere in the code do you have logic to create an instance of class extendingParam

Don't get confused between the getInstance and the new operator.

The new operator will create an instance of the desired object.

The getInstance method is a static function (i.e. it can be called without first creating an instance of the object) and (in this case) this method checks the type of file being passed in to decide whether to create an XmlParamHandler or a TextParamHandler

If you want it to create your class you would have to add to the logic something like
    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        if ( preg_match( "/\.exc$/i", $filename )) {
            return new extendingParam( $filename );
        }
        return new TextParamHandler( $filename );
    }

Open in new window

And invoke it like so
$file = 'test.exc';
$test4 = ParamHandler::getInstance( $file );
$arr4 = $test4->getAllParams();
print_r($arr4);

Open in new window

Following on from my previous post.

To further determine which of the methods is being called you can do the following.
In the parent change the definition of getAllParams to
function getAllParams() {
  echo "I am in the parent class";
  return $this->params;
}

Open in new window

And in your derived class change getAllParams to
function getAllParams() {
  echo "I am in the derived class";
  return $this->params;
}

Open in new window

When you invoke the getAllParams method you will be able to see which of the two was invoked
Avatar of rgb192

ASKER

<?php
abstract class ParamHandler {
    protected $source;
    protected $params = array();

    function __construct( $source ) {
        $this->source = $source;
    }

    function addParam( $key, $val ) {
        $this->params[$key] = $val;
    }

    function getAllParams() {
        return $this->params;
    }

    protected function openSource( $flag ) {
        $fh = @fopen( $this->source, $flag );
        if ( empty( $fh ) ) {
            throw new Exception( "could not open: $this->source!" );
        }
        return $fh;
    }

    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        return new TextParamHandler( $filename );
    }

    abstract function write();
    abstract function read();
}

class XmlParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        fputs( $fh, "<params>\n" );
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "\t<param>\n" );
            fputs( $fh, "\t\t<key>$key</key>\n" );
            fputs( $fh, "\t\t<val>$val</val>\n" );
            fputs( $fh, "\t</param>\n" );
        }
        fputs( $fh, "</params>\n" );
        fclose( $fh );
        return true;
    }

    function read() {
        $el = @simplexml_load_file( $this->source ); 
        if ( empty( $el ) ) { 
            throw new Exception( "could not parse $this->source" );
        } 
        foreach ( $el->param as $param ) {
            $this->params["$param->key"] = "$param->val";
        }
        return true;
    } 

}

class TextParamHandler extends ParamHandler {

    function write() {
        $fh = $this->openSource('w');
        foreach ( $this->params as $key=>$val ) {
            fputs( $fh, "$key:$val\n" );
        }
        fclose( $fh );
        return true;
    }

    function read() {
        $lines = file( $this->source );
        foreach ( $lines as $line ) {
            $line = trim( $line );
            list( $key, $val ) = explode( ':', $line );
            $this->params[$key]=$val;
        }
        return true;
    } 
}

class extendingParam extends ParamHandler{
      function getAllParams() {
        return $this->params;
    }
    function read(){
      //parent::read();
      echo '<br>'.__METHOD__.'<br>';
    }
    function write(){
      parent::write();
    }
    static function getInstance( $filename ) {
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        if ( preg_match( "/\.exc$/i", $filename )) {
            return new extendingParam( $filename );
        }
        return new TextParamHandler( $filename );
    }    
}

$file = "./texttest-fake.xmlfake"; 
//$file = "./texttest.txt"; 
$test = ParamHandler::getInstance( $file );
$test->addParam("key1", "val1" );
$test->addParam("key2", "val2" );
$test->addParam("key3", "val3" );
$test->write();

$test2 = ParamHandler::getInstance( $file );
$test2->read();

$arr2 = $test2->getAllParams();
print_r( $arr2 );

$test3=extendingParam::getInstance($file);
$test3->read();

$arr3= $test3->getAllParams();
print_r($arr3);

Open in new window


since adding new method
extendingParam::getInstance

$test3=extendingParam::getInstance($file);
extendingParam::getInstance is called



$test3->read();

something magical that I do not understand happens in
extendingParam::getInstance

and


textparamhandler::read() is called


extendingParam::read() is not called
Taken together, this just makes no sense and should probably throw a fatal error. Here's why I think this is off-course.

Line 2:
abstract class ParamHandler

Line 33 and 34 inside abstract class ParamHandler:
abstract function write();
abstract function read();

Line 88:
class extendingParam extends ParamHandler

Now at this point we have established that read() and write() methods of ParamHandler are abstract, meaning that ParamHandler cannot be instantiated and any class that extends ParamHandler must provide a definition for the read() and write() methods.  But...

Line 96-98:
function write(){
   parent::write();
}

By this definition, when write() is called on the concrete instance of extendingParam, a call is made to the abstract method().  I don't see how this can end well.
textparamhandler::read() is called
extendingParam::read() is not called

That is because you are calling the static getInstance method on the extendingParam class.
$test3=extendingParam::getInstance($file);

Open in new window

Lets look at what that does. ($filename = "./texttest-fake.xmlfake" by line 110 of your source listing)
static function getInstance( $filename ) {

        // THIS IS NOT GOING TO MATCH BECAUSE THE REGEX IS
        // LOOKING FOR A STRING THAT ENDS .xml AND 
        // $filename ENDS .xmlfake
        if ( preg_match( "/\.xml$/i", $filename )) {
            return new XmlParamHandler( $filename );
        }
        // THIS IS ALSO NOT GOING TO MATCH
        if ( preg_match( "/\.exc$/i", $filename )) {
            return new extendingParam( $filename );
        }

        // SO THIS IS WHAT EXECUTES
        // YOU ARE RETURNING AN OBJECT OF TYPE
        // TextParamHandler
        return new TextParamHandler( $filename );
    }    

Open in new window

Therefore when you invoke the method
$test3->read()

Open in new window

You are going to be calling the read method defined in the TextParamHandler class and not the one defined in extendingParam.

Try changing
$filename = "./texttest-fake.exc";

Open in new window

and see what happens.
Avatar of rgb192

ASKER

$filename = "./texttest-fake.exc";

now
extendingParam::read() is called


so now my question:
how can
$test3=extendingParam::getInstance($file);
return an object


            return new XmlParamHandler( $filename );
   
            return new extendingParam( $filename );
       
        return new TextParamHandler( $filename );
   

because when object is returned the next method calls change based upon the object that was called

$test3->read();
$test3->getAllParams();
---------------------------------------------------------------------------------



I do not know if Ray's comments reflect Julian's method
https://www.experts-exchange.com/viewCodeSnippet.jsp?refID=40042672&rtid=20&icsi=2

and/or I do not understand the changes I should make
how can
$test3=extendingParam::getInstance($file);
return an object
Because that is how the class hierarchy has been defined. The following example should explain this more clearly

Define a parent class
Class parent 
{
    function read()
    {
        echo "parent read";
    }

    function another_function()
    {
        echo "parent another function";
    }
}

Open in new window

Now define two child classes in each case override the read() function but only in the second extend the another_function()
class firstChild extends parent
{
    function read()
    {
        echo "firstChild read overrides parent";
    }
}

class secondChild extends parent
{
    function read()
    {
        echo "secondChild read overrids parent";
    }
    
    function another_function()
    {
        parent::another_function();
        echo "secondChild another_function extends parent";
    }
}
// BLOCK A
$parent = new parent();
$parent->read();
$parent->another_function();
// Output
// parent read
// parent another function

// BLOCK B
$firstChild = new firstChild();
$firstChild->read();
$firstChild->another_function();
// Output
// firstChild read overrides parent
// parent another function

// BLOCK C
$secondChild = new secondChild();
$secondChild->read();
$secondChild->another_function();
// Output
// secondChild read overrides parent
// parent another function
// secondChild another function extends parent

Open in new window

If we look at the first block (BLOCK A) where we create a new parent object. When we call read() and another_function() the interpreter starts at the parent class and looks to see if this class defines those two methods - which it does so it invokes those

If we look at the second block (BLOCK B) where we create a new firstChild object. When we call read() the interpreter finds a definition of read in the firstChild class - so it invokes that. However when we call another_function there is no definition for another_function in the firstChild class so the interpreter goes one level up to firstChild's parent to see if it can find a definition of the function there. The interpreter will stop traversing the hierarchy as soon as it finds a matching definition of the called method.

In the case of the read() function for firstChild and secondChild - you will notice that the parent read() is not callsed - the read() methods in the child classes override the functionality in the parent. To invoke the parent functionality you would have to specifically call the method in question from within the method local to the class. This is demonstrated in the secondChild another_function()

If we look at the third block (BLOCK C) where we create a new secondChild object. The same outcome for read will occur as for firstChild. The read() method defined in the secondChild class will be invoked. When the another_function method is called on secondChild a definition for another_function exists in the secondChild class - but you will notice the first thing this function does is to invoke the parent method before continuing to do its output. In this scenario the class has extended the functionality of another_function by calling the parent version and then adding to it.

To answer you question (if I understand what you are asking)
because when object is returned the next method calls change based upon the object that was called
This will happen because of the way the parent and child have been setup - if you are referring to the getAllParams() call - then you will notice this is defined in the parent ParamHandler class and it is defined in the extendingParam class but not in the TextParamHandler class or the XmlParamHandler class.

So when you invoke the getAllParams() method on an object of either TextParamHandler or XmlParamHandler - this method is not defined in these classes so the parent method will be invoked.

In the case of the ParamHandler class - a getAllParams() method does exist in this class so it overrides the parent.

Does that answer your question
Avatar of rgb192

ASKER

<?php
Class parent1 
{
    function read()
    {
        echo "parent read";
    }

    function another_function()
    {
        echo "parent another function";
    }
}


class firstChild extends parent1
{
    function read()
    {
        echo "firstChild read overrides parent";
    }
}

class secondChild extends parent1
{
    function read()
    {
        echo "secondChild read overrids parent";
    }
    
    function another_function()
    {
        parent::another_function();
        echo "secondChild another_function extends parent";
    }
}
// BLOCK A
$parent = new parent1();
$parent->read();
$parent->another_function();
// Output
// parent read
// parent another function

// BLOCK B
$firstChild = new firstChild();
$firstChild->read();
$firstChild->another_function();
// Output
// firstChild read overrides parent
// parent another function

// BLOCK C
$secondChild = new secondChild();
$secondChild->read();
$secondChild->another_function();
// Output
// secondChild read overrides parent
// parent another function
// secondChild another function extends parent

Open in new window


I understand method overriding in this basic example
but do not understand complex examples where an object is a return


Don't get confused between the getInstance and the new operator.

The new operator will create an instance of the desired object.

could you give an example because I only have one example: code sample from original question.
ASKER CERTIFIED SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of rgb192

ASKER

getInstance method returns an object. The important thing is that it can return different types of object based on the extension of the file name given


thanks


Also I have a followup question asking for more examples of an instance of a class created inside a class
https://www.experts-exchange.com/questions/28430190/creating-an-instance-of-a-class-inside-a-class.html