简体   繁体   中英

Extend Class with Final Constructor on PHP

I want extend class which have final constructor (in my case it's SimpleXMLElement), but i have problems because when i use:

    class myclass extends SimpleXMLElement {
        function __construct($xmlVersion='1.0', $xmlEncoding='ISO-8859-1', $rootName='root'){
            parent::__construct("<?xml version='$xmlVersion' encoding='$xmlEncoding'?><$rootName />");
        }

I get error:

Fatal error: Cannot override final method SimpleXMLElement::__construct()

When i delete constructor i get this error:

Fatal error: Uncaught exception 'Exception' with message 'SimpleXMLElement::__construct() expects at least 1 parameter, 0 given'

I miss something or doesn't understand how properly call parent constructor which is final. I don't want override methods just expand class, but i can't expand because it required __construct(). So i missed something and back where started.

Can somebody explain where i was wrong?

I just went through this exact thing. You don't need to extend it. Make a class that holds SimpleXMLElement objects. I believe this is what Nikola meant.

class XmlResultSet
{
    public $xmlObjs = array();

    public function __construct(array $xmlFiles)
    {
      foreach ($xmlFiles as $file) {
          $this->xmlObjs[] = new XmlResult($file);
      }
    }
}

class XmlResult
{
    private $xmlObj;

    public function __construct($file)
    {
        try {
            $this->xmlObj = new SimpleXMLElement($file, 0, true);
        }
        catch (Exception $e) {
            throw new MyException("Invalid argument ($this)($file)(" . $e .
            ")", PHP_ERRORS);
        }
    }

    public function otherFunctions()
    {
        return $this->xmlObj->movie['name']; // whatever
    }
}

I would use Delegate wrapper design in this case. You should consider composition instead of inheritance here.

class myclass extends SimpleXMLElement {
   public static function getInstance($xmlversion = '1.0', $xmlencoding = 'ISO-8859-1', $rootName='root') {
      return new self("<?xml version='$xmlVersion' encoding='$xmlEncoding'?><$rootName />");
   }
}

Well, final means final. No overriding the method. Even if you ask nicely. I suggest adding a static make() method to your new class. Something like:

class myclass extends SimpleXMLElement { 
    static function make($data, $xmlVersion='1.0', $xmlEncoding='ISO-8859-1', $rootName='root'){ 
        $obj=parent::__construct($data); 
        $obj->x=$xmlVersion;
        $obj->e=$xmlEncoding;
        $obj->r=$rootName;

        return $obj;
    } 
}

There can be valid reasons to extend a third-party "final" class, resulting in more readable/maintainable code than completely duplicating it or creating some convoluted work-around. And certainly preferable over changing the third-party source code and removing the "final" keyword.

PHP supplies the extension "Componere" to accomplish such a feat in those rare cases where it truly is the best option. Below an example that shows how a the child class can be defined as a "trait", which can then be used to dynamically extend a final parent class:

<?php
declare(strict_types=1);

/*
 *  Final class would normally prevent extending.
 */
final class ParentC
{
    public $parentvar;
    public $secondvar;

    function __construct() { echo( "\r\n<br/>".$this->parentvar = 'set by '.get_class().'->parentconstruct' ); }
    function parentf() { echo( "\r\n<br/>".get_class().'->parentf >> '.$this->parentvar ); }
}

/*
 *  Extended class members.
 */
trait ChildC /* extends ParentC */
{
    function __construct() {
        // Call parent constructor.
        parent::__construct();
        // Access an inherited property set by parent constructor.
        echo( "\r\n<br/>".get_class().'->overridden_constructor >> '.$this->parentvar );
    }

    function parentf() {
        // Call overridden parent method.
        parent::parentf();
        // Access an inherited property set by constructor.
        echo( "\r\n<br/>".get_class().'->overridden_parentf >> '.$this->parentvar );
    }

    function dynamicf( $parm = null ) {
        // Populate a parent class property.
        $this->secondvar = empty( $parm ) ? 'set by '.get_class().'->dynamicf' : $parm;
        // Access an inherited property set by parent constructor.
        echo( "\r\n<br/>".get_class().'->dynamicf >> '.$this->parentvar );
    }
}

/*
 *  Register the dynamic child class "ChildC", which is
 *  derived by extending "ParentC" with members supplied as "ChildC" trait.
 */
$definition = new \Componere\Definition( 'ChildC', ParentC::class );
$definition->addTrait( 'ChildC' );
$definition->register();

/*
 *  Instantiate the dynamic child class,
 *  and access its own and inherited members.
 */
$dyno = new ChildC;
$dyno->parentf();
$dyno->dynamicf( 'myvalue ');

// Our object is also recognized as instance of parent!
var_dump( $dyno instanceof ChildC, $dyno instanceof ParentC, is_a( $dyno, 'ParentC') );
var_dump( $dyno );
?>

I know this is an old post but I had a similar problem just now. I was actually including the same class file twice. Use include_once() or require_once() instead of include() or require().

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM