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);
+    }
 }