AVRO-2855: PHP Add alias support and drop endian condition (#906)
* AVRO-2842: Add phpcs.
* AVRO-2842: Add PSR4 and code formatting.
* AVRO-2842: Fixed not found callbacks.
* AVRO-2842: Fixed PSR12 violations using phpcbf.
* AVRO-2842: Fixed PSR12 violations.
* Fixed left overs.
* Fixed example
* Added autoloader for non composer usage, updated readme and test bootstrap
* Added license
* Fixed psr issues
* Add aliases support.
* Added license
* Fixed some cs
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c62aec3
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+version: "3"
+services:
+ avro:
+ build:
+ context: .
+ dockerfile: ./share/docker/Dockerfile
+ volumes:
+ - ".:/avro"
diff --git a/lang/php/lib/Avro.php b/lang/php/lib/Avro.php
index fc43b49..8e032bc 100644
--- a/lang/php/lib/Avro.php
+++ b/lang/php/lib/Avro.php
@@ -36,28 +36,14 @@
* @var string version number of Avro specification to which
* this implemenation complies
*/
- const SPEC_VERSION = '1.3.3';
+ public const SPEC_VERSION = '1.4.0';
/**#@+
- * Constant to enumerate endianness.
- * @access private
- * @var int
- */
- const BIG_ENDIAN = 0x00;
- const LITTLE_ENDIAN = 0x01;
- /**#@-*/
- /**#@+
* Constant to enumerate biginteger handling mode.
* GMP is used, if available, on 32-bit platforms.
*/
- const PHP_BIGINTEGER_MODE = 0x00;
- const GMP_BIGINTEGER_MODE = 0x01;
- /**
- * Memoized result of self::setEndianness()
- * @var int self::BIG_ENDIAN or self::LITTLE_ENDIAN
- * @see self::setEndianness()
- */
- private static $endianness;
+ private const PHP_BIGINTEGER_MODE = 0x00;
+ private const GMP_BIGINTEGER_MODE = 0x01;
/**#@-*/
/**
* @var int
@@ -75,7 +61,6 @@
public static function checkPlatform()
{
self::check64Bit();
- self::checkLittleEndian();
}
/**
@@ -98,71 +83,6 @@
}
/**
- * Determines if the host platform is little endian,
- * required for processing double and float data.
- *
- * @throws AvroException if the platform is not little endian.
- */
- private static function checkLittleEndian()
- {
- if (!self::isLittleEndianPlatform()) {
- throw new AvroException('This is not a little-endian platform');
- }
- }
-
- /**
- * @returns boolean true if the host platform is little endian,
- * and false otherwise.
- * @uses self::is_bin_endian_platform()
- */
- private static function isLittleEndianPlatform()
- {
- return !self::isBigEndianPlatform();
- }
-
- /**
- * @returns boolean true if the host platform is big endian
- * and false otherwise.
- * @uses self::setEndianness()
- */
- private static function isBigEndianPlatform()
- {
- if (is_null(self::$endianness)) {
- self::setEndianness();
- }
-
- return (self::BIG_ENDIAN == self::$endianness);
- }
-
- /**
- * Determines the endianness of the host platform and memoizes
- * the result to Avro::$endianness.
- *
- * Based on a similar check perfomed in https://pear.php.net/package/Math_BinaryUtils
- *
- * @throws AvroException if the endianness cannot be determined.
- */
- private static function setEndianness()
- {
- $packed = pack('d', 1);
- switch ($packed) {
- case "\77\360\0\0\0\0\0\0":
- self::$endianness = self::BIG_ENDIAN;
- break;
- case "\0\0\0\0\0\0\360\77":
- self::$endianness = self::LITTLE_ENDIAN;
- break;
- default:
- throw new AvroException(
- sprintf(
- 'Error determining platform endianness: %s',
- AvroDebug::hexString($packed)
- )
- );
- }
- }
-
- /**
* @returns boolean true if the PHP GMP extension is used and false otherwise.
* @internal Requires Avro::check64Bit() (exposed via Avro::checkPlatform())
* to have been called to set Avro::$biginteger_mode.
diff --git a/lang/php/lib/AvroUtil.php b/lang/php/lib/AvroUtil.php
index 99a2c68..c61d498 100644
--- a/lang/php/lib/AvroUtil.php
+++ b/lang/php/lib/AvroUtil.php
@@ -37,7 +37,7 @@
* @returns true if the array is a list and false otherwise.
*
*/
- public static function isList($ary)
+ public static function isList($ary): bool
{
if (is_array($ary)) {
$i = 0;
@@ -51,15 +51,4 @@
}
return false;
}
-
- /**
- * @param array $ary
- * @param string $key
- * @returns mixed the value of $ary[$key] if it is set,
- * and null otherwise.
- */
- public static function arrayValue($ary, $key)
- {
- return $ary[$key] ?? null;
- }
}
diff --git a/lang/php/lib/Datum/AvroIODatumReader.php b/lang/php/lib/Datum/AvroIODatumReader.php
index da65f7d..02431d9 100644
--- a/lang/php/lib/Datum/AvroIODatumReader.php
+++ b/lang/php/lib/Datum/AvroIODatumReader.php
@@ -21,6 +21,7 @@
namespace Apache\Avro\Datum;
use Apache\Avro\AvroException;
+use Apache\Avro\Schema\AvroName;
use Apache\Avro\Schema\AvroSchema;
/**
@@ -81,14 +82,10 @@
*/
public function readData($writers_schema, $readers_schema, $decoder)
{
- if (!self::schemasMatch($writers_schema, $readers_schema)) {
- throw new AvroIOSchemaMatchException($writers_schema, $readers_schema);
- }
-
// Schema resolution: reader's schema is a union, writer's schema is not
if (
- AvroSchema::UNION_SCHEMA == $readers_schema->type()
- && AvroSchema::UNION_SCHEMA != $writers_schema->type()
+ AvroSchema::UNION_SCHEMA === $readers_schema->type()
+ && AvroSchema::UNION_SCHEMA !== $writers_schema->type()
) {
foreach ($readers_schema->schemas() as $schema) {
if (self::schemasMatch($writers_schema, $schema)) {
@@ -149,96 +146,82 @@
$writers_schema_type = $writers_schema->type;
$readers_schema_type = $readers_schema->type;
+ if (AvroSchema::UNION_SCHEMA === $writers_schema_type || AvroSchema::UNION_SCHEMA === $readers_schema_type) {
+ return true;
+ }
+
+ if (AvroSchema::isPrimitiveType($writers_schema_type)) {
+ return true;
+ }
+
+ switch ($readers_schema_type) {
+ case AvroSchema::MAP_SCHEMA:
+ return self::attributesMatch(
+ $writers_schema->values(),
+ $readers_schema->values(),
+ [AvroSchema::TYPE_ATTR]
+ );
+ case AvroSchema::ARRAY_SCHEMA:
+ return self::attributesMatch(
+ $writers_schema->items(),
+ $readers_schema->items(),
+ [AvroSchema::TYPE_ATTR]
+ );
+ case AvroSchema::ENUM_SCHEMA:
+ return self::attributesMatch(
+ $writers_schema,
+ $readers_schema,
+ [AvroSchema::FULLNAME_ATTR]
+ );
+ case AvroSchema::FIXED_SCHEMA:
+ return self::attributesMatch(
+ $writers_schema,
+ $readers_schema,
+ [
+ AvroSchema::FULLNAME_ATTR,
+ AvroSchema::SIZE_ATTR
+ ]
+ );
+ case AvroSchema::RECORD_SCHEMA:
+ case AvroSchema::ERROR_SCHEMA:
+ return self::attributesMatch(
+ $writers_schema,
+ $readers_schema,
+ [AvroSchema::FULLNAME_ATTR]
+ );
+ case AvroSchema::REQUEST_SCHEMA:
+ // XXX: This seems wrong
+ return true;
+ // XXX: no default
+ }
+
if (
- AvroSchema::UNION_SCHEMA == $writers_schema_type
- || AvroSchema::UNION_SCHEMA == $readers_schema_type
+ AvroSchema::INT_TYPE === $writers_schema_type
+ && in_array($readers_schema_type, [
+ AvroSchema::LONG_TYPE,
+ AvroSchema::FLOAT_TYPE,
+ AvroSchema::DOUBLE_TYPE
+ ])
) {
return true;
}
- if ($writers_schema_type == $readers_schema_type) {
- if (AvroSchema::isPrimitiveType($writers_schema_type)) {
- return true;
- }
-
- switch ($readers_schema_type) {
- case AvroSchema::MAP_SCHEMA:
- return self::attributesMatch(
- $writers_schema->values(),
- $readers_schema->values(),
- array(AvroSchema::TYPE_ATTR)
- );
- case AvroSchema::ARRAY_SCHEMA:
- return self::attributesMatch(
- $writers_schema->items(),
- $readers_schema->items(),
- array(AvroSchema::TYPE_ATTR)
- );
- case AvroSchema::ENUM_SCHEMA:
- return self::attributesMatch(
- $writers_schema,
- $readers_schema,
- array(AvroSchema::FULLNAME_ATTR)
- );
- case AvroSchema::FIXED_SCHEMA:
- return self::attributesMatch(
- $writers_schema,
- $readers_schema,
- array(
- AvroSchema::FULLNAME_ATTR,
- AvroSchema::SIZE_ATTR
- )
- );
- case AvroSchema::RECORD_SCHEMA:
- case AvroSchema::ERROR_SCHEMA:
- return self::attributesMatch(
- $writers_schema,
- $readers_schema,
- array(AvroSchema::FULLNAME_ATTR)
- );
- case AvroSchema::REQUEST_SCHEMA:
- // XXX: This seems wrong
- return true;
- // XXX: no default
- }
-
- if (
- AvroSchema::INT_TYPE == $writers_schema_type
- && in_array($readers_schema_type, array(
- AvroSchema::LONG_TYPE,
- AvroSchema::FLOAT_TYPE,
- AvroSchema::DOUBLE_TYPE
- ))
- ) {
- return true;
- }
-
- if (
- AvroSchema::LONG_TYPE == $writers_schema_type
- && in_array($readers_schema_type, array(
- AvroSchema::FLOAT_TYPE,
- AvroSchema::DOUBLE_TYPE
- ))
- ) {
- return true;
- }
-
- if (
- AvroSchema::FLOAT_TYPE == $writers_schema_type
- && AvroSchema::DOUBLE_TYPE == $readers_schema_type
- ) {
- return true;
- }
-
- return false;
+ if (
+ AvroSchema::LONG_TYPE === $writers_schema_type
+ && in_array($readers_schema_type, [
+ AvroSchema::FLOAT_TYPE,
+ AvroSchema::DOUBLE_TYPE
+ ])
+ ) {
+ return true;
}
- }
- /**#@+
- * @param AvroSchema $writers_schema
- * @param AvroSchema $readers_schema
- * @param AvroIOBinaryDecoder $decoder
- */
+ if (AvroSchema::FLOAT_TYPE === $writers_schema_type && AvroSchema::DOUBLE_TYPE === $readers_schema_type) {
+ return true;
+ }
+
+ return false;
+ }
/**
* Checks equivalence of the given attributes of the two given schemas.
@@ -252,10 +235,20 @@
public static function attributesMatch($schema_one, $schema_two, $attribute_names)
{
foreach ($attribute_names as $attribute_name) {
- if (
- $schema_one->attribute($attribute_name)
- !== $schema_two->attribute($attribute_name)
- ) {
+ if ($schema_one->attribute($attribute_name) !== $schema_two->attribute($attribute_name)) {
+ if ($attribute_name === AvroSchema::FULLNAME_ATTR) {
+ foreach ($schema_two->getAliases() as $alias) {
+ if (
+ $schema_one->attribute($attribute_name) === (new AvroName(
+ $alias,
+ $schema_two->attribute(AvroSchema::NAMESPACE_ATTR),
+ null
+ ))->fullname()
+ ) {
+ return true;
+ }
+ }
+ }
return false;
}
}
@@ -350,41 +343,34 @@
public function readRecord($writers_schema, $readers_schema, $decoder)
{
$readers_fields = $readers_schema->fieldsHash();
- $record = array();
+ $record = [];
foreach ($writers_schema->fields() as $writers_field) {
$type = $writers_field->type();
- if (isset($readers_fields[$writers_field->name()])) {
- $record[$writers_field->name()]
- = $this->readData(
- $type,
- $readers_fields[$writers_field->name()]->type(),
- $decoder
- );
+ $readers_field = $readers_fields[$writers_field->name()] ?? null;
+ if ($readers_field) {
+ $record[$writers_field->name()] = $this->readData($type, $readers_field->type(), $decoder);
+ } elseif (isset($readers_schema->fieldsByAlias()[$writers_field->name()])) {
+ $readers_field = $readers_schema->fieldsByAlias()[$writers_field->name()];
+ $field_val = $this->readData($writers_field->type(), $readers_field->type(), $decoder);
+ $record[$readers_field->name()] = $field_val;
} else {
$this->skipData($type, $decoder);
}
}
// Fill in default values
- if (count($readers_fields) > count($record)) {
- $writers_fields = $writers_schema->fieldsHash();
- foreach ($readers_fields as $field_name => $field) {
- if (!isset($writers_fields[$field_name])) {
- if ($field->hasDefaultValue()) {
- $record[$field->name()]
- = $this->readDefaultValue(
- $field->type(),
- $field->defaultValue()
- );
- } else {
- null;
- } // FIXME: unset
- }
+ foreach ($readers_fields as $field_name => $field) {
+ if (isset($writers_fields[$field_name])) {
+ continue;
+ }
+ if ($field->hasDefaultValue()) {
+ $record[$field->name()] = $this->readDefaultValue($field->type(), $field->defaultValue());
+ } else {
+ null;
}
}
return $record;
}
- /**#@-*/
/**
* @param AvroSchema $writers_schema
diff --git a/lang/php/lib/Schema/AvroEnumSchema.php b/lang/php/lib/Schema/AvroEnumSchema.php
index 4d3035f..523fedd 100644
--- a/lang/php/lib/Schema/AvroEnumSchema.php
+++ b/lang/php/lib/Schema/AvroEnumSchema.php
@@ -40,7 +40,7 @@
* @param AvroNamedSchemata &$schemata
* @throws AvroSchemaParseException
*/
- public function __construct($name, $doc, $symbols, &$schemata = null)
+ public function __construct($name, $doc, $symbols, &$schemata = null, $aliases = null)
{
if (!AvroUtil::isList($symbols)) {
throw new AvroSchemaParseException('Enum Schema symbols are not a list');
@@ -60,7 +60,7 @@
}
}
- parent::__construct(AvroSchema::ENUM_SCHEMA, $name, $doc, $schemata);
+ parent::__construct(AvroSchema::ENUM_SCHEMA, $name, $doc, $schemata, $aliases);
$this->symbols = $symbols;
}
diff --git a/lang/php/lib/Schema/AvroField.php b/lang/php/lib/Schema/AvroField.php
index 9e63c1d..f30070c 100644
--- a/lang/php/lib/Schema/AvroField.php
+++ b/lang/php/lib/Schema/AvroField.php
@@ -85,14 +85,20 @@
* defined in the AvroNamedSchemata instance
*/
private $isTypeFromSchemata;
+ /**
+ * @var array|null
+ */
+ private $aliases;
/**
- * @param string $type
* @param string $name
* @param AvroSchema $schema
* @param boolean $is_type_from_schemata
+ * @param $has_default
* @param string $default
* @param string $order
+ * @param array $aliases
+ * @throws AvroSchemaParseException
* @todo Check validity of $default value
* @todo Check validity of $order value
*/
@@ -102,13 +108,14 @@
$is_type_from_schemata,
$has_default,
$default,
- $order = null
+ $order = null,
+ $aliases = null
) {
if (!AvroName::isWellFormedName($name)) {
throw new AvroSchemaParseException('Field requires a "name" attribute');
}
- $this->type = $schema;
+ parent::__construct($schema);
$this->isTypeFromSchemata = $is_type_from_schemata;
$this->name = $name;
$this->hasDefault = $has_default;
@@ -117,6 +124,8 @@
}
self::checkOrderValue($order);
$this->order = $order;
+ self::hasValidAliases($aliases);
+ $this->aliases = $aliases;
}
/**
@@ -186,4 +195,14 @@
{
return $this->hasDefault;
}
+
+ public function getAliases()
+ {
+ return $this->aliases;
+ }
+
+ public function hasAliases()
+ {
+ return $this->aliases !== null;
+ }
}
diff --git a/lang/php/lib/Schema/AvroFixedSchema.php b/lang/php/lib/Schema/AvroFixedSchema.php
index 1091765..3ace00e 100644
--- a/lang/php/lib/Schema/AvroFixedSchema.php
+++ b/lang/php/lib/Schema/AvroFixedSchema.php
@@ -36,15 +36,17 @@
* @param string $doc Set to null, as fixed schemas don't have doc strings
* @param int $size byte count of this fixed schema data value
* @param AvroNamedSchemata &$schemata
+ * @param array $aliases
+ * @throws AvroSchemaParseException
*/
- public function __construct($name, $doc, $size, &$schemata = null)
+ public function __construct($name, $doc, $size, &$schemata = null, $aliases = null)
{
if (!is_int($size)) {
throw new AvroSchemaParseException(
'Fixed Schema requires a valid integer for "size" attribute'
);
}
- parent::__construct(AvroSchema::FIXED_SCHEMA, $name, null, $schemata);
+ parent::__construct(AvroSchema::FIXED_SCHEMA, $name, null, $schemata, $aliases);
$this->size = $size;
}
diff --git a/lang/php/lib/Schema/AvroNamedSchema.php b/lang/php/lib/Schema/AvroNamedSchema.php
index ef7fed1..7db254d 100644
--- a/lang/php/lib/Schema/AvroNamedSchema.php
+++ b/lang/php/lib/Schema/AvroNamedSchema.php
@@ -37,15 +37,20 @@
* @var string documentation string
*/
private $doc;
+ /**
+ * @var array
+ */
+ private $aliases;
/**
* @param string $type
* @param AvroName $name
* @param string $doc documentation string
* @param AvroNamedSchemata &$schemata
+ * @param array $aliases
* @throws AvroSchemaParseException
*/
- public function __construct($type, $name, $doc = null, &$schemata = null)
+ public function __construct($type, $name, $doc = null, &$schemata = null, $aliases = null)
{
parent::__construct($type);
$this->name = $name;
@@ -54,12 +59,21 @@
throw new AvroSchemaParseException('Schema doc attribute must be a string');
}
$this->doc = $doc;
+ if ($aliases) {
+ self::hasValidAliases($aliases);
+ $this->aliases = $aliases;
+ }
if (!is_null($schemata)) {
$schemata = $schemata->cloneWithNewSchema($this);
}
}
+ public function getAliases()
+ {
+ return $this->aliases;
+ }
+
/**
* @returns mixed
*/
@@ -74,6 +88,9 @@
if (!is_null($this->doc)) {
$avro[AvroSchema::DOC_ATTR] = $this->doc;
}
+ if (!is_null($this->aliases)) {
+ $avro[AvroSchema::ALIASES_ATTR] = $this->aliases;
+ }
return $avro;
}
diff --git a/lang/php/lib/Schema/AvroRecordSchema.php b/lang/php/lib/Schema/AvroRecordSchema.php
index b3b2404..354aebd 100644
--- a/lang/php/lib/Schema/AvroRecordSchema.php
+++ b/lang/php/lib/Schema/AvroRecordSchema.php
@@ -20,15 +20,13 @@
namespace Apache\Avro\Schema;
-use Apache\Avro\AvroUtil;
-
/**
* @package Avro
*/
class AvroRecordSchema extends AvroNamedSchema
{
/**
- * @var AvroSchema[] array of AvroNamedSchema field definitions of
+ * @var AvroNamedSchema[] array of AvroNamedSchema field definitions of
* this AvroRecordSchema
*/
private $fields;
@@ -39,7 +37,7 @@
private $fieldsHash;
/**
- * @param string $name
+ * @param AvroName $name
* @param string $namespace
* @param string $doc
* @param array $fields
@@ -52,7 +50,8 @@
$doc,
$fields,
&$schemata = null,
- $schema_type = AvroSchema::RECORD_SCHEMA
+ $schema_type = AvroSchema::RECORD_SCHEMA,
+ $aliases = null
) {
if (is_null($fields)) {
throw new AvroSchemaParseException(
@@ -63,7 +62,7 @@
if (AvroSchema::REQUEST_SCHEMA == $schema_type) {
parent::__construct($schema_type, $name);
} else {
- parent::__construct($schema_type, $name, $doc, $schemata);
+ parent::__construct($schema_type, $name, $doc, $schemata, $aliases);
}
[$x, $namespace] = $name->nameAndNamespace();
@@ -81,10 +80,12 @@
{
$fields = array();
$field_names = array();
+ $alias_names = [];
foreach ($field_data as $index => $field) {
- $name = AvroUtil::arrayValue($field, AvroField::FIELD_NAME_ATTR);
- $type = AvroUtil::arrayValue($field, AvroSchema::TYPE_ATTR);
- $order = AvroUtil::arrayValue($field, AvroField::ORDER_ATTR);
+ $name = $field[AvroField::FIELD_NAME_ATTR] ?? null;
+ $type = $field[AvroSchema::TYPE_ATTR] ?? null;
+ $order = $field[AvroField::ORDER_ATTR] ?? null;
+ $aliases = $field[AvroField::ALIASES_ATTR] ?? null;
$default = null;
$has_default = false;
@@ -118,10 +119,17 @@
$is_schema_from_schemata,
$has_default,
$default,
- $order
+ $order,
+ $aliases
);
- $field_names [] = $name;
- $fields [] = $new_field;
+ $field_names[] = $name;
+ if ($new_field->hasAliases() && array_intersect($alias_names, $new_field->getAliases())) {
+ throw new AvroSchemaParseException("Alias already in use");
+ }
+ if ($new_field->hasAliases()) {
+ array_push($alias_names, ...$new_field->getAliases());
+ }
+ $fields[] = $new_field;
}
return $fields;
}
@@ -135,10 +143,10 @@
$fields_avro = array();
foreach ($this->fields as $field) {
- $fields_avro [] = $field->toAvro();
+ $fields_avro[] = $field->toAvro();
}
- if (AvroSchema::REQUEST_SCHEMA == $this->type) {
+ if (AvroSchema::REQUEST_SCHEMA === $this->type) {
return $fields_avro;
}
@@ -170,4 +178,17 @@
}
return $this->fieldsHash;
}
+
+ public function fieldsByAlias()
+ {
+ $hash = [];
+ foreach ($this->fields as $field) {
+ if ($field->hasAliases()) {
+ foreach ($field->getAliases() as $a) {
+ $hash[$a] = $field;
+ }
+ }
+ }
+ return $hash;
+ }
}
diff --git a/lang/php/lib/Schema/AvroSchema.php b/lang/php/lib/Schema/AvroSchema.php
index 6cbc73a..10dc2a9 100644
--- a/lang/php/lib/Schema/AvroSchema.php
+++ b/lang/php/lib/Schema/AvroSchema.php
@@ -221,6 +221,9 @@
* @var string document string attribute name
*/
const DOC_ATTR = 'doc';
+
+ /** @var string aliases string attribute name */
+ const ALIASES_ATTR = 'aliases';
/**
* @var array list of primitive schema type names
@@ -258,6 +261,10 @@
self::SYMBOLS_ATTR,
self::VALUES_ATTR
);
+ /**
+ * @var string|AvroNamedSchema
+ */
+ public $type;
/**
* @param string $type a schema type name
@@ -294,46 +301,50 @@
}
if (is_array($avro)) {
- $type = AvroUtil::arrayValue($avro, self::TYPE_ATTR);
+ $type = $avro[self::TYPE_ATTR] ?? null;
if (self::isPrimitiveType($type)) {
return new AvroPrimitiveSchema($type);
- } elseif (self::isNamedType($type)) {
- $name = AvroUtil::arrayValue($avro, self::NAME_ATTR);
- $namespace = AvroUtil::arrayValue($avro, self::NAMESPACE_ATTR);
+ }
+
+ if (self::isNamedType($type)) {
+ $name = $avro[self::NAME_ATTR] ?? null;
+ $namespace = $avro[self::NAMESPACE_ATTR] ?? null;
$new_name = new AvroName($name, $namespace, $default_namespace);
- $doc = AvroUtil::arrayValue($avro, self::DOC_ATTR);
+ $doc = $avro[self::DOC_ATTR] ?? null;
+ $aliases = $avro[self::ALIASES_ATTR] ?? null;
switch ($type) {
case self::FIXED_SCHEMA:
- $size = AvroUtil::arrayValue($avro, self::SIZE_ATTR);
+ $size = $avro[self::SIZE_ATTR] ?? null;
return new AvroFixedSchema(
$new_name,
$doc,
$size,
- $schemata
+ $schemata,
+ $aliases
);
case self::ENUM_SCHEMA:
- $symbols = AvroUtil::arrayValue($avro, self::SYMBOLS_ATTR);
+ $symbols = $avro[self::SYMBOLS_ATTR] ?? null;
return new AvroEnumSchema(
$new_name,
$doc,
$symbols,
- $schemata
+ $schemata,
+ $aliases
);
case self::RECORD_SCHEMA:
case self::ERROR_SCHEMA:
- $fields = AvroUtil::arrayValue($avro, self::FIELDS_ATTR);
+ $fields = $avro[self::FIELDS_ATTR] ?? null;
return new AvroRecordSchema(
$new_name,
$doc,
$fields,
$schemata,
- $type
+ $type,
+ $aliases
);
default:
- throw new AvroSchemaParseException(
- sprintf('Unknown named type: %s', $type)
- );
+ throw new AvroSchemaParseException(sprintf('Unknown named type: %s', $type));
}
} elseif (self::isValidType($type)) {
switch ($type) {
@@ -415,6 +426,25 @@
return in_array($type, self::$namedTypes);
}
+ public static function hasValidAliases($aliases)
+ {
+ if ($aliases === null) {
+ return false;
+ }
+ if (!is_array($aliases)) {
+ throw new AvroSchemaParseException(
+ 'Invalid aliases value. Must be an array of strings.'
+ );
+ }
+ foreach ((array) $aliases as $alias) {
+ if (!is_string($alias)) {
+ throw new AvroSchemaParseException(
+ 'Invalid aliases value. Must be an array of strings.'
+ );
+ }
+ }
+ }
+
/**
* @returns boolean true if $datum is valid for $expected_schema
* and false otherwise.
diff --git a/lang/php/test/IODatumReaderTest.php b/lang/php/test/IODatumReaderTest.php
index e15d3ac..dc6ead3 100644
--- a/lang/php/test/IODatumReaderTest.php
+++ b/lang/php/test/IODatumReaderTest.php
@@ -19,6 +19,7 @@
namespace Apache\Avro\Tests;
+use Apache\Avro\Datum\AvroIOBinaryDecoder;
use Apache\Avro\Datum\AvroIOBinaryEncoder;
use Apache\Avro\Datum\AvroIODatumReader;
use Apache\Avro\Datum\AvroIODatumWriter;
@@ -40,6 +41,34 @@
AvroSchema::parse($readers_schema)));
}
+ public function test_aliased()
+ {
+ $writers_schema = AvroSchema::parse(<<<SCHEMA
+{"type":"record", "name":"Rec1", "fields":[
+{"name":"field1", "type":"int"}
+]}
+SCHEMA);
+ $readers_schema = AvroSchema::parse(<<<SCHEMA
+ {"type":"record", "name":"Rec2", "aliases":["Rec1"], "fields":[
+ {"name":"field2", "aliases":["field1"], "type":"int"}
+ ]}
+ SCHEMA);
+
+ $io = new AvroStringIO();
+ $writer = new AvroIODatumWriter();
+ $writer->writeData($writers_schema, ['field1' => 1], new AvroIOBinaryEncoder($io));
+
+ $bin = $io->string();
+ $reader = new AvroIODatumReader();
+ $record = $reader->readRecord(
+ $writers_schema,
+ $readers_schema,
+ new AvroIOBinaryDecoder(new AvroStringIO($bin))
+ );
+
+ $this->assertEquals(['field2' => 1], $record);
+ }
+
public function testRecordNullField()
{
$schema_json = <<<_JSON
diff --git a/lang/php/test/SchemaTest.php b/lang/php/test/SchemaTest.php
index 1a142b4..aaa2240 100644
--- a/lang/php/test/SchemaTest.php
+++ b/lang/php/test/SchemaTest.php
@@ -25,13 +25,13 @@
class SchemaExample
{
- var $schema_string;
- var $is_valid;
- var $name;
- var $comment;
- var $normalized_schema_string;
+ public $schema_string;
+ public $is_valid;
+ public $name;
+ public $comment;
+ public $normalized_schema_string;
- function __construct(
+ public function __construct(
$schema_string,
$is_valid,
$normalized_schema_string = null,
@@ -52,7 +52,7 @@
static $examples = array();
static $valid_examples = array();
- function test_json_decode()
+ public function test_json_decode()
{
$this->assertEquals(json_decode('null', true), null);
$this->assertEquals(json_decode('32', true), 32);
@@ -72,15 +72,14 @@
$this->assertEquals(json_decode('"boolean"'), 'boolean');
}
- function schema_examples_provider()
+ public function schema_examples_provider()
{
self::make_examples();
$ary = array();
foreach (self::$examples as $example) {
- $ary [] = array($example);
+ $ary[] = array($example);
}
return $ary;
- return array(array(1), array(2), array(3));
}
protected static function make_examples()
@@ -154,7 +153,7 @@
'[{"type":"record","name":"subtract","namespace":"com.example","fields":[{"name":"minuend","type":"int"},{"name":"subtrahend","type":"int"}]},{"type":"record","name":"divide","namespace":"com.example","fields":[{"name":"quotient","type":"int"},{"name":"dividend","type":"int"}]},{"type":"array","items":"string"}]'),
);
- $fixed_examples = array(
+ $fixed_examples = [
new SchemaExample('{"type": "fixed", "name": "Test", "size": 1}', true),
new SchemaExample('
{"type": "fixed",
@@ -182,7 +181,7 @@
"type": "fixed",
"size": 32 }', true,
'{"type":"fixed","name":"bar","namespace":"com.example","size":32}')
- );
+ ];
$fixed_examples [] = new SchemaExample(
'{"type":"fixed","name":"_x.bar","size":4}', true,
@@ -192,6 +191,8 @@
'{"type":"fixed","name":"_x","namespace":"baz","size":4}');
$fixed_examples [] = new SchemaExample(
'{"type":"fixed","name":"baz.3x","size":4}', false);
+ $fixed_examples[] = new SchemaExample(
+ '{"type":"fixed", "name":"Fixed2", "aliases":["Fixed1"], "size": 2}', true);
$enum_examples = array(
new SchemaExample('{"type": "enum", "name": "Test", "symbols": ["A", "B"]}', true),
@@ -426,6 +427,8 @@
{"type":"record", "name":"foo", "doc":"doc string",
"fields":[{"name":"bar", "type":"int", "order":"bad"}]}
', false);
+ $record_examples[] = new SchemaExample(
+ '{"type":"record", "name":"Record2", "aliases":["Record1"]}', false);
self::$examples = array_merge($primitive_examples,
$fixed_examples,
@@ -445,7 +448,7 @@
protected static function make_primitive_examples()
{
$examples = array();
- foreach (array(
+ foreach ([
'null',
'boolean',
'int',
@@ -454,7 +457,7 @@
'double',
'bytes',
'string'
- )
+ ]
as $type) {
$examples [] = new SchemaExample(sprintf('"%s"', $type), true);
$examples [] = new SchemaExample(sprintf('{"type": "%s"}', $type), true, sprintf('"%s"', $type));
@@ -474,7 +477,7 @@
$this->assertTrue($example->is_valid,
sprintf("schema_string: %s\n",
$schema_string));
- $this->assertEquals($normalized_schema_string, strval($schema));
+ $this->assertEquals($normalized_schema_string, (string) $schema);
} catch (AvroSchemaParseException $e) {
$this->assertFalse($example->is_valid,
sprintf("schema_string: %s\n%s",
@@ -482,4 +485,119 @@
$e->getMessage()));
}
}
+
+ public function testToAvroIncludesAliases()
+ {
+ $hash = <<<SCHEMA
+{
+ "type": "record",
+ "name": "test_record",
+ "aliases": ["alt_record"],
+ "fields": [
+ { "name": "f", "type": { "type": "fixed", "size": 2, "name": "test_fixed", "aliases": ["alt_fixed"] } },
+ { "name": "e", "type": { "type": "enum", "symbols": ["A", "B"], "name": "test_enum", "aliases": ["alt_enum"] } }
+ ]
+}
+SCHEMA;
+ $schema = AvroSchema::parse($hash);
+ $this->assertEquals($schema->toAvro(), json_decode($hash, true));
+ }
+
+ public function testValidateFieldAliases()
+ {
+ $this->expectException(AvroSchemaParseException::class);
+ $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.');
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "record",
+ "name": "fruits",
+ "fields": [
+ {
+ "name": "banana",
+ "type": "string",
+ "aliases": "banane"
+ }
+ ]
+}
+SCHEMA);
+ }
+
+ public function testValidateRecordAliases()
+ {
+ $this->expectException(AvroSchemaParseException::class);
+ $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.');
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "record",
+ "name": "fruits",
+ "aliases": ["foods", 2],
+ "fields": []
+}
+SCHEMA);
+ }
+
+ public function testValidateFixedAliases()
+ {
+ $this->expectException(AvroSchemaParseException::class);
+ $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.');
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "fixed",
+ "name": "uuid",
+ "size": 36,
+ "aliases": "unique_id"
+}
+SCHEMA);
+ }
+
+ public function testValidateEnumAliases()
+ {
+ $this->expectException(AvroSchemaParseException::class);
+ $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.');
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "enum",
+ "name": "vowels",
+ "aliases": [1, 2],
+ "symbols": ["A", "E", "I", "O", "U"]
+}
+SCHEMA);
+ }
+
+ public function testValidateSameAliasMultipleFields()
+ {
+ $this->expectException(AvroSchemaParseException::class);
+ $this->expectExceptionMessage('Alias already in use');
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "record",
+ "name": "fruits",
+ "fields": [
+ {"name": "banana", "type": "string", "aliases": [ "yellow" ]},
+ {"name": "lemo", "type": "string", "aliases": [ "yellow" ]}
+ ]
+}
+SCHEMA);
+ }
+
+ public function testValidateRepeatedAliases()
+ {
+ $this->expectNotToPerformAssertions();
+ AvroSchema::parse(<<<SCHEMA
+{
+ "type": "record",
+ "name": "fruits",
+ "fields": [
+ {
+ "name": "banana",
+ "type": "string",
+ "aliases": [
+ "yellow",
+ "yellow"
+ ]
+ }
+ ]
+}
+SCHEMA);
+ }
}