PHP drop-down list - Part 4 (Cleaning things up a bit)

February 14, 2017 by PHP  

Up until this point we've got a very basic, but working drop-down list implementation, it is however quite fragile at the moment, since we're not doing enough validation checks not to mention all properties being public.

Lets have a look at the public properties exposed by our HtmlSelectElement.

class HtmlSelectElement extends HtmlFormControlElement implements IHtmlInnerHtml { public $Name; public $Disabled; public $Children; public $Selected;

...


The $Name property is used internally to retrieve the value retrieved via GetUserValue, if the developer were to change this property value in his script, this mechanism will break, but then again why would he want to? Lets make this property private.

Not too concerned about the $Disabled property, the developer will need access to this property if he wishes to disabled or enable the drop-down list - a boolean value will be required, if we're using PHP 7, we might want to consider type hinting the primitive boolean value, via a setter method.

The $Children property needs to be a very specific set of values, a list of HtmlOptionElements, lets make this property private.

The $Selected property is dependant on the child elements and needs to be private as well, but it is also something we can reuse among HtmlFormControlElements, lets move it to its base class (decorate it as protected, seeing that inheritance is involved) and give it a more generic name.

abstract class HtmlFormControlElement extends HtmlElement
{
	protected $Value;
	
	public function GetValue() {
		return $this->Value;
	}
	
	abstract function SetValue($value);


Getting the protected value should be a fairly generic thing, setting it however will likely differ from element to element, so we're making its "setter" abstract.

Our SetSelected method now becomes SetValue.

Getting back to the $Children property, we need to write a setter to allow the developer to add proper value to this property, now some of the code is very similar to the SetValue method, so we're going to reuse some of it.

Observe the updated snippet.

class HtmlSelectElement extends HtmlFormControlElement
implements IHtmlInnerHtml
{
	public $Disabled;
	
	private $Name;
	private $Children;
	
	public function __construct($name, 
		array $children = [], 
		$value = null,
		$disabled = false,
		$context = 'POST')
	{
		$this->Name = $name;
		$this->Disabled = $disabled;
		$userValue = $this->GetUserValue($context);
		$this->SetChildren($children, ($userValue === null) ? $value : $userValue);
	}

	private function setChild($child, $value) {
		$optionValue = (string)$child;
		$child->Selected = ($optionValue == $value);
		if ($child->Selected) {
			$this->Value = $optionValue;
		}
	}
	
	public function SetValue($value) {
		foreach($this->Children as $child) {
			$this->setChild($child, $value);
		}
	}
	
	public function SetChildren(array $children, $value = null)
	{
		foreach($children as $child) {
			if ($child instanceof HtmlOptionElement) {
				$this->setChild($child, $value);
			} else {
				throw new Exception("Type of HtmlOptionElement expected in drop-down list $this->Name");
			}
		}
		
		if (count($children) != count(array_unique($children))) {
			throw new Exception("Non unique values assigned to drop-down list $this->Name");
		}
		
		$this->Children = $children;
	}

You will notice the the SetChildren (our Children setter), explicitly looks for instances of HtmlOptionElement, if the developer assigns something bogus to it, it will let him/her know what they did wrong.

Furthermore the drop-down list now requires a list of unique values to be assigned to it, which makes sense in a single selection control, else the drop-down list will have no way of knowing which value was selected.

I also needed to write a custom toString method for the HtmlOptionElement, which simplifies value comparisons (required by array_unique to work properly).

class HtmlOptionElement extends HtmlElement
implements IHtmlInnerText
{
...
	public function __toString() {
        return (string)(($this->Value === null) ? $this->Text : $this->Value);
    }
}

If you analyse the git repository for this series of posts, you will notice that I also finally added namespaces to the files, something you would probably want to include from the start, but I excluded it for brevity purposes.

Not going to go into too much details about the namespaces, its uh. namespaces, what more can I really say about it (Minus that I hate the namespaces syntax and usage in PHP)? Have a look at composer maybe?

In Part 5 we're going to have a look at how to decouple our serialization of our HTML elements from our controls, which will enable the developer to provide custom markup, e.g. instead of XHTML he/she can provide normal HTML markup (the source code for part 5 is already available in the git repository if you would like to have an early preview).


Leave a Comment