Removing method and properties from a child class?

I am trying, to create a PHP representation of a HTML form; it break down into essentially 3 levels of classes:

class form {}
class block extends form{}
class control extends block{}

My main issue with this is that certain methods and attributes should not be inherited by the children.

For example:
class form { action=“http://url.com”;}

But I don’t want new blocks or controls to have the property “action”. ( I was supposing I could UNSET this in the class description or constructor , still that seems clumsy and what would I do about methods you can’t UNSET a FUNCTION can you??)

I considered reversing the creation order of my classes, but it’s the same issue, I would not want a form to have a “label” property, inherited from the control class.

I could make the 3 unrelated classes, but since many properties and functions do overlap ( name, CSSID, CSSclass, etc.) this seems like it would cause a lot redundant code.

I guess my question is is there a way to OMIT certain properties and methods from begin inherited by child classes? Any other suggestions will also be gladly entertained.

Hi dresden_phoenix

First sorry for the wacky formatting, but everytime I try to paste my code from my Linux Eclipse it removes all formatting, so I go through it like a demon just to try to make it readable. I takes a while with this many examples.

I don’t know if this is anywhere close to what you are trying to do, but this is how I do my OOP forms

These are all the form classes. You can see that I extend the widgets from the abstract class. You can see where I am going with the FormHandler (fudging here in this examples, but you can understand how to implement a FormHandler that simply process the form objects and $_POST data supplied.


/* In Forms.php */
<?php
abstract class Widget {
    public function paint() {
        return $this->asHtml();
    }
}

abstract class WidgetDecorator {
    public $widget;

    public function WidgetDecorator ($widget) {
        $this->widget = $widget;
    }

    public function paint() {
        return $this->WidgetDecorator($widget);
    }
}


class TextInput extends Widget {
    public $name;
    public $value;
    public $id;
    public $css_class;

    function __construct($name, $value='', $id='noid', $css_class ='noclass') {
        $this->name = $name;
        $this->value = $value;
        $this->id = $id;
        $this->css_class = $css_class;
    }

    protected function asHtml() {
        return '
        <input type="text" name="' . $this->name
        .'"  id="'. $this->id
        . '"  value="'. $this->value
        . '"  class="'. $this->css_class
        .'" />';
    }
}


class CheckboxInput extends Widget {
    protected $checkbox_name;
    protected $options;
    protected $value;
    protected $css_class;

    function __construct($checkbox_name, $options, $value, $css_class ='noclass') {
        $this->checkbox_name = $checkbox_name;
        $this->options = $options;
        $this->value = $value;
        $this->css_class = $css_class;
    }

    protected function asHtml() {
        if (is_array($this->options)) {
            $html = '';

                foreach($this->options as $checkbox_name => $cn) {
                    $html_fragment = "<br /><input type='checkbox' name =";
                    foreach($cn as $name => $value) {
                        $html .= $html_fragment;
                        $html .= "'$name' value='$value'";
                        if (!empty($this->value) and is_array($this->value)) {
                                 if (in_array($value, $this->value)) { $html .= " CHECKED ";}
                        }
                    }
                    $html .= "/> $checkbox_name";
                    $html .= "\
";
                }
            return $html;
        } else {
            // Throw exception
        }
    }
}


class RadioInput extends Widget {


    protected $radio_name;
    protected $options;
    protected $value;
    protected $css_class;


    function __construct($radio_name, $options, $value, $css_class ='noclass') {
        $this->radio_name = $radio_name;
        $this->options = $options;
        $this->value = $value;
        $this->css_class = $css_class;
    }


    protected function asHtml() {
        if (is_array($this->options)) {
            $html = '';


                foreach($this->options as $radio_name => $ra) {
                    $html_fragment = "<br /><input type='radio' name =";
                    foreach($ra as $name => $value) {
                        $html .= $html_fragment;
                        $html .= "'$name' value='$value'";
                        if (!empty($this->value ) and $this->value == $value) { $html .= " CHECKED ";}
                        $html .= "/> $radio_name";
                        $html .= "\
";
                    }
                }
            return $html;
        } else {
            // Throw exception
        }
    }
}

class SelectInput extends Widget {
    public $options;
    public $css_class;
    public $id;
    public $select_name;

    function __construct($select_name, $options, $value, $id='noid', $css_class ='noclass') {
        $this->select_name = $select_name;
        $this->options = $options;
        $this->value = $value;
        $this->id = $id;
        $this->css_class = $css_class;
    }


    protected function asHtml() {
        if (is_array($this->options)) {
            $html = "<select name='$this->select_name' id='$this->id' class='$this->css_class'> " . "\
";
                foreach($this->options as $value => $name) {
                    $html .= "<option value='$value'";
                    if (!empty($this->value ) and $this->value == $value) { $html .= " SELECTED ";}
                    $html .= ">$name</option>" . "\
";
                }
            $html .= '</select>';
            return $html;
        } else {
            // Throw exception
        }
    }
}

class Labeled extends WidgetDecorator {
    public $lable;
    public $css_class;

    public function __construct($lable, $widget, $css_class ='noclass') {
        $this->lable = $lable;
        $this->css_class = $css_class;
        $this->WidgetDecorator($widget);
    }


    public function paint() {
        return '<span class="' .$this->css_class .'">' .$this->lable . ':</span> ' . $this->widget->paint();
    }
}

class TopLabeled extends WidgetDecorator {
    public $lable;
    public $lable_class;
    public $lable_id;
    public $id;

    public function __construct($lable, $widget, $lable_class ='nolableclass', $id='noid', $lable_id='nolableid') {
        $this->lable = $lable;
        $this->lable_class = $lable_class;
        $this->lable_id = $lable_id;
        $this->id = $id;
        $this->WidgetDecorator($widget);
    }

    public function paint() {
        if (!empty($this->lable)) {
            return '
            <div id="' . $this->id .'">
                <div id="' . $this->lable_id .'" class="' .$this->lable_class .'">' .$this->lable . ':</div>'
                 . $this->widget->paint()
            . '</div>' ;
        }  else {
            return '
            <div id="' . $this->id .'">
                <div id="' . $this->lable_id .'" class="' .$this->lable_class .'">&nbsp;</div>'
                .  $this->widget->paint()
            . '</div>' ;
        }
    }
}


class TableLabeled extends WidgetDecorator {
    public $lable;
    public $lable_class;
    public $lable_id;
    public $id;


    public function __construct($lable, $widget, $lable_class ='nolableclass', $id='noid', $lable_id='nolableid') {
        $this->lable = $lable;
        $this->lable_class = $lable_class;
        $this->lable_id = $lable_id;
        $this->id = $id;
        $this->WidgetDecorator($widget);
    }


    public function paint() {
        if (!empty($this->lable)) {
            return '
                <td>
                <div>
                <div class="' .$this->lable_class .'">' .$this->lable . ':</div>'
                 . $this->widget->paint()
            . '</div>
            </td>' ;
        }
    }
}


class Invalid extends WidgetDecorator {
    function paint() {
        return '<span class="invalid">' . $this->widget->paint() . '</span> ';
    }
}


class FormHandler {
    public $widgets;
    public $form;

    function __construct() {
        $this->widgets = NULL;
        $this->form = NULL;
    }

    function build($post) {
        $checkbox_name = 'test_checkbox';
        $checkbox_options = array();
        $checkbox_options['I have a bike'] = array('bike' => 'Bike');
        $checkbox_options['I have a car'] = array('car' => 'Car');
        $checkbox_options['I have a plane'] = array('plane' => 'Plane');
        $checkbox_obj =  new CheckboxInput("$checkbox_name" , $checkbox_options,$value=$post->get(array('bike', 'car', 'plane')));

        $radio_name = 'test_radio';
        $radio_options = array();
        $radio_options['Male'] = array('sex' => 'male');
        $radio_options['Female'] = array('sex' => 'female');
        $radio_obj =  new RadioInput("$radio_name" , $radio_options,$value=$post->get('sex'), $class='align-left');

        $select_name = 'test_select';
        $options = array('one' =>'one', 'two' => 'two', 'three' => 'three');
        $select_obj = new SelectInput("$select_name" , $options,$value=$post->get('test_select'), $id='test_select', $class='align-left');

        $first_name ='fname';
        $first_name_obj = new TextInput($first_name, $value=$post->get($first_name), $id='first_name', $class='align-left');
        $last_name ='lname';
        $last_name_obj = new TextInput($last_name, $value=$post->get($last_name), $id='last_name', $class='align-left');
        $email_name ='email';
        $email_obj = new TextInput($email_name, $value=$post->get($email_name), $id='email', $class='align-left');

        $this->widgets = array(
            $checkbox_name => $checkbox_obj
            , $radio_name => $radio_obj
            , $select_name => $select_obj
            , $first_name =>  $first_name_obj
            , $last_name => $last_name_obj
            , $email_name =>  $email_obj);

        $this->form = array(
            'Checkboxes' => new Labeled('Checkboxes', $this->widgets[$checkbox_name], $class='align-right')
            ,'Radio' => new Labeled('Radio', $this->widgets[$radio_name], $class='align-right')
            ,'Options' => new Labeled('Select Options',$this->widgets[$select_name], $class='align-right')
            ,'FirstName' =>new Labeled('First Name',$this->widgets[$first_name], $class='align-right')
            ,'LastName' => new Labeled('Last Name', $this->widgets[$last_name], $class='align-left')
            ,'Emai' =>new Labeled('Email',$this->widgets[$email_name] , $class='align-left')
        );
        return $this->form;
    }


    function validate($post) {
        //Checkbox;
        $bike = $post->get('bike');
        $car = $post->get('car');
        $plane = $post->get('car');
        if(!isset($bike) || !isset($car)  || !isset($plane) ){
            $this->form['Checkboxes'] =& new Invalid($this->form['Checkboxes'] );
            //var_dump($this->form);
        }
        //first name required
        if (!strlen($post->get('fname'))) {
            $this->form['FirstName'] =& new Invalid($this->form['FirstName'] );
        }

        if (!strlen($post->get('lname'))) {
            $this->form['LastName'] =& new Invalid($this->form['LastName']);
        }
 return $this->form;
    }
}
class Post {
    public $store = array();


    public function get($key) {
        if (is_array($key)) {
            //code is for multiple checked or select form elements such as checkboxes
            $keys = array();
            foreach($key as $selected) {
                if (array_key_exists($selected, $this->store)) {
                    $keys[] = $this->store[$selected];
                }
            }
            return $keys;
        } else {
            //code is for single input elements such as radio or text inputs.
            if (array_key_exists($key, $this->store)) {
                return $this->store[$key];
            }
        }
    }


    public function set($key, $val) {
        $this->store[$key] = $val;
    }


    public function autoFill() {
        $ret =new Post;
        foreach($_POST as $key => $value) {
            $ret->set($key, $value);
        }
        return $ret;
    }
}



This is an example of usage…


<html>
<head>
    <style type='text/css'>
        .invalid {color: red; }
        .invalid input { background-color: red; color: yellow; }
        #myform input { position: absolute; left: 110px; width: 250px; font-weight: bold; }
    </style>
</head>
<body>
<form action='form_examples.php' method='post'>


    <div id='myform'>
    <?php
    require_once("../lib/forms/Forms.php");
  $post_obj = new Post();
    $post = $post_obj->autoFill();


    $form_handler_obj = new FormHandler();
    $form = $form_handler_obj->build($post);
     if($_POST) {
        $form=$form_handler_obj->validate(&$post);
     }


    foreach($form as $widget) {
        echo $widget->paint(), "<br />\
";
    }


?>
    <input type="submit" value="Submit" />
    </div>
</form>
</body>
</html>

And here are some of the basic tests


<head>
    <style>
        .invalid {
            font-weight: bold;
            color: red;
            font-size: 20px;
        }
    </style>
</head>
<?php
     error_reporting(E_ALL);
    require_once("../../simpletest/autorun.php");
    require_once("../lib/forms/Forms.php");


    class WidgetTestCase extends UnitTestCase {
        function test_TextInput() {
            $text = new TextInput('foo', 'bar');
            $output = $text->paint();
            $this->assertWantedPattern('~<input type="text"[^\\>]*~i', $output);
            $this->assertWantedPattern('~name="foo"~i', $output);
            $this->assertWantedPattern('~value="bar"~i', $output);
            unset($text);
        }


        function testLabeled() {
            $text = new Labeled ('Email' , new TextInput('email'));
            $output = $text->paint();
            $this->assertWantedPattern('~<span class="noclass">Email:</span> <input~i', $output);
            unset($text);
        }


        function testInvalid() {
            $text = new Invalid (new TextInput('email'));
            $output = $text->paint();
            $this->assertWantedPattern('~<span class="invalid"><input~i', $output);
            unset($text);
        }


    function testInvalidLabeled() {
          $text = new Invalid (
           new Labeled (
         'Email' , new TextInput('email')));
        $output = $text->paint();
        $this->assertPattern('~<span class="invalid"><span class="noclass">Email:</span> <input~i', $output);
         unset($text);
    }


    function testValidateMissingname() {
        $post = new Post;
        $post->set('fname', 'Steve');
        $post->set('email', 'sbrowning@itmosaic.com');
        $form_obj = new FormHandler();
        $form = $form_obj->build($post);
        var_dump($form);
        $this->assertFalse($form_obj->validate(&$post));
        $this->assertNoUnwantedPattern('/invalid/i', $form[3]->paint());
        //echo $form[4]->paint();
        $this->assertPattern('~<span class="invalid"><span~i', $form[4]->paint());
        $this->assertNoUnwantedPattern('/invalid/i', $form[5]->paint());
    }
}


       class FormHandlerTestCase extends UnitTestcase {
        function testbuild() {
            $this->assertIsA($form = FormHandler::build(new Post), 'Array');
            $this->assertEqual(6, count($form));
            $this->assertIsA($form[1], 'Labeled');
            $this->assertWantedPattern('~email~i', $form[5]->paint());
        }


    }

Hopes this gives you some ideas.

Regards,
Steve

Maybe I’m being overly simplistic here, but this seems a case of needing to set the correct visibility on your members and methods. Check out http://www.php.net/manual/en/language.oop5.visibility.php - there are some good examples on there.

SS,
thank you for your response. I am afraid that that misses what I am trying to do (maybe it because it assumes that the code outputs together a form, when in fact my code is actually meant to put together more code). The goal is for the end user to be able instance a form object and assemble blocks and element within it as desired.

something like

$f=new form(argument, argument, argument…);
$f->addBlock(Name, argument, argument…);
$f->block->addControl(To-Name, argument, argument…);
$f->block->addControl(To-Name, argument, argument…);

or something resembling that. This is, of course rather basic, except for what i tried to convey earlier. Child classes inherit proerties and method of their parents (some of these classes and methods are desired, some are not) .

Reiterating my previous example. $f should have a property called " action" , another called “id” , another called “blocks”, and another called “class” .

$f->blocks is an array that contain objects of class block. Blocks have many of the properties of the form except “action” (for example). so it seems wasteful to NOT to make one a child of the other and abstract classes still pass properties and function to their children.

I hope I have not missed the mark too badly here in what I am trying to explain

PRIVATE METHODS aren’t inherited… that slipped my mind…
now the question is… can constructors/destructors be private?

Thanks Leonard

FWIW, this breaks the Liskov Substitution Principle.

Substitutability is a principle in object-oriented programming. It states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.). More formally, the Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address entitled Data abstraction and hierarchy. It is a semantic rather than merely syntactic relation because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular.

I think the root problem here is that you’re using inheritance where you shouldn’t be. Remember to apply the “is-a” and “has-a” tests. Is it correct to say that a control is-a form? No. So the control class should not inherit from form. Though, it is correct to say that a form has-a control(s).

I think the root problem here is that you’re using inheritance where you shouldn’t be. Remember to apply the “is-a” and “has-a” tests. Is it correct to say that a control is-a form? No. So the control class should not inherit from form. Though, it is correct to say that a form has-a control(s).

Actually, when I started this project that’s exactly what I did. The problem Is the DIRECT CHILD issue. so a FORM can have a BLOCK, but NOT any of the CHILDREN of the BLOCKs. I would have stared with the children… and then worked UP towards a a FORM class, except i need several control classses… so that didn’t make sense

PLAN:

  • Form have: TEXT || CONTROL blocks ONLY.

  • CONTROL blocks have: CONTROLS || INLINETXT || CONTROL blocks || TEXT blocks (Essentially everything BUT Forms) and serve as UI ( in other words, user accesses children through here)

–* CONTROLS have:
–inputs have : properties
–buttons have: properties
–TAs have: properties
–Selects have: properties
–* INLINE TEXT have: “text”/properties

  • TEXT Blocks blocks has: text/properies, and serve as UI ( in other words, user accesses text block properties here)