让一个孩子在PHP中扩展已初始化的父级

I've been having a hard time coming up a solution with this one. I'm hoping you all can help me out.

Best described with an example:

class Parent {
    public $nationality;

    function __construct($nationality)
    {
        $this->nationality = $nationality
    }
}

class Child extends Parent {
    function __construct() {
        echo $this->nationality; // hispanic
    }
}

// Usage:
$parent = new Parent('hispanic');
$child = new Child();

I want the child to inherit properties and methods from a parent that is already initialized.


EDIT: Thanks all for the responses - let me give you some background. I'm trying to make a templating system. I have two classes - say Tag.php, and Form.php.

I'd like it to look like this:

class Tag {
    public $class_location;
    public $other_tag_specific_info;
    public $args;

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

    public function wrap($wrapper) {
        ...
    }

    // More public methods Form can use.
}

class Form extends Tag {
    function __construct() {
        print_r($this->args()) // 0 => 'wahoo', 1 => 'ok'
        echo $this->class_location; // "/library/form/form.php"
        $this->wrap('form');
    }

    function __tostring() {
        return '<input type = "text" />';
    }
}

// Usage:
$tag = new Tag(array('wahoo', 'ok'));
$tag->class_location = "/library/form/form.php";
$tag->other_tag_specific_info = "...";
$form = new Form();

The reason I don't like the composite pattern is that it doesn't make sense to me why I would be passing in an instance of Tag to the constructor of one of its subclass, afterall - Form is a type of Tag right?

Thanks! Matt Mueller

We've edited quite a bit here already so I'll just post a new answer, hope that's ok. Will leave my old answer up for the sake of it.

Anyway, we're starting off from your latest example (Form extends Tag).

Seems like you're trying to mimic some kind of Builder pattern, so bear with me and let's go with a short example:

class TagBuilder
{
    protected $args;

    protected $className;

    protected $classLocation;

    function __construct()
    {

    }

    public function setClass($file, $class)
    {
        /* @todo Use reflection maybe to determine if $class is a Tag subclass */

        if (file_exists($file))
        {
            require_once $file; // Or use the autoloader
        }

        $this->classLocation = $file;
        $this->className = $class;
    }

    /**
     * @return Tag the built tag (or tag subclass) instance
     */
    public function getTag()
    {
        $fullyQualifiedClassName = $this->getFullyQualifiedClassName();

        $newTag = new $fullyQualifiedClassName($this->args);
        $newTag->class_location = $this->classLocation;

        return $newTag;
    }

    protected function getFullyQualifiedClassName()
    {
        return $this->className;
    }

    public function setArgs($args)
    {
        $this->args = $args;
    }
}

Additionally, you will have to modify the Form constructor to call it's parent::__construct($args) method in order to pass the $args dependency.

And then run it with

$builder = new TagBuilder();
$builder->setClass('/library/form/form.php', 'Form');
$builder->setArgs(array('wahoo', 'ok'));

$form = $builder->getTag();

This is a bit of a long example and it implies changing some of the OP's API, but if I understood the question correctly, then this is the desired behaviour (or part of it) that the OP wants to mimic in his application.


Note: I am aware that this isn't a classical example of the Builder pattern and that the getTag method might seem a bit ambiguous to some purists.

What you want to do is arguably bad design. What happens if you create several Parent instances with different nationalities and then create a new Child instance. Which nationality does this child receive then?

Why not just make the Child class a composite, give it a parent in his constructor?

class Child
{
    private $parent;

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

    function getNationality()
    {
        return $this->parent->nationality;
    }
}

and then create it with

$parent = new Parent('hispanic');
$child = new Child($parent);

Or with a factory method from parent... the rest is up to your imagination.


Note: I am ignoring the fact the I did not use a getter for nationality on the parent class, nor did I supply a superclass for Child (Parent maybe? Doesn't really make sense). These things are all design-relevant points but are not within the context of this question.

This shouldn't be considered good practise, I only wrote the function as proof of concept to see whether it could be done or not.

class MyClass
{
    public    $_initial    = NULL;

    public function setInitial($value) {
        $this->_initial = $value;
    }

}


class MyClass2 extends MyClass
{
    private    $_reversed = NULL;

    private function reverseInitial() {
        $this->_reversed = strrev($this->_initial);
    }

    public function getReversed() {
        $this->reverseInitial();
        return $this->_reversed;
    }

}


$class1 = new MyClass();
$class1->setInitial('rekaB kraM');

inherit($class1,'MyClass','MyClass2');

echo $class1->getReversed();

function inherit(&$object, $oldClassName, $newClassName) {
    $oldClass = new ReflectionClass($oldClassName);
    $newClass = new ReflectionClass($newClassName);

    $wrkSpace = serialize($object);
    $wrkTmp = explode('{',$wrkSpace);
    $startBlock = array_shift($wrkTmp);

    $oldClassProperties = $oldClass->getProperties();
    $oldClassPropertyNames = array();
    foreach($oldClassProperties as $oldClassProperty) {
        if (!$oldClassProperty->isStatic()) {
            if ($oldClassProperty->isPrivate()) {
                $oldClassPropertyNames[] = "\x0".$oldClassName."\x0".$oldClassProperty->getName();
            } elseif($oldClassProperty->isProtected()) {
                $oldClassPropertyNames[] = "\x0*\x0".$oldClassProperty->getName();
            } else {
                $oldClassPropertyNames[] = $oldClassProperty->getName();
            }
        }
    }

    $newClassProperties = $newClass->getProperties();
    $newClassPropertyValues = $newClass->getDefaultProperties();

    $newClassPropertyNames = array();
    foreach($newClassProperties as $newClassProperty) {
        $propertyName = $newClassProperty->getName();
        if (!$newClassProperty->isStatic()) {
            if ($newClassProperty->isPrivate()) {
                $newPropertyName = "\x0".$newClassName."\x0".$propertyName;
            } elseif($newClassProperty->isProtected()) {
                $newPropertyName = "\x0*\x0".$propertyName;
            } else {
                $newPropertyName = $propertyName;
            }
            $newClassPropertyNames[] = $newPropertyName;

            if (isset($newClassPropertyValues[$propertyName])) {
                $newClassPropertyValues[$newPropertyName] = $newClassPropertyValues[$propertyName];
            }
        }
    }


    $newClassPropertySet = array_diff($newClassPropertyNames,$oldClassPropertyNames);

    $toClassName = 'O:'.strlen($newClassName).':"'.$newClassName.'":'.(count($newClassPropertySet)+count($oldClassPropertyNames)).':';

    $newPropertyEntries = array_fill_keys($newClassPropertySet,NULL);
    foreach($newPropertyEntries as $newPropertyName => $newClassProperty) {
        if (isset($newClassPropertyValues[$newPropertyName])) {
            $newPropertyEntries[$newPropertyName] = $newClassPropertyValues[$newPropertyName];
        }
    }
    $newPropertyEntries = substr(serialize($newPropertyEntries),4+strlen(count($newClassPropertySet)),-1);

    $newSerialized = $toClassName.'{'.$newPropertyEntries.implode('{',$wrkTmp);

    $object = unserialize($newSerialized);
}

It doesn't do exactly what you want... it takes an instance of the parent class and changes it to an instance of the child class, but it could probably be modified to create a new child while leaving the parent intact.

Okay, other people are telling you this is a bad design idea. I'm going to quasi-dissent and tell you that it's possible you may have run across a problem domain where class-based OO hierarchies won't do you all the favors you'd like. If you're curious about this idea, I recommend reading Steve Yegge's "The Universal Design Pattern" (http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html ).

But he's a little long-winded, so it might be briefer and just as interesting to consider that in JavaScript, what you're talking about here would be trivial:

t = new Tag({arg1: val1, arg2: val2})
// ...
// later, when you decide t is a Form
t.prototype = Form.prototype. 

Much of the reason this works with JavaScript has to do with its prototype-based OO, but a lot of is also the fact that functions are first-class values that can be assigned to variables and passed around. PHP has some facilities for calling functions dynamically (and with 5.3, for assigning/passing them around). I think if you were interested and willing to step outside of the class-based facilities of the language, you could probably do something a lot like this.

First, you'd do what a lot of people writing object-oriented code in languages without classes do: your "classes" can just be hashes/associative arrays (AAs).

$quasiObj = array('prop1' => $val1, 'prop2' => $val2, '_class' => 'quasiClass');

Your methods can just be functions which take an AA reference as their first argument.

function quasiClass_method1Name($this,$args = array()) {
   $this['prop1'] = $args[1] * $args[2]; 
}

If you want class-independent method invocation, you can have it with a wrapper function:

function method($this,$methodName,$args) {
   $fullMethodName = "{$this['_class']}_$methodName";
   $fullMethodName($this,$args);
}

// and now, the invocation
method($quasiObj,'method1Name',array(6,7));

Under this system, when you want to promote your $quasiObject from Tag to Form, it's a simple matter of changing the _class key of the array. If there need to be internal state changes, you can write a function/method to take care of that.

You do lose the interpreter keeping track of things for you, and some people really fret over that. Most of those people are using Java because PHP plays too fast and loose for them, though. It's a dynamic language. Take advantage of that fact. :)