{"id":13593117,"url":"https://github.com/swaggest/php-json-schema","last_synced_at":"2025-05-14T04:07:53.459Z","repository":{"id":44800453,"uuid":"80339376","full_name":"swaggest/php-json-schema","owner":"swaggest","description":"High definition PHP structures with JSON-schema based validation","archived":false,"fork":false,"pushed_at":"2024-12-22T21:18:51.000Z","size":698,"stargazers_count":463,"open_issues_count":25,"forks_count":52,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-10T22:29:11.001Z","etag":null,"topics":["hacktoberfest","json-schema","php","php-structures"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/swaggest.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-29T10:07:17.000Z","updated_at":"2025-04-09T18:57:30.000Z","dependencies_parsed_at":"2024-01-20T16:49:27.273Z","dependency_job_id":"e1e7e38c-ba22-4d29-a5ee-192811d66fc5","html_url":"https://github.com/swaggest/php-json-schema","commit_stats":{"total_commits":248,"total_committers":16,"mean_commits":15.5,"dds":0.08064516129032262,"last_synced_commit":"1f3a77a382c5d273a0f1fe34be3b8af4060a88cd"},"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swaggest%2Fphp-json-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swaggest%2Fphp-json-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swaggest%2Fphp-json-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swaggest%2Fphp-json-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swaggest","download_url":"https://codeload.github.com/swaggest/php-json-schema/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254069447,"owners_count":22009556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","json-schema","php","php-structures"],"created_at":"2024-08-01T16:01:16.747Z","updated_at":"2025-05-14T04:07:48.443Z","avatar_url":"https://github.com/swaggest.png","language":"PHP","readme":"# Swaggest JSON-schema implementation for PHP\n\n[![Build Status](https://travis-ci.org/swaggest/php-json-schema.svg?branch=master)](https://travis-ci.org/swaggest/php-json-schema)\n[![codecov](https://codecov.io/gh/swaggest/php-json-schema/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/php-json-schema)\n[![time tracker](https://wakatime.com/badge/github/swaggest/php-json-schema.svg)](https://wakatime.com/badge/github/swaggest/php-json-schema)\n![Code lines](https://sloc.xyz/github/swaggest/php-json-schema/?category=code)\n![Comments](https://sloc.xyz/github/swaggest/php-json-schema/?category=comments)\n\nHigh definition PHP structures with JSON-schema based validation.\n\nSupported schemas:\n\n* [JSON Schema Draft 7](http://json-schema.org/specification-links.html#draft-7)\n* [JSON Schema Draft 6](http://json-schema.org/specification-links.html#draft-6)\n* [JSON Schema Draft 4](http://json-schema.org/specification-links.html#draft-4)\n\n## Installation\n\n```\ncomposer require swaggest/json-schema\n```\n\n## Usage\n\nStructure definition can be done either with `json-schema` or with\n`PHP` class extending `Swaggest\\JsonSchema\\Structure\\ClassStructure`\n\n### Validating JSON data against given schema\n\nDefine your json-schema\n\n```php\n$schemaJson = \u003c\u003c\u003c'JSON'\n{\n    \"type\": \"object\",\n    \"properties\": {\n        \"id\": {\n            \"type\": \"integer\"\n        },\n        \"name\": {\n            \"type\": \"string\"\n        },\n        \"orders\": {\n            \"type\": \"array\",\n            \"items\": {\n                \"$ref\": \"#/definitions/order\"\n            }\n        }\n    },\n    \"required\":[\"id\"],\n    \"definitions\": {\n        \"order\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"price\": {\n                    \"type\": \"number\"\n                },\n                \"updated\": {\n                    \"type\": \"string\",\n                    \"format\": \"date-time\"\n                }\n            },\n            \"required\":[\"id\"]\n        }\n    }\n}\nJSON;\n```\n\nLoad it\n\n```php\nuse Swaggest\\JsonSchema\\Schema;\n$schema = Schema::import(json_decode($schemaJson));\n```\n\nValidate data\n\n```php\n$schema-\u003ein(json_decode(\u003c\u003c\u003c'JSON'\n{\n    \"id\": 1,\n    \"name\":\"John Doe\",\n    \"orders\":[\n        {\n            \"id\":1\n        },\n        {\n            \"price\":1.0\n        }\n    ]\n}\nJSON\n)); // Exception: Required property missing: id at #-\u003eproperties:orders-\u003eitems[1]-\u003e#/definitions/order\n```\n\nYou can also call `Schema::import` on string `uri` to schema json data.\n\n```php\n$schema = Schema::import('http://localhost:1234/my_schema.json');\n```\n\nOr with boolean argument.\n\n```php\n$schema = Schema::import(true); // permissive schema, always validates\n$schema = Schema::import(false); // restrictive schema, always invalidates\n```\n\n### Understanding error cause\n\nWith complex schemas it may be hard to find out what's wrong with your data. Exception message can look like:\n\n```\nNo valid results for oneOf {\n 0: Enum failed, enum: [\"a\"], data: \"f\" at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[0]\n 1: Enum failed, enum: [\"b\"], data: \"f\" at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[1]\n 2: No valid results for anyOf {\n   0: Enum failed, enum: [\"c\"], data: \"f\" at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[2]-\u003e$ref[#/cde]-\u003eanyOf[0]\n   1: Enum failed, enum: [\"d\"], data: \"f\" at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[2]-\u003e$ref[#/cde]-\u003eanyOf[1]\n   2: Enum failed, enum: [\"e\"], data: \"f\" at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[2]-\u003e$ref[#/cde]-\u003eanyOf[2]\n } at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo-\u003eoneOf[2]-\u003e$ref[#/cde]\n} at #-\u003eproperties:root-\u003epatternProperties[^[a-zA-Z0-9_]+$]:zoo\n```\n\nFor ambiguous schemas defined with `oneOf`/`anyOf` message is indented multi-line string.\n\nProcessing path is a combination of schema and data pointers. You can use `InvalidValue-\u003egetSchemaPointer()`\nand `InvalidValue-\u003egetDataPointer()` to extract schema/data pointer.\n\nYou can receive `Schema` instance that failed validation with `InvalidValue-\u003egetFailedSubSchema`.\n\nYou can build error tree using `InvalidValue-\u003einspect()`.\n\n### PHP structured classes with validation\n\n```php\n/**\n * @property int $quantity PHPDoc defined dynamic properties will be validated on every set\n */\nclass User extends ClassStructure\n{\n    /* Native (public) properties will be validated only on import and export of structure data */\n\n    /** @var int */\n    public $id;\n    public $name;\n    /** @var Order[] */\n    public $orders;\n\n    /** @var UserInfo */\n    public $info;\n\n    /**\n     * @param Properties|static $properties\n     * @param Schema $ownerSchema\n     */\n    public static function setUpProperties($properties, Schema $ownerSchema)\n    {\n        // You can add custom meta to your schema\n        $dbTable = new DbTable;\n        $dbTable-\u003etableName = 'users';\n        $ownerSchema-\u003eaddMeta($dbTable);\n\n        // Setup property schemas\n        $properties-\u003eid = Schema::integer();\n        $properties-\u003eid-\u003eaddMeta(new DbId($dbTable)); // You can add meta to property.\n\n        $properties-\u003ename = Schema::string();\n\n        // You can embed structures to main level with nested schemas\n        $properties-\u003einfo = UserInfo::schema()-\u003enested();\n\n        // You can set default value for property\n        $defaultOptions = new UserOptions();\n        $defaultOptions-\u003eautoLogin = true;\n        $defaultOptions-\u003egroupName = 'guest';\n        // UserOptions::schema() is safe to change as it is protected with lazy cloning\n        $properties-\u003eoptions = UserOptions::schema()-\u003esetDefault(UserOptions::export($defaultOptions));\n\n        // Dynamic (phpdoc-defined) properties can be used as well\n        $properties-\u003equantity = Schema::integer();\n        $properties-\u003equantity-\u003eminimum = 0;\n\n        // Property can be any complex structure\n        $properties-\u003eorders = Schema::create();\n        $properties-\u003eorders-\u003eitems = Order::schema();\n\n        $ownerSchema-\u003erequired = array(self::names()-\u003eid);\n    }\n}\n\nclass UserInfo extends ClassStructure {\n    public $firstName;\n    public $lastName;\n    public $birthDay;\n\n    /**\n     * @param Properties|static $properties\n     * @param Schema $ownerSchema\n     */\n    public static function setUpProperties($properties, Schema $ownerSchema)\n    {\n        $properties-\u003efirstName = Schema::string();\n        $properties-\u003elastName = Schema::string();\n        $properties-\u003ebirthDay = Schema::string();\n    }\n}\n\nclass UserOptions extends ClassStructure\n{\n    public $autoLogin;\n    public $groupName;\n\n    /**\n     * @param Properties|static $properties\n     * @param Schema $ownerSchema\n     */\n    public static function setUpProperties($properties, Schema $ownerSchema)\n    {\n        $properties-\u003eautoLogin = Schema::boolean();\n        $properties-\u003egroupName = Schema::string();\n    }\n}\n\nclass Order implements ClassStructureContract\n{\n    use ClassStructureTrait; // You can use trait if you can't/don't want to extend ClassStructure\n\n    const FANCY_MAPPING = 'fAnCy'; // You can create additional mapping namespace\n\n    public $id;\n    public $userId;\n    public $dateTime;\n    public $price;\n\n    /**\n     * @param Properties|static $properties\n     * @param Schema $ownerSchema\n     */\n    public static function setUpProperties($properties, Schema $ownerSchema)\n    {\n        // Add some meta data to your schema\n        $dbMeta = new DbTable();\n        $dbMeta-\u003etableName = 'orders';\n        $ownerSchema-\u003eaddMeta($dbMeta);\n\n        // Define properties\n        $properties-\u003eid = Schema::integer();\n        $properties-\u003euserId = User::properties()-\u003eid; // referencing property of another schema keeps meta\n        $properties-\u003edateTime = Schema::string();\n        $properties-\u003edateTime-\u003eformat = Format::DATE_TIME;\n        $properties-\u003eprice = Schema::number();\n\n        $ownerSchema-\u003esetFromRef('#/definitions/order');\n\n        // Define default mapping if any.\n        $ownerSchema-\u003eaddPropertyMapping('date_time', Order::names()-\u003edateTime);\n\n        // Use mapped name references after the default mapping was configured.\n        $names = self::names($ownerSchema-\u003eproperties);\n        $ownerSchema-\u003erequired = array(\n            $names-\u003eid,         \n            $names-\u003edateTime, // \"date_time\"\n            $names-\u003eprice       \n        );\n\n        // Define additional mapping\n        $ownerSchema-\u003eaddPropertyMapping('DaTe_TiMe', Order::names()-\u003edateTime, self::FANCY_MAPPING);\n        $ownerSchema-\u003eaddPropertyMapping('Id', Order::names()-\u003eid, self::FANCY_MAPPING);\n        $ownerSchema-\u003eaddPropertyMapping('PrIcE', Order::names()-\u003eprice, self::FANCY_MAPPING);\n    }\n}\n```\n\nValidation of dynamic properties is performed on set, this can help to find source of invalid data at cost of some\nperformance drop\n\n```php\n$user = new User();\n$user-\u003equantity = -1; // Exception: Value more than 0 expected, -1 received\n```\n\nValidation of native properties is performed only on import/export\n\n```php\n$user = new User();\n$user-\u003equantity = 10;\nUser::export($user); // Exception: Required property missing: id\n```\n\nError messages provide a path to invalid data\n\n```php\n$user = new User();\n$user-\u003eid = 1;\n$user-\u003ename = 'John Doe';\n\n$order = new Order();\n$order-\u003edateTime = (new \\DateTime())-\u003eformat(DATE_RFC3339);\n$user-\u003eorders[] = $order;\n\nUser::export($user); // Exception: Required property missing: id at #-\u003eproperties:orders-\u003eitems[0]\n```\n\n#### Nested structures\n\nNested structures allow you to make composition: flatten several objects in one and separate back.\n\n```php\n$user = new User();\n$user-\u003eid = 1;\n\n$info = new UserInfo();\n$info-\u003efirstName = 'John';\n$info-\u003elastName = 'Doe';\n$info-\u003ebirthDay = '1970-01-01';\n$user-\u003einfo = $info;\n\n$json = \u003c\u003c\u003cJSON\n{\n    \"id\": 1,\n    \"firstName\": \"John\",\n    \"lastName\": \"Doe\",\n    \"birthDay\": \"1970-01-01\"\n}\nJSON;\n$exported = User::export($user);\n$this-\u003eassertSame($json, json_encode($exported, JSON_PRETTY_PRINT));\n\n$imported = User::import(json_decode($json));\n$this-\u003eassertSame('John', $imported-\u003einfo-\u003efirstName);\n$this-\u003eassertSame('Doe', $imported-\u003einfo-\u003elastName);\n```\n\nYou can also use `\\Swaggest\\JsonSchema\\Structure\\Composition` to dynamically create schema compositions. This can be\nhelpful to deal with results of database query on joined data.\n\n```php\n$schema = new Composition(UserInfo::schema(), Order::schema());\n$json = \u003c\u003c\u003cJSON\n{\n    \"id\": 1,\n    \"firstName\": \"John\",\n    \"lastName\": \"Doe\",\n    \"price\": 2.66\n}\nJSON;\n$object = $schema-\u003eimport(json_decode($json));\n\n// Get particular object with `pick` accessor\n$info = UserInfo::pick($object);\n$order = Order::pick($object);\n\n// Data is imported objects of according classes\n$this-\u003eassertTrue($order instanceof Order);\n$this-\u003eassertTrue($info instanceof UserInfo);\n\n$this-\u003eassertSame(1, $order-\u003eid);\n$this-\u003eassertSame('John', $info-\u003efirstName);\n$this-\u003eassertSame('Doe', $info-\u003elastName);\n$this-\u003eassertSame(2.66, $order-\u003eprice);\n```\n\n#### Keys mapping\n\nIf property names of PHP objects should be different from raw data you can call `-\u003eaddPropertyMapping` on owner schema.\n\n```php\n// Define default mapping if any\n$ownerSchema-\u003eaddPropertyMapping('date_time', Order::names()-\u003edateTime);\n\n// Define additional mapping\n$ownerSchema-\u003eaddPropertyMapping('DaTe_TiMe', Order::names()-\u003edateTime, self::FANCY_MAPPING);\n$ownerSchema-\u003eaddPropertyMapping('Id', Order::names()-\u003eid, self::FANCY_MAPPING);\n$ownerSchema-\u003eaddPropertyMapping('PrIcE', Order::names()-\u003eprice, self::FANCY_MAPPING);\n```\n\nIt will affect data mapping:\n\n```php\n$order = new Order();\n$order-\u003eid = 1;\n$order-\u003edateTime = '2015-10-28T07:28:00Z';\n$order-\u003eprice = 2.2;\n$exported = Order::export($order);\n$json = \u003c\u003c\u003cJSON\n{\n    \"id\": 1,\n    \"date_time\": \"2015-10-28T07:28:00Z\",\n    \"price\": 2.2\n}\nJSON;\n$this-\u003eassertSame($json, json_encode($exported, JSON_PRETTY_PRINT));\n\n$imported = Order::import(json_decode($json));\n$this-\u003eassertSame('2015-10-28T07:28:00Z', $imported-\u003edateTime);\n```\n\nYou can have multiple mapping namespaces, controlling with `mapping` property of `Context`\n\n```php\n$options = new Context();\n$options-\u003emapping = Order::FANCY_MAPPING;\n\n$exported = Order::export($order, $options);\n$json = \u003c\u003c\u003cJSON\n{\n    \"Id\": 1,\n    \"DaTe_TiMe\": \"2015-10-28T07:28:00Z\",\n    \"PrIcE\": 2.2\n}\nJSON;\n$this-\u003eassertSame($json, json_encode($exported, JSON_PRETTY_PRINT));\n\n$imported = Order::import(json_decode($json), $options);\n$this-\u003eassertSame('2015-10-28T07:28:00Z', $imported-\u003edateTime);\n```\n\nYou can create your own pre-processor implementing `Swaggest\\JsonSchema\\DataPreProcessor`.\n\n#### Meta\n\n`Meta` is a way to complement `Schema` with your own data. You can keep and retrieve it.\n\nYou can store it.\n\n```php\n$dbMeta = new DbTable();\n$dbMeta-\u003etableName = 'orders';\n$ownerSchema-\u003eaddMeta($dbMeta);\n```\n\nAnd get back.\n\n```php\n// Retrieving meta\n$dbTable = DbTable::get(Order::schema());\n$this-\u003eassertSame('orders', $dbTable-\u003etableName);\n```\n\n#### Mapping without validation\n\nIf you want to tolerate invalid data or improve mapping performance you can specify `skipValidation` flag in\nprocessing `Context`\n\n```php\n$schema = Schema::object();\n$schema-\u003esetProperty('one', Schema::integer());\n$schema-\u003eproperties-\u003eone-\u003eminimum = 5;\n\n$options = new Context();\n$options-\u003eskipValidation = true;\n\n$res = $schema-\u003ein(json_decode('{\"one\":4}'), $options);\n$this-\u003eassertSame(4, $res-\u003eone);\n```\n\n#### Overriding mapping classes\n\nIf you want to map data to a different class you can register mapping at top level of your importer structure.\n\n```php\nclass CustomSwaggerSchema extends SwaggerSchema\n{\n    public static function import($data, ?Context $options = null)\n    {\n        if ($options === null) {\n            $options = new Context();\n        }\n        $options-\u003eobjectItemClassMapping[Schema::className()] = CustomSchema::className();\n        return parent::import($data, $options);\n    }\n}\n```\n\nOr specify it in processing context\n\n```php\n$context = new Context();\n$context-\u003eobjectItemClassMapping[Schema::className()] = CustomSchema::className();\n$schema = SwaggerSchema::schema()-\u003ein(json_decode(\n    file_get_contents(__DIR__ . '/../../../../spec/petstore-swagger.json')\n), $context);\n$this-\u003eassertInstanceOf(CustomSchema::className(), $schema-\u003edefinitions['User']);\n```\n\n## Code quality and test coverage\n\nSome code quality best practices are deliberately violated here\n(\nsee [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swaggest/php-json-schema/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swaggest/php-json-schema/?branch=master)\n) to allow best performance at maintenance cost.\n\nThose violations are secured by comprehensive test coverage:\n\n* draft-04, draft-06, draft-07 of [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)\n* test cases (excluding `$data` and few tests)\n  of [epoberezkin/ajv](https://github.com/epoberezkin/ajv/tree/master/spec) (a mature js implementation)\n\n## Contributing\n\nIssues and pull requests are welcome!\n\n[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/0)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/0)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/1)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/1)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/2)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/2)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/3)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/3)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/4)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/4)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/5)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/5)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/6)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/6)[![](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/images/7)](https://sourcerer.io/fame/vearutop/swaggest/php-json-schema/links/7)\n\nDevelopment supported by [JetBrains](https://www.jetbrains.com/community/opensource/#support).\n","funding_links":[],"categories":["PHP","类库"],"sub_categories":["未归类"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswaggest%2Fphp-json-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswaggest%2Fphp-json-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswaggest%2Fphp-json-schema/lists"}