PHP drop-down list - Part 5 (Custom Serialization)

February 15, 2017 by PHP  

In part 2 we quickly "talked" about having different serialization strategies for our html elements, but as an interim solution, I chose to use the XHTML spec as my standard markup, in this post we're going to decouple our serialization strategy.

Previously we defined two interfaces, IHtmlInnerHtml and IHtmlInnerText, along with an abstract base class, with two abstract methods that we needed to implement in order to tell the serialization code what to do with an element.

Now instead of requiring that our elements implement these interfaces, I created serialization specific classes for each element, I also moved the abstract methods previously defined in the HtmlElement class to an interface of its own.

interface IHtmlElement
{
	function GetAttributes();
	function GetTagName();
}


I also removed all serialization implementation from the HtmElement class.

abstract class HtmlElement
{	
	public function Render(IHtmlSerializer $serializer = null) {
		if ($serializer == null) {
			$serializer = HtmlSettings::$Serializer;
		}
		return $serializer->Serialize($this);
	}
}


The Render method now optionally accepts a serialization class (implementing IHtmlSerializer) as parameter, if not supplied it will use default values defined in a newly introduced, HtmlSettings class.

Not going to go into too much detail about the IHtmlSerializer implementations, they basically contain the same code previously located in the HtmlElement class, with some subtle differences (depending on serialization strategy). You can have a look at XHtmlSerializer and HtmlSerializer.

The part I would like to point out is the getSerializer method as seen below.

class XHtmlSerializer
implements IHtmlSerializer
{
...
	private function getSerializer($element) {
		if ($element instanceof HtmlSelectElement) {
			return new XHtmlSelectSerializer($element);
		} else if ($element instanceof HtmlOptionElement) {
			return new XHtmlOptionSerializer($element);
		}
		throw new \Exception('No metadata found for element '.get_class($element));
	}
...
}

This is where we associate elements with serializers, perhaps we can look at a cleaner association mechanism in future, but this is where associations happens.

A basic example of the HtmlSelectElement serializers can be see in the following snippets.

class XHtmlSelectSerializer 
implements IHtmlElement, IHtmlInnerHtml
{
	private $element;
	
	public function __construct(HtmlSelectElement $element) {
		$this->element = $element;
	}
	
	public function GetAttributes() {
		return [
			'name' => $this->element->GetName(),
			'disabled' => ($this->element->Disabled) ? 'disabled' : null
		];
	}
	
	public function GetTagName() {
		return 'select';
	}
	
	public function GetInnerHtml() {
		return $this->element->GetChildren();
	}
}
class HtmlSelectSerializer 
implements IHtmlElement, IHtmlInnerHtml
{
	private $element;
	
	public function __construct(HtmlSelectElement $element) {
		$this->element = $element;
	}
	
	public function GetAttributes() {
		return [
			'name' => $this->element->GetName(),
			'disabled' => ($this->element->Disabled) ? '' : null
		];
	}
	
	public function GetTagName() {
		return 'select';
	}
	
	public function GetInnerHtml() {
		return $this->element->GetChildren();
	}
}

You will notice subtle differences in the two serializers (perhaps we should consider using one as a base class and override whatever we need to in the other?).

But why stop at XHTML/HTML schema specific serializers, why not create a Bootstrap specific strategy? Or a material design strategy, instead of rewriting the same markup over and over?

That is pretty much the just of it, go have a look at the Serialization folder in the git repository.

In part 6 we're going to add support for optgroups to the codebase, the sources for part 6 is already part of the repository if you would like an early preview.


Leave a Comment