Coder Social home page Coder Social logo

metadata-parser's Introduction

Liip Metadata Parser

This project is Open Sourced based on work that we did initially as closed source at Liip, it may be lacking some documentation. If there is anything that you need or have questions about we would love to see you open an issue! :)

This is a parser for building model metadata from PHP classes. The metadata model can then be used to generate code or configuration. For example a serializer or ElasticSearch schema for types.

The metadata is geared toward this use case. PHP level constructs that represent the same information are grouped together: Methods for virtual properties and fields that have the same serialized name, but are valid in different versions.

This extensible parser can process PHP code and annotations or other metadata. You could write your own parsers, but this library comes with support for:

  • Reflection
  • PhpDoc
  • JMSSerializer annotations

Contributing

If you want to contribute to the project (awesome!!), please read the Contributing Guidelines and adhere to our Code Of Conduct

Where do I go for help?

If you need, open an issue.

Setup

use Doctrine\Common\Annotations\AnnotationReader;
use Liip\MetadataParser\Builder;
use Liip\MetadataParser\Parser;
use Liip\MetadataParser\RecursionChecker;
use Liip\MetadataParser\ModelParser\JMSParser;
use Liip\MetadataParser\ModelParser\LiipMetadataAnnotationParser;
use Liip\MetadataParser\ModelParser\PhpDocParser;
use Liip\MetadataParser\ModelParser\ReflectionParser;
use Liip\MetadataParser\ModelParser\VisibilityAwarePropertyAccessGuesser;

$parser = new Parser(
    new ReflectionParser(),
    new PhpDocParser(),
    new JMSParser(new AnnotationReader()),
    new VisibilityAwarePropertyAccessGuesser(),
    new LiipMetadataAnnotationParser(new AnnotationReader()),
);

$recursionChecker = new RecursionChecker(new NullLogger());

$builder = new Builder($parser, $recursionChecker);

Usage

The Builder::build method is the main entry point to get a ClassMetadata object. The builder accepts an array of Reducer to select which property to use when there are several options. A reducer can lead to drop a property if none of the variants is acceptable. Multiple options for a single field mainly come from the @SerializedName and @VirtualProperty annotations of JMSSerializer:

  • GroupReducer: Select the property based on whether it is in any of the specified groups;
  • VersionReducer: Select the property based on whether it is included in the specified version;
  • TakeBestReducer: Make sure that we end up with the property that has the same name as the serialized name, if we still have multiple options after the other reducers.
use Liip\MetadataParser\Reducer\GroupReducer;
use Liip\MetadataParser\Reducer\PreferredReducer;
use Liip\MetadataParser\Reducer\TakeBestReducer;
use Liip\MetadataParser\Reducer\VersionReducer;

$reducers = [
    new VersionReducer('2'),
    new GroupReducer(['api', 'detail']),
    new PreferredReducer(),
    new TakeBestReducer(),
];
$metadata = $builder->build(MyClass::class, $reducers);

The ClassMetadata provides all information on a class. Properties have a PropertyType to tell what kind of property they are. Properties that hold another class, are of the type PropertyTypeClass that has the method getClassMetadata() to get the metadata of the nested class. This structure is validated to not contain any infinite recursion.

Property naming strategy

By default, property names will be translated from a camelCased to a lower and snake_cased name (e.g. myProperty becomes my_property). If you want to keep the property name as is, you can change the strategy to identical via the following code:

\Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection::useIdenticalNamingStrategy();

Handling Edge Cases with @Preferred

This library provides its own annotation in Liip\MetadataParser\Annotation\Preferred to specify which property to use in case there are several options. This can be useful for example when serializing models without specifying a version, when they use different virtual properties in different versions.

use JMS\Serializer\Annotation as JMS;
use Liip\MetadataParser\Annotation as Liip;

class Product
{
    /**
     * @JMS\Since("2")
     * @JMS\Type("string")
     */
    public $name;
    
    /**
     * @JMS\Until("1")
     * @JMS\SerializedName("name")
     * @JMS\Type("string")
     * @Liip\Preferred
     */
    public $legacyName;
}

Expected Recursion: Working with Flawed Models

JMS annotation

If you are using JMS annotations, you can add the MaxDepth annotation to properties that might be recursive.

The following example will tell the metadata parser that the recursion is expected up to a maximum depth of 3.

use JMS\Serializer\Annotation as JMS;

class RecursionModel 
{
    /**
     * @JMS\MaxDepth(3)
     * @JMS\Type("RecursionModel")
     */
    public $recursion;
}

Pure PHP

The RecursionChecker accepts a second parameter to specify places where to break recursion. This is useful if your model tree looks like it has recursions but actually does not have them. JMSSerializer always acts on the actual data and therefore does not notice a recursion as long as it is not infinite.

For example, lets say you have a Product that has a field variants which is again list of Product. Those variant products use the same class and therefore have the variants field. However, in real data a variant never contains further variants. To avoid a recursion exception for this example, you would specify:

$expectedRecursions = [
    ['variants', 'variants'],
];
$recursionChecker = new RecursionChecker(new NullLogger(), $expectedRecursions);

With this configuration, the ClassMetadata found in the property type for the variants property of the final model will have no field variants, so that code working on the metadata does not need to worry about infinite recursion.

Extending the metadata parser

This library comes with a couple of parsers, but you can write your own to handle custom information specific to your project. Use the PropertyVariationMetadata::setCustomInformation method to add custom data, and use PropertyMetadata::getCustomInformation to read it in your metadata consumers.

metadata-parser's People

Contributors

brutal-factories avatar christianriesen avatar dbu avatar jlnfsslr avatar meyerbaptiste avatar michellesanver avatar mjanser avatar neur0toxine avatar spea avatar thepanz avatar tobion avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

metadata-parser's Issues

JMSParser change property name for snake_case by default

Where we are using JMS annotations JMSParser by default it changed property name to snake_case notation.

liip/metadata-parser/src/ModelParser/JMSParser.php:294

That's huge issue because JMS serializer works that if we don't annotate @SerializedName by default we are getting property name, because of that serialized property name in jsonData is not matching to object.
Deserialization Class:

/**
  * @Serializer\Type("boolean")
  * @Serializer\Accessor(getter="isTotalHotelBoxesCountApproximate", setter="setIsTotalHotelBoxesCountApproximate")
  *
  * @var bool $isTotalHotelBoxesCountApproximate
  */
private $isTotalHotelBoxesCountApproximate;

Generated liip file:

if (isset($modelTotalHotelBoxesCount)) {
    $model->setTotalHotelBoxesCount($modelTotalHotelBoxesCount);

}
unset($modelTotalHotelBoxesCount);
if (isset($jsonData['is_total_hotel_boxes_count_approximate'])) {
    $modelIsTotalHotelBoxesCountApproximate = $jsonData['is_total_hotel_boxes_count_approximate'];

But $jsonData is keeping isTotalHotelBoxesCountApproximate instead of is_total_hotel_boxes_count_approximate

There is also no possibility to force serializedName even property name is the same when we are using also PhpDocParser.
Error which appear You can not rename is_total_hotel_boxes_count_approximate into isTotalHotelBoxesCountApproximate as it is the same property. Did you miss to handle camelCase properties with PropertyCollection::serializedName?

Here I used that code
Class:

/**
  * @Serializer\SerializedName(value="isTotalHotelBoxesCountApproximate")
  * @Serializer\Type("boolean")
  * @Serializer\Accessor(getter="isTotalHotelBoxesCountApproximate", setter="setIsTotalHotelBoxesCountApproximate")
  *
  * @var bool $isTotalHotelBoxesCountApproximate
  */
private $isTotalHotelBoxesCountApproximate;

Parser:

$parsers = [
    new PhpDocParser(),
    new JMSParser(new AnnotationReader()),
];
$builder = new Builder(new Parser($parsers), new RecursionChecker(new NullLogger(), []));

test models extend from different namespace

we should add tests with models that are in different namespaces to make sure we handle relative class names correctly. see comments in #22 (where we found the bugs only when testing the library in a real project)

release 1.0?

time to tag a stable version 1?

we use this in a large project since a while and have not run into problems.

@Spea you contributed a couple of things, do you have any input on this? or any BC breaks in mind that we should do while we still can?

PHP 8.0 Support

Hello! I'm working on the API client library which uses a JMS serializer under the hood. Recently, I found out about liip/serializer and decided to give it a try because JMS serializer is painfully slow even on small datasets.

This attempt got me a 5x performance boost, which is obviously good.

My library should be available for PHP 8, but it's not possible yet because neither liip/serializer nor liip/metadata-parser are tested on PHP 8.

I am willing to work on that. Also, I just ran a full test suite on my library (it involves way too many calls to the generated code) and it seems that everything works like a charm. I also tested code generation - it works fine.

Will you accept PR with PHP 8 support if there will be one?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.