Примечание. Чтобы предотвратить отрицательные отзывы, поскольку рекомендуемая практика может быть основана на мнении, вы также можете перефразировать вопрос следующим образом: Каковы недостатки возвращаемых значений проверки типов для компенсации Отсутствие дженериков в PHP? (я не использовал это, так как это подразумевает наличие существующих недостатков).
Вопрос
Пришедшая из мира Java/C# свободная обработка типов в PHP всегда несколько раздражала. Стало лучше, когда были введены подсказки типов для входных параметров, но мне по-прежнему не хватает универсальных шаблонов и -подсказка для возвращаемых значений.
Я обнаружил, что иногда обхожу это, явно проверяя типы в моем коде, что кажется несколько неправильным, поскольку сам язык может справиться с этим за меня, и я хотел бы задать эти вопросы сообществу:
- Являются ли возвращаемые значения проверки типов хорошей практикой, чтобы компенсировать отсутствие обобщений в PHP?
- Есть ли лучший/более стандартный способ сделать это?
- Обсуждаются ли в настоящее время дженерики для будущей реализации в PHP?
Пример
Чтобы лучше понять, почему это проблематично, рассмотрим следующий пример:
Предположим, мы создаем структуру для преобразования входных данных в некоторые другие выходные данные. Примеры:
Преобразуйте строку, представляющую XML-документ в DomDocument, в другую строку, выбрав заголовок указанного DomDocument с помощью выражения xpath.
(string) $xml =[TransformToDomDocument]=> (DomDocument) $doc =[TransformToString]=> (string) $title
Теперь давайте предположим, что входные данные представляют собой не строку, содержащую XML, а Json (но в остальном содержат те же данные). Теперь нам нужно преобразовать ввод Json в объект Json и выбрать заголовок с помощью JsonPath. выражение.
(string) $jsonString =[TransformToJson]=> (Json) $jsonObject =[TransformToString]=> (string) $title
(Примечание: второй пример должен прояснить, что вся структура должна быть действительно гибкой.)
Преобразование выполняется с помощью цепочки объектов-адаптеров, которые обрабатывают преобразование из ввода в вывод:
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;
}
}
Применение:
$input = "..."
$xmlToDom = new XmlToDomDocumentAdapater();
$domToString = DomDocumentToStringAdapter();
$domToString->setPredecessorAdapter($xmlToDom);
$output = $domToString->transform($input);
Проблемная часть возникает, когда адаптер полагается на своего предшественника, чтобы вернуть правильный ввод.
if($this->predecessor !== null){
$data = $this->predecessor->transform($data);
// At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
}
В С# я бы решил эту проблему, используя дженерики а>:
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'
}
//...
Поскольку дженерики не поддерживаются в PHP, я спрашиваю себя, хорошо ли добавлять проверку типа после каждого вызова transform($data)
, например:
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'
}
Мой текущий обходной путь
В настоящее время я использую несколько интерфейсов для определения вывода метода transform
следующим образом:
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);
}
В каждом преобразователе я принимаю только подходящий интерфейс в качестве предшественника:
class DomDocumentToStringAdapter implements ToStringAdapterInterface {
private $xpathExpression;
private $predecessor;
public function __construct(ToDomDocumentAdapterInterface $predecessor){
$this->predecessor = $predecessor;
}
// ...
}
\My\InvalidArgumentException::assertIs($returnValue, 'string')
- person deceze♦   schedule 29.08.2014switch
или подобного. Эти функции могут сделать код более кратким, но их отсутствие принципиально не влияет на вашу способность писать такие же функции. - person deceze♦   schedule 29.08.2014