Hoi,

Ik probeer de overgang van PHP Procedural naar PHP OOP te maken.
Om te begrijpen hoe OOP werkt en op welke manier het nuttiger kan zijn dan procedural wil ik graag jullie mening, opmerkingen, kritiek, etc horen over mijn eerste OOP script.


<?php
function printr($a){
	echo '<pre>';print_r($a);echo'</pre>';
}

class Form {
	public function __construct($fName){
		$this->fName = '<form method="POST" name="' . $fName . '">'.PHP_EOL;
	}
	
	public function getForm(){
		return $this->fName;
	}
	
	public function endForm(){
		return '</form>'.PHP_EOL;
	}
}

class Input {
	public function __construct($iName){
		$this->iName = $iName;
	}
	
	public function addType($iType){
		$this->allowedTypes = Array('text','password','date','email','submit','checkbox','radio');
		
		if(in_array($iType,$this->allowedTypes))
		{
			$this->iType = $iType;
		}
		else
		{
			throw new Exception('Input Type not allowed');
		}
	}
	
	public function checked($iChecked){
		if(($this->iType == 'checkbox' || $this->iType == 'radio') && $iChecked == TRUE)
		{
			$this->iChecked = TRUE;
		}
		else
		{
			$this->iChecked = FALSE;
		}
	}
	
	public function group($iGroup){
		if($iGroup == TRUE)
		{
			$this->iGroup = TRUE;
		}
	}
	
	public function addValue($iValue){
		$this->iValue = $iValue;
	}
	
	public function addPlaceholder($iPlaceholder){
		$this->iPlaceholder = $iPlaceholder;
	}
	
	public function getInput(){
		$input = '<input';
		if(isset($this->iType))
		{
			$input .= ' type="' . $this->iType . '"';
		}
		if(isset($this->iName))
		{
			if(isset($this->iType) && ($this->iType == 'checkbox' || $this->iType == 'radio'))
			{
				$input .= ' name="' . $this->iName. '[]"';
			}
			else
			{
				$input .= ' name="' . $this->iName . '"';
			}
			$input .= ' id="' . $this->iName . '"';
		}
		if(isset($this->iValue))
		{
			$input .= ' value="' . $this->iValue . '"';
		}
		if(isset($this->iPlaceholder))
		{
			$input .= ' placeholder="' . $this->iPlaceholder .'"';
		}
		if(isset($this->iChecked) && $this->iChecked == TRUE)
		{
			$input .= ' checked="checked"';
		}
		$input .= ' /><br />'.PHP_EOL;
		
		return $input;
	}
}

class Select {
	public function __construct($sName){
		$this->sName = $sName;
	}
	
	public function addOption($sOptionValue,$sOption){
		$this->sOption[][$sOptionValue] = $sOption;
	}
	
	public function getSelect(){
		$selectbox = '<select name="' . $this->sName . '">'.PHP_EOL;
		foreach($this->sOption AS $optionArray)
		{
			foreach($optionArray AS $value=>$option)
			{
				$selectbox .= '<option value="' . $value . '">' . $option . '</option>'.PHP_EOL;
			}
		}
		$selectbox .= '</select><br />'.PHP_EOL;
		return $selectbox;
	}
}

class Box {
	public function __construct($bName){
		$this->bName = $bName;
	}
	
	public function addValue($bValue){
		$this->bValue = $bValue;
	}
	
	public function addPlaceholder($bPlaceholder){
		$this->bPlaceholder = $bPlaceholder;
	}
	
	public function getBox(){
		$box = '<textarea name="' . $this->bName . '"';
		if(isset($this->bPlaceholder)) { $box .= ' placeholder="' . $this->bPlaceholder . '"'; }
		$box .= '>';
		if(isset($this->bValue)) { $box .= $this->bValue; }
		$box .= '</textarea><br />'.PHP_EOL;
		return $box;
	}
}

class Button {
	public function __construct($bValue){
		$this->bValue = $bValue;
	}
	
	public function getButton(){
		return '<button>' . $this->bValue . '</button><br />'.PHP_EOL;
	}
}

if(strtolower($_SERVER['REQUEST_METHOD']) == 'post')
{
	printr($_POST);
}

$form = new Form('test');
echo $form->getForm();

$select = new Select('aanhef');
$select->addOption('','Maak een keuze');
$select->addOption('Meneer','Mr.');
$select->addOption('De heer','Dhr.');
$select->addOption('Mevrouw','Mw.');
$select->addOption('Dokter','Dr.');
$select->addOption('Anders','Anders');
echo $select->getSelect();

$input = new Input('name');
$input->addType('text');
$input->addValue('Jan');
$input->addPlaceholder('Naam van persoon');
echo $input->getInput();

$input = new Input('age');
$input->addType('text');
$input->addValue('36');
$input->addPlaceholder('Leeftijd van persoon');
echo $input->getInput();

$input = new Input('pass');
$input->addType('password');
$input->addValue('secret');
$input->addPlaceholder('Wachtwoord');
echo $input->getInput();

$input = new Input('optie');
$input->addType('checkbox');
$input->addValue('check optie 1');
$input->checked(TRUE);
echo $input->getInput();

$input = new Input('optie');
$input->addType('checkbox');
$input->addValue('check optie 2');
$input->checked(FALSE);
echo $input->getInput();

$input = new Input('optie');
$input->addType('radio');
$input->addValue('radio optie 1');
$input->checked(TRUE);
echo $input->getInput();

$input = new Input('optie');
$input->addType('radio');
$input->addValue('radio optie 2');
$input->checked(FALSE);
echo $input->getInput();

$box = new Box('message');
$box->addValue('');
$box->addPlaceholder('Typ hier je bericht');
echo $box->getBox();

$button = new Button('Opslaan');
echo $button->getButton();

echo $form->endForm();

?>
@Erwin, Ik probeer verder te programmeren op jou script en deze wil ik uitbreiden met label en button.
Nou heb ik jou laatste voorbeeld proberen te evenaren (maar dan zonder interface implementatie). Tot zover wordt de button meegenomen, maar hoe geef ik de child nou mee, dat begrijp ik niet uit jou voorbeeld.


<?php
class Button{
	private $attributes;
	private $children;
	private $allowedTypes = Array('button','reset','submit');

	public function __construct($type,$name = null){
		$this->attributes = new Attributes;
		$this->attributes->setAttribute('type',$type);
		$this->attributes->setAttribute('name',$name);

		$this->type = $type; //?????
	}
	
	public function addChild( $child ){
		$this->children[] = $child;
	}
	
	public function writeTag(){
		if(in_array(strtolower($this->type),$this->allowedTypes))
		{
			$str = '<button'. $this->attributes->writeAttributes() .'>';
			foreach($this->children AS $child)
			{
				$str .= $child;
			}
			$str .= '</button><br />'.PHP_EOL;
			
			return $str;
		}
		else
		{
			throw new Exception('Button Type not allowed');
		}
	}
}

$form = new Form();
$form->addChild( new Input( 'text' , 'name' , 'name' ) );
$form->addChild( new Button( 'submit', 'save' ) );
echo $form->writeTag();
?>


Output

Warning: Invalid argument supplied for foreach() in ** on line 130
<form method="post" name="formulier1" action="/oo2.php">
<input type="text" id="name" name="name" /><br />
<input type="email" id="email" name="email" /><br />
<input type="tel" id="phone" name="phone" /><br />
<input type="date" id="birthdate" name="birthdate" /><br />
<input type="password" id="password" name="password" /><br />
<input type="number" id="aantal" name="aantal" /><br />
<button type="submit" name="save"></button><br />
</form>

De warning komt omdat de child voor de button niet wordt weergegeven

[edit]
En op regel 12 zie je dat ik $type nogmaals opsla om deze op regel 20 te gebruiken, maar dit lijkt me onlogisch omdat het eigenlijk al als attribute wordt opgeslagen, maar kan ik deze ook uitlezen?
Dat was een foutje van mij. De $children property moet je als array definieren, zodat je nooit een probleem krijgt in je foreach. Anders, als je geen kinderen meegeeft krijg je die foutmelding omdat $children null is en geen array.

<?php
private $children = array();
?>

Inmiddels ook aangepast in mijn vorige post.
Erwin H op 22/10/2013 14:06:53

Dat was een foutje van mij. De $children property moet je als array definieren, zodat je nooit een probleem krijgt in je foreach. Anders, als je geen kinderen meegeeft krijg je die foutmelding omdat $children null is en geen array.

<?php
private $children = array();
?>

Inmiddels ook aangepast in mijn vorige post.

Oops dat was van ons beide niet zo slim. Uiteraard moet dat een Array zijn :-)
De warning blijft daarmee weg, maar ik heb nog geen 'value' voor de button

<button type="submit" name="save"></button><br />
In dit geval kan je voor twee mogelijkheden kiezen om die value mee te geven. Ofwel je maakt een soort 'text only' class (die dus geen html tags bouwt, maar alleen maar platte tekst), ofwel je geeft de value ook als parameter mee aan de constructor (of via een andere setter). In de writeTag() method plaats je value dan gewoon tussen de tags in de string die je teruggeeft.
Sorry dat ik deze topic kaap, maar omdat ik toch ook mijn eigen idee over classes en functies heb wil ik graag weten wat jullie van mijn formcontroller vinden

<?php
class Form {
	public $form = array();
	private $_method;
	private $_methodes = array('POST','GET');
	private $_action;
	private $_enctype;
	private $_enctypes = array('application/x-www-form-urlencoded','multipart/form-data','text/plain','upload');
	private $_error = array();
	
	public function __construct($action,$method='POST',$enctype='application/x-www-form-urlencoded'){
		$this->_action = $action;
		$this->_method = strtoupper($method);
		$this->_enctype = strtolower($enctype);
		if(strtolower($this->_enctype) == 'upload') {
			$this->_enctype = 'multipart/form-data';
		}

		
		if(!in_array($this->_method,$this->_methodes)) {
			$this->_error[] = 'Deze methode is niet toegestaan! Toegestane methodes: ' . implode(', ',$this->_methodes);
		}
		if(!in_array($enctype,$this->_enctypes)) {
			$this->_error[] = 'Deze enctype is niet toegestaan! Toegestane enctypes: ' . implode(', ',$this->_enctypes);
		}
		if(empty($this->_error)) {
			$this->form[]='<form method="' . $this->_method . '" action="' . $this->_action . '" ecntype="' . $this->_enctype . '">';
		} else {
			throw new Exception('De form kon niet gegenereerd worden! | ' . implode(' | ',$this->_error));
		}
	}
	
	public function append($input) {
		$this->form[]= $input->input;
	}
}


class Input {
	private $_type;
	private $_types = array('hidden','text','password','radio','checkbox','select','file','textarea','button','submit','reset');
	public $input;
	
	public function __construct($type, $name,$value='',$description='',$newLine=false) {
		$differs = array('textarea','select');
		if(in_array(strtolower($type),$this->_types)) {
			if($description<>'') {
				$this->input = '<label for="' . $name . '">' . $description . '</label>';
			}
			$this->_type = strtolower($type);
			if($this->_type == 'textarea') {
				$this->input .= '<textarea name="' . $name . '" id="' . $name . '">' . $value . '</textarea>';
			} elseif($this->_type == 'select') {
				if(is_array($value)) {
					$this->input .= '<select name="' . $name . '" id="' . $name . '">';
					foreach($value as $value => $description) {
						$this->input .= '<option value="' . $value . '" id="' . $name . '">' . $description . '</option>';
					}
					$this->input .= '</select>';
				} else {
						throw new Exception('De select input heeft meerdere keuze opties! Defineer deze als array("Waarde"=>"Omschrijving")');
				}
			} else {
				$this->input .= '<input type="' . $type . '" name="' . $name . '" id="' . $name . '"value="' . $value . '">';
			}
			if($newLine) {
				$this->input .= '<br>';
			}
		} else {
			throw new Exception('Deze type input is niet toegestaan! Toegestane types zijn: ' . implode(' ,',$this->_types));
		}
	}
}
?>
Als ik public properties zie dan haak ik al af. Dat is wat mij betreft de grootste no-no in een OOP omgeving.
Als je echt een applicatie in OOP wilt ontwikkelen dan is 1 van de kenmerken dat elke class verantwoordelijk is voor zijn eigen doen en laten. De class moet ervoor zorgen dat input gechecked wordt en dat output altijd correct is en bruikbaar is, of als de class niet in staat is om daarvoor te zorgen dat er een foutmelding wordt opgeworpen zodat een andere class het kan overnemen. Met publieke properties is zowel input als output checken onmogelijk geworden.
Wat als ik bij jouw form class dit doe:

<?php
$form = new Form( '' );
$form->form = 'gekaapt!';
?>

Kan jouw class me dan nog garanderen dat ik een mooi form op mijn scherm krijg?

Daarnaast mis je in je form ook de afsluitende form tag, dus je class kan alleen maar garanderen dat er nooit een correct form op het scherm komt.

Kijk verder ook naar mijn opmerkingen over de attributes op de vorige pagina. Ook jij doet dit weer in beide classes. Dubbel werk dus en volkomen inflexibel.
Ik kom nog even terug op de interface die Michael nu nog weglaat en omdat die in juist bovengenoemde situatie zo handig is.

<?php

interface iFormField
{
public function render();
}

class Button implements iFormField
{
public function render()
{
// return $html;
}
}

class Textfield implements iFormField
{
public function render()
{
// return $html;
}
}

?>

Wat ik probeer duidelijk te maken is dat door de interface iedere afgeleide class ook een functie render() MOET hebben.
Stel dat je na een half jaar opeens nog een nieuwe 'FormField' wilt toevoegen aan het rijtje. één blik op de interface en je weet het. De class die je gaat maken MOET een render() functie hebben. Je hoeft niet meer je code door te spitten of erger nog gaan debuggen omdat je dan iets over het hoofd ziet.

een interface is dus super handig als er meerder classen van afgeleid worden en daarom ook een must.
Frank: wat is class Textfield ->>>>>implements<<<<<-----
Dat is het keyword dat aangeeft dat de betreffende class de interface implementeert. Net als het keyword 'extends' aangeeft dat de class een overerving is van de andere class:

<?php
class New_Class extends Old_Class implements Some_Interface{

}
?>

De class New_Class is dus een overerving van Old_Class en implementeert de interface Some_Interface.
Donny:

Voor een class gebruik je extends ...
Voor een interface gebruik je implements ...

voorbeeld
<?php

// interface
interface iFormField
{
}

// baseclass
class FormField implements iFormField
{
}

// afgeleide class
class Textfield extends FormField
{
}

?>

Reageren