This library can parse a TypeSchema specification either from a JSON file, or from PHP classes using reflection and annotations.

Overview

PSX Schema

About

This library can parse a TypeSchema specification either from a JSON file, or from PHP classes using reflection and annotations. Based on this schema it can generate source code and transform raw JSON data into DTO objects. Through this you can work with fully typed objects in your API for incoming and outgoing data. It provides basically the following features:

  • Transform raw JSON data into DTO objects
  • Generate source code based on a schema (i.e. PHP, Typescript)
  • Validate data according to the provided schema

Usage

At first, we need to describe our data format with a TypeSchema specification. Then we can generate based on this specification the fitting PHP classes.

{
  "definitions": {
    "Person": {
      "type": "object",
      "properties": {
        "firstName": {
          "type": "string"
        },
        "lastName": {
          "type": "string"
        },
        "age": {
          "description": "Age in years",
          "type": "integer",
          "minimum": 0
        }
      },
      "required": [
        "firstName",
        "lastName"
      ]
    }
  },
  "$ref": "Person"
}

To generate the PHP classes we use the following command:

vendor/bin/schema schema:parse --format=php schema.json

This generates the following Person.php file:

firstName = $firstName; } /** * @return string|null */ public function getFirstName() : ?string { return $this->firstName; } /** * @param string|null $lastName */ public function setLastName(?string $lastName) : void { $this->lastName = $lastName; } /** * @return string|null */ public function getLastName() : ?string { return $this->lastName; } /** * @param int|null $age */ public function setAge(?int $age) : void { $this->age = $age; } /** * @return int|null */ public function getAge() : ?int { return $this->age; } public function jsonSerialize() { return (object) array_filter(array('firstName' => $this->firstName, 'lastName' => $this->lastName, 'age' => $this->age), static function ($value) : bool { return $value !== null; }); } } ">


declare(strict_types = 1);

/**
 * @Required({"firstName", "lastName"})
 */
class Person implements \JsonSerializable
{
    /**
     * @var string|null
     */
    protected $firstName;
    /**
     * @var string|null
     */
    protected $lastName;
    /**
     * @var int|null
     * @Description("Age in years")
     * @Minimum(0)
     */
    protected $age;
    /**
     * @param string|null $firstName
     */
    public function setFirstName(?string $firstName) : void
    {
        $this->firstName = $firstName;
    }
    /**
     * @return string|null
     */
    public function getFirstName() : ?string
    {
        return $this->firstName;
    }
    /**
     * @param string|null $lastName
     */
    public function setLastName(?string $lastName) : void
    {
        $this->lastName = $lastName;
    }
    /**
     * @return string|null
     */
    public function getLastName() : ?string
    {
        return $this->lastName;
    }
    /**
     * @param int|null $age
     */
    public function setAge(?int $age) : void
    {
        $this->age = $age;
    }
    /**
     * @return int|null
     */
    public function getAge() : ?int
    {
        return $this->age;
    }
    public function jsonSerialize()
    {
        return (object) array_filter(array('firstName' => $this->firstName, 'lastName' => $this->lastName, 'age' => $this->age), static function ($value) : bool {
            return $value !== null;
        });
    }
}

Now we can parse raw JSON data and fill this in to our object model:

getSchema(Person::class); try { $person = (new SchemaTraverser())->traverse($data, $schema, new TypeVisitor()); // $example contains now an instance of the Person class containing // the firstName and lastName property echo $person->getFirstName(); } catch (\PSX\Schema\ValidationException $e) { // the validation failed echo $e->getMessage(); } ">
// the data which we want to import
$data = json_decode('{"firstName": "foo", "lastName": "bar"}');

$schemaManager = new \PSX\Schema\SchemaManager();

// we read the schema from the class
$schema = $schemaManager->getSchema(Person::class);

try {
    $person = (new SchemaTraverser())->traverse($data, $schema, new TypeVisitor());
    
    // $example contains now an instance of the Person class containing 
    // the firstName and lastName property
    echo $person->getFirstName();

} catch (\PSX\Schema\ValidationException $e) {
    // the validation failed
    echo $e->getMessage();
}

Every generated PHP class implements also the JsonSerializable interface so you can simply encode an object to json.

$schema = new Person();
$schema->setFirstName('foo');
$schema->setLastName('bar');
$schema->setAge(12);

echo json_encode($schema);

// would result in
// {"firstName": "foo", "lastName": "bar", "age": 12}

Generator

Beside PHP classes this library can generate the following types:

  • CSharp
  • Go
  • HTML
  • Java
  • JsonSchema
  • Markdown
  • PHP
  • Protobuf
  • Python
  • Swift
  • TypeSchema
  • TypeScript

Annotations

Please note that we use the doctrine/annotations library to parse the annotations. Because of this you need to setup a fitting annotation loader which can load the classes. To use the registered autoloader you can simply use: AnnotationRegistry::registerLoader('class_exists'). More information how to configure the loader at the documentation. For PHP 8 we plan to use native annotations so in the long term we will remove the doctrine/annotations library.

The following annotations are available:

Annotation Target Example
@Deprecated Property @Deprecated(true)
@Description Class/Property @Description("content")
@Discriminator Property @Discriminator("type")
@Enum Property @Enum({"foo", "bar"})
@Exclude Property @Exclude
@ExclusiveMaximum Property @ExclusiveMaximum(true)
@ExclusiveMinimum Property @ExclusiveMinimum(true)
@Format Property @Format("uri")
@Key Property @Key("$ref")
@Maximum Property @Maximum(16)
@MaxItems Property @MaxItems(16)
@MaxLength Property @MaxLength(16)
@MaxProperties Class @MaxProperties(16)
@Minimum Property @Minimum(4)
@MinItems Property @MinItems(4)
@MinLength Property @MinLength(4)
@MinProperties Property @MinProperties(4)
@MultipleOf Property @MultipleOf(2)
@Nullable Property @Nullable(true)
@Pattern Property @Pattern("A-z+")
@Required Class @Required({"name", "title"})
@Title Class @Title("foo")
@UniqueItems Property @UniqueItems(true)
Comments
  • register annotation autoloader

    register annotation autoloader

    Just like schema script,

    \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader, 'loadClass']);  
    

    We should also register autoloader callback in SchemaManager::construct(), 'cause Doctrine just checks if class exists without autoload

    opened by guweigang 6
  • [Semantical Error] Annotation @Items is not allowed to be declared on property

    [Semantical Error] Annotation @Items is not allowed to be declared on property

    I am having issues using the @Items annotation as of release v2.1.1, specifically:

    [Semantical Error] Annotation @Items is not allowed to be declared on property Data::$items. You may only use this annotation on these code elements: CLASS.

    According to README.md the @Items annotation should target PROPERTY. I double checked the source and it appears this is incorrectly targeting CLASS instead. Simply changing @Target("CLASS") to @Target("PROPERTY") in PSX\Schema\Parser\Popo\Annotation\Items.php makes everything work properly for me.

    If I'm missing something please let me know, otherwise I would be happy to create a PR for this fix.

    Thanks

    opened by bstoots 6
  • Problem dumping ArrayObjects in php 7.2.8

    Problem dumping ArrayObjects in php 7.2.8

    Hi, Christoph

    Just discovered your framework and I'm working psx/model it into an extension for Slim Framework. Just using the Swagger model by now.

    Found that PSX\Schema\Parser\Popo\Dumper doesn't really create JSON for me as expected. I think also your test would fail too, but was not able to execute the tests when executing them on your repo. Anyway. Seem to fail on the "Paths" ArrayObject.

    {
        "swagger": "2.0",
        "host": "localhost",
        "basePath": "\/",
        "schemes": ["http"],
        "consumes": ["application\/json"],
        "produces": ["application\/json"],
        "paths": {
            "\/": {},
            "\/resource": {},
            "\/routes": {},
            "\/swagger.json": {}
        },
        "definitions": {}
    }
    

    I fixed it for now with this function, not a beauty, but it works for me:

    use PSX\Schema\Parser\Popo\Dumper;
    
    class Util {
    
        /**
         * Utility class to solve
         *
         * @param $mixed
         * @return mixed
         */
        static public function dump($mixed) {
            static $dumper;
            if (is_null($dumper)) {
                $dumper = new Dumper();
            }
            $dumped = $dumper->dump($mixed);
            if(is_iterable($dumped)){
                foreach ($dumped as $prop => $value) {
                    if(is_a($value, "ArrayObject")){
                        $new_array = [];
                        foreach ($value->getArrayCopy() as $value=>$element){
                            $new_array[$value] = self::dump($element);
                        }
                        $dumped[$prop] = $new_array;
                    } else {
                        $dumped[$prop] = self::dump($value);
                    }
                }
            }
            return $dumped;
        }
    }
    

    Maybe this can be of any help for you.

    Anyway, the way you have structured and open sourced the framework is great. And fitting these models into my small project is really easy.

    best regards, Michael

    opened by mijohansen 4
  • Generate JsonSchema from PHP Annoation (for Fusio)

    Generate JsonSchema from PHP Annoation (for Fusio)

    I had some problems generating a JSON schema from PHP Annoations for Fusio (couldn't find a good example). After some trial & error I found the following solution ( @k42b3 Is there a better way?):

    // generateJsonSchema.php Generate JSON schema for class Test
    
    use PSX\Schema\Parser\Popo\Annotation\Description;
    use PSX\Schema\Parser\Popo\Annotation\Format;
    use PSX\Schema\Parser\Popo\Annotation\MaxProperties;
    use PSX\Schema\Parser\Popo\Annotation\MinProperties;
    use PSX\Schema\Parser\Popo\Annotation\Required;
    use PSX\Schema\Parser\Popo\Annotation\Title;
    use PSX\Schema\Parser\Popo\Annotation\Type;
    
    /**
     * Dummy class
     *
     * @Title("Test")
     * @Description("Test model")
     * @Required({"id", "url"})
     * @MinProperties(2)
     * @MaxProperties(4)
     */
    class Test
    {
        /**
         * @var int
         * @Title("ID of Test")
         */
        protected $id;
    
        /**
         * @var string
         * @Title("Url of test")
         * @Type("string")
         * @Format("Uri")
         */
        protected $url;
    
        /**
         * @var string
         * @Title("Name of test")
         * @Type("string")
         */
        protected $name;
    }
    
    // Add composer autoload to AnnotationRegistry
    $loader = require_once __DIR__ . '/../vendor/autoload.php';
    \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader, 'loadClass']);
    
    // Create SchemaManager ( Uses 'PSX\\Schema\\Parser\\Popo\\Annotation' if no reader is set)
    // Enable debug
    $schemaManager = new \PSX\Schema\SchemaManager(null, null, true);
    $interface = $schemaManager->getSchema(Test::class);
    
    // Generate pretty JSON schema
    $jsonSchema = new PSX\Schema\Generator\JsonSchema();
    $json = $jsonSchema->generate($interface);
    
    echo $json;
    

    Result:

    {
        "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
        "id": "urn:schema.phpsx.org#",
        "type": "object",
        "title": "Test",
        "description": "Test model",
        "properties": {
            "id": {
                "title": "ID of Test"
            },
            "url": {
                "type": "string",
                "title": "Url of test",
                "format": "Uri"
            },
            "name": {
                "type": "string",
                "title": "Name of test"
            }
        },
        "minProperties": 2,
        "maxProperties": 4,
        "required": [
            "id",
            "url"
        ],
        "class": "Test"
    }
    

    Result:

    Maybe you can add this example or a better example to the Fusion (and psx-schema?) documentation?

    opened by SchwarzwaldFalke 4
  • Traverse assoc array instead of stdClass with json_decode

    Traverse assoc array instead of stdClass with json_decode

    I'm coming from an ancient version of psx-schema (https://github.com/apioo/psx-schema/commit/10446f644a7bffc87329fa7d22a615962f1116ed). There, it was possible to traverse array to an object.

    Since I updated to the latest version, I get this error:

    PSX\Schema\ValidationException: / must be of type object
    
    vendor/psx/schema/src/SchemaTraverser.php:477
    vendor/psx/schema/src/SchemaTraverser.php:101
    vendor/psx/schema/src/SchemaTraverser.php:83
    

    My code is as follows:

    use PSX\Schema\Parser\Popo\Annotation\AdditionalProperties;
    use PSX\Schema\Parser\Popo\Annotation\Description;
    use PSX\Schema\Parser\Popo\Annotation\Key;
    use PSX\Schema\Parser\Popo\Annotation\Required;
    use PSX\Schema\Parser\Popo\Annotation\Title;
    use PSX\Schema\Parser\Popo\Annotation\Type;
    
    /**
     * @Title("Interest")
     * @Description("POST /api/interest <br>Can be used relate a user to an event that at some point has been interesting. Useful for recommendations.")
     * @AdditionalProperties(false)
     */
    class Interest
    {
        /**
         * @Key("userId")
         * @Type("string")
         * @Required
         *
         * @var string
         */
        private $userId;
    
        /**
         * @Key("eventId")
         * @Type("string")
         * @Required
         *
         * @var string
         */
        private $eventId;
    
        /**
         * @return string
         */
        public function getUserId() : string
        {
            return $this->userId;
        }
    
        /**
         * @param string $userId
         */
        public function setUserId(string $userId)
        {
            $this->userId = $userId;
        }
    
        /**
         * @return string
         */
        public function getEventId() : string
        {
            return $this->eventId;
        }
    
        /**
         * @param string $eventId
         */
        public function setEventId(string $eventId)
        {
            $this->eventId = $eventId;
        }
    }
    
    $data = [
        'userId'  => '1',
        'eventId' => '9df073ed-acb2-466c-9207-da166f3148dc',
    ];
    $schemaName = Interest::class;
    
    $schema = $this->managerschemaManager->getSchema($schemaName);
    $traverser = new SchemaTraverser();
    
    return $traverser->traverse($data, $schema, new TypeVisitor());
    

    Is it correct that this is no longer supported? And that I should instead provide a stdClass with the data?

    opened by ruudk 2
  • Multiple types for property (PHP Annotation to JSON schema)

    Multiple types for property (PHP Annotation to JSON schema)

    I'm trying to generate a JSON schema from PHP Annoations. This works great as long as my properties have a single type (either string or integer or ...), but I have some properties which have type integer AND type null. In JSON schema this can be written as { "type": ["integer", "null"] } (JSON schema), but I couldn't find a way to generate such a JSON schema with the @Type anonnation.

    Do I miss something or does the @Type annoation only support a single type? Would you be interested in a PR to add this feature and if so which syntax would you expect?

    Workaround: @OneOf(@Schema(type="integer"), @Schema(type="null"))

    opened by christoph-bessei 2
  • "default" not available on `PropertyType`

    It doesn't seem to possible to get the default value of a property. I have the following schema file:

    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "additionalProperties": false,
      "properties": {
        "quantity": {
          "type": "integer",
          "default": 1234,
          "minimum": 1
        }
      },
      "required": [
        "quantity"
      ],
      "type": "object"
    }
    

    My code is as follows:

    use PSX\Schema\Parser\JsonSchema;
    use PSX\Schema\Schema;
    
    $schema     = JsonSchema::fromFile($path);
    $properties = $schema->getDefinition()->getProperties();
    
    $quantity = $properties['quantity'];
    $default  = $quantity->getDefault();
    
    // $default is NULL
    

    I started digging into the code and found the \PSX\Schema\Parser\JsonSchema\Document class, which has a method getRecProperty. Fetching defaults works if I add the following code to the method right after the enum property population:

    if (isset($data['default'])) {
        $property->setDefault($data['default']);
    }
    

    Not sure if that's the right solution but I can't see the 1234 value (default) being set anywhere if I dump the PropertyType instance.

    opened by codeaid 2
  • Use of renamed SchemaCommand

    Use of renamed SchemaCommand

    SchemaCommand was renamed to ParseCommand in this commit.

    But bin/schema still calls SchemaCommand, which causes "Class not found" errors:

    • https://github.com/apioo/psx-schema/blob/79cc1a82785846e80747d6f220290d090f58bb5d/bin/schema#L47
    opened by christoph-bessei 1
  • Add InputOption for namespace of generated PHP classes

    Add InputOption for namespace of generated PHP classes

    Currently it's not possible to set the namespace for generated PHP classes. This PR adds an InputOption "--namespace" which is forwarded to Generator\PHP. Example command:

    vendor/bin/schema schema --namespace \\Example\\Namespace schema.json php
    
    opened by christoph-bessei 1
  • Use key as title inside definition

    Use key as title inside definition

    Inside the definitions keyword we should automatically set the key as title in case it is not available. Or maybe we should do this in general. The problem with using the key is that we then get multiple objects even if they are the same object i.e. different properties reference the same object. But then we are not required to use a title to generate a proper class name. See also the Kubernetes spec: https://github.com/kubernetes/kubernetes/blob/master/api/openapi-spec/swagger.json

    opened by chriskapp 0
Owner
Apioo
Apioo
The VarDumper component provides mechanisms for walking through any arbitrary PHP variable. It provides a better dump() function that you can use instead of var_dump().

VarDumper Component The VarDumper component provides mechanisms for walking through any arbitrary PHP variable. It provides a better dump() function t

Symfony 7.1k Dec 23, 2022
A better YAML configuration file management virion for PocketMine-MP 4

BetterConfig WARNING: This virion is under development and haven't been tested. Do not use this virion unless you know what you're doing. Contribution

KygekTeam International 1 Apr 30, 2022
PHP client library for the Square Connect APIs

Square Connect PHP SDK - RETIRED replaced by square/square-php-sdk NOTICE: Square Connect PHP SDK retired The Square Connect PHP SDK is retired (EOL)

Square 113 Dec 30, 2022
Simple yet expressive schema-based configuration library for PHP apps

league/config helps you define nested configuration arrays with strict schemas and access configuration values with dot notation.

The League of Extraordinary Packages 282 Jan 6, 2023
LOAD is a PHP library for configuration loading to APCu

LOAD LOAD is a PHP library for configuration loading to APCu Sources Available sources for configuration loading are: PHP file Consul Environment vari

Beat Labs 4 Jan 18, 2022
An object-oriented option parser library for PHP, which supports type constraints, flag, multiple flag, multiple values, required value checking

GetOptionKit Code Quality Versions & Stats A powerful option parser toolkit for PHP, supporting type constraints, flag, multiple flag, multiple values

Yo-An Lin 140 Sep 28, 2022
Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.

PHP dotenv Loads environment variables from .env to getenv(), $_ENV and $_SERVER automagically. Why .env? You should never store sensitive credentials

Vance Lucas 12.3k Jan 7, 2023
AWS SDK with readable code and async responses

AsyncAws client If you are one of those people that like the Amazon PHP SDK but hate the fact that you need to download Guzzle, PSR-7 and every AWS AP

Async AWS 375 Dec 24, 2022
A beautiful, fully open-source, tunneling service - written in pure PHP

Expose A completely open-source ngrok alternative - written in pure PHP. Documentation For installation instructions, in-depth usage and deployment de

Beyond Code 3.9k Dec 29, 2022
All PHP functions, rewritten to throw exceptions instead of returning false

Safe PHP This project is deprecated Because of how this project needs to be in sync with the official PHP documentation, maintaining a set of function

TheCodingMachine 2.1k Jan 2, 2023
A PHP parser for TOML

TOML parser for PHP A PHP parser for TOML compatible with TOML v0.4.0. Support: Installation Requires PHP >= 7.1. Use Composer to install this package

Yo! Symfony 175 Dec 26, 2022
:crystal_ball: Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API.

Better Reflection Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API. Why is it b

Roave, LLC 1.1k Dec 15, 2022
JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

Eboubaker Eboubaker 2 Jul 31, 2022
The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

Mark Armendariz 0 Jan 7, 2022
Miniset allows you to create compact sets of fields that either combine into a string of classes, or return a simple array of values

Miniset allows you to create compact sets of fields that either combine into a string of classes, or return a simple array of values. Miniset

Jack Sleight 5 Jun 13, 2022
Allows generate class files parse from json and map json to php object, including multi-level and complex objects;

nixihz/php-object Allows generate class files parse from json and map json to php object, including multi-level and complex objects; Installation You

zhixin 2 Sep 9, 2022
Simplexcel.php - Easily read / parse / convert / write between Microsoft Excel XML / CSV / TSV / HTML / JSON / etc spreadsheet tabular file formats

Simple Excel Easily parse / convert / write between Microsoft Excel XML / CSV / TSV / HTML / JSON / etc formats For further deatails see the GitHuib P

Faisal Salman 550 Dec 27, 2022
Library emulating the PHP internal reflection using just the tokenized source code

PHP Token Reflection In short, this library emulates the PHP reflection model using the tokenized PHP source. The basic concept is, that any reflectio

Ondřej Nešpor 190 Jul 25, 2022
Quickly and easily expose Doctrine entities as REST resource endpoints with the use of simple configuration with annotations, yaml, json or a PHP array.

Drest Dress up doctrine entities and expose them as REST resources This library allows you to quickly annotate your doctrine entities into restful res

Lee Davis 88 Nov 5, 2022