diff options
author | C. Scott Ananian <cscott@cscott.net> | 2023-11-17 13:31:19 -0500 |
---|---|---|
committer | James D. Forrester <jforrester@wikimedia.org> | 2024-10-15 20:09:51 -0400 |
commit | 3bc172d0e4e8048a415b6992af3b6db84929cc02 (patch) | |
tree | b3785adc7fd7e9acf0817480c2d4e250702dcae3 /tests/phpunit/mocks/json | |
parent | 1b65e8ba83db8158a5a946b9dd006718520d4f68 (diff) | |
download | mediawikicore-3bc172d0e4e8048a415b6992af3b6db84929cc02.tar.gz mediawikicore-3bc172d0e4e8048a415b6992af3b6db84929cc02.zip |
[JsonCodec] Use wikimedia/json-codec to implement JsonCodec
This adds support for serializing/deserializing objects which
implement the JsonCodecable interface from the wikimedia/json-codec
library used by Parsoid. JsonCodecable allows customizing the encoding
of objects of a given class using a class-specific codec object, and
JsonCodecable is an interface which is defined and can be used outside
mediawiki core.
In addition json-codec supports deserialization in the presence of
aliased class names, fixing T353883.
Backward and forward compatibility established via the mechanism
described in
https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility
Test data generated by this patch was added in
I109640b510cef9b3b870a8c188f3b4f086d75d06 to ensure forward
compatibility with the output after this patch is merged.
Benchmarks:
PHP 7.4.33 PHP 8.2.19 PHP 8.3.6
BEFORE AFTER BEFORE AFTER BEFORE AFTER
Serialize: 926.7/s 1424.8/s 978.5/s 1542.4/s 1023.5/s 1488.6/s
Serialize (assoc): 930.2/s 1378.6/s 974.6/s 1541.9/s 1022.4/s 1463.4/s
Deserialize: 1942.7/s 1961.3/s 2118.8/s 2175.9/s 2129.8/s 2063.5/s
Deserialize (assoc): 1952.0/s 1905.7/s 2107.5/s 2192.1/s 2153.3/s 2011.1/s
These numbers definitely do not have as many significant digits as
written here. But they should be sufficient to demonstrate that
performance is not impaired by this patch and in fact serialization
speed improves slightly.
Bug: T273540
Bug: T327439
Bug: T346829
Bug: T353883
Depends-On: If1d70ba18712839615c1f4fea236843ffebc8645
Change-Id: Ia1017dcef462f3ac1ff5112106f7df81f5cc384f
Diffstat (limited to 'tests/phpunit/mocks/json')
-rw-r--r-- | tests/phpunit/mocks/json/JsonDeserializableSubClass.php | 4 | ||||
-rw-r--r-- | tests/phpunit/mocks/json/ManagedObject.php | 41 | ||||
-rw-r--r-- | tests/phpunit/mocks/json/ManagedObjectFactory.php | 61 | ||||
-rw-r--r-- | tests/phpunit/mocks/json/SampleContainerObject.php | 70 | ||||
-rw-r--r-- | tests/phpunit/mocks/json/SampleObject.php | 54 |
5 files changed, 230 insertions, 0 deletions
diff --git a/tests/phpunit/mocks/json/JsonDeserializableSubClass.php b/tests/phpunit/mocks/json/JsonDeserializableSubClass.php index 5026aa860342..24ed6167846a 100644 --- a/tests/phpunit/mocks/json/JsonDeserializableSubClass.php +++ b/tests/phpunit/mocks/json/JsonDeserializableSubClass.php @@ -33,3 +33,7 @@ class JsonDeserializableSubClass extends JsonDeserializableSuperClass { ]; } } + +// This class_alias exists for testing alias support in JsonCodec and +// should not be removed. +class_alias( JsonDeserializableSubClass::class, 'MediaWiki\\Tests\\Json\\JsonDeserializableSubClassAlias' ); diff --git a/tests/phpunit/mocks/json/ManagedObject.php b/tests/phpunit/mocks/json/ManagedObject.php new file mode 100644 index 000000000000..45f2638bf427 --- /dev/null +++ b/tests/phpunit/mocks/json/ManagedObject.php @@ -0,0 +1,41 @@ +<?php +namespace MediaWiki\Tests\Json; + +use Psr\Container\ContainerInterface; +use Wikimedia\JsonCodec\JsonClassCodec; +use Wikimedia\JsonCodec\JsonCodecable; +use Wikimedia\JsonCodec\JsonCodecInterface; + +/** + * Managed object which uses a factory in a service. + */ +class ManagedObject implements JsonCodecable { + + /** @var string */ + public string $name; + /** @var int */ + public int $data; + + /** + * Create a new ManagedObject which stores $property. This constructor + * shouldn't be invoked directly by anyone except ManagedObjectFactory. + * + * @param string $name + * @param int $data + * @internal + */ + public function __construct( string $name, int $data ) { + $this->name = $name; + $this->data = $data; + } + + // Implement JsonCodecable by delegating serialization/deserialization + // to the 'ManagedObjectFactory' service. + + /** @inheritDoc */ + public static function jsonClassCodec( + JsonCodecInterface $codec, ContainerInterface $serviceContainer + ): JsonClassCodec { + return $serviceContainer->get( 'ManagedObjectFactory' ); + } +} diff --git a/tests/phpunit/mocks/json/ManagedObjectFactory.php b/tests/phpunit/mocks/json/ManagedObjectFactory.php new file mode 100644 index 000000000000..91d0fdeb8e69 --- /dev/null +++ b/tests/phpunit/mocks/json/ManagedObjectFactory.php @@ -0,0 +1,61 @@ +<?php +namespace MediaWiki\Tests\Json; + +use Wikimedia\JsonCodec\JsonClassCodec; + +/** + * Managed object factory which also handles serialization/deserialization + * of the objects it manages. + * + * @implements JsonClassCodec<ManagedObject> + */ +class ManagedObjectFactory implements JsonClassCodec { + /** @var array<string,ManagedObject> Fake database */ + private $storage = []; + + /** + * Create and store an object with $name and $value in the database. + * @param string $name + * @param int $value + * @return ManagedObject + */ + public function create( string $name, int $value ) { + if ( isset( $this->storage[$name] ) ) { + throw new \Error( "duplicate name" ); + } + $this->storage[$name] = $o = new ManagedObject( $name, $value ); + return $o; + } + + /** + * Lookup $name in the database. + * @param string $name + * @return ManagedObject + */ + public function lookup( string $name ): ManagedObject { + if ( !isset( $this->storage[$name] ) ) { + throw new \Error( "not found" ); + } + return $this->storage[$name]; + } + + /** @inheritDoc */ + public function toJsonArray( $obj ): array { + '@phan-var ManagedObject $obj'; + // Not necessary to serialize all the properties, since they + // will be reloaded from the "database" during deserialization + return [ 'name' => $obj->name ]; + } + + /** @inheritDoc */ + public function newFromJsonArray( string $className, array $json ): ManagedObject { + // @phan-suppress-next-line PhanTypeMismatchReturn template limitations + return $this->lookup( $json['name'] ); + } + + /** @inheritDoc */ + public function jsonClassHintFor( string $className, string $key ): ?string { + // no hints + return null; + } +} diff --git a/tests/phpunit/mocks/json/SampleContainerObject.php b/tests/phpunit/mocks/json/SampleContainerObject.php new file mode 100644 index 000000000000..dc39b2371fff --- /dev/null +++ b/tests/phpunit/mocks/json/SampleContainerObject.php @@ -0,0 +1,70 @@ +<?php +namespace MediaWiki\Tests\Json; + +use stdClass; +use Wikimedia\Assert\Assert; +use Wikimedia\JsonCodec\JsonCodecable; +use Wikimedia\JsonCodec\JsonCodecableTrait; + +/** + * Sample container object which uses JsonCodecableTrait to directly implement + * serialization/deserialization and uses class hints. + */ +class SampleContainerObject implements JsonCodecable { + use JsonCodecableTrait; + + /** @var mixed */ + public $contents; + + /** + * Create a new SampleContainerObject which stores $contents + * @param mixed $contents + */ + public function __construct( $contents ) { + $this->contents = $contents; + } + + // Implement JsonCodecable using the JsonCodecableTrait + + /** @inheritDoc */ + public function toJsonArray(): array { + return [ + 'contents' => $this->contents, + // Note that json array keys need not correspond to property names + 'test' => (object)[], + // Test "array of" hints as well as regular hints + 'array' => [ $this->contents ], + ]; + } + + /** @inheritDoc */ + public static function newFromJsonArray( array $json ): SampleContainerObject { + Assert::invariant( + get_class( $json['test'] ) === stdClass::class && + count( get_object_vars( $json['test'] ) ) === 0, + "Ensure that the 'test' key is restored correctly" + ); + Assert::invariant( + is_array( $json['array'] ) && count( $json['array'] ) === 1, + "Ensure that the 'array' key is restored correctly" + ); + return new SampleContainerObject( $json['contents'] ); + } + + /** @inheritDoc */ + public static function jsonClassHintFor( string $keyName ): ?string { + if ( $keyName === 'contents' ) { + // Hint that the contained value is a SampleObject. It might be! + return SampleObject::class; + } elseif ( $keyName === 'array' ) { + // Hint that the contained value is a *array of* SampleObject. + // It might be! + return SampleObject::class . '[]'; + } elseif ( $keyName === 'test' ) { + // This hint will always be correct; note that this is a key + // name not a property of SampleContainerObject + return stdClass::class; + } + return null; + } +} diff --git a/tests/phpunit/mocks/json/SampleObject.php b/tests/phpunit/mocks/json/SampleObject.php new file mode 100644 index 000000000000..9b1faaea7c2f --- /dev/null +++ b/tests/phpunit/mocks/json/SampleObject.php @@ -0,0 +1,54 @@ +<?php +namespace MediaWiki\Tests\Json; + +use Wikimedia\Assert\Assert; +use Wikimedia\JsonCodec\JsonCodecable; +use Wikimedia\JsonCodec\JsonCodecableTrait; + +/** + * Sample object which uses JsonCodecableTrait to directly implement + * serialization/deserialization. + */ +class SampleObject implements JsonCodecable { + use JsonCodecableTrait; + + /** @var string */ + public string $property; + + /** + * Create a new SampleObject which stores $property. + * @param string $property + */ + public function __construct( string $property ) { + $this->property = $property; + } + + // Implement JsonCodecable using the JsonCodecableTrait + + /** @inheritDoc */ + public function toJsonArray(): array { + if ( $this->property !== 'use _type_' ) { + // Allow testing both with and without the '_type_' special case + return [ 'property' => $this->property ]; + } + return [ + 'property' => $this->property, + // Implementers shouldn't have to know which properties the + // codec is using for its own purposes; this will still work + // fine: + '_type_' => 'check123', + ]; + } + + /** @inheritDoc */ + public static function newFromJsonArray( array $json ): SampleObject { + if ( $json['property'] === 'use _type_' ) { + Assert::invariant( $json['_type_'] === 'check123', 'protected field' ); + } + return new SampleObject( $json['property'] ); + } +} + +// This class_alias exists for testing alias support in JsonCodec and +// should not be removed. +class_alias( SampleObject::class, 'MediaWiki\\Tests\\Json\\SampleObjectAlias' ); |