From 3a130bd6b1ff3255fb66ee32c3ec4feeea71d9ff Mon Sep 17 00:00:00 2001
From: Michael Wallner
Last modified:
- 2022-01-28 09:29:47 UTC
+ 2022-03-03 08:45:59 UTC
diff --git a/docs/v0.1/ion.html b/docs/v0.1/ion.html
index 8924ffa..b80a5c6 100644
--- a/docs/v0.1/ion.html
+++ b/docs/v0.1/ion.html
@@ -45,6 +45,11 @@
+
Serialize a PHP value as ION data.
-string
ion\serialize(mixed
$data, [?ion\Serializer $serializer = NULL
])
string
ion\serialize(mixed
$data, [ion\Serializer|array
|null
$serializer = NULL
])
Unserialize ION data (stream) as PHP value(s).
-mixed
ion\unserialize(string
|resource
$data, [?ion\Unserializer $unserializer = NULL
])
mixed
ion\unserialize(string
|resource
$data, [ion\Unserializer|array
|null
$unserializer = NULL
])
Welcome to the ext-ion tutorial. We'll look into what Amazon's ION format is and how to use it from within PHP.
+Parts of this documentation are replicated verbatim of the official Amazon ION documentation licensed under the Apache 2.0 License.
+
+                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
   1. Definitions.
      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.
      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.
      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.
      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.
      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.
      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.
      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).
      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.
      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."
      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.
   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.
   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.
   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:
      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and
      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and
      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and
      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.
      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.
   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.
   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.
   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.
   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.
   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.
   END OF TERMS AND CONDITIONS
+
+ The PHP extension for Ion requires PHP version 8.1 or greater, see the Install section.
Applications can seamlessly consume Ion data in either its text or binary forms without loss of data fidelity. While the expectation is that most Ion data is in binary form, the text form promotes human readability, simplifying discovery and diagnosis.
Notably, the text format is a superset of JSON, making all JSON data valid Ion data. You probably already know how to read and author Ion.
Like JSON, Ionâs text format is concise and clearly legible. It is intelligible to humans and may be edited using familiar tools like a text editor. This makes Ion well-suited to rapid prototyping: developers can quickly mock up the their data in text Ion, knowing that their application will ultimately seamlessly process the more efficient binary Ion format. Once the application is up and running, the users can debug it by intercepting the binary Ion data and converting it to text Ion with full fidelity. After analysis, hand-edited records can be re-inserted into a processing pipeline as needed to support debugging and prototyping.
Text-only formats are more expensive to parse, which is why Ion offers the option of the length-prefixed binary format. This binary format supports rapid skip-scanning of data to materialize only key values within Ion streams.
Ionâs interoperable formats avoid the kinds of semantic mismatches encountered when attempting to mix and match separate text and binary formats.
Standalone binary formats such as CBOR sacrifice human-readability in favor of an encoding that is more compact and efficient to parse. Although CBOR is based on JSON, transcoding between the two is not always straightforward because CBORâs more expressive types do not necessarily map cleanly to JSON types. For example, CBORâs bignum
must be base-64 encoded and represented as a JSON string
in order to avoid numeric overflow when read by a JSON parser, while a CBOR map
may be directly converted to a JSON object
only if all its keys are UTF-8 strings.
Ionâs type system is a superset of JSONâs: in addition to strings, booleans, arrays (lists), objects (structs), and nulls, Ion adds support for arbitrary-precision timestamps, embedded binary values (blobs and clobs), and symbolic expressions. Ion also expands JSONâs number
specification by defining distinct types for arbitrary-size integers, IEEE-754 binary floating point numbers, and infinite-precision decimals. Decimals are particularly useful for precision-sensitive applications such as financial transaction record-keeping. JSONâs number
type is underspecified; in practice, many implementations represent all JSON numbers as fixed-precision base-2 floats, which are subject to rounding errors and other loss of precision.
Ion timestamps are W3C-compliant representations of calendar dates and time, supporting variable precision including year, month, day, hours, minutes, seconds, and fractional seconds. Ion timestamps may optionally encode a time zone offset.
By defining timestamps as a distinct type, Ion eliminated the ambiguity involved with representing dates as strings, as the semantics are clearly defined. Unlike a number, which counts from some âepochâ, arbitrary precision timestamps also allow applications to represent deliberate ambiguity.
Ionâs blob
and clob
types allow applications to tunnel binary data through Ion. This allows such applications to transmit opaque binary payloads (e.g. media, code, and non-UTF-8 text) in Ion without the need to apply additional processing to the payloads to make them conform to a different Ion type.
For example, a blob
could be used to transmit a bitmap image, while a clob
could be used to transmit Shift JIS text or an XML payload.
The Ion specification defines a distinct syntax for symbolic expressions (S-expressions), but does not define how they should be processed. This allows applications to use S-expressions to convey domain-specific semantics in a first-class Ion type.
Formats that lack S-expressions as a first-class type are often left to choose between two imperfect options: adding a pre-processor (e.g. Jsonnet on top of JSON) to work around the inability to represent expressions as data, or tunneling domain-specific language text as opaque strings or binary payloads.
The Ion specification provides a formal mechanism for applications to annotate any Ion value without the need to enclose the value in a container. These annotations are not interpreted by Ion readers and may be used, for example, to add type information to a struct
, time units to an integer
or decimal value, or a description of the contents of a blob value.
Like JSON and CBOR, Ion is a self-describing format, meaning that it does not require external metadata (i.e. a schema) in order to interpret the structural characteristics of data denoted by the format. Notably, Ion payloads are free from build-time binding that inhibits independent innovation and evolution across service boundaries. This provides greater flexibility over schema-based formats such as protocol buffers, Thrift, and Avro, as data may be sparsely encoded and the implicit schema may be changed without explicit renegotiation of the schema among all consumers. These benefits come at the cost of a less compact encoding, but in our experience the positive impact on agility has been more valuable than an efficient but brittle contract.
Ionâs binary format is optimized according to the following principles:
In the spirit of these principles, the Ion specification includes features that make Ionâs binary encoding more efficient to read than other schema-free formats. These features include length-prefixing of binary values and Ionâs use of symbol tables.
Because most reads are sparse, binary Ion invests some encoding space to length-prefix each value in a stream. This makes seeking to the next relevant value for a particular application inexpensive, and enables efficient skip-scanning of data. This allows applications to cherry-pick only the relevant values from the stream for deeper parsing, and to economize parsing of irrelevant values.
In binary Ion, common text tokens such as struct field names are automatically stored in a symbol table. This allows these tokens to be efficiently encoded as table offsets instead of repeated copies of the same text. As a further space optimization, symbol tables can be pre-shared between producer and consumer so that only the table name and version are included in the payload, eliminating the overhead involved with repeatedly defining the same symbols across multiple pieces of Ion data.
You can get started installing ext-ion the usual ways:
pecl install ion
+./configure
make #-j$(nproc)
sudo make install #INSTALL=install
+
See https://pharext.org and https://replicator.pharext.org/?ion
+curl -sSO https://replicator.pharext.org/phars/ion/ion-0.1.0.ext.phar
php ./ion-0.1.0.ext.phar --sudo --ini <path/to/pecl.ini>
+
+# download phar, signature and public key
curl -sSO https://replicator.pharext.org/replicator.pub
curl -sSO https://replicator.pharext.org/phars/ion/ion-0.1.0.ext.phar
curl -sSO https://replicator.pharext.org/sigs/ion/ion-0.1.0.ext.phar.sig
# verify signature against public key
openssl dgst \
  -verify    replicator.pub \
  -signature ion-0.1.0.ext.phar.sig \
             ion-0.1.0.ext.phar
# install verified extension
php ./ion-0.1.0.ext.phar --sudo --ini <path/to/pecl.ini>
+
php -m | grep ^ion
should show: ion
.
Quoting the official Ion documentation:
Amazon Ion is a richly-typed, self-describing, hierarchical data serialization format offering interchangeable binary and text representations. The text format (a superset of JSON) is easy to read and author, supporting rapid prototyping.
The binary representation is efficient to store, transmit, and skip-scan parse.
The rich type system provides unambiguous semantics for longterm preservation of data which can survive multiple generations of software evolution.
Ion was built to address rapid development, decoupling, and efficiency challenges faced every day while engineering large-scale, service-oriented architectures.
+<?=
ion\serialize([
  "key" => "value",
  "more" => [
    "data" => 123
  ]
]);
?>
+
+
+{key:"value",more:{data:123}}
+
If you now think that this looks a lot like JSON, you're probably right, because Ion is a superset of JSON:
+<?=
json_encode([
  "key" => "value",
  "more" => [
    "data" => 123
  ]
]);
?>
+
+
+{"key":"value","more":{"data":123}}
+
So, all valid JSON is also valid Ion. Please refer to the official spec to learn more about this topic.
+<?=
var_representation(
  ion\unserialize('{key:"value",more:{data:123}}')
);
?>
+
+
+[
  'key' => 'value',
  'more' => [
    'data' => 123,
  ],
]
+
If you try the same with the JSON equivalent, you'll see that it's basically valid Ion, too:
+<?=
var_representation(
  ion\unserialize('{"key":"value","more":{"data":123}}')
);
?>
+
+
+[
  'key' => 'value',
  'more' => [
    'data' => 123,
  ],
]
+
Ion supports multiple sequences of documents within a single stream; consider the following:
+<?=
var_representation(
  ion\unserialize('
    {"key":"value","more":{"data":123}}
    {"key":"value","more":{"data":456}}
  ', new ion\Unserializer\PHP(multiSequence: true)
  )
);
?>
+
+
+[
  [
    'key' => 'value',
    'more' => [
      'data' => 123,
    ],
  ],
  [
    'key' => 'value',
    'more' => [
      'data' => 456,
    ],
  ],
]
+
Any Ion value can include one or more annotation symbols denoting the semantics of the content. This can be used to:
blob
or clob
value.In the text format, type annotations are denoted by a non-null
symbol token and double
-colons preceding any value. Multiple annotations on the same value are separated by double
-colons:
+int32::12                                // Suggests 32 bits as end-user type
degrees::'celsius'::100                  // You can have multiple annotaions on a value
'my.custom.type' :: { x : 12 , y : -1 }  // Gives a struct a user-defined type
{ field: some_annotation::value }        // Field's name must precede annotations of its value
jpeg :: {{ ... }}                        // Indicates the blob contains jpeg data
bool :: null.int                         // A very misleading annotation on the integer null
'' :: 1                                  // An empty annotation
null.symbol :: 1                         // ERROR: type annotation cannot be nullÂ
+
Except for a small number of predefined system and PHP annotations, Ion itself neither defines nor validates such annotations; that behavior is left to applications or tools (such as schema validators).
Itâs important to understand that annotations are symbol tokens, not symbol values. That means they do not have annotations themselves. In particular, the text a::c
is a single value consisting of three textual tokens (a symbol, a double
-colon, and another symbol); the first symbol token is an annotation on the value, and the second is the content of the value.
+<?php
foreach (ion\Symbol\System::cases() as $e) {
  printf("%30s:: => %s\n", $e->value, $e->name);
}
/*
                          $ion:: => Ion
                      $ion_1_0:: => Ivm_1_0
             $ion_symbol_table:: => IonSymbolTable
                          name:: => Name
                       version:: => Version
                       imports:: => Imports
                       symbols:: => Symbols
                        max_id:: => MaxId
      $ion_shared_symbol_table:: => SharedSymbolTable
*/
?>
+
+
There are two handful of annotations used by PHP, which are centralized in the ion\Symbol\PHP enumeration:
+<?php
foreach (ion\Symbol\PHP::cases() as $e) {
  printf("%3s:: => %s\n", $e->value, $e->name);
}
/*
  PHP:: => PHP
    R:: => Reference
    r:: => Backref
    p:: => Property
    o:: => Object
    c:: => ClassObject
    O:: => MagicObject
    C:: => CustomObject
    E:: => Enum
    S:: => Serializable
*/
?>
+
+
Quoting the official Ion documentation:
+Amazon Ion is a richly-typed, self-describing, hierarchical data serialization format offering interchangeable binary and text representations. The text format (a superset of JSON) is easy to read and author, supporting rapid prototyping. The binary representation is efficient to store, transmit, and skip-scan parse. The rich type system provides unambiguous semantics for
+long
-term preservation of data which can survive multiple generations of software evolution.Ion was built to address rapid development, decoupling, and efficiency challenges faced every day while engineering large-scale, service-oriented architectures. It has been addressing these challenges within Amazon for nearly a decade, and we believe others will benefit as well.
+
+<?php
echo ion\serialize([
  "key" => "value",
  "more" => [
    "data" => 123
  ]
]);
?>
+
+
+{key:"value",more:{data:123}}
+
If you now think that this looks a lot like JSON, you're probably right, because Ion is a superset of JSON:
+<?php
echo json_encode([
  "key" => "value",
  "more" => [
    "data" => 123
  ]
]);
?>
+
+
+{"key":"value","more":{"data":123}}
+
So, all valid JSON is also valid Ion. Please refer to the official spec to learn more about this topic.
+<?=
var_representation(
  ion\unserialize('{key:"value",more:{data:123}}')
);
?>
+
+
+[
  'key' => 'value',
  'more' => [
    'data' => 123,
  ],
]
+
If you try the same with the JSON equivalent, you'll see that it's basically valid Ion, too:
+<?=
var_representation(
  ion\unserialize('{"key":"value","more":{"data":123}}')
);
?>
+
+
+[
  'key' => 'value',
  'more' => [
    'data' => 123,
  ],
]
+
ION supports many of PHP's data types:
NULL
#
+Additonally to the plain and simple NULL
, ION can attach a type to NULL
values.
+<?php
 Â
$writer = new ion\Writer\Stream\Writer(STDOUT);
$writer->writeNull();
$writer->writeTypedNull(ion\Type::Int);
$writer->writeTypedNull(ion\Type::String);
$writer->flush();
/*
    null null.int null.string
*/
?>
+
+
There are a handful of data types treated in a specific way in PHP; consider the following examples:
Serializable
#
++NOTE:
+
+The interfaceSerializable
has been deprecated in 8.1 and should be replaced with magic serialize methods.
+<?php
 Â
class srlzbl implements \Serializable {
  private $data = "foo";
  public function serialize() {Â
    return "bar";Â
  }
  public function unserialize($data) {Â
    $this->data = $data;Â
  }
}
$srlzbl = new srlzbl;
var_dump($srlzbl);
$srlzd = ion\serialize($srlzbl);
echo $srlzd;
/*
    object(srlzbl)#4 (1) {
        ["data":"srlzbl":private]=>
        string(3) "foo"
    }
   Â
    S::srlzbl::{{"bar"}}
*/
?>
+
+
Everything as expected so far, Serializable
return a string
, but since it cannot indicate whether it's a valid UTF-8 string
, a ion\Type::CLob or ion\Type::BLob, CLobs are assumed.
Unserialization does not offer any surprises, either:
+<?phpÂ
 Â
var_dump(ion\unserialize($srlzd));
/*
  object(srlzbl)#4 (1) {
    ["data":"srlzbl":private]=>
    string(3) "bar"
  }
*/
?>
+
+
Implementing serialization behavior with magic methods is the preferred way since 8.1:
+<?php
 Â
class magic {
  private string $foo = "foo";
  function __serialize() : array {
    return ["foo" => "bar"];
  }
  function __unserialize(array $data) : void {
    foreach ($data as $k => $v)Â
      $this->$k = $v;
  }
}
$magic = new magic;
var_dump($magic);
$srlzd = ion\serialize($magic);
echo $srlzd;
/*
  object(magic)#6 (1) {
    ["foo":"magic":private]=>
    string(3) "foo"
  }
  O::magic::{foo:"bar"}
*/
?>
+
+
Again, unserialization yields the expected results:
+<?php
 Â
var_dump(ion\unserialize($srlzd));
/*
  object(magic)#7 (1) {
    ["foo":"magic":private]=>
    string(3) "bar"
  }
*/
?>
+
+
Customly serializable objects work like magic serializable objects, with custom names for the magic methods.
+<?php
 Â
class custom {
  private array $data;
  function init(array $data) : void {
    $this->data = $data;
  }
  function export() : array {
    return $this->data;
  }
}
$custom = new custom;
$custom->init(["foo"Â =>Â "bar"]);
echo $srlzd = ion\serialize($custom);
/*
    c::custom::{data:p::custom::{foo:"bar"}}
   Â
*/
?>
+
+
The above is actually the result of serializing a plain old PHP object, because we didn't implement any serialization primitives and did neither specify a custom method to call. So let's just do that:
+<?php
 Â
$srlzr = new ion\Serializer\PHP(callCustomSerialize: "export");
echo $srlzd = ion\serialize($custom, $srlzr);
/*
    C::custom::{foo:"bar"}
*/
?>
+
+
Note how this output compares to the output of the standard magic serializable object.
Unserialization works as used to, except sepcifying thwe custom unserialization method to call:
+<?php
 Â
$unsrlzr = new ion\Unserializer\PHP(callCustomUnserialize: "init");
var_dump(ion\unserialize($srlzd, $unsrlzr));
/*
    object(custom)#10 (1) {
    ["data":"custom":private]=>
    array(1) {
      ["foo"]=>
      string(3) "bar"
    }
  }
*/
?>
+
+
Instances of ion\Timestamp are really just plain \DateTime
objects augmented with Stringable
and ION specific formatting.
+<?=
new ion\Timestamp(
    precision: ion\Timestamp\Precision::FracTZ,
)Â
 Â
  // 2022-02-25T16:11:54.118+00:00
 Â
?>
+
+
+<?=
new ion\Timestamp(
    precision: ion\Timestamp\Precision::Day
)Â
 Â
  // 2022-02-25T
 Â
?>
+
+
+<?=
new ion\Timestamp(
  precision: ion\Timestamp\Precision::MinTZ,
  format: ion\Timestamp\Format::Min,
  datetime: "2020-03-15T12:34",
  timezone: new DateTimeZone("Europe/Vienna")
)Â
 Â
  // 2020-03-15T12:34+01:00
 Â
?>
+
+
+ ION supports many of PHP's data types:
Additonally to the plain and simple NULL
, ION can attach a type to NULL
values.
+<?php
 Â
$writer = new ion\Writer\Stream\Writer(STDOUT);
$writer->writeNull();
$writer->writeTypedNull(ion\Type::Int);
$writer->writeTypedNull(ion\Type::String);
$writer->flush();
/*
    null
    null.int
    null.string
*/
?>
+
+
The bool
type does not need a lot of explanation:
+<?php
$writer->writeBool(true);
$writer->writeBool(false);
$writer->flush();
/*
    true
    false
*/
?>
+
+
The int
type comprises signed integers of arbitrary size.
+<?php
 Â
$writer->writeInt(123);
$writer->writeInt("12345678901234567890");
$writer->flush();
/*
    123
    12345678901234567890
*/
?>
+
+
In ION-Text, underscores are allowed to separate digits:
+<?php
 Â
$reader = new ion\Reader\Buffer\Reader("-123_456_789");
$reader->next();
var_dump($reader->getType());
var_dump($reader->readInt());
/*
    enum(ion\Type::Int)
    int(-123456789)
*/
?>
+
+
Hexadecimal as well as binary notation are supported, too:
+<?php
$reader = new ion\Reader\Buffer\Reader("0xdead_beef");
$reader->next();
var_dump($reader->readInt());
/*
    int(3735928559)
*/
$reader = new ion\Reader\Buffer\Reader("0b10000100_11001001");
$reader->next();
var_dump($reader->readInt());
/*
    int(33993)
*/
?>
+
+
Ion supports both binary and lossless decimal encodings of real
numbers as, respectively, types float
and decimal
. In the text format, float
values are denoted much like the decimal formats in C or Java; decimal
values use d
instead of e
to start the exponent.
Reals without an exponent are treated as decimal. As with JSON, extra leading zeros are not allowed. Digits may be separated with an underscore.
+<?php
var_dump(ion\serialize(0.123));
/*
    string(25) "0.12299999999999999822e+0"
*/
var_dump(ion\unserialize("[0.123e, 123e-3]"));
/*
  array(2) {
    [0]=>
    float(0.123)
    [1]=>
    float(0.123)
  }
*/
?>
+
+
+<?php
var_dump(ion\serialize(new ion\Decimal("0.123")));
/*
    string(5) "0.123"
*/
var_dump(ion\unserialize("[0.123d0, 123d-3]"));
/*
  array(2) {
    [0]=>
    object(ion\Decimal)#8 (2) {
      ["number"]=>
      string(5) "0.123"
      // ...
    }
    [1]=>
    object(ion\Decimal)#11 (2) {
      ["number"]=>
      string(5) "0.123"
      // ...
    }
  }
*/
?>
+
+
Ion strings are Unicode character sequences of arbitrary length.
In the text format, strings are delimited by double
-quotes and follow common backslash-escape conventions (see official spec). The binary format always uses UTF-8 encoding.
+<?=
ion\serialize([
  "abc", "new
line"
]);
/*
    ["abc", "new\nline"]
*/
?>
+
+
The text format supports an alternate syntax for âlong stringsâ, including those that break across lines. Sequences bounded by three single-quotes (''') can cross multiple lines and still count as a valid, single string
. In addition, any number of adjacent triple-quoted strings are concatenated into a single value.
The concatenation happens within the Ion text parser and is neither detectable via the data model nor applicable to the binary format. Note that comments are always treated as whitespace, so concatenation still occurs when a comment falls between two long
strings.
+<?php
 Â
var_dump(ion\unserialize("
'''
  here areÂ
  several new
  lines
'''
"));
/*
string(35)Â "
  here areÂ
  several new
  lines
"
*/
?>
+
+
Ion defines three container types: structures, lists, and S-expressions. These types are defined recursively and may contain values of any Ion type.
Lists are ordered collections of values. The contents of the list are heterogeneous (that is, each element can have a different type). In the text format, lists are bounded by square brackets and elements are separated by commas.
+<?=
ion\serialize([1, "yes", null]);
/*
  [1,"yes",null]
*/
?>
+
+
Structures are unordered collections of name/value pairs. The names are symbol tokens, and the values are unrestricted. Each name/value pair is called a field.
In the text format, structures are wrapped by curly braces, with a colon between each name and value, and a comma between the fields. For the purposes of JSON compatibility, itâs also legal to use strings for field names, but they are converted to symbol tokens by the parser.
+<?=
ion\serialize([
  "outlaw",
  "key" => "value",
  "obj" => (object)["key" => "value"]
]);
/*
    {'0':"outlaw",key:"value",obj:o::{key:"value"}}
*/
?>
+
+
+<?php
var_dump(ion\unserialize(
  '{\'0\':"outlaw",key:"value",obj:o::{key:"value"}}'
));
/*
  array(3) {
    [0]=>
    string(6) "outlaw"
    ["key"]=>
    string(5) "value"
    ["obj"]=>
    object(stdClass)#10 (1) {
      ["key"]=>
      string(5) "value"
    }
  }
*/
?>
+
+
There are a handful of data types treated in a specific way in PHP; consider the following examples:
Symbols are much like strings, in that they are Unicode character sequences. The primary difference is the intended semantics: symbols represent semantic identifiers as opposed to textual literal values. Symbols are case sensitive.
In the text format, symbols are delimited by single-quotes and use the same escape characters.
See Ion Symbols for more details about symbol representations and symbol tables, and our section on Symbols, Tables and Catalogs for a distilled read.
See the section on reals for an introduction.
+<?php
$d = new ion\Decimal(123);
echo ion\serialize($d), " = ", $d->isInt() ? "int" : "noint", "\n";
//Â 123d0Â =Â int
$d = new ion\Decimal("123.123");
echo ion\serialize($d), " = ", $d->isInt() ? "int" : "noint" ,"\n";
//Â 123.123Â =Â noint
?>
+
+
See the official ION spec about real numbers and also Ion Float and Ion Decimals for more notes.
The blob
type allows embedding of arbitrary raw binary data. Ion treats such data as a single (though often very large) value. It does no processing of such data other than passing it through intact.
In the text format, blob
values are denoted as RFC 4648-compliant Base64 text within two pairs of curly braces.
+{{Â dHdvIHBhZGRpbmcgY2hhcmFjdGVycw==Â }}
+
The clob
type is similar to blob
in that it holds uninterpreted binary data. The difference is that the content is expected to be text, so we use a text notation thatâs more readable than Base64.
+{{ "This is a CLOB of text." }}
+
See the official ION specification on Blobs and Clobs.
Timestamps represent a specific moment in time, always include a local offset, and are capable of arbitrary precision.
Instances of ion\Timestamp are really just plain \DateTime
objects augmented with Stringable
and ION specific formatting.
+<?=
new ion\Timestamp(
    precision: ion\Timestamp\Precision::FracTZ,
)Â
 Â
  // 2022-02-25T16:11:54.118+00:00
 Â
?>
+
+
+<?=
new ion\Timestamp(
    precision: ion\Timestamp\Precision::Day
)Â
 Â
  // 2022-02-25T
 Â
?>
+
+
+<?=
new ion\Timestamp(
  precision: ion\Timestamp\Precision::MinTZ,
  format: ion\Timestamp\Format::Min,
  datetime: "2020-03-15T12:34",
  timezone: new DateTimeZone("Europe/Vienna")
)Â
 Â
  // 2020-03-15T12:34+01:00
 Â
?>
+
+
See also the official ION Timestamp specification.
+NOTE:
+
+The interfaceSerializable
has been deprecated in 8.1 and should be replaced with magic serialize methods.
+<?php
 Â
class srlzbl implements \Serializable {
  private $data = "foo";
  public function serialize() {Â
    return "bar";Â
  }
  public function unserialize($data) {Â
    $this->data = $data;Â
  }
}
$srlzbl = new srlzbl;
var_dump($srlzbl);
$srlzd = ion\serialize($srlzbl);
echo $srlzd;
/*
    object(srlzbl)#4 (1) {
        ["data":"srlzbl":private]=>
        string(3) "foo"
    }
   Â
    S::srlzbl::{{"bar"}}
*/
?>
+
+
Everything as expected so far, Serializable
return a string
, but since they cannot indicate whether it's a valid UTF-8 string
, a ion\Type::CLob or ion\Type::BLob, CLobs are assumed.
Unserialization does not offer any surprises, either:
+<?phpÂ
 Â
var_dump(ion\unserialize($srlzd));
/*
  object(srlzbl)#4 (1) {
    ["data":"srlzbl":private]=>
    string(3) "bar"
  }
*/
?>
+
+
Implementing serialization behavior with magic methods is the preferred way since 8.1:
+<?php
 Â
class magic {
  private string $foo = "foo";
  function __serialize() : array {
    return ["foo" => "bar"];
  }
  function __unserialize(array $data) : void {
    foreach ($data as $k => $v)Â
      $this->$k = $v;
  }
}
$magic = new magic;
var_dump($magic);
$srlzd = ion\serialize($magic);
echo $srlzd;
/*
  object(magic)#6 (1) {
    ["foo":"magic":private]=>
    string(3) "foo"
  }
  O::magic::{foo:"bar"}
*/
?>
+
+
Again, unserialization yields the expected results:
+<?php
 Â
var_dump(ion\unserialize($srlzd));
/*
  object(magic)#7 (1) {
    ["foo":"magic":private]=>
    string(3) "bar"
  }
*/
?>
+
+
Customly serializable objects work like magic serializable objects, with custom names for the magic methods.
+<?php
 Â
class custom {
  private array $data;
  function init(array $data) : void {
    $this->data = $data;
  }
  function export() : array {
    return $this->data;
  }
}
$custom = new custom;
$custom->init(["foo"Â =>Â "bar"]);
echo $srlzd = ion\serialize($custom);
/*
    c::custom::{data:p::custom::{foo:"bar"}}
   Â
*/
?>
+
+
The above is actually the result of serializing a standard class backed PHP object, because we didn't implement any serialization primitives and did neither specify a custom method to call. So let's just do that:
+<?php
 Â
$srlzr = new ion\Serializer\PHP(callCustomSerialize: "export");
echo $srlzd = ion\serialize($custom, $srlzr);
/*
    C::custom::{foo:"bar"}
*/
?>
+
+
Note how this output compares to the output of the standard magic serializable object.
Unserialization works as used to, except sepcifying thwe custom unserialization method to call:
+<?php
 Â
$unsrlzr = new ion\Unserializer\PHP(callCustomUnserialize: "init");
var_dump(ion\unserialize($srlzd, $unsrlzr));
/*
    object(custom)#10 (1) {
    ["data":"custom":private]=>
    array(1) {
      ["foo"]=>
      string(3) "bar"
    }
  }
*/
?>
+
+
An S-expression (or symbolic expression) is much like a list in that itâs an ordered collection of values. However, the notation aligns with Lisp syntax to connote use of application semantics like function calls or programming-language statements. As such, correct interpretation requires a higher-level context other than the raw Ion parser and data model.
In the text format, S-expressions are bounded by parentheses. S-expressions also allow unquoted operator symbols (in addition to the unquoted identifier symbols allowed everywhere), so commas are interpreted as values rather than element separators.
+null.sexp         // A null S-expression value
()                // An empty expression value
(cons 1 2)        // S-expression of three values
([hello][there])  // S-expression containing two lists
(a+-b)  ( 'a' '+-' 'b' )    // Equivalent; three symbols
(a.b;)  ( 'a' '.' 'b' ';')  // Equivalent; four symbols
+
Although Ion S-expressions use a syntax similar to Lisp expressions, Ion does not define their interpretation or any semantics at all, beyond the pure sequence-of-values data model indicated above.
The Catalog holds a collection of ion\Symbol\Table instances queried from ion\Reader and ion\Writer instances.
See also the ION spec's symbol guide chapter on catalog.
+<?php
$catalog = new ion\Catalog;
$symtab = ion\Symbol\PHP::asTable();
$catalog->add($symtab);
?>
+
+
+
+Â Â +---------+
  | Catalog |
  +----+----+-------------------------------+
  |    |                                    |
  |    |    +-------------+                 |
  |    +--->| SymbolTable |                 |
  |    |    +---+---------+---------------+ |
  |    |    |   |                         | |
  |    |    |   |     +-------------------+ |
  |    |    |   |     | Symbol (ID, Text) | |
  |    |    |   +---->| Symbol (ID, Text) | |
  |    |    |         | ...               | |
  |    |    +---------+-------------------+ |
  |    |                                    |
  |    |    +-------------+                 |
  |    +--->| SymbolTable |                 |
  |    |    +---+---------+---------------+ |
  |    |    |   |                         | |
  |    .    |   |     +-------------------+ |
  |    .    |   +---->| Symbol (ID, Text) | |
  |    .    |         | ...               | |
  |    .    +---------+-------------------+ |
  |    .                                    |
  +-----------------------------------------+
+
The Catalog holds a collection of ion\Symbol\Table instances queried from ion\Reader and ion\Writer instances.
See also the ION spec's symbol guide chapter on catalogs.
+<?php
$catalog = new ion\Catalog;
$symtab = ion\Symbol\PHP::asTable();
$catalog->add($symtab);
?>
+
+
There are three types of symbol tables:
Local symbol tables do not have names, while shared symbol tables require them; only shared symbol tables may be added to a catalog or to a writerâs list of imports.
Local symbol tables are managed internally by Ion readers and writers. No application configuration is required to tell Ion readers or writers that local symbol tables should be used.
Using local symbol tables requires the local symbol table (including all of its symbols) to be written at the beginning of the value stream. Consider an Ion stream that represents CSV data with many columns. Although local symbol tables will optimize writing and reading each value, including the entire symbol table itself in the value stream adds overhead that increases with the number of columns.
If it is feasible for the writers and readers of the stream to agree on a pre-defined shared symbol table, this overhead can be reduced.
Consider the following CSV in a file called test.csv
.
+Â id,type,state
 1,foo,false
 2,bar,true
 3,baz,true
 ...
+
An application that wishes to convert this data into the Ion format can generate a symbol table containing the column names. This reduces encoding size and improves read efficiency.
Consider the following shared symbol table that declares the column names of test.csv
as symbols. Note that the shared symbol table may have been generated by hand or programmatically.
+Â $ion_shared_symbol_table::{
   name: "test.csv.columns",
   version: 1,
   symbols: ["id", "type", "state"],
 }
+
This shared symbol table can be stored in a file (or in a database, etc.) to be resurrected into a symbol table at runtime.
Because the value stream written using the shared symbol table does not contain the symbol mappings, a reader of the stream needs to access the shared symbol table using a catalog.
Consider the following complete example:
+<?php
/**
 * Representing a CSV row
 */
class Row {
  public function __construct(
    public readonly int $id,
    public readonly string $type,
    public readonly bool $state = true
  ) {}
}
/* Fetch the shared symbol table from file, db, etc. */
$symtab = ion\unserialize(<<<'SymbolTable'
 $ion_shared_symbol_table::{
   name: "test.csv.columns",
   version: 1,
   symbols: ["id", "type", "state"],
 }
SymbolTable
);
/* Add the shared symbol table to a catalog */
$catalog = new ion\Catalog;
$catalog->add($symtab);
/* Use the catalog when writing the data */
$writer = new class(options: new ion\Writer\Options(
  catalog: $catalog,
  outputBinary: true
)) extends ion\Writer\Buffer\Writer {
  public function writeRow(Row $row) : void {
    $this->startContainer(ion\Type::Struct);
   Â
    $this->writeFieldname("id");
    $this->writeInt($row->id);
   Â
    $this->writeFieldName("type");
    $this->writeString($row->type);
   Â
    $this->writeFieldName("state");
    $this->writeBool($row->state);
   Â
    $this->finishContainer();
  }
};
$writer->writeRow(new Row(1, "foo", false));
$writer->writeRow(new Row(2, "bar"));
$writer->writeRow(new Row(3, "baz"));
$writer->flush();
?>
+
+
Let's inspect the binary ION stream and verify that the column names are actually replaced by SymbolIDs:
+<?php
 Â
foreach (str_split($writer->getBuffer(), 8) as $line) {
    printf("%-26s", chunk_split(bin2hex($line), 2, " "));
    foreach (str_split($line) as $byte) {
        echo $byte >= ' ' && $byte <= '~' ? $byte : ".";
    }
    echo "\n";
}
echo "\n";
/*
  e0 01 00 ea ee a2 81 83   ........  \Â
  de 9e 86 be 9b de 99 84   ........   |
  8e 90 74 65 73 74 2e 63   ..test.c    > here's ION symbol table metadata
  73 76 2e 63 6f 6c 75 6d   sv.colum   |
  6e 73 85 21 01 88 21 03   ns.!..!.  <
  da 8a 21 01 8b 83 66 6f   ..!...fo   |
  6f 8c 11 da 8a 21 02 8b   o....!..    > here's the actual data
  83 62 61 72 8c 11 da 8a   .bar....   |
  21 03 8b 83 62 61 7a 8c   !...baz.  /
  11                        .
*/
?>
+
+
When unserializing without knowing the used symbols, our column name will actually be just symbol IDs $<SID>
:
+<?php
var_dump(ion\unserialize($writer->getBuffer(), [
  "multiSequence" => true,
]));
/*
array(3)Â {
  [0]=>
  array(3) {
    ["$10"]=>
    int(1)
    ["$11"]=>
    string(3) "foo"
    ["$12"]=>
    bool(false)
  }
  [1]=>
  array(3) {
    ["$10"]=>
    int(2)
    ["$11"]=>
    string(3) "bar"
    ["$12"]=>
    bool(true)
  }
  [2]=>
  array(3) {
    ["$10"]=>
    int(3)
    ["$11"]=>
    string(3) "baz"
    ["$12"]=>
    bool(true)
  }
}
*/
?>
+
+
When unserializing with known symbols, the symbol IDs will be resolved when using the catatalog with the appropriate symbol tables:
+<?php
 Â
var_dump(ion\unserialize($writer->getBuffer(), [
  "multiSequence" => true,
  "readerOptions" => [
    "catalog" => $catalog
  ]
]));
/*
  array(3) {
    [0]=>
    array(3) {
      ["id"]=>
      int(1)
      ["type"]=>
      string(3) "foo"
      ["state"]=>
      bool(false)
    }
    [1]=>
    array(3) {
      ["id"]=>
      int(2)
      ["type"]=>
      string(3) "bar"
      ["state"]=>
      bool(true)
    }
    [2]=>
    array(3) {
      ["id"]=>
      int(3)
      ["type"]=>
      string(3) "baz"
      ["state"]=>
      bool(true)
    }
  }
*/
?>
+
+
+ None.
NULL
array
|null
$writerOptions# = NULL
bool
$multiSequence# = false
Create a new PHP ION serializer.
-void
ion\Serializer\PHP::__construct([?ion\Writer\Options $writerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicSerialize = true
, [?string
$callCustomSerialize = NULL
]]]])
void
ion\Serializer\PHP::__construct([ion\Writer\Options|array
|null
$writerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicSerialize = true
, [?string
$callCustomSerialize = NULL
]]]])
void
ion\Serializer\PHP::__construct([?ion\Writer\Options $writerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicSerialize = true
, [?string
$callCustomSerialize = NULL
]]]])Create a new PHP ION serializer.
void
ion\Serializer\PHP::__construct([ion\Writer\Options|array
|null
$writerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicSerialize = true
, [?string
$callCustomSerialize = NULL
]]]])Create a new PHP ION serializer.
NULL
array
|null
$writerOptions# = NULL
bool
$multiSequence# = false
array
as multiple ION sequences.Create a new ION timestamp.
-void
ion\Timestamp::__construct(ion\Timestamp\Precision|int
$precision, [ion\Timestamp\Format|string
|null
$format = NULL
, [?string
$datetime = NULL
, [?DateTimeZone
$timezone = NULL
]]])
void
ion\Timestamp::__construct(ion\Timestamp\Precision|int
$precision, [ion\Timestamp\Format|string
|null
$format = NULL
, [?string
$datetime = NULL
, [DateTimeZone
|string
|null
$timezone = NULL
]]])
void
ion\Timestamp::__construct(ion\Timestamp\Precision|int
$precision, [ion\Timestamp\Format|string
|null
$format = NULL
, [?string
$datetime = NULL
, [?DateTimeZone
$timezone = NULL
]]])Create a new ION timestamp.
void
ion\Timestamp::__construct(ion\Timestamp\Precision|int
$precision, [ion\Timestamp\Format|string
|null
$format = NULL
, [?string
$datetime = NULL
, [DateTimeZone
|string
|null
$timezone = NULL
]]])Create a new ION timestamp.
int
$precision#string
$datetime# = NULL
DateTimeZone
$timezone# = NULL
DateTimeZone
|string
|null
$timezone# = NULL
None.
NULL
array
|null
$readerOptions# = NULL
bool
$multiSequence# = false
Create a new ION PHP unserializer.
-void
ion\Unserializer\PHP::__construct([?ion\Reader\Options $readerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicUnserialize = true
, [?string
$callCustomUnserialize = NULL
]]]])
void
ion\Unserializer\PHP::__construct([ion\Reader\Options|array
|null
$readerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicUnserialize = true
, [?string
$callCustomUnserialize = NULL
]]]])
void
ion\Unserializer\PHP::__construct([?ion\Reader\Options $readerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicUnserialize = true
, [?string
$callCustomUnserialize = NULL
]]]])Create a new ION PHP unserializer.
void
ion\Unserializer\PHP::__construct([ion\Reader\Options|array
|null
$readerOptions = NULL
, [bool
$multiSequence = false
, [bool
$callMagicUnserialize = true
, [?string
$callCustomUnserialize = NULL
]]]])Create a new ION PHP unserializer.
NULL
array
|null
$readerOptions# = NULL
bool
$multiSequence# = false
string
ion\serialize(mixed
$data, [?ion\Serializer $serializer = NULL
])Serialize a PHP value as ION data.
Serializes supported PHP values with the optionally provided \ion\Serializer:
string
ion\serialize(mixed
$data, [ion\Serializer|array
|null
$serializer = NULL
])Serialize a PHP value as ION data.
Serializes supported PHP values with the optionally provided \ion\Serializer:
NULL
bool
int
mixed
$data#NULL
array
|null
$serializer# = NULL
mixed
ion\unserialize(string
|resource
$data, [?ion\Unserializer $unserializer = NULL
])Unserialize ION data (stream) as PHP value(s).
mixed
ion\unserialize(string
|resource
$data, [ion\Unserializer|array
|null
$unserializer = NULL
])Unserialize ION data (stream) as PHP value(s).
string
|resource
$data#string
buffer or stream.NULL
array
|null
$unserializer# = NULL