views:

94

answers:

4

Hey all,

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

+6  A: 

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.

Denis 'Alpheus' Čahuk
Thanks for the response. I did this before, but it didn't really sit well with me in my particular application - I'm hoping someone has an alternative solution. Thanks!
Matt
Is creating new instances problematic? Are you perhaps using some kind of legacy API which you cannot change? It would be helpful if you gave us some background on why you want to create such a "hierarchy".
Denis 'Alpheus' Čahuk
Ah ok. Thanks for explaining why my method is considered bad practice. In my case it would work but I don't think I should do this. I added a little bit of background about my case.
Matt
A: 

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.

Mark Baker
hehe, fun stuff. points for academic prowess. don't try this at home though ;)
Kris
I'd certainly never use it in production code... but one of these days I'll get interested in the idea again and write a disinherit() function as well.
Mark Baker
i would've upvoted this just because it made me giggle, but i'm "scared" some people might get it into their heads to actually do this sort of thing if it moves too far up on the page.
Kris
Perhaps I should add a legal rider that I accept no liability for any adverse effects that anybody suffers if they're actually stupid enough to try using the function... yeah unto the n'th generation
Mark Baker
haha this is great. Thanks - I'll probably just stick with a composite pattern.
Matt
+3  A: 

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.

Denis 'Alpheus' Čahuk
Hey thanks so much for the detailed response. Unfortunately it's not working. Does Form extend Tag or TagBuilder? I believe this solves the problem of instantiating Tag(builder) before Form, but it doesn't solve the `echo $this->class_location` in the constructor of Form.
Matt
With `$this->class_location`, being set in Tag or TagBuilder.
Matt
The form class is the same as in your example. The whole idea is that instead of carrying around a tag to inherit from, you create a builder and pass it along anywhere it's needed, without having to pass around the information as to how the builder is configured. I might have misunderstood your intent in which case I can remove the answer.
Denis 'Alpheus' Čahuk
I've edited the builder to include the class location.
Denis 'Alpheus' Čahuk
Ahh ok. I get what you're doing. Yah, this would work if I didn't need that information in __construct(). Right now as soon as you call `new $fullyQualifiedClassName($this->args);` its going to run form's constructor which asks for `echo $this->class_location;` before `$newTag->class_location = $this->classLocation;` gets set.
Matt
The whole point of this is to allow other people to develop components (like Form) freely, without having to worry about when the data becomes available. I'm thinking I'll just disallow __construct() and then require a init() command to be implemented in Form instead. By then all the data will be available. Does this seem reasonable?
Matt
If you require the tag name in the Tag class then by all means make it abstract and add it to the constructor, i.e. making the constructor of Tag `__construct($classLocation, $args)` since the builder will always supply the location. The init() function could work if you're *always* using the builder to create new instances, but I would advise against it if manual instantiation is possible since it leaves your objects in a state where they cannot be safely used.
Denis 'Alpheus' Čahuk
Awesome. Yah I don't I'll make it so they aren't called unless TagBuilder is called first. Thanks!
Matt
A: 

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. :)

Weston C
There is no reason why he wouldn't be able to do this OO-pure with Form being a composite owning a Tag delegate/proxy.
Denis 'Alpheus' Čahuk
*shrug*. Sure, and I think the Asker realized that was an option. I'd guess he asked anyway because of the obvious overhead. If there's a lot of internal state changes in the class switch, it's probably worth it. Similarly true if there's a lot of class-independent method calls (which are somewhat more expensive under my proposed system). Other than that, it's really largely an aesthetic call, and this seemed as good a moment as any to point out that you *can* get this as a feature rather than using composite patterns. If you prefer using design patterns, though, knock yourself out.
Weston C