krak/struct-gen

安装次数: 6,611

依赖者: 0

建议者: 0

安全性: 0

星标: 2

关注者: 2

分支: 0

开放问题: 0

类型:composer-plugin

v0.1.3 2021-02-25 18:44 UTC

This package is auto-updated.

Last update: 2024-08-26 02:55:50 UTC


README

使用简单的PHP类定义struct,包含带类型定义的属性。包含StructTrait,让库为您生成所有模板代码,以便制作不可变的值对象,这些对象与IDE和静态分析工作得很好。

安装

使用composer安装 krak/struct-gen

用法

给定一些使用特殊特质 {className}Struct(其中{className}是实际的类名)的类定义

<?php

namespace App\Catalog;

final class Product
{
    use ProductStruct;
    
    /** @var int */
    private $id;
    /** @var ?string */
    private $code;
    /** @var Category[] */
    private $categories;
}

final class Category
{
    use CategoryStruct;
    
    /** @var int */
    private $id;
    /** @var string */
    private $name;
}

运行 composer struct-gen:generate,然后这些特质会被填充方法以允许类似以下API的操作

<?php

$product = new App\Catalog\Product(1, null, []);
$product->id();
$product->code();
$product = $product->withCategories([
    App\Catalog\Category::fromValidatedArray(['id' => 1, 'name' => 'Nike'])
]);
$product->toArray();
// ['id' => 1, 'code' => null, [['id' => 1', 'name' => 'Nike']]]

生成的Struct特质提供了制作不可变值对象所需的所有模板代码。

<?php

namespace App\Catalog;

trait ProductStruct
{
    /** @param Category[] $categories */
    public function __construct(int $id, ?string $code, array $categories)
    {
        $this->id = $id;
        $this->code = $code;
        $this->categories = $categories;
    }
    public static function fromValidatedArray(array $data) : self
    {
        return new self($data['id'], $data['code'], \array_map(function (array $value) : Category {
            return Category::fromValidatedArray($value);
        }, $data['categories']));
    }
    public function toArray() : array
    {
        return ['id' => $this->id, 'code' => $this->code, 'categories' => \array_map(function (Category $value) : array {
            return $value->toArray();
        }, $this->categories)];
    }
    public function id() : int
    {
        return $this->id;
    }
    public function code() : ?string
    {
        return $this->code;
    }
    /** @return Category[] */
    public function categories() : array
    {
        return $this->categories;
    }
    public function withId(int $id) : self
    {
        $self = clone $this;
        $self->id = $id;
        return $self;
    }
    public function withCode(?string $code) : self
    {
        $self = clone $this;
        $self->code = $code;
        return $self;
    }
    /** @param Category[] $categories */
    public function withCategories(array $categories) : self
    {
        $self = clone $this;
        $self->categories = $categories;
        return $self;
    }
}
trait CategoryStruct
{
    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
    public static function fromValidatedArray(array $data) : self
    {
        return new self($data['id'], $data['name']);
    }
    public function toArray() : array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }
    public function id() : int
    {
        return $this->id;
    }
    public function name() : string
    {
        return $this->name;
    }
    public function withId(int $id) : self
    {
        $self = clone $this;
        $self->id = $id;
        return $self;
    }
    public function withName(string $name) : self
    {
        $self = clone $this;
        $self->name = $name;
        return $self;
    }
}

配置路径

要配置要生成struct的路径,您可以轻松地将路径传递给 struct-gen:generate 命令。默认情况下,它查找 ./src 文件夹。

您还可以在composer.json中配置要搜索的路径,这样您就不必每次运行命令时都列出路径。

{
  "extra": {
    "struct-gen": {
      "paths": ["src/*/DTO", "lib/DTO"]
    }
  }
}

生成文件与内联生成

默认情况下,struct-gen将生成的struct保存到与原始类文件内联的php文件中。在此格式中,生成的struct应提交到您的仓库中。

但是,您可以配置struct-gen将所有生成的struct保存到一个单独的文件中,并自动让composer将该文件注册为类映射。

要这样做,只需更新您的composer json配置如下

{
  "extra": {
    "struct-gen": {
      "generated-path": ".generated-structs.php" 
    }
  }
}

该文件可以提交到您的仓库,或在CI管道上运行以确保struct的最新版本可用。

生成器

struct是从一组实现CreateStructStatements接口的生成器生成的。每个生成器负责构建最终struct的一部分。

构造函数生成

基于类中的所有属性生成构造函数。如果系统检测到已定义了构造函数,则不会向struct特质添加构造函数。

从验证数组构造函数生成

静态构造函数接受一个假定有效的数组并将其转换为对象表示形式。这可以看作是 toArray 的逆操作。术语 validated 的意思是,此操作在非验证用户数据上调用是不安全的。

此构造函数可以处理嵌套struct和struct集合。库天真地假设任何类型为类的属性都必须实现 fromValidatedArray 函数。所以如果您的struct包含没有该函数的对象,则在调用 fromValidatedArray 时会收到错误。

获取器生成

所有获取器都是基于属性生成的,不使用 get 前缀。它们只是将属性名作为函数调用。

Wither生成

所有struct默认为不可变,因此,如果您想更改struct的值,可以使用 with{propName} 约定来设置值并返回一个具有更改值的新的实例。

转换为数组

将struct转换为数组表示形式。这也适用于嵌套struct和struct集合。

生成struct选项

use {className}Struct 之上,您可以指定用于该特定类的选项,这些选项可以通过 @struct-gen 文档块标签影响生成过程。

格式为 @struct-gen {option-name} ?{option-value}。其中值是可选的,可以是简单的字符串、逗号分隔的列表或 JSON 字符串。

以下是一个示例

<?php

class Acme {
    /** @struct-gen generate getters,withers */
    use AcmeStruct;
    // ...
}

您可以有多个 struct-gen 标签,具有不同的选项名称和值,所有这些都会合并在一起。

生成

生成选项允许您控制特定结构使用的生成器。如果您只想使用获取器和设置器,可以相应地指定。

您可以使用以下生成器名称列表:constructor,from-validated-array,to-array,getters,withers

持续集成设置

如果您直接在仓库中提交 struct gen 变更,那么您需要确保在将任何提交合并到主分支之前,struct-gen 已正确运行和测试。

在这种情况下,您应该在 CI 测试管道中运行 struct gen 时使用 --fail-on-changes 标志。如果检测到任何更改,它将以退出代码 1 失败,在大多数 CI 管道中这将导致构建失败,确保提交补丁的开发者已使用最新更改进行了测试,并且没有修改任何生成的文件。

示例

- composer struct-gen:generate --fail-on-changes

如果您通过 generatedPath 选项将结构生成到外部文件,并且在该文件中忽略 CVS,那么您需要确保在运行 composer install 之前运行 struct-gen。

- composer struct-gen:generate -vv
# some point later
- composer install --no-dev -o

为什么使用静态生成?

大多数 DTO 或结构的库对 IDE 不友好,并且通过运行时魔法和反射提供对结构的有用方法的访问。这些方法不仅会有轻微的性能损失,而且难以在同时与 IDE 和静态分析工具良好协作的情况下实现类型安全。

开发

运行 composer

composer install

Psalm

./vendor/bin/psalm

PHPUnit

./vendor/bin/phpunit

测试 Composer 插件

composer 插件目前正在另一个本地仓库中手动仔细测试,该仓库需要本地使用 struct-gen 包。我通常会手动测试所有功能。

路线图

  • 从非验证数组创建
  • 改进 Psalm 对类型定义的支持
  • 插件系统
  • 从 Open Api 3 生成
  • 导出到 Open API 3