简体   繁体   English

将 PHP 接口导出为 Typescript 接口,反之亦然?

[英]Export PHP interface to Typescript interface, or vice versa?

I'm experimenting with Typescript, and at my current contract I code backend in PHP.我正在试验 Typescript,在我目前的合同中,我用 PHP 编写后端代码。

In a couple of projects I've written Typescript interfaces for the sort of AJAX responses my back end code gives so that the frontend developer (sometimes also me, sometimes someone else) knows what to expect and gets type checking and so on.在几个项目中,我为后端代码提供的 AJAX 响应类型编写了 Typescript 接口,以便前端开发人员(有时也是我,有时是其他人)知道会发生什么并进行类型检查等等。

After writing a few such back end services it seems like the interface and related classes for the responses should exist on the PHP side too.在编写了一些这样的后端服务之后,响应的接口和相关类似乎也应该存在于 PHP 端。 And that makes me think that it'd be nice if I could write them in just one of the two languages and run some build-time tool (I'd invoke it with a gulp task, before the Typescript compiler runs) to export these interfaces to the other language.这让我觉得如果我能用两种语言中的一种来编写它们并运行一些构建时工具(我会在 Typescript 编译器运行之前用 gulp 任务调用它)来导出这些,那就太好了与其他语言的接口。

Does such a thing exist?这样的事情存在吗? Is it possible?是否可以? Practical?实际的?

(I realize PHP is not strongly typed, but if the interfaces were written in PHP there could be some type hinting there such as docstrings which the exporter recognizes and carries over to Typescript.) (我意识到 PHP 不是强类型的,但是如果接口是用 PHP 编写的,那么可能会有一些类型提示,例如导出器识别并转移到 Typescript 的文档字符串。)

You can use amazing nikic/PHP-Parser to create a tool for converting selected PHP classes (those with @TypeScriptMe string in phpDoc) to TypeScript interfaces quite easily.您可以使用惊人的nikic/PHP-Parser创建一个工具,用于将选定的 PHP 类( @TypeScriptMe中带有@TypeScriptMe字符串的类)轻松转换为 TypeScript 接口。 The following script is really simple one but I think you can expand it and you can generate TypeScript interfaces automatically and possibly track changes through git.下面的脚本非常简单,但我认为你可以扩展它,你可以自动生成 TypeScript 接口,并可能通过 git 跟踪更改。

Example例子

For this input:对于此输入:

<?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() {

    }
}

you'll get:你会得到:

interface Person {
  name: string,
  age: number,
  mixed: any
}

Source codes源代码

index.php:索引.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作曲家.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"
}

您可以查看TypeSchema ,您可以在其中根据 PHP 模型生成 JSON 格式,并将这些 JSON 格式转换回不同的语言,即 TypeScript、Java 等,这可以解决您所描述的问题。

A bit late, but in case you use Symfony as a framework to your project, you can use https://github.com/snakedove/php-to-typescript-converter .有点晚了,但如果你使用 Symfony 作为你项目的框架,你可以使用https://github.com/snakedove/php-to-typescript-converter It adds a command, eg "ts-create-all" to your console, and lets you transform all POPOs, eg DTOs, of one folder, to TypeScript interfaces.它向您的控制台添加了一个命令,例如“ts-create-all”,并允许您将一个文件夹的所有 POPO(例如 DTO)转换为 TypeScript 接口。 Works one-way only.仅单向工作。 It has a special option to translate iterables to arrays of a given Type, which might be useful in some cases.它有一个特殊的选项可以将可迭代对象转换为给定类型的数组,这在某些情况下可能很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM