PHP drop-down list - Part 3 (Maintaining State)

February 14, 2017 by PHP  

Ok sweet, now that we've got everything serializing to HTML (or rather XHTML) properly, how do we retrieve selected values and maintain that state?

In PHP there are a number of ways to retrieve request values, notably the super globals $_GET, $_POST, $_COOKIE or $_REQUEST can be used.

Personally I would avoid using $_REQUEST all together, simply because the population order of this variable is configurable outside the context of our scripts via request_order or variables_order, which introduces inconsistency into our code.

request_order string
This directive describes the order in which PHP registers GET, POST and Cookie variables into the _REQUEST array. Registration is done from left to right, newer values override older values.

If this directive is not set, variables_order is used for $_REQUEST contents.

I would rather use $_POST or $_GET, but which one must I use? Have a look at the following snippet.

function _REQUEST($key) {
	if (isset($_POST[$key])) {
		return $_POST[$key];
	}
	else if (isset($_GET[$key])) {
		return $_GET[$key];
	}
	return null;
}


Undoubtedly you have seen something similar to this before, similar to the super global $_REQUEST, but not plagued by configuration issues, at least we've got consistency, a slightly better approach, but I am still not quite satisfied.

Why in the world would I want to give users the ability to set the value of my drop-down list via the url query string if that is not what is intended by design? This is where "expected context" comes into play.

Have a look at the updated snippet below.

class HtmlSelectElement extends HtmlElement
implements IHtmlInnerHtml
{
...	
	public function __construct($name, 
		array $children = [], 
		$selected = null,
		$disabled = false,
		$context = 'POST')
	{
...
		$value = $this->getUserValue($context);
		$this->SetSelected(($value === null) ? $selected : $value);
	}
	
	private function getUserValue($context)
	{
		if ($context == 'POST' && isset($_POST[$this->Name])) {
			return htmlspecialchars_decode($_POST[$this->Name]);
		} else if ($context == 'GET' && isset($_GET[$this->Name])) {
			return htmlspecialchars_decode($_GET[$this->Name]);
		}
		return null;
	}
	
	public function SetSelected($value) {
		foreach($this->Children as $child) {
			$optionValue = ($child->Value == null) ? $child->Text : $child->Value;
			if ($optionValue == $value) {
				$child->Selected = true;
				$this->Selected = $optionValue;
				return;
			}
		}
	}
...
}


Ideally we would simply infer our context from a parent element, but in this case it is not possible (unless we implement our form element server side as well). So for now we're simply going to assume a context (POST) and allow the developer to pass a different context via the constructor if needed.

Additionally you will notice a selected argument added to the constructor and a SetSelected method, this allows the developer to easily set a selected value.

Since we encoded our values previously (looking at getUserValue), we need to decode them again if we want to do a proper string comparison, thereby maintaining the state of user submitted values by re-assigning their selection back to the drop-down list.

Hmm, but wait. The getUserValue method is something we can reuse among other elements, more specifically form control elements.

Lets move it out into its own base class.

abstract class HtmlFormControlElement extends HtmlElement
{
	protected function GetUserValue($context = 'POST')
	{
		if ($context == 'POST' && isset($_POST[$this->Name])) {
			return htmlspecialchars_decode($_POST[$this->Name]);
		} else if ($context == 'GET' && isset($_GET[$this->Name])) {
			return htmlspecialchars_decode($_GET[$this->Name]);
		}
		return null;
	}	
}


A form control element is still basically just a HTML element, so we inherit from that base class to create our new base class.

Subsequently we need to update our HtmlSelectElement class as well and remove the getUserValue function defined on that element.

class HtmlSelectElement extends HtmlFormControlElement
implements IHtmlInnerHtml
{
...

 
In part 4 we're going to do a bit of a code cleanup and look at adding some basic rules to our drop-down list.


Leave a Comment