PHP: Are form validation return values good practice to make up for the lack of PHP generics?
Note . To prevent downvotes, because good practice might be opinion based, you can also rephrase the question as follows: What are the disadvantages of type checking return values to compensate for PHP's lack of PHP? (I haven't used this as it means there are drawbacks).
Question
Coming from the Java / C # world, PHP bulk type manipulation has always been somewhat annoying. It got better when type-hinting for input parameters was introduced, but I'm still missing generics and type-hinting for return values.
I sometimes run into this when explicitly checking types within my code - which seems a bit wrong since the language itself can handle this for me - and I'd like to pass these questions on to the community:
- Are form validation return values good practice to make up for the lack of PHP generics?
- Is there a better / more standard way to do this?
- Are general concepts currently being considered for a future PHP implementation?
Example
To better understand why this is problematic, consider the following example:
Let's say we are building a framework to transform input data into some other output. Examples:
Convert a string representing an XML document in a DomDocument to another string by selecting the title of the specified DomDocument using the xpath expression.
(string) $xml =[TransformToDomDocument]=> (DomDocument) $doc =[TransformToString]=> (string) $title
Now, suppose the input is not a string containing XML, but Json (but at the same time contains the same data). Now we want to convert the Json input to a Json object and write the header using the JsonPath expression .
(string) $jsonString =[TransformToJson]=> (Json) $jsonObject =[TransformToString]=> (string) $title
(Note: In the second example, it should be clear that the whole structure should be really flexible.)
The conversion is done using a chaining of adaptive objects that handle the conversion from input to output:
interface AdapterInterface{
/**
* Transform some input data into something else.
* @param mixed $data
* @return mixed
*/
public function transform($data);
/**
* Set the Adapter that is used to preprocess the $data before calling $this->transform($data)
* @param AdapterInterface $adapter
*/
public function setPredecessorAdapter(AdapterInterface $adapter);
}
class XmlToDomDocumentAdapter implements AdapterInterface{
private $predecessor;
/**
* Transform an xml string into a DOMDocument.
* @param mixed $data
* @return DomDocument
*/
public function transform($data){
if($this->predecessor !== null){
$data = $this->predecessor->transform($data);
// At this point, we just have to "trust" that the predecessor returns a (string)
}
$doc = new DomDocument();
$doc->loadXml($data);
return $doc;
}
}
class DomDocumentToStringAdapter implements AdapterInterface{
private $xpathExpression;
private $predecessor;
/**
* Transform a DomDocument into a string.
* @param mixed $data
* @return string
*/
public function transform($data){
if($this->predecessor !== null){
$data = $this->predecessor->transform($data);
// At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
}
$xpath = new DOMXpath($data);
$nodes = $xapth->query($this->xpathExpression);
if($nodes->length > 0){
throw new UnexpectedValueException("Xpath didn't match");
}
$result = $nodes->item(0)->nodeValue;
return $result;
}
}
Using:
$input = "..."
$xmlToDom = new XmlToDomDocumentAdapater();
$domToString = DomDocumentToStringAdapter();
$domToString->setPredecessorAdapter($xmlToDom);
$output = $domToString->transform($input);
The problematic part arises when the adapter relies on it by its predecessor to return the correct input.
if($this->predecessor !== null){
$data = $this->predecessor->transform($data);
// At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
}
In C #, I would solve this problem using generics :
interface AdapterInterface{
/**
* Tranform some input data into something else.
* @param mixed $data
* @return T
*/
public function T transform<T>(object data);
}
/* using it */
//...
if(this.predecessor !== null){
data = this.predecessor.transform<string>(data);
// we now know for sure that the data is of type 'string'
}
//...
Since generics are not supported in PHP, I ask myself if it is worth adding a type check after each call transform($data)
like this:
if($this->predecessor !== null){
$data = $this->predecessor->transform($data);
if(!is_string($data){
throw new UnexpectedValueException("data is not a string!");
}
// we now know for sure that the data is of type 'string'
}
My current workaround
I am currently using several interfaces to define the output of a method transform
like this:
interface ToStringAdapterInterface extends AdapterInterface{
/**
* Transform some input data into something else.
* @param mixed $data
* @return string <<< define expected output
*/
public function transform($data);
}
interface ToDomDocumentAdapterInterface extends AdapterInterface{
/**
* Transform some input data into something else.
* @param mixed $data
* @return DOMDocument<<< define expected output
*/
public function transform($data);
}
In every transformer, I make sure to only accept a suitable interface as a predecessor:
class DomDocumentToStringAdapter implements ToStringAdapterInterface {
private $xpathExpression;
private $predecessor;
public function __construct(ToDomDocumentAdapterInterface $predecessor){
$this->predecessor = $predecessor;
}
// ...
}
source to share
I would follow your approach: check the datatype of the return value $this->predecessor->transform($data)
and throw an exception if it is not what was expected.
I don't know if you might be interested in Hacking the Facebook Programming Language :
Hack is a programming language for HHVM that communicates seamlessly with PHP. Hack aligns the fast PHP development cycle with the discipline provided by static typing , adding many of the features commonly found in other modern programming languages.
source to share