Я экспериментирую с Typescript, и по своему текущему контракту я пишу бэкэнд на PHP.
В нескольких проектах я написал интерфейсы Typescript для ответов AJAX, которые дает мой внутренний код, чтобы разработчик внешнего интерфейса (иногда и я, иногда кто-то еще) знал, чего ожидать, и получал проверку типов и так далее.
После написания нескольких таких серверных сервисов кажется, что интерфейс и связанные классы для ответов должны существовать и на стороне PHP. И это заставляет меня думать, что было бы неплохо, если бы я мог написать их только на одном из двух языков и запустить какой-нибудь инструмент времени сборки (я бы вызвал его с задачей gulp до запуска компилятора Typescript) для экспорта этих интерфейсы к другому языку.
Существует ли такая вещь? Является ли это возможным? Практическая?
(Я понимаю, что PHP не является строго типизированным, но если бы интерфейсы были написаны на PHP, там мог бы быть некоторый тип подсказки, такой как строки документации, которые экспортер распознает и переносит в Typescript.)
Вы можете использовать удивительные nikic / PHP-Parser создать инструмент для преобразования выбранных классов PHP (с @TypeScriptMe
Строка в phpDoc) к интерфейсам TypeScript довольно легко. Следующий скрипт действительно прост, но я думаю, что вы можете его расширить и автоматически генерировать интерфейсы TypeScript и, возможно, отслеживать изменения с помощью git.
пример
Для этого ввода:
<?php
/**
* @TypeScriptMe
*/
class Person
{
/**
* @var string
*/
public $name;
/**
* @var int
*/
public $age;
/**
* @var \stdClass
*/
public $mixed;
/**
* @var string
*/
private $propertyIsPrivateItWontShow;
}
class IgnoreMe {
public function test() {
}
}
ты получишь:
interface Person {
name: string,
age: number,
mixed: any
}
Исходники
index.php:
<?php
namespace TypeScript {
class Property_
{
/** @var string */
public $name;
/** @var string */
public $type;
public function __construct($name, $type = "any")
{
$this->name = $name;
$this->type = $type;
}
public function __toString()
{
return "{$this->name}: {$this->type}";
}
}
class Interface_
{
/** @var string */
public $name;
/** @var Property_[] */
public $properties = [];
public function __construct($name)
{
$this->name = $name;
}
public function __toString()
{
$result = "interface {$this->name} {\n";
$result .= implode(",\n", array_map(function ($p) { return " " . (string)$p;}, $this->properties));
$result .= "\n}";
return $result;
}
}
}
namespace MyParser {
ini_set('display_errors', 1);
require __DIR__ . "/vendor/autoload.php";
use PhpParser;
use PhpParser\Node;
use TypeScript;
class Visitor extends PhpParser\NodeVisitorAbstract
{
private $isActive = false;
/** @var TypeScript/Interface_[] */
private $output = [];
/** @var TypeScript\Interface_ */
private $currentInterface;
public function enterNode(Node $node)
{
if ($node instanceof PhpParser\Node\Stmt\Class_) {
/** @var PhpParser\Node\Stmt\Class_ $class */
$class = $node;
// If there is "@TypeScriptMe" in the class phpDoc, then ...
if ($class->getDocComment() && strpos($class->getDocComment()->getText(), "@TypeScriptMe") !== false) {
$this->isActive = true;
$this->output[] = $this->currentInterface = new TypeScript\Interface_($class->name);
}
}
if ($this->isActive) {
if ($node instanceof PhpParser\Node\Stmt\Property) {
/** @var PhpParser\Node\Stmt\Property $property */
$property = $node;
if ($property->isPublic()) {
$type = $this->parsePhpDocForProperty($property->getDocComment());
$this->currentInterface->properties[] = new TypeScript\Property_($property->props[0]->name, $type);
}
}
}
}
public function leaveNode(Node $node)
{
if ($node instanceof PhpParser\Node\Stmt\Class_) {
$this->isActive = false;
}
}
/**
* @param \PhpParser\Comment|null $phpDoc
*/
private function parsePhpDocForProperty($phpDoc)
{
$result = "any";
if ($phpDoc !== null) {
if (preg_match('/@var[ \t]+([a-z0-9]+)/i', $phpDoc->getText(), $matches)) {
$t = trim(strtolower($matches[1]));
if ($t === "int") {
$result = "number";
}
elseif ($t === "string") {
$result = "string";
}
}
}
return $result;
}
public function getOutput()
{
return implode("\n\n", array_map(function ($i) { return (string)$i;}, $this->output));
}
}
### Start of the main part
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser;
$visitor = new Visitor;
$traverser->addVisitor($visitor);
try {
// @todo Get files from a folder recursively
//$code = file_get_contents($fileName);
$code = <<<'EOD'
<?php
/**
* @TypeScriptMe
*/
class Person
{
/**
* @var string
*/
public $name;
/**
* @var int
*/
public $age;
/**
* @var \stdClass
*/
public $mixed;
/**
* @var string
*/
private $propertyIsPrivateItWontShow;
}
class IgnoreMe {
public function test() {
}
}
EOD;
// parse
$stmts = $parser->parse($code);
// traverse
$stmts = $traverser->traverse($stmts);
echo "<pre><code>" . $visitor->getOutput() . "</code></pre>";
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
}
composer.json
{
"name": "experiment/experiment",
"description": "...",
"homepage": "http://example.com",
"type": "project",
"license": ["Unlicense"],
"authors": [
{
"name": "MrX",
"homepage": "http://example.com"}
],
"require": {
"php": ">= 5.4.0",
"nikic/php-parser": "^1.4"},
"minimum-stability": "stable"}
Других решений пока нет …