boesing/typed-arrays

哈希表和集合


README

Continuous Integration type-coverage psalm

一个完全类型的库,用于处理列表或映射。

安装

要在项目中使用此库,请通过composer安装

$ composer require boesing/typed-arrays

用法

创建此库的主要原因在于PHP中的每个数组都是一个哈希表。如果您主要使用API,您可能已经体验过数组类型的json_encode有时会导致令人烦恼的问题。

为了避免将数组传递通过应用程序,OrderedListInterfaceMapInterface变得非常有用。为了向开发人员提供大多数array_*函数,大多数这些数组函数在OrderedListInterfaceMapInterface中都有方法。

常见错误

让我们通过一些真实世界的用例来更好地反映这个库背后的思想

$listOfIntegers = [1, 2, 3, 4];

$myObject = new stdClass();
$myObject->integers = $listOfIntegers;

echo json_encode($myObject) . PHP_EOL;

// Output of the code above will be: `{"integers":[1,2,3,4]}`
// Now some refactoring has to be made since the requirement changed. The requirement now is that the integers list
// must not contain odd values anymore. So `array_filter` to the rescue, right?

$listOfEvenIntegers = array_filter([1, 2, 3, 4], static fn (int $integer): int => $integer % 2 === 0);

$myObject = new stdClass();
$myObject->integers = $listOfEvenIntegers;

echo json_encode($myObject) . PHP_EOL; 

// Output of the refactored code above now became: `{"integers":{"1":2,"3":4}}`
// So what now happened is a huge problem for highly type-sensitive API clients since we changed a list to a hashmap
// Same happens with hashmaps which suddenly become empty.

$hashmap = [
    'foo' => 'bar',
];
$myObject = new stdClass();
$myObject->map = $hashmap;

echo json_encode($myObject) . PHP_EOL; 

// Output of the code above will be: `{"map":{"foo":"bar"}}`
// So now some properties are being added, some are being removed, the definition of your API says
// "the object will contain additional properties because heck I do not want to declare every property"
// "so to make it easier, every property has a string value"
// can be easily done with something like this in JSONSchema: `{"type": "object", "additional_properties": {"type": "string"}}`
// Now, some string value might become `null` due to whatever reason, lets say it was a bug and thus the happy path always returned a string
// The most logical way here is, due to our lazyness, to use something like `array_filter` to get rid of all our non-string values

$hashmap = [
    'foo' => null,
];
$myObject = new stdClass();
$myObject->map = array_filter($hashmap);

echo json_encode($myObject) . PHP_EOL;

// Output of the refactored code above now became: `{"map":[]}`
// So in case that every array value is being wiped due to the filtering, we suddenly have a type-change from
// a hashmap to a list. This is ofc also problematic since we do not want to have a list here but an empty object like
// so: `{"map":{}}`

(上述示例可以在3v4l.org - 一个PHP沙盒上验证:https://3v4l.org/Gfogn#v8.1.6)

typed-arrays救星

所以,有了这个库,处理数组时可以稍微安全一些。然而,如果MapInterface为空,它在json_encode中实际上会变成null

所以让我们结合我们的工厂来使用上述示例

use Boesing\TypedArrays\TypedArrayFactory;
$factory = new TypedArrayFactory();

$listOfIntegers = $factory->createOrderedList([1, 2, 3, 4]);

$myObject = new stdClass();
$myObject->integers = $listOfIntegers;

echo json_encode($myObject) . PHP_EOL;

// Output of the code above will be: `{"integers":[1,2,3,4]}`
// Now some refactoring has to be made since the requirement changed. The requirement now is that the integers list
// must not contain odd values anymore. So `array_filter` to the rescue, right?

$listOfEvenIntegers = $factory->createOrderedList([1, 2, 3, 4])->filter(static fn (int $integer): int => $integer % 2 === 0);

$myObject = new stdClass();
$myObject->integers = $listOfEvenIntegers;

echo json_encode($myObject) . PHP_EOL; 

// Output of the refactored code above now became: `{"integers":[2, 4]}`
// Due to the internal handling of `array_filter`, the `OrderedListInterface` won't change its type.

// Even hashmaps can be filtered, the type stays the same but in case of an empty map, `null` is being passed to the JSON object
$hashmap = $factory->createMap([
    'foo' => 'bar',
]);
$myObject = new stdClass();
$myObject->map = $hashmap;

echo json_encode($myObject) . PHP_EOL; 

// Output of the code above will be: `{"map":{"foo":"bar"}}`
// So now some properties are being added, some are being removed, the definition of your API says
// "the object will contain additional properties because heck I do not want to declare every property"
// "so to make it easier, every property has a string value"
// can be easily done with something like this in JSONSchema: `{"type": "object", "additional_properties": {"type": "string"}}`
// Now, some string value might become `null` due to whatever reason, lets say it was a bug and thus the happy path always returned a string
// The most logical way here is, due to our lazyness, to use something like `array_filter` to get rid of all our non-string values

$hashmap = $factory->createMap([
    'foo' => null,
]);
$myObject = new stdClass();
$myObject->map = $hashmap->filter(static fn ($value) => $value !== null);

echo json_encode($myObject) . PHP_EOL;

// Output of the refactored code above now became: `{"map":null}`
// So in case that every array value is being wiped due to the filtering, we suddenly have a type-change from
// a hashmap to a list. This is ofc also problematic since we do not want to have a list here but an empty object like
// so: `{"map":{}}`

结论

当涉及到API响应时,您可能不想依赖于PHP数组结构。始终优先选择具有真实属性和真实属性类型提示的真实对象,而不是non-empty-array