Added JsonProperty and BencodeProperty attributes for easier object encoding.

This commit is contained in:
flash 2024-09-29 23:53:44 +00:00
parent 1339de93bf
commit caf4eef02d
8 changed files with 776 additions and 60 deletions

View file

@ -1 +1 @@
0.2408.401738 0.2408.610150

118
composer.lock generated
View file

@ -69,16 +69,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.1.0", "version": "v5.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a",
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -121,9 +121,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0"
}, },
"time": "2024-07-01T20:03:41+00:00" "time": "2024-09-29T13:56:26+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -245,16 +245,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.11.10", "version": "1.12.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "640410b32995914bde3eed26fa89552f9c2c082f" "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17",
"reference": "640410b32995914bde3eed26fa89552f9c2c082f", "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -299,36 +299,36 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-08-08T09:02:50+00:00" "time": "2024-09-26T12:45:22+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "11.0.5", "version": "11.0.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
"reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^5.0", "nikic/php-parser": "^5.1.0",
"php": ">=8.2", "php": ">=8.2",
"phpunit/php-file-iterator": "^5.0", "phpunit/php-file-iterator": "^5.0.1",
"phpunit/php-text-template": "^4.0", "phpunit/php-text-template": "^4.0.1",
"sebastian/code-unit-reverse-lookup": "^4.0", "sebastian/code-unit-reverse-lookup": "^4.0.1",
"sebastian/complexity": "^4.0", "sebastian/complexity": "^4.0.1",
"sebastian/environment": "^7.0", "sebastian/environment": "^7.2.0",
"sebastian/lines-of-code": "^3.0", "sebastian/lines-of-code": "^3.0.1",
"sebastian/version": "^5.0", "sebastian/version": "^5.0.1",
"theseer/tokenizer": "^1.2.0" "theseer/tokenizer": "^1.2.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^11.0" "phpunit/phpunit": "^11.0"
@ -340,7 +340,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "11.0-dev" "dev-main": "11.0.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -369,7 +369,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6"
}, },
"funding": [ "funding": [
{ {
@ -377,20 +377,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-07-03T05:05:37+00:00" "time": "2024-08-22T04:37:56+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
"version": "5.0.1", "version": "5.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26" "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26", "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
"reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26", "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -430,7 +430,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1" "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0"
}, },
"funding": [ "funding": [
{ {
@ -438,7 +438,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-07-03T05:06:37+00:00" "time": "2024-08-27T05:02:59+00:00"
}, },
{ {
"name": "phpunit/php-invoker", "name": "phpunit/php-invoker",
@ -626,16 +626,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "11.3.1", "version": "11.3.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fe179875ef0c14e90b75617002767eae0a742641" "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fe179875ef0c14e90b75617002767eae0a742641", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d62c45a19c665bb872c2a47023a0baf41a98bb2b",
"reference": "fe179875ef0c14e90b75617002767eae0a742641", "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -649,20 +649,20 @@
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.2.1",
"php": ">=8.2", "php": ">=8.2",
"phpunit/php-code-coverage": "^11.0.5", "phpunit/php-code-coverage": "^11.0.6",
"phpunit/php-file-iterator": "^5.0.1", "phpunit/php-file-iterator": "^5.1.0",
"phpunit/php-invoker": "^5.0.1", "phpunit/php-invoker": "^5.0.1",
"phpunit/php-text-template": "^4.0.1", "phpunit/php-text-template": "^4.0.1",
"phpunit/php-timer": "^7.0.1", "phpunit/php-timer": "^7.0.1",
"sebastian/cli-parser": "^3.0.2", "sebastian/cli-parser": "^3.0.2",
"sebastian/code-unit": "^3.0.1", "sebastian/code-unit": "^3.0.1",
"sebastian/comparator": "^6.0.2", "sebastian/comparator": "^6.1.0",
"sebastian/diff": "^6.0.2", "sebastian/diff": "^6.0.2",
"sebastian/environment": "^7.2.0", "sebastian/environment": "^7.2.0",
"sebastian/exporter": "^6.1.3", "sebastian/exporter": "^6.1.3",
"sebastian/global-state": "^7.0.2", "sebastian/global-state": "^7.0.2",
"sebastian/object-enumerator": "^6.0.1", "sebastian/object-enumerator": "^6.0.1",
"sebastian/type": "^5.0.1", "sebastian/type": "^5.1.0",
"sebastian/version": "^5.0.1" "sebastian/version": "^5.0.1"
}, },
"suggest": { "suggest": {
@ -706,7 +706,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.1" "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.6"
}, },
"funding": [ "funding": [
{ {
@ -722,7 +722,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-08-13T06:14:23+00:00" "time": "2024-09-19T10:54:28+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -896,16 +896,16 @@
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
"version": "6.0.2", "version": "6.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git", "url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81" "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/450d8f237bd611c45b5acf0733ce43e6bb280f81", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
"reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81", "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -916,12 +916,12 @@
"sebastian/exporter": "^6.0" "sebastian/exporter": "^6.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^11.0" "phpunit/phpunit": "^11.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "6.0-dev" "dev-main": "6.1-dev"
} }
}, },
"autoload": { "autoload": {
@ -961,7 +961,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues", "issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy", "security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/6.0.2" "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0"
}, },
"funding": [ "funding": [
{ {
@ -969,7 +969,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-08-12T06:07:25+00:00" "time": "2024-09-11T15:42:56+00:00"
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
@ -1538,28 +1538,28 @@
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
"version": "5.0.1", "version": "5.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/type.git", "url": "https://github.com/sebastianbergmann/type.git",
"reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.2" "php": ">=8.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^11.0" "phpunit/phpunit": "^11.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "5.0-dev" "dev-main": "5.1-dev"
} }
}, },
"autoload": { "autoload": {
@ -1583,7 +1583,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/type/issues", "issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy", "security": "https://github.com/sebastianbergmann/type/security/policy",
"source": "https://github.com/sebastianbergmann/type/tree/5.0.1" "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
}, },
"funding": [ "funding": [
{ {
@ -1591,7 +1591,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-07-03T05:11:49+00:00" "time": "2024-09-17T13:12:04+00:00"
}, },
{ {
"name": "sebastian/version", "name": "sebastian/version",

View file

@ -0,0 +1,89 @@
<?php
// BencodeProperty.php
// Created: 2024-09-29
// Updated: 2024-09-29
namespace Index\Bencode;
use Attribute;
/**
* Defines a class method or property as a Bencoded object property.
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
final class BencodeProperty {
/** @var mixed[] */
private array $omitIfValue;
/**
* @param string $name Name of the property in the Bencoded object.
* @param int $order Ordering of the property in the Bencoded object.
* @param bool $omitIfNull Whether the value should be omitted if its null.
* @param mixed|mixed[] $omitIfValue Which values should be omitted.
* @param bool $numbersAsString Whether numbers should be cast to strings.
*/
public function __construct(
private ?string $name = null,
private int $order = 0,
private bool $omitIfNull = true,
mixed $omitIfValue = [],
private bool $numbersAsString = false
) {
$this->omitIfValue = is_array($omitIfValue) ? $omitIfValue : [$omitIfValue];
}
/**
* Gets name for the property value in the Bencoded object.
*
* @return ?string
*/
public function getName(): ?string {
return $this->name;
}
/**
* Gets the order value for this property.
*
* @return int
*/
public function getOrder(): int {
return $this->order;
}
/**
* Gets whether this should be omitted if the value is null.
*
* @return bool
*/
public function shouldOmitIfNull(): bool {
return $this->omitIfNull;
}
/**
* Gets which values should be omitted.
*
* @return mixed[]
*/
public function getOmittedValues(): array {
return $this->omitIfValue;
}
/**
* Checks whether a given value should be omitted from the Bencoded object.
*
* @param mixed $value Value to check.
* @return bool
*/
public function shouldBeOmitted(mixed $value): bool {
return ($this->omitIfNull && $value === null)
|| in_array($value, $this->omitIfValue);
}
/**
* Gets whether numbers should be converted to string.
*
* @return bool
*/
public function shouldConvertNumbersToString(): bool {
return $this->numbersAsString;
}
}

View file

@ -0,0 +1,93 @@
<?php
// BencodeSerialisableTrait.php
// Created: 2024-09-29
// Updated: 2024-09-29
namespace Index\Bencode;
use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty;
use RuntimeException;
/**
* Implements support for the BencodeProperty attribute.
*/
trait BencodeSerialisableTrait {
/**
* Constructs Bencode object based on BencodeProperty attributes.
*
* @return array<string, mixed>
*/
public function bencodeSerialise(): mixed {
$objectInfo = new ReflectionObject($this);
$props = [];
$propInfos = $objectInfo->getProperties(ReflectionProperty::IS_PUBLIC);
foreach($propInfos as $propInfo) {
$attrInfos = $propInfo->getAttributes(BencodeProperty::class);
if(count($attrInfos) < 1)
continue;
if(count($attrInfos) > 1)
throw new RuntimeException('Properties may only carry a single instance of the BencodeProperty attribute.');
$info = $attrInfos[0]->newInstance();
$name = $info->getName() ?? $propInfo->getName();
if(array_key_exists($name, $props))
throw new RuntimeException('A property with that name has already been defined.');
$value = $propInfo->getValue($propInfo->isStatic() ? null : $this);
if($info->shouldBeOmitted($value))
continue;
if(is_float($value) || (is_int($value) && $info->shouldConvertNumbersToString()))
$value = (string)$value;
elseif(is_bool($value))
$value = $value ? 1 : 0;
$props[$name] = [
'name' => $name,
'value' => $value,
'order' => $info->getOrder(),
];
}
$methodInfos = $objectInfo->getMethods(ReflectionMethod::IS_PUBLIC);
foreach($methodInfos as $methodInfo) {
if($methodInfo->getNumberOfRequiredParameters() > 0)
throw new RuntimeException('Methods marked with the BencodeProperty attribute must not have any required arguments.');
$attrInfos = $methodInfo->getAttributes(BencodeProperty::class);
if(count($attrInfos) < 1)
continue;
if(count($attrInfos) > 1)
throw new RuntimeException('Methods may only carry a single instance of the BencodeProperty attribute.');
$info = $attrInfos[0]->newInstance();
$name = $info->getName() ?? $methodInfo->getName();
if(array_key_exists($name, $props))
throw new RuntimeException('A property with that name has already been defined.');
$value = $methodInfo->invoke($methodInfo->isStatic() ? null : $this);
if($info->shouldBeOmitted($value))
continue;
if(is_float($value) || (is_int($value) && $info->shouldConvertNumbersToString()))
$value = (string)$value;
elseif(is_bool($value))
$value = $value ? 1 : 0;
$props[$name] = [
'name' => $name,
'value' => $value,
'order' => $info->getOrder(),
];
}
usort($props, fn($a, $b) => $a['order'] <=> $b['order']);
return array_column($props, 'value', 'name');
}
}

89
src/Json/JsonProperty.php Normal file
View file

@ -0,0 +1,89 @@
<?php
// JsonProperty.php
// Created: 2024-09-29
// Updated: 2024-09-29
namespace Index\Json;
use Attribute;
/**
* Defines a class method or property as a JSON object property.
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
final class JsonProperty {
/** @var mixed[] */
private array $omitIfValue;
/**
* @param string $name Name of the property in the JSON object.
* @param int $order Ordering of the property in the JSON object.
* @param bool $omitIfNull Whether the value should be omitted if its null.
* @param mixed|mixed[] $omitIfValue Which values should be omitted.
* @param bool $numbersAsString Whether numbers should be cast to strings.
*/
public function __construct(
private ?string $name = null,
private int $order = 0,
private bool $omitIfNull = true,
mixed $omitIfValue = [],
private bool $numbersAsString = false
) {
$this->omitIfValue = is_array($omitIfValue) ? $omitIfValue : [$omitIfValue];
}
/**
* Gets name for the property value in the JSON object.
*
* @return ?string
*/
public function getName(): ?string {
return $this->name;
}
/**
* Gets the order value for this property.
*
* @return int
*/
public function getOrder(): int {
return $this->order;
}
/**
* Gets whether this should be omitted if the value is null.
*
* @return bool
*/
public function shouldOmitIfNull(): bool {
return $this->omitIfNull;
}
/**
* Gets which values should be omitted.
*
* @return mixed[]
*/
public function getOmittedValues(): array {
return $this->omitIfValue;
}
/**
* Checks whether a given value should be omitted from the JSON object.
*
* @param mixed $value Value to check.
* @return bool
*/
public function shouldBeOmitted(mixed $value): bool {
return ($this->omitIfNull && $value === null)
|| in_array($value, $this->omitIfValue);
}
/**
* Gets whether numbers should be converted to string.
*
* @return bool
*/
public function shouldConvertNumbersToString(): bool {
return $this->numbersAsString;
}
}

View file

@ -0,0 +1,89 @@
<?php
// JsonSerializableTrait.php
// Created: 2024-09-29
// Updated: 2024-09-29
namespace Index\Json;
use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty;
use RuntimeException;
/**
* Implements support for the JsonProperty attribute.
*/
trait JsonSerializableTrait {
/**
* Constructs JSON object based on JsonProperty attributes.
*
* @return array<string, mixed>
*/
public function jsonSerialize(): mixed {
$objectInfo = new ReflectionObject($this);
$props = [];
$propInfos = $objectInfo->getProperties(ReflectionProperty::IS_PUBLIC);
foreach($propInfos as $propInfo) {
$attrInfos = $propInfo->getAttributes(JsonProperty::class);
if(count($attrInfos) < 1)
continue;
if(count($attrInfos) > 1)
throw new RuntimeException('Properties may only carry a single instance of the JsonProperty attribute.');
$info = $attrInfos[0]->newInstance();
$name = $info->getName() ?? $propInfo->getName();
if(array_key_exists($name, $props))
throw new RuntimeException('A property with that name has already been defined.');
$value = $propInfo->getValue($propInfo->isStatic() ? null : $this);
if($info->shouldBeOmitted($value))
continue;
if((is_int($value) || is_float($value)) && $info->shouldConvertNumbersToString())
$value = (string)$value;
$props[$name] = [
'name' => $name,
'value' => $value,
'order' => $info->getOrder(),
];
}
$methodInfos = $objectInfo->getMethods(ReflectionMethod::IS_PUBLIC);
foreach($methodInfos as $methodInfo) {
if($methodInfo->getNumberOfRequiredParameters() > 0)
throw new RuntimeException('Methods marked with the JsonProperty attribute must not have any required arguments.');
$attrInfos = $methodInfo->getAttributes(JsonProperty::class);
if(count($attrInfos) < 1)
continue;
if(count($attrInfos) > 1)
throw new RuntimeException('Methods may only carry a single instance of the JsonProperty attribute.');
$info = $attrInfos[0]->newInstance();
$name = $info->getName() ?? $methodInfo->getName();
if(array_key_exists($name, $props))
throw new RuntimeException('A property with that name has already been defined.');
$value = $methodInfo->invoke($methodInfo->isStatic() ? null : $this);
if($info->shouldBeOmitted($value))
continue;
if((is_int($value) || is_float($value)) && $info->shouldConvertNumbersToString())
$value = (string)$value;
$props[$name] = [
'name' => $name,
'value' => $value,
'order' => $info->getOrder(),
];
}
usort($props, fn($a, $b) => $a['order'] <=> $b['order']);
return array_column($props, 'value', 'name');
}
}

View file

@ -0,0 +1,179 @@
<?php
// BencodeSerialisableTest.php
// Created: 2024-09-29
// Updated: 2024-09-29
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
use Index\Bencode\{Bencode,BencodeProperty,BencodeSerialisableTrait,IBencodeSerialisable};
#[CoversClass(BencodeProperty::class)]
#[CoversClass(BencodeSerialisableTrait::class)]
#[UsesClass(Bencode::class)]
#[UsesClass(IBencodeSerialisable::class)]
final class BencodeSerialisableTest extends TestCase {
private const BASIC_BENCODED_ENCODED = 'd9:stringVal12:string value6:intVali1234e9:intStrVal4:56788:floatVal5:12.3411:floatStrVal5:56.787:trueVali1e22:truePossiblyOmittedVali1e8:falseVali0e14:nullValPresent10:scalarValsli0ei1234e5:12.343:stre16:stringVal_method12:string value9:getIntVali1234e11:getFloatVal5:12.3417:getFloatStringVal5:56.7810:getTrueVali1e25:getTruePossiblyOmittedVali1e11:getFalseVali0e17:getNullValPresent13:getScalarValsli0ei1234e5:12.343:stre5:innerd3:objd3:str21:wow this is illegibleee15:getIntStringVal4:5678e';
public function testBasicBencodedObject(): void {
$test = new class implements IBencodeSerialisable {
use BencodeSerialisableTrait;
#[BencodeProperty]
public string $stringVal = 'string value';
#[BencodeProperty]
public int $intVal = 1234;
#[BencodeProperty(numbersAsString: true)]
public int $intStrVal = 5678;
#[BencodeProperty]
public float $floatVal = 12.34;
#[BencodeProperty(numbersAsString: true)]
public float $floatStrVal = 56.78;
#[BencodeProperty]
public bool $trueVal = true;
#[BencodeProperty(omitIfValue: false)]
public bool $truePossiblyOmittedVal = true;
#[BencodeProperty]
public bool $falseVal = false;
#[BencodeProperty(omitIfValue: false)]
public bool $falseOmittedVal = false;
#[BencodeProperty]
public mixed $nullValOmitted = null;
#[BencodeProperty(omitIfNull: false)]
public mixed $nullValPresent = null;
#[BencodeProperty]
public array $scalarVals = [null, 0, 1234, 12.34, 'str', true, false];
#[BencodeProperty('stringVal_method')]
public function getStringVal(): string {
return 'string value';
}
#[BencodeProperty]
public function getIntVal(): int {
return 1234;
}
#[BencodeProperty(order: 1, numbersAsString: true)]
public function getIntStringVal(): int {
return 5678;
}
#[BencodeProperty]
public function getFloatVal(): float {
return 12.34;
}
#[BencodeProperty(numbersAsString: true)]
public function getFloatStringVal(): float {
return 56.78;
}
#[BencodeProperty]
public function getTrueVal(): bool {
return true;
}
#[BencodeProperty(omitIfValue: false)]
public function getTruePossiblyOmittedVal(): bool {
return true;
}
#[BencodeProperty]
public function getFalseVal(): bool {
return false;
}
#[BencodeProperty(omitIfValue: false)]
public function getFalseOmittedVal(): bool {
return false;
}
#[BencodeProperty]
public function getNullValOmitted(): mixed {
return null;
}
#[BencodeProperty(omitIfNull: false)]
public function getNullValPresent(): mixed {
return null;
}
#[BencodeProperty]
public function getScalarVals(): array {
return [null, 0, 1234, 12.34, 'str', true, false];
}
#[BencodeProperty('inner')]
public function getObject(): object {
return new class implements IBencodeSerialisable {
use BencodeSerialisableTrait;
#[BencodeProperty('obj')]
public function getObject(): object {
return new class implements IBencodeSerialisable {
use BencodeSerialisableTrait;
#[BencodeProperty('str')]
public function getString(): string {
return 'wow this is illegible';
}
};
}
};
}
};
$this->assertEquals(self::BASIC_BENCODED_ENCODED, Bencode::encode($test));
}
public function testDoubleBencodedProperty(): void {
$this->expectException(RuntimeException::class);
$test = new class implements IBencodeSerialisable {
use BencodeSerialisableTrait;
#[BencodeProperty('test1')]
#[BencodeProperty('test2')]
public string $stringVal = 'string value';
#[BencodeProperty('test3')]
#[BencodeProperty('test4')]
public function getIntVal(): int {
return 1234;
}
};
Bencode::encode($test);
}
public function testDuplicateBencodedProperty(): void {
$this->expectException(RuntimeException::class);
$test = new class implements IBencodeSerialisable {
use BencodeSerialisableTrait;
#[BencodeProperty]
public string $test1 = 'string value';
#[BencodeProperty('test1')]
public function getIntVal(): int {
return 1234;
}
};
Bencode::encode($test);
}
}

View file

@ -0,0 +1,177 @@
<?php
// JsonSerializableTest.php
// Created: 2024-09-29
// Updated: 2024-09-29
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Json\{JsonProperty,JsonSerializableTrait};
#[CoversClass(JsonProperty::class)]
#[CoversClass(JsonSerializableTrait::class)]
final class JsonSerializableTest extends TestCase {
private const BASIC_JSON_ENCODED = '{"stringVal":"string value","intVal":1234,"intStrVal":"5678","floatVal":12.34,"floatStrVal":"56.78","trueVal":true,"truePossiblyOmittedVal":true,"falseVal":false,"nullValPresent":null,"scalarVals":[null,0,1234,12.34,"str",true,false],"stringVal_method":"string value","getIntVal":1234,"getFloatVal":12.34,"getFloatStringVal":"56.78","getTrueVal":true,"getTruePossiblyOmittedVal":true,"getFalseVal":false,"getNullValPresent":null,"getScalarVals":[null,0,1234,12.34,"str",true,false],"inner":{"obj":{"str":"wow this is illegible"}},"getIntStringVal":"5678"}';
public function testBasicJsonObject(): void {
$test = new class implements JsonSerializable {
use JsonSerializableTrait;
#[JsonProperty]
public string $stringVal = 'string value';
#[JsonProperty]
public int $intVal = 1234;
#[JsonProperty(numbersAsString: true)]
public int $intStrVal = 5678;
#[JsonProperty]
public float $floatVal = 12.34;
#[JsonProperty(numbersAsString: true)]
public float $floatStrVal = 56.78;
#[JsonProperty]
public bool $trueVal = true;
#[JsonProperty(omitIfValue: false)]
public bool $truePossiblyOmittedVal = true;
#[JsonProperty]
public bool $falseVal = false;
#[JsonProperty(omitIfValue: false)]
public bool $falseOmittedVal = false;
#[JsonProperty]
public mixed $nullValOmitted = null;
#[JsonProperty(omitIfNull: false)]
public mixed $nullValPresent = null;
#[JsonProperty]
public array $scalarVals = [null, 0, 1234, 12.34, 'str', true, false];
#[JsonProperty('stringVal_method')]
public function getStringVal(): string {
return 'string value';
}
#[JsonProperty]
public function getIntVal(): int {
return 1234;
}
#[JsonProperty(order: 1, numbersAsString: true)]
public function getIntStringVal(): int {
return 5678;
}
#[JsonProperty]
public function getFloatVal(): float {
return 12.34;
}
#[JsonProperty(numbersAsString: true)]
public function getFloatStringVal(): float {
return 56.78;
}
#[JsonProperty]
public function getTrueVal(): bool {
return true;
}
#[JsonProperty(omitIfValue: false)]
public function getTruePossiblyOmittedVal(): bool {
return true;
}
#[JsonProperty]
public function getFalseVal(): bool {
return false;
}
#[JsonProperty(omitIfValue: false)]
public function getFalseOmittedVal(): bool {
return false;
}
#[JsonProperty]
public function getNullValOmitted(): mixed {
return null;
}
#[JsonProperty(omitIfNull: false)]
public function getNullValPresent(): mixed {
return null;
}
#[JsonProperty]
public function getScalarVals(): array {
return [null, 0, 1234, 12.34, 'str', true, false];
}
#[JsonProperty('inner')]
public function getObject(): object {
return new class implements JsonSerializable {
use JsonSerializableTrait;
#[JsonProperty('obj')]
public function getObject(): object {
return new class implements JsonSerializable {
use JsonSerializableTrait;
#[JsonProperty('str')]
public function getString(): string {
return 'wow this is illegible';
}
};
}
};
}
};
$this->assertEquals(self::BASIC_JSON_ENCODED, json_encode($test));
}
public function testDoubleJsonProperty(): void {
$this->expectException(RuntimeException::class);
$test = new class implements JsonSerializable {
use JsonSerializableTrait;
#[JsonProperty('test1')]
#[JsonProperty('test2')]
public string $stringVal = 'string value';
#[JsonProperty('test3')]
#[JsonProperty('test4')]
public function getIntVal(): int {
return 1234;
}
};
json_encode($test);
}
public function testDuplicateJsonProperty(): void {
$this->expectException(RuntimeException::class);
$test = new class implements JsonSerializable {
use JsonSerializableTrait;
#[JsonProperty]
public string $test1 = 'string value';
#[JsonProperty('test1')]
public function getIntVal(): int {
return 1234;
}
};
json_encode($test);
}
}