PHPackages                             skrz/meta - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. skrz/meta

ActiveLibrary

skrz/meta
=========

Different wire formats, different data sources, single object model

3.1.4(2y ago)6689.9k↓100%5[10 issues](https://github.com/skrz/meta/issues)[2 PRs](https://github.com/skrz/meta/pulls)MITPHPPHP &gt;=7.4CI failing

Since Nov 1Pushed 2y ago7 watchersCompare

[ Source](https://github.com/skrz/meta)[ Packagist](https://packagist.org/packages/skrz/meta)[ RSS](/packages/skrz-meta/feed)WikiDiscussions master Synced today

READMEChangelog (4)Dependencies (5)Versions (21)Used By (0)

Skrz\\Meta
==========

[](#skrzmeta)

[![Build Status](https://camo.githubusercontent.com/2e62de76f41c92e5c34fa1855e91ffcb20bb252ab87294865d2b451d2d4e8a9a/68747470733a2f2f7472617669732d63692e6f72672f736b727a2f6d6574612e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/skrz/meta)[![Downloads this Month](https://camo.githubusercontent.com/71a8987b802937f1a55765e6644e836ab563125a32a7b4fa4e4480ca04e4a8b1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f736b727a2f6d6574612e737667)](https://packagist.org/packages/skrz/meta)[![Latest stable](https://camo.githubusercontent.com/9cf536a9f6193f485220c8a3776e7c934f635157b06ac656cfcdb94a3542bb44/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736b727a2f6d6574612e737667)](https://packagist.org/packages/skrz/meta)

> Different wire formats, different data sources, single object model

Requirements
------------

[](#requirements)

`Skrz\Meta` requires PHP `>= 5.4.0` and Symfony `>= 2.7.0`.

Installation
------------

[](#installation)

Add as [Composer](https://getcomposer.org/) dependency:

```
$ composer require skrz/meta
```

Why?
----

[](#why)

At [Skrz.cz](http://skrz.cz/), we work heavily with many different input/output formats and data sources (databases). E.g. data from partners come in as **XML feeds**; internally our **micro-service architecture** encodes data into **JSON** as wire format; data can come from **MySQL, Redis, and Elasticsearch** databases, and also has to be put in there.

However, in our PHP code base we want single object model that we could also share between projects. This need came mainly from **micro services' protocols** that got quite wild - nobody really knew what services sent to each other.

Serialization/deserialization had to be **fast**, therefore we created concept of so-called *meta classes*. A meta class is an object's companion class that handles object's serialization/deserialization from/into many different formats. Every class has exactly one meta class, in which methods from different *modules* are combined - *modules* can use each others methods (e.g. `JsonModule` uses methods generated by `PhpModule`).

Usage
-----

[](#usage)

Have simple value object:

```
namespace Skrz\API;

class Category
{
    /** @var string */
    public $name;

    /** @var string */
    public $slug;

    /** @var Category */
    public $parentCategory;

}
```

You would like to serialize object into JSON. What you might do is to create method `toJson`:

```
public function toJson()
{
    return json_encode(array(
        "name" => $this->name,
        "slug" => $this->slug,
        "parentCategory" => $this->parentCategory ? $this->parentCategory->toJson() : null
    ));
}
```

Creating such method for every value object that gets sent over wire is tedious and error-prone. So you generate meta class that implements such methods.

Meta classes are generated according to *meta spec*. A meta spec is a class extending `Skrz\Meta|AbstractMetaSpec`:

```
namespace Skrz;

use Skrz\Meta\AbstractMetaSpec;
use Skrz\Meta\JSON\JsonModule;
use Skrz\Meta\PHP\PhpModule;

class ApiMetaSpec extends AbstractMetaSpec
{

    protected function configure()
    {
        $this->match("Skrz\\API\\*")
            ->addModule(new PhpModule())
            ->addModule(new JsonModule());
    }

}
```

Method `configure()` initializes spec with *matchers* and *modules*. A matcher is a set of classes that satisfy certain criteria (e.g. namespace, class name). A module is generator that takes class matched by the matcher and generates module-specific methods in the meta class. `ApiMetaSpec` creates meta classes for every class directly in `Skrz\API` namespace (it does not include classes in sub-namespaces, e.g. `Skrz\API\Meta`). The meta classes are generated from PHP and JSON modules (`Skrz\Meta\BaseModule` providing basic functionality of a meta class is added automatically).

To actually generate classes, you have supply some files to spec to process:

```
use Symfony\Component\Finder\Finder;

$files = array_map(function (\SplFileInfo $file) {
    return $file->getPathname();
}, iterator_to_array(
    (new Finder())
        ->in(__DIR__ . "/API")
        ->name("*.php")
        ->notName("*Meta*")
        ->files()
));

$spec = new ApiMetaSpec();
$spec->processFiles($files);
```

Similar code should be part of your build process (or in development part of Grunt watch task etc.).

By default, spec generates meta class in `Meta` sub-namespace with `Meta` suffix (e.g. `Skrz\API\Category` -&gt; `Skrz\API\Meta\CategoryMeta`) and stores it inside `Meta` sub-directory of original class's directory.

After the meta classes has been generated, usage is quite simple:

```
use Skrz\API\Category;
use Skrz\API\Meta\CategoryMeta;

$parentCategory = new Category();
$parentCategory->name = "The parent category";
$parentCategory->slug = "parent-category";

$childCategory = new Category();
$childCategory->name = "The child category";
$childCategory->slug = "child-category";
$childCategory->parentCategory = $parentCategory;

var_export(CategoryMeta::toArray($childCategory));
// array(
//     "name" => "The child category",
//     "slug" => "child-category",
//     "parentCategory" => array(
//         "name" => "The parent category",
//         "slug" => "parent-category",
//         "parentCategory" => null,
//     ),
// )

echo CategoryMeta::toJson($childCategory);
// {"name":"The child category","slug":"child-category","parentCategory":{"name":"The parent category","slug":"parent-category","parentCategory":null}}

$someCategory = CategoryMeta::fromJson(array(
    "name" => "Some category",
    "ufo" => 42, // unknown fields are ignored
));

var_export($someCategory instanceof Category);
// TRUE

var_export($someCategory->name === "Some category");
// TRUE
```

### Fields

[](#fields)

- Fields represent set of symbolic field paths.
- They are composite (fields can have sub-fields).
- Fields can be supplied as `$filter` parameters in `to*()` methods.

```
use Skrz\API\Category;
use Skrz\API\Meta\CategoryMeta;
use Skrz\Meta\Fields\Fields;

$parentCategory = new Category();
$parentCategory->name = "The parent category";
$parentCategory->slug = "parent-category";

$childCategory = new Category();
$childCategory->name = "The child category";
$childCategory->slug = "child-category";
$childCategory->parentCategory = $parentCategory;

var_export(CategoryMeta::toArray($childCategory, null, Fields::fromString("name,parentCategory{name}")));
// array(
//     "name" => "The child category",
//     "parentCategory" => array(
//         "name" => "The parent category",
//     ),
// )
```

Fields are inspired by:

- [Facebook Graph API's `?fields=...` query parameter](https://developers.facebook.com/docs/graph-api/using-graph-api#fields)
- [Google Protocol Buffers' `FieldMask`](https://github.com/google/protobuf/blob/master/src/google/protobuf/field_mask.proto) (and its [JSON serialization](https://developers.google.com/protocol-buffers/docs/proto3#json))

### Annotations

[](#annotations)

`Skrz\Meta` uses [Doctrine annotation parser](https://github.com/doctrine/annotations). Annotations can change mappings. Also `Skrz\Meta` offers so called *groups* - different sources can offer different field names, however, they map onto same object.

### `@PhpArrayOffset`

[](#phparrayoffset)

`@PhpArrayOffset` annotation can be used to change name of outputted keys in arrays generated by `toArray` and inputs to `fromArray`:

```
namespace Skrz\API;

use Skrz\Meta\PHP\PhpArrayOffset;

class Category
{
    /**
     * @var string
     *
     * @PhpArrayOffset("THE_NAME")
     * @PhpArrayOffset("name", group="javascript")
     */
    protected $name;

    /**
     * @var string
     *
     * @PhpArrayOffset("THE_SLUG")
     * @PhpArrayOffset("slug", group="javascript")
     */
    protected $slug;

    public function getName() { return $this->name; }

    public function getSlug() { return $this->slug; }

}

// ...

use Skrz\API\Meta\CategoryMeta;

$category = CategoryMeta::fromArray(array(
    "THE_NAME" => "My category name",
    "THE_SLUG" => "category",
    "name" => "Different name" // name is not an unknown field, so it is ignored
));

var_export($category->getName());
// "My category name"

var_export($category->getSlug());
// "category"

var_export(CategoryMeta::toArray($category, "javascript"));
// array(
//     "name" => "My category name",
//     "slug" => "category",
// )
```

### `@JsonProperty`

[](#jsonproperty)

`@JsonProperty` marks names of JSON properties. (Internally every group created by `@JsonProperty` creates PHP group prefixed by `json:` - PHP object is first mapped to array using `json:` group, then the array is serialized using `json_encode()`.)

```
namespace Skrz\API;

use Skrz\Meta\PHP\PhpArrayOffset;
use Skrz\Meta\JSON\JsonProperty;

class Category
{
    /**
     * @var string
     *
     * @PhpArrayOffset("THE_NAME")
     * @JsonProperty("NAME")
     */
    protected $name;

    /**
     * @var string
     *
     * @PhpArrayOffset("THE_SLUG")
     * @JsonProperty("sLuG")
     */
    protected $slug;

    public function getName() { return $this->name; }

    public function getSlug() { return $this->slug; }

}

// ...

use Skrz\API\Meta\CategoryMeta;

$category = CategoryMeta::fromArray(array(
    "THE_NAME" => "My category name",
    "THE_SLUG" => "category",
));

var_export(CategoryMeta::toJson($category));
// {"NAME":"My category name","sLuG":"category"}
```

### `@XmlElement` &amp; `@XmlElementWrapper` &amp; `@XmlAttribute` &amp; `@XmlValue`

[](#xmlelement--xmlelementwrapper--xmlattribute--xmlvalue)

- Modelled after [javax.xml.bind.annotation](http://docs.oracle.com/javaee/7/api/javax/xml/bind/annotation/package-summary.html).
- Works with [`XMLWriter`](php.net/xmlwriter) or [`DOMDocument`](php.net/domdocument) (for streaming or DOM-based XML APIs).

```
// example: serialize object to XMLWriter

/**
 * @XmlElement(name="SHOPITEM")
 */
class Product
{
    /**
     * @var string
     *
     * @XmlElement(name="ITEM_ID")
     */
    public $itemId;

    /**
     * @var string[]
     *
     * @XmlElement(name="CATEGORYTEXT")
     */
    public $categoryTexts;
}

$product = new Product();
$product->itemId = "SKU123";
$product->categoryTexts = array("Home Appliances", "Dishwashers");

$xml = new \XMLWriter();
$xml->openMemory();
$xml->setIndent(true);
$xml->startDocument();
$meta->toXml($product, null, $xml);
$xml->endDocument();

echo $xml->outputMemory();
//
//
//   SKU123
//   Home Appliances
//   Dishwashers
//
```

For more examples see classes in `test/Skrz/Meta/Fixtures/XML` and `test/Skrz/Meta/XmlModuleTest.php`.

### `@PhpDiscriminatorMap` &amp; `@JsonDiscriminatorMap`

[](#phpdiscriminatormap--jsondiscriminatormap)

`@PhpDiscriminatorMap` and `@JsonDiscriminatorMap` encapsulate inheritance.

```
namespace Animals;

use Skrz\Meta\PHP\PhpArrayOffset;

/**
 * @PhpDiscriminatorMap({
 *     "cat" => "Animals\Cat", // specify subclass
 *     "dog" => "Animals\Dog"
 * })
 */
class Animal
{

    /**
     * @var string
     */
    protected $name;

}

class Cat extends Animal
{
    public function meow() { echo "{$this->name}: meow"; }
}

class Dog extends Animal
{
    public function bark() { echo "{$this->name}: woof"; }
}

// ...

use Animals\Meta\AnimalMeta;

$cat = AnimalMeta::fromArray(["cat" => ["name" => "Oreo"]]);
$cat->meow();
// prints "Oreo: meow"

$dog = AnimalMeta::fromArray(["dog" => ["name" => "Mutt"]]);
$dog->bark();
// prints "Mutt: woof"
```

### `@PhpDiscriminatorOffset` &amp; `@JsonDiscriminatorProperty`

[](#phpdiscriminatoroffset--jsondiscriminatorproperty)

`@PhpDiscriminatorOffset` and `@JsonDiscriminatorProperty` make subclasses differentiated using offset/property.

```
namespace Animals;

use Skrz\Meta\PHP\PhpArrayOffset;

/**
 * @PhpDiscriminatorOffset("type")
 * @PhpDiscriminatorMap({
 *     "cat" => "Animals\Cat", // specify subclass
 *     "dog" => "Animals\Dog"
 * })
 */
class Animal
{

    /**
     * @var string
     */
    protected $type;

    /**
     * @var string
     */
    protected $name;

}

class Cat extends Animal
{
    public function meow() { echo "{$this->name}: meow"; }
}

class Dog extends Animal
{
    public function bark() { echo "{$this->name}: woof"; }
}

// ...

use Animals\Meta\AnimalMeta;

$cat = AnimalMeta::fromArray(["type" => "cat", "name" => "Oreo"]);
$cat->meow();
// prints "Oreo: meow"

$dog = AnimalMeta::fromArray(["type" => "dog", "name" => "Mutt"]);
$dog->bark();
// prints "Mutt: woof"
```

Known limitations
-----------------

[](#known-limitations)

- `private` properties cannot be hydrated. Hydration of private properties would require using reflection, or using `unserialize()` hack, which is contrary to requirement of being fast. Therefore meta classes compilation will fail if there is a `private`property. If you need a `private` property, mark it using `@Transient` annotation and it will be ignored.
- There can be at most 31/63 groups in one meta class. Group name is encoded using bit in integer type. PHP integer is platform dependent and always signed, therefore there can be at most 31/63 groups depending on platform the PHP's running on.

TODO
----

[](#todo)

- YAML - just like JSON
- `@XmlElementRef`

License
-------

[](#license)

The MIT license. See `LICENSE` file.

###  Health Score

41

↑

FairBetter than 89% of packages

Maintenance17

Infrequent updates — may be unmaintained

Popularity40

Moderate usage in the ecosystem

Community19

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 78.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~178 days

Recently: every ~565 days

Total

20

Last Release

768d ago

Major Versions

v1.3.0 → v2.0.02015-09-23

v2.1.0 → v3.0.02016-02-18

PHP version history (3 changes)v2.0.0PHP &gt;=5.3

v3.0.0PHP &gt;=5.4

3.1.3PHP &gt;=7.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/a9a85a90ea8b2060137fbfb3baaf7604c0681d27022331e090fd42401d578880?d=identicon)[Skrz](/maintainers/Skrz)

---

Top Contributors

[![jakubkulhan](https://avatars.githubusercontent.com/u/95001?v=4)](https://github.com/jakubkulhan "jakubkulhan (52 commits)")[![klatys](https://avatars.githubusercontent.com/u/725081?v=4)](https://github.com/klatys "klatys (6 commits)")[![mzstic](https://avatars.githubusercontent.com/u/4610204?v=4)](https://github.com/mzstic "mzstic (3 commits)")[![Zemistr](https://avatars.githubusercontent.com/u/2613208?v=4)](https://github.com/Zemistr "Zemistr (2 commits)")[![jerryx-jx](https://avatars.githubusercontent.com/u/6798717?v=4)](https://github.com/jerryx-jx "jerryx-jx (1 commits)")[![senfix](https://avatars.githubusercontent.com/u/19265254?v=4)](https://github.com/senfix "senfix (1 commits)")[![ssteklik](https://avatars.githubusercontent.com/u/15930629?v=4)](https://github.com/ssteklik "ssteklik (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/skrz-meta/health.svg)

```
[![Health](https://phpackages.com/badges/skrz-meta/health.svg)](https://phpackages.com/packages/skrz-meta)
```

###  Alternatives

[spiral/framework

Spiral, High-Performance PHP/Go Framework

2.0k1.6M53](/packages/spiral-framework)[oro/platform

Business Application Platform (BAP)

648132.0k71](/packages/oro-platform)[mmoreram/gearman-bundle

Adds gearman support to your and Symfony4, Symfony5 project

236394.3k2](/packages/mmoreram-gearman-bundle)[handcraftedinthealps/rest-routing-bundle

This bundle provides automatic route registration for the Controllers

581.9M2](/packages/handcraftedinthealps-rest-routing-bundle)[thelia/thelia

Thelia is an ecommerce CMS.

8715.1k](/packages/thelia-thelia)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

707.5k20](/packages/open-dxp-opendxp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
