hi-folks/data-block

用于管理嵌套数组和JSON数据的数据类。

v0.3.9 2024-08-08 20:07 UTC

README

PHP Data Block package

数据块包

Latest Version Total Downloads
Packagist License Supported PHP Versions GitHub last commit
Tests

用于管理嵌套数据的PHP包

此PHP包提供类和方法,以轻松管理、查询、过滤和设置嵌套数据结构。无论您是在处理复杂的JSON数据、层次结构配置还是深层嵌套的数组,此包都提供了一种简化的方法来轻松处理嵌套数据。

Block类

Block类提供了一套全面的方法来创建、管理和访问嵌套数据结构。

Block类提供以下各种方法,包括

  • 从数组、JSON和YAML文件创建结构。
  • 轻松查询嵌套数据。
  • 根据特定标准过滤数据。
  • 设置和更新深层嵌套结构中的值。

安装和使用Block类

要将其添加到项目中,您可以使用composer require命令运行带有方法和帮助器的Block类

composer require hi-folks/data-block

为了支持开发,您可以在仓库中加星:https://github.com/Hi-Folks/data-block

然后在您的PHP文件中,您可以导入HiFolks\DataType\Block命名空间

use HiFolks\DataType\Block;

创建Block对象的方法

为了展示以下方法的性能,我将使用这个嵌套关联数组

$fruitsArray = [
    "avocado" =>
    [
        'name' => 'Avocado',
        'fruit' => '🥑',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Avocado',
        'color'=>'green',
        'rating' => 8
    ],
    "apple" =>
    [
        'name' => 'Apple',
        'fruit' => '🍎',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Apple',
        'color' => 'red',
        'rating' => 7
    ],
    "banana" =>
    [
        'name' => 'Banana',
        'fruit' => '🍌',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Banana',
        'color' => 'yellow',
        'rating' => 8.5
    ],
    "cherry" =>
    [
        'name' => 'Cherry',
        'fruit' => '🍒',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Cherry',
        'color' => 'red',
        'rating' => 9
    ],
];

静态make()方法

使用静态make()方法,您可以从关联数组生成Block对象

$data = Block::make($fruitsArray);

$data对象是Block类的一个实例。

如果您想初始化一个空的Block对象,您可以不带参数调用make()方法

$data = Block::make();

一旦初始化了Block对象,您就可以使用其方法。

get()方法

get()方法支持使用点(或自定义)表示法检索嵌套数组的值,并返回数据类型的原始类型。如果您需要获取一个Block对象,请使用getBlock()方法而不是get()。例如

$data->get('avocado'); // returns an array
$data->get('avocado.color'); // returns the string "green"

例如,使用$fruitsArray样本数据,$data->get("avocado")

  • 一个数组;
  • 有5个元素;

例如,$data->get("avocado.color")

  • 一个字符串;
  • 值为"green";

$data->get("avocado.rating")

  • 一个数字;
  • 特别是一个整数;

$data->get("banana.rating")

  • 一个数字;
  • 特别是一个浮点数;

您可以自定义不同的字符表示法

$data->get('apple#fruit', charNestedKey: '#'); // 🍎

如果您要访问一个不存在的键,将返回一个null

$value = $data->get('apple.notexists'); // null

您可以定义一个默认值,以防键不存在

$value = $data->get(
    'apple.notexists',
    '🫠'
); // 🫠

并且您可以组合默认值和嵌套字符

$value = $data->get(
    'apple#notexists',
    '🫠',
    '#'
); // 🫠

getBlock()方法

如果您需要管理一个复杂的数组(嵌套数组)或从复杂JSON结构中获取的数组,您可以通过getBlock()方法访问数组的一部分并获取Block对象。

让我们看一个例子

$appleData = $data->getBlock("apple")
// $data is the Block instance so that you can access
// to the Block methods like count()
$data->getBlock("apple")->count();

如果通过getBlock()访问的元素是标量类型(整数、浮点数、字符串等),则将使用getBlock()返回一个包含单个元素的Block对象。

例如,$data->getBlock("avocado") 返回一个包含五个元素的 Block 对象。

例如,$data->getBlock("avocado.color") 返回一个仅包含一个元素的 Block 对象。

如果您尝试访问一个无效的键,将返回一个空的 Block 对象,因此 $data->getBlock("avocado.notexists") 返回一个长度等于 0 的 Block 对象。

set() 方法

set() 方法支持使用点(或自定义)符号设置嵌套数据的值。如果键不存在,则 set() 方法会创建一个并设置值。如果键已存在,则 set() 方法将替换与该键相关的值。

参数

  • key (int|string):应分配值的键。如果提供字符串,则可以使用点符号设置嵌套值。
  • value (mixed):要分配给指定键的值。
  • charNestedKey (string,可选):嵌套键中使用的点符号字符。默认为 .

返回值

  • self:返回类的实例以进行方法链式调用。

示例用法

$articleText = "Some words as a sample sentence";
$textField = Block::make();
$textField->set("type", "doc");
$textField->set("content.0.content.0.text", $articleText);
$textField->set("content.0.content.0.type", "text");
$textField->set("content.0.type", "paragraph");

因此,当您尝试将嵌套键设置为 "content.0.content.0.text" 时,它将创建一个嵌套数组元素。

设置值后,您可以通过 get()(或 getBlock())方法访问它们

$textField->get("content.0.content.0.text");

提取键

通过 keys() 方法,您可以检索键的列表

$data = Block::make($fruitsArray);
$keys = $data->keys();
/*
Array
(
    [0] => avocado
    [1] => apple
    [2] => banana
    [3] => cherry
)
*/

您可以通过结合使用 getBlock()keys() 来检索嵌套元素的键

$data = Block::make($fruitsArray);
$keys = $data->getBlock("avocado")->keys();

/*
Array
(
    [0] => name
    [1] => fruit
    [2] => wikipedia
    [3] => color
    [4] => rating
)
*/

导出数据

使用 toArray() 导出到数组

toArray() 方法可以访问原生数组(关联和嵌套)。

这在使用 Block 类操作数据并在某个时刻需要将数据发送到您的函数或第三方包的函数时很有用,该函数期望接收原生数组作为参数。

$file = "./composer.json";
$composerContent = Block::fromJsonFile($file);
// here you can manage $composerContent with Block methods
// end then exports the Block data into a native array
$array = $composerContent->toArray();

使用 toJson() 导出到 JSON 字符串

如果您需要使用 Block 对象的内容生成有效的 JSON 字符串,则可以使用 toJson() 方法。

这在您使用 Block 类操作数据并在某个时刻需要以 JSON 字符串格式将数据发送到您的函数或第三方包的函数时很有用,该函数期望接收 JSON 字符串作为参数。

$data = Block::make($fruitsArray);
$jsonString = $data->toJson(); // JSON string with "pretty print"

使用 toYaml() 导出到 YAML 字符串

如果您需要使用 Block 对象的内容生成有效的 YAML 字符串,则可以使用 toYaml() 方法。

这在您使用 Block 类操作数据并在某个时刻需要以 YAML 字符串格式将数据发送到您的函数或第三方包的函数时很有用,该函数期望接收 YAML 字符串作为参数。

$data = Block::make($fruitsArray);
$yamlString = $data->toYaml(); // YAML string

加载数据

从 JSON 文件加载数据

$file = "./composer.json";
$composerContent = Block::fromJsonFile($file);
echo $composerContent->get("name"); // for example: "hi-folks/data-block"
echo $composerContent->get("authors.0.name"); // for example: "Roberto B."

从 JSON URL 加载数据

您可以从远程 JSON(如 API)构建您的 Block 数据。例如,您可以使用 fromJsonUrl() 方法从 GitHub API 通过最新提交构建 Block 对象。将 JSON API 检索到 Block 对象中非常有用,例如,应用 Block 类提供的方法,例如,过滤数据。在示例中,我将根据提交作者的名称过滤提交

$url = "https://api.github.com/repos/hi-folks/data-block/commits";
$commits = Block::fromJsonUrl($url);
$myCommits = $commits->where("commit.author.name", "like", "Roberto");
foreach ($myCommits as $value) {
    echo $value->get("commit.message") . PHP_EOL;
}

从 YAML 文件加载数据

$file = "./.github/workflows/run-tests.yml";
$workflow = Block::fromYamlFile($file);
echo $workflow->get("name"); // Name of the GitHub Action Workflow
echo $workflow->get("jobs.test.runs-on");
echo $workflow->get("on.0"); // push , the first event

添加和追加元素

将 Block 对象的元素追加到另一个 Block 对象

如果您有一个 Block 对象,您可以添加来自另一个 Block 对象的元素。一个用例是如果您有多个 JSON 文件,并希望从 API 获取分页内容。在这种情况下,您希望创建一个包含每个 JSON 文件中所有元素的 Block 对象。

$data1 = Block::fromJsonFile("./data/commits-10-p1.json");
$data2 = Block::fromJsonFile("./data/commits-10-p2.json");
$data1->count(); // 10
$data2->count(); // 10
$data1->append($data2);
$data1->count(); // 20
$data2->count(); // 10

将数组的元素追加到 Block 对象

如果你有一个数组,你可以将元素添加到Block对象中。在底层,Block对象是一个数组(可能是嵌套数组)。追加一个数组将在根级别添加元素

$data1 = Block::make(["a","b"]);
$arrayData2 = ["c","d"];
$data1->count(); // 2
$data1->append($arrayData2);
$data1->count(); // 4

追加一个元素

如果你需要追加一个元素作为单个元素(即使它是一个数组或Block对象),可以使用appendItem()函数

$data1 = Block::make(["a", "b"]);
$arrayData2 = ["c", "d"];
$data1->appendItem($arrayData2);
$data1->count(); // 3 because a, b, and the whole array c,d as single element
$data1->toArray();
/*
[
    'a',
    'b',
    [
        'c',
        'd',
    ],
]
*/

查询、排序数据

where()方法

你可以使用特定的值来过滤具有特定键的数据元素。你还可以设置运算符

$composerContent = Block::fromJsonString($jsonString);
$banners = $composerContent->getBlock("story.content.body")->where(
    "component",
    "==",
    "banner",
);

使用where()方法,过滤后的数据保留原始键。如果你想避免保留键并从0开始设置新的整数键,可以将第四个参数(preserveKeys)设置为false

    $composerContent = Block::fromJsonString($jsonString);
    $banners = $composerContent->getBlock("story.content.body")->where(
        "component",
        "!=",
        "banner",
+        false
    );

使用where()方法,你可以使用不同的运算符,如"==", ">", "<"等。如果你的嵌套数据包含数组,你还可以使用in运算符。例如,如果你有帖子,每个帖子可以有多个标签,你可以过滤具有特定标签的帖子

$url = "https://dummyjson.com/posts";
$posts = Block
    ::fromJsonUrl($url)
    ->getBlock("posts");

$lovePosts = $posts->where("tags", "in", "love");

orderBy()方法

你可以根据特定的键对数据进行排序或排序。例如,如果你想检索story.content.body键下的数据,并按component键排序

$composerContent = Block::fromJsonString($jsonString);
$bodyComponents = $composerContent->getBlock("story.content.body")->orderBy(
    "component", "asc"
);

你也可以对嵌套属性进行排序。考虑检索远程JSON,如示例JSON帖子,然后通过reactions.likes嵌套字段按降序对帖子进行排序

use HiFolks\DataType\Block;

$posts = Block
    ::fromJsonUrl("https://dummyjson.com/posts")
    ->getBlock("posts");
echo $posts->count(); // 30
$mostLikedPosts = $posts->orderBy("reactions.likes", "desc");
$mostLikedPosts->dump();

select()方法

select()方法允许你仅选择所需的字段。你可以将所需的字段名作为select()方法的参数列出。例如

use HiFolks\DataType\Block;
$dataTable = [
    ['product' => 'Desk', 'price' => 200, 'active' => true],
    ['product' => 'Chair', 'price' => 100, 'active' => true],
    ['product' => 'Door', 'price' => 300, 'active' => false],
    ['product' => 'Bookcase', 'price' => 150, 'active' => true],
    ['product' => 'Door', 'price' => 100, 'active' => true],
];
$table = Block::make($dataTable);
$data = $table
    ->select('product' , 'price');
print_r($data->toArray());

你可以组合select()where()orderBy()方法。如果你想检索具有productprice键的元素,价格大于100,并按price排序

$table = Block::make($dataTable);
$data = $table
    ->select('product' , 'price')
    ->where('price', ">", 100)
    ->orderBy("price");
print_r($data->toArray());
/*
Array
(
    [0] => Array
        (
            [product] => Bookcase
            [price] => 150
        )

    [1] => Array
        (
            [product] => Desk
            [price] => 200
        )

    [2] => Array
        (
            [product] => Door
            [price] => 300
        )

)
*/

groupBy()方法

按指定字段对Block对象中的元素进行分组。

此方法接受一个字段名作为参数,并根据该字段值对Block对象中的元素进行分组。每个元素都被分到一个关联数组中,键是指定字段的值,值是与该键共享的元素数组。

use HiFolks\DataType\Block;
$data = Block::make([
    ['type' => 'fruit', 'name' => 'apple'],
    ['type' => 'fruit', 'name' => 'banana'],
    ['type' => 'vegetable', 'name' => 'carrot'],
]);
$grouped = $data->groupBy('type');
$grouped->dumpJson();
/*
{
    "fruit": [
        {
            "type": "fruit",
            "name": "apple"
        },
        {
            "type": "fruit",
            "name": "banana"
        }
    ],
    "vegetable": [
        {
            "type": "vegetable",
            "name": "carrot"
        }
    ]
}
*/

exists()方法

你可以使用exists()方法来检查是否存在满足特定条件的元素。这是一个方便的方法,可以确定是否有任何记录与你的查询匹配,而无需显式计数。

以下是使用方法

$has = $composerContent
    ->getBlock("story.content.body")->where(
        "component",
        "banner",
    )->exists();

如果存在横幅组件,则返回true;如果不存在,则返回false。

循环数据

Block类实现了Iterator接口。当通过Block循环数组时,默认情况下,如果当前元素应该是数组,则会返回Block,这样你就可以访问Block方法来处理循环中的当前数组项。例如,在上面的代码中,如果你循环$data(它是Block对象),则循环的每个迭代中的每个元素都将是一个包含两个元素的数组,键为productprice。如果在循环中你需要通过Block类管理当前元素,你应该手动调用Block::make,例如

$table = Block::make($dataTable);
foreach ($table as $key => $item) {
    echo $item->get("price");
}

你可以应用过滤器,然后循环结果

$table = Block::make($dataTable);
$data = $table
    ->select('product', 'price')
    ->where('price', ">", 100, false);
foreach ($data as $key => $item) {
    echo $item->get("price"); // returns an integer
}

如果你想要循环通过$data并获取当前$item变量作为数组,你应该在静态make()方法的第二个参数中将它设置为false

$table = Block::make($dataTable, false);
$data = $table->select('product', 'price')->where('price', ">", 100, false);
foreach ($data as $key => $item) {
    print_r($item); // $item is an array
}

iterateBlock()方法

使用iterateBlock()方法,你可以在已将其实例化为Block对象的主Block对象内部嵌套列表之间切换从数组到Block。在上面的示例中,你有$table Block对象。你可以遍历$table对象的项。如果循环中的每个项本身是数组(即数组中的数组),你可以根据需要将其检索为数组或Block

$table = Block::make($dataTable);
foreach ($table as $key => $item) {
    expect($item)->toBeInstanceOf(Block::class);
    expect($key)->toBeInt();
    expect($item->get("price"))->toBeGreaterThan(10);
}

// iterateBlock(false if you need array instad of a nested Block)
foreach ($table->iterateBlock(false) as $key => $item) {
    expect($item)->toBeArray();
    expect($key)->toBeInt();
    expect($item["price"])->toBeGreaterThan(10);
}

使用forEach()方法

Block类实现了forEach()方法。

如果您需要遍历Block对象,可以使用forEach()方法。您可以将函数作为forEach()方法的参数,以管理每个单独的元素。

$url = "https://dummyjson.com/posts";
$posts = Block::fromJsonUrl($url) // Load the Block from the remote URL
    ->getBlock("posts") // get the `posts` as Block object
    ->where(
        field:"tags",
        operator: "in",
        value: "love",
        preseveKeys: false,
    ) // filter the posts, selecting only the posts with tags "love"
    ->forEach(fn($element): array => [
        "title" => strtoupper((string) $element->get("title")),
        "tags" => count($element->get("tags")),
    ]);
// The `$posts` object is an instance of the `Block` class.
// The `$posts` object contains the items that matches the `where` method.
// You can access to the elements via the nested keys
// $posts->get("0.title"); // "HOPES AND DREAMS WERE DASHED THAT DAY."
// $posts->get("0.tags"); // 3

数据验证

您可以使用JSON schema在Block对象中验证数据。JSON Schema是一种用于注释和验证JSON文档的词汇。

有关JSON Schema的更多信息: https://json-schema.fullstack.org.cn/learn/getting-started-step-by-step

如果您需要一些常见的/流行的schema,您可以在以下位置找到一些schema: https://www.schemastore.org/json/ 例如

或者,您可以根据JSON schema规范构建自己的schema: https://json-schema.fullstack.org.cn/learn/getting-started-step-by-step#create-a-schema-definition

$file = "./.github/workflows/run-tests.yml";
$workflow = Block::fromYamlFile($file);
$workflow->validateJsonViaUrl(
    'https://json.schemastore.org/github-workflow'
    ); // TRUE if the Block is a valid GitHub Actions Workflow

或者,您可以定义自己的schema

$schemaJson = <<<'JSON'
{
    "type": "array",
    "items" : {
        "type": "object",
        "properties": {
            "name": {
                "type": "string"
            },
            "fruit": {
                "type": "string"
            },
            "wikipedia": {
                "type": "string"
            },
            "color": {
                "type": "string"
            },
            "rating": {
                "type": "number"
            }
        }
    }
}
JSON;

然后使用Block对象进行验证

$fruitsArray = [
    [
        'name' => 'Avocado',
        'fruit' => '🥑',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Avocado',
        'color' => 'green',
        'rating' => 8,
    ],
    [
        'name' => 'Apple',
        'fruit' => '🍎',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Apple',
        'color' => 'red',
        'rating' => 7,
    ],
    [
        'name' => 'Banana',
        'fruit' => '🍌',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Banana',
        'color' => 'yellow',
        'rating' => 8.5,
    ],
    [
        'name' => 'Cherry',
        'fruit' => '🍒',
        'wikipedia' => 'https://en.wikipedia.org/wiki/Cherry',
        'color' => 'red',
        'rating' => 9,
    ],
];

$data = Block::make($fruitsArray);
$data->validateJsonWithSchema($schemaJson);
// true if the Block is valid.

如果您刚开始使用Data Block并进行测试以增强信心,实现不同的场景或测试非有效的JSON,请尝试将"rating"类型从number更改为integer(验证应该失败,因为在JSON中我们有一些小数评分)。是的,要实时更改schema,您可以使用Block对象 :)

// load the schema as Block object...
$schemaBlock = Block::fromJsonString($schemaJson);
// so that you can change the type
$schemaBlock->set(
    "items.properties.rating.type",
    "integer"
);
// the validation should be false because integer vs number
$data->validateJsonWithSchema(
    $schemaBlock->toJson()
);

应用函数

applyField()方法将可调用的函数应用于指定字段的值,并将结果设置到另一个字段。此方法支持方法链。

参数

  • key(字符串|整数):要处理的字段的键。
  • targetKey(字符串|整数):可调用函数结果应存储的键。
  • callable(可调用):应用于字段值的函数。此函数应接受单个参数(字段的值)并返回处理后的值。

返回值

  • self:返回类的实例以进行方法链式调用。

示例用法

<?php

// Assuming $object is an instance of the class that contains the applyField method
$object
    ->set('name', 'John Doe')
    ->applyField('name', 'uppercase_name', function($value) {
        return strtoupper($value);
    });

echo $object->get('uppercase_name'); // Outputs: JOHN DOE

## Testing

```bash
composer test

变更日志

请参阅CHANGELOG以获取有关最近更改的更多信息。

贡献

请参阅CONTRIBUTING以获取详细信息。

安全漏洞

请审查我们关于报告安全漏洞的安全策略我们的安全策略

致谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件