consolidation/output-formatters

通过应用插件格式化程序提供的转换来格式化文本。

4.5.0 2024-04-02 15:18 UTC

This package is auto-updated.

Last update: 2024-09-02 11:22:47 UTC


README

将转换应用到结构化数据上,以便以不同的格式编写输出。

ci scrutinizer codecov license

动机

格式化程序用于允许以完全独立于Symfony Console输出界面的方式实现简单的命令行工具命令。命令通过其方法参数接收输入,并将结果作为结构化数据(例如PHP标准对象或数组)返回。然后由格式化程序格式化结构化数据,并打印结果。

此过程由Consolidation/AnnotatedCommand项目管理。

库使用

这是一个打算在其他项目中使用的库。请在您的composer.json文件中要求它

    "require": {
        "consolidation/output-formatters": "^4"
    },

示例格式化程序

简单的格式化程序非常容易编写。

class YamlFormatter implements FormatterInterface
{
    public function write(OutputInterface $output, $data, FormatterOptions $options)
    {
        $dumper = new Dumper();
        $output->writeln($dumper->dump($data));
    }
}

格式化程序接收用户在命令行上提供的$options集合。如果需要,可以检查这些选项来更改格式化程序的行为。

格式化程序也可以实现不同的接口来改变渲染引擎的行为。

  • ValidationInterface:格式化程序应该实现此接口以测试提供的数据类型是否可以处理。任何没有实现此接口的格式化程序都假定仅使用PHP数组进行操作。格式化程序管理器将在将数据传递给未实现ValidationInterface的格式化程序之前始终将提供的数据转换为数组。这些格式化程序将在返回的数据类型无法转换为数组时不可用。
  • OverrideRestructureInterface:实现此接口的格式化程序将有机会在格式化程序自身重构之前对提供的数据对象进行操作。有关数据重构的详细信息,请参阅以下关于结构化数据的部分。
  • UnstructuredInterface:默认情况下,实现此接口的格式化程序不能由string格式化程序格式化。未实现此接口的数据结构在适用时将自动转换为字符串;如果转换失败,则不会生成输出。
  • StringTransformationInterface:实现此接口允许数据类型为将数据转换为字符串提供特定的实现。实现UnstructuredInterfaceStringTransformationInterface的数据类型可以使用string格式。

为命令配置格式

命令使用@return注解声明它们返回的数据类型,如通常操作

    /**
     * Demonstrate formatters.  Default format is 'table'.
     *
     * @field-labels
     *   first: I
     *   second: II
     *   third: III
     * @default-string-field second
     * @usage try:formatters --format=yaml
     * @usage try:formatters --format=csv
     * @usage try:formatters --fields=first,third
     * @usage try:formatters --fields=III,II
     *
     * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
     */
    public function tryFormatters($somthing = 'default', $options = ['format' => 'table', 'fields' => ''])
    {
        $outputData = [
            'en' => [ 'first' => 'One',  'second' => 'Two',  'third' => 'Three' ],
            'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei'  ],
            'jp' => [ 'first' => 'Ichi', 'second' => 'Ni',   'third' => 'San'   ],
            'es' => [ 'first' => 'Uno',  'second' => 'Dos',  'third' => 'Tres'  ],
        ];
        return new RowsOfFields($outputData);
    }

output-formatters库通过查询所有可用的格式并选择能够处理返回数据类型的任何格式来确定命令适用的输出格式。因此,如果向程序添加了新的格式,它将自动通过任何与其合作的命令可用。没有必要在各个命令中逐个手动选择可用的格式。

结构化数据

大多数格式化程序将操作任何数组或ArrayObject数据。一些格式化程序要求使用特定的数据类型。以下数据类型,所有这些都是ArrayObject的子类,可供使用

  • RowsOfFields:每一行包含一个字段:值对的关联数组。还假设每一行的字段对于每一行都是相同的。这种格式非常适合在表格中显示,其中第一行是标签。
  • RowsOfFieldsWithMetadata:与RowsOfFields等效,但允许将元数据附加到结果中。元数据不以表格格式显示,但如果将数据转换为另一种格式(例如yamljson),则可明显看出。表格数据可以嵌套在特别指定的元素中,其他元素用作元数据,或者, alternately,元数据可以嵌套在元素中,所有其他元素用作数据。
  • PropertyList:每一行包含一个字段:值对。每个字段是唯一的。这种格式非常适合在表格中显示,其中第一列是标签,第二列是值。
  • UnstructuredListData:假设结果是项的列表,其中每一行的键用作行ID。数据元素可以包含任何类型的数组数据。每一行的元素不必统一,并且数据可以嵌套到任意深度。
  • UnstructuredData:结果是嵌套到任意级别的非结构化数组。
  • DOMDocument:当使用XML输出格式时,可以使用标准PHP DOM文档类来指定精确的属性和子元素,以便函数能够精确指定。
  • ListDataFromKeys:此数据结构已弃用。请改用UnstructuredListData

需要生成XML输出的命令应返回DOMDocument作为其返回类型。格式化管理器将尽力将数组转换为DOMDocument,或将DOMDocument转换为数组,如需。重要的是要注意,DOMDocument与PHP数组没有1对1的映射。DOM元素包含属性和元素;一个简单的字符串属性'foo'可以表示为属性或值。此外,可能有多个具有相同名称的XML元素,而PHP关联数组必须始终具有唯一的键。在从数组转换为DOM文档时,XML格式化器将默认将数组的字符串属性表示为元素的属性。具有相同名称的元素集只能在它们被包装在包含父元素中时使用,例如one two。可以使用XMLSchema类来控制是否将属性渲染为属性或元素;但是,在XML输出模式很重要的情况下,最好让函数以DOMDocument而不是数组的形式返回其结果。

函数还可以定义自己的结构化数据类型以返回,通常是通过扩展上面提到的类型之一。如果自定义结构化数据类实现了适当的接口,则它可以提供自己的转换函数到其他数据类型之一。

  • DomDataInterface:数据对象可以通过其getDomData()方法生成DOMDocument,该方法将在需要DOM文档的任何实例中调用——通常与xml格式化器一起使用。
  • ListDataInterface:任何实现此接口的结构化数据对象都可以使用getListData()方法生成将用于列表格式化器的数据集。
  • TableDataInterface:任何实现此接口的结构化数据对象都可以使用getTableData()方法生成将用于表格格式化器的数据集。
  • RenderCellInterface:结构化数据还可以通过实现RenderCellInterface来提供对表格中每个单元格如何渲染的细粒度控制。有关如何做到这一点的信息,请参阅下面的部分。
  • RestructureInterface:重构接口可以通过结构化数据对象实现,以响应用户提供的选项来重构数据。例如,RowsOfFields 和 PropertyList 数据类型使用此接口来选择和重新排序要在输出中显示的字段。自定义数据类型通常不需要实现此接口,因为它们可以通过扩展 RowsOfFields 或 PropertyList 继承此行为。

此外,结构化数据可以通过数组简化对象简化为数组。要提供数组简化器,实现 SimplifyToArrayInterface,并通过 FormatterManager::addSimplifier() 注册简化器。

字段

某些命令生成的输出包含字段。字段可以是键/值对中的键,或者它是表格输出中使用的标签等。

声明默认字段

如果命令声明了大量的字段,可以通过 @default-fields 注释仅显示可用选项的子集。以下示例来自 Drush

    /**
     * @command cache:get
     * @field-labels
     *   cid: Cache ID
     *   data: Data
     *   created: Created
     *   expire: Expire
     *   tags: Tags
     *   checksum: Checksum
     *   valid: Valid
     * @default-fields cid,data,created,expire,tags
     * @return \Consolidation\OutputFormatters\StructuredData\PropertyList
     */
    public function get($cid, $bin = 'default', $options = ['format' => 'json'])
    {
        $result = ...
        return new PropertyList($result);
    }

所有可用字段都将列在命令的 help 输出中,并且可以通过通过 --fields 选项明确列出所需的字段来选择。

要包含所有可用字段,使用 --fields=*

请注意,使用 @default-fields 注释将减少包括非结构化格式(例如 json 和 yaml)在内的所有格式中包含的字段数量。要在仅使用人类可读输出格式(例如表格)时仅显示一组减少的字段,请使用 @default-table-fields 注释。

重新排序字段

返回具有字段的结构化数据的命令可以通过使用 --fields 选项进行过滤和/或重新排序。这些结构化数据类型还可以在过滤后格式化为更通用的类型,例如 yaml 或 json。如果数据以裸 php 数组的形式返回,则这些功能不可用。必须使用 RowsOfFieldsPropertyListUnstructuredListData(或类似)。

当提供 --fields 选项时,用户可以指定每行上要列出的确切字段及其顺序。例如,如果命令通常使用 RowsOfFields 数据类型生成输出,如下所示

$ ./app try:formatters
 ------ ------ ------- 
  I      II     III    
 ------ ------ ------- 
  One    Two    Three  
  Eins   Zwei   Drei   
  Ichi   Ni     San    
  Uno    Dos    Tres   
 ------ ------ ------- 

则可以选择第三和第一字段如下

 $ ./app try:formatters --fields=III,I
 ------- ------ 
  III     I     
 ------- ------ 
  Three   One   
  Drei    Eins  
  San     Ichi  
  Tres    Uno   
 ------- ------ 

要选择单个列并删除所有格式,请使用 --field 选项

$ ./app try:formatters --field=II
Two
Zwei
Ni
Dos

生成使用 UnstructuredDataUnstructuredListData 数据类型的深度嵌套数据结构的命令也可以使用 --fields--field 选项进行操作。可以使用点符号来引用层次结构中的项目。

UnstructuredData 类型表示没有统一结构要求的单个嵌套数组。UnstructuredListData 类型类似;它表示 UnstructuredData 类型列表。列表中的不同元素不需要具有所有相同的字段或结构,尽管预计会有一定程度的相似性。

在下面的示例中,命令返回不同类型的商店列表。每个商店都有常见的顶级元素,如 nameproductssale-items。每个商店可能具有具有不同属性的不同类型的产品

$ ./app try:nested
bills-hardware:
  name: 'Bill''s Hardware'
  products:
    tools:
      electric-drill:
        price: '79.98'
      screwdriver:
        price: '8.99'
  sale-items:
    screwdriver: '4.99'
alberts-supermarket:
  name: 'Albert''s Supermarket'
  products:
    fruits:
      strawberries:
        price: '2'
        units: lbs
      watermellons:
        price: '5'
        units: each
  sale-items:
    watermellons: '4.50'

与表格输出一样,可以只选择要显示的特定字段集

$ ./app try:nested --fields=sale-items
bills-hardware:
  sale-items:
    screwdriver: '4.99'
alberts-supermarket:
  sale-items:
    watermellons: '4.50'

在非结构化数据中,还可以将字段名称重新映射为其他名称

$ ./robo try:nested --fields='sale-items as items'
bills-hardware:
  items:
    screwdriver: '4.99'
alberts-supermarket:
  items:
    watermellons: '4.50'

字段名称 . 是特殊的:它表示应省略指定的元素,并将其值或子元素直接应用于结果行

$ ./app try:nested --fields='sale-items as .'
bills-hardware:
  screwdriver: '4.99'
alberts-supermarket:
  watermellons: '4.50'

最后,还可以深入到嵌套数据结构中,并使用“点”符号标识的元素或元素提取信息

$ ./app try:nested --fields=products.fruits.strawberries
bills-hardware: {  }
alberts-supermarket:
  strawberries:
    price: '2'
    units: lbs

使用 RowsOfFieldsPropertyList 返回类型的命令,在执行任何字段重映射时,将自动转换为 UnstructuredListDataUnstructuredData。这仅适用于可以渲染非结构化数据类型的 yamljson 等数据类型。即使结果数据恰好是统一的,也无法在表中渲染非结构化数据。

过滤特定行

命令可能允许用户使用简单的布尔逻辑和/或正则表达式来过滤特定数据行。有关详细信息,请参阅提供此功能的外部库 consolidation/filter-via-dot-access-data

渲染表格单元格

默认情况下,RowsOfFields 和 PropertyList 数据类型假定每个单元格的内容是简单的字符串。要渲染更复杂的单元格内容,可以通过扩展 RowsOfFields 或 PropertyList 创建一个自定义的结构化数据类,并实现 RenderCellInterface。然后,您的类的 renderCell() 方法将为每个单元格调用,您可以对其进行适当的操作。

public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
    // 'my-field' is always an array; convert it to a comma-separated list.
    if ($key == 'my-field') {
        return implode(',', $cellData);
    }
    // MyStructuredCellType has its own render function
    if ($cellData instanceof MyStructuredCellType) {
        return $cellData->myRenderfunction();
    }
    // If we do not recognize the cell data, return it unchnaged.
    return $cellData;
}

请注意,如果使用除表格格式器之外的格式器打印数据结构,它仍将根据所选字段进行排序,但单元格渲染将不执行

RowsOfFields 和 PropertyList 数据类型还允许将实现 RenderCellInterface 的对象以及匿名函数直接添加到数据结构对象本身。如果这样做,则渲染器将为表中的每个单元格调用。下面展示了作为匿名函数实现的附加渲染器的示例。

    return (new RowsOfFields($data))->addRendererFunction(
        function ($key, $cellData, FormatterOptions $options, $rowData) {
            if ($key == 'my-field') {
                return implode(',', $cellData);
            }
            return $cellData;
        }
    );

此项目还提供了一种内置的单元格渲染器,NumericCellRenderer,它将在千位处添加逗号并将标记为数字的列右对齐。下面展示了附加到数据集两列的数字渲染器的示例。

use Consolidation\OutputFormatters\StructuredData\NumericCellRenderer;
...
    return (new RowsOfFields($data))->addRenderer(
         new NumericCellRenderer($data, ['population','cats-per-capita'])
    );

API 使用

建议使用 Consolidation/AnnotatedCommand 来管理命令和格式化程序。有关详细信息,请参阅 AnnotatedCommand API 使用

如果需要,也可以直接使用 FormatterManager。

/**
 * @param OutputInterface $output Output stream to write to
 * @param string $format Data format to output in
 * @param mixed $structuredOutput Data to output
 * @param FormatterOptions $options Configuration informatin and User options
 */
function doFormat(
    OutputInterface $output,
    string $format, 
    array $data,
    FormatterOptions $options) 
{
    $formatterManager = new FormatterManager();
    $formatterManager->write(output, $format, $data, $options);
}

FormatterOptions 类用于保存命令输出的配置,例如表格输出的默认字段列表等,以及渲染期间使用的当前用户选择的选项,这些选项可以使用 Symfony InputInterface 对象提供。

public function execute(InputInterface $input, OutputInterface $output)
{
    $options = new FormatterOptions();
    $options
      ->setInput($input)
      ->setFieldLabels(['id' => 'ID', 'one' => 'First', 'two' => 'Second'])
      ->setDefaultStringField('id');

    $data = new RowsOfFields($this->getSomeData($input));
    return $this->doFormat($output, $options->getFormat(), $data, $options);
}

与现有解决方案的比较

格式化程序自 Drush 5 版本以来一直在使用。Drush 允许使用简单的类定义格式化程序,其中一些可以使用元数据进行配置。此外,还允许嵌套格式化程序;例如,列表格式化程序可以分配另一个格式化程序来格式化其每一行。嵌套格式化程序还需要嵌套元数据,这导致构建格式化程序的代码变得非常复杂和难以管理。

Consolidation/OutputFormatters 保持 Drush 格式化程序提供的简单使用性,但放弃了嵌套元数据配置,转而使用格式化程序中的代码来自动配置自己,以保持代码的简单性。