Skip to content

alexchexes/php-schema2class

 
 

Repository files navigation

JSON Schema to PHP Class Converter

Generate PHP classes from JSON Schema automatically. Use it from PHP code or the CLI, and enjoy immutability, self‑validation, and easily update the generated classes as your schema evolves.

Installation

Install with Composer:

composer require --dev helmich/schema2class

This fork is not released on Packagist yet. To install the current version from this repository, you can do the following:

  1. Add this to your composer.json and run composer install:
{
	"require-dev": {
		"helmich/schema2class": "dev-enhanced",
	},
	"repositories": [
		{
			"type": "vcs",
			"url": "https://github.com/alexchexes/php-schema2class"
		}
	]
}
  1. Or simply clone this repo and use it right away:
git clone https://github.com/alexchexes/php-schema2class
cd php-schema2class
# using spec file:
bin/s2c generate:fromspec PATH/TO/YOUR/SPEC-CONFIG.yaml
# using CLI options:
bin/s2c generate:fromschema path/to/schema.json path/to/output-dir

A release on Packagist is planned in the near future.

Usage

You can use this tool in three different ways:

A. Via the CLI – passing options and the schema path directly

vendor/bin/s2c generate:fromschema --class User my-schema.json src/TargetDir   # or ./my-schema.yaml

# On Windows CMD:
php vendor\bin\s2c generate:fromschema --class User my-schema.json src\TargetDir

See all available options.

B. Via the CLI – using a configuration file

  1. Create a configuration file, for example my-config.yaml (or the equivalent .json file):
options:
  targetPHPVersion: "7.4" # or "5.6", "8", "8.3", etc.
  noSetters: true
files:
  - input: "path/to/schema.json" # On Windows: "D:/path/to/schema.json"
    className: "MyClass" # Omit this if your schema is a list of definitions
    options:
      targetDirectory: "src/TargetDir"
      targetNamespace: "My\\Namespace"
  1. Run the generator:
vendor/bin/s2c generate:fromspec my-config.yaml   # or my-config.json

You can also name the config file .s2c.yaml and execute the command without arguments:

vendor/bin/s2c generate:fromspec

.s2c.yaml will be used automatically.

C. Programmatically – from your PHP code

To use the generator programmatically, create an instance of Schema2Class class and call its methods.

Generate from a configuration file:

use Helmich\Schema2Class\Schema2Class;

$generator = new Schema2Class();
$generator->generateFromSpec('my-config.yaml');

Or from a configuration defined as a PHP array:

$generator->generateFromSpec([
    'options' => [
        'targetPHPVersion' => '8.4',
    ],
    'files' => [
        [
            'input'     => 'path/to/schema.json', // or inline PHP assoc array
            'className' => 'MyClass',
            'options'   => [
                'targetDirectory' => 'src/TargetDir',
                'targetNamespace' => 'My\Namespace',
            ],
        ],
    ],
]);

Or from a schema provided as a PHP array:

$schema = [
    'required'   => ['name'],
    'properties' => [
        'name' => ['type' => 'string'],
    ],
];

$generator->generateFromSchema($schema, 'MyClass', [
            'targetDirectory' => 'MyDir',
            'targetNamespace' => 'My\Namespace',
        ]);

See also the advanced programmatic usage section.

Options

Both the CLI and specification files accept the following options:

CLI option Config file option Description
(1st argument) input JSON Schema file path (.json, .yml, or .yaml are supported) or an inline json schema object.
(2nd argument) targetDirectory Directory in which the generated files will be placed.
--clean-dir cleanTargetDirectory Remove all files from the target directory before writing new ones.
--target-namespace targetNamespace Namespace to use for every generated class (automatically determined from composer.json if omitted).
--class, -c className Class name to generate. Use this only when generating a single class from a schema that contains one top‑level object (not from a schema with multiple definitions).
--target-php, -p targetPHPVersion PHP version with which the generated code must be compatible. Numeric value without exact subversion like 5, 7 or 8 resolves to the latest (5.6, 7.4, 8.x), not to 5.0/7.0/8.0
--disable-strict-types disableStrictTypes Omit the strict_types declaration.
--inline-allof inlineAllofReferences Inline allOf references before generating classes.
--validator-expr newValidatorExpr Expression used to create a validator instance (e.g. new MyValidator()).
--arr-to-obj-expr arrayToObjectExpr Expression used to recursively convert arrays to objects (e.g. Utils::arrayToObjectRecursive - no call parens!).
--preserve-property-names preservePropertyNames Keep property names as is instead of converting them to camelCase (non-valid identifiers names will be sanitized).
--no-getters noGetters If true, no getter methods are generated and all properties are public.
--no-setters noSetters Do not generate withX() / withoutX() methods.
--mutable-setters[=chainable] mutableSetters Generate mutable setX() methods. Use chainable to return $this.
--no-schema-metadata noSchemaMetadata Remove description, title and other non-validation metadata fields from the embedded schema.
--single-line-schema singleLineSchema Store the validation schema on a single line in the generated class to make the .php file smaller.
--no-enums noEnums Disable generation of PHP enum classes even when targeting PHP 8.1 or newer.
--dry-run Print the output to the console instead of writing files.

Every option except input and className can appear both at the top level of the config file and within each item in the files array to override the default settings.
See the example config file.

Example workflow

To demonstrate Schema2Class's capabilities and how to use the generated code, consider the following workflow:

  1. You have a JSON Schema file like this (can be .json, .yml, or .yaml).

  2. You define the desired behaviour of the generator and run it:

    vendor/bin/s2c generate:fromspec basic-example-config.yaml
    • or by passing the same options directly to the CLI:
    vendor/bin/s2c generate:fromschema \
      examples/basic/basic-example.json \
      examples/basic/generated \
      --target-namespace Example\\Basic \
      --target-php 7.4 \
      --single-line-schema --no-schema-metadata
  3. By this point you're all set: Schema2Class has automatically created the PHP classes [1] and [2]:

    examples/basic/generated
       ├── Address.php
       └── User.php
    
    • Note: In a real project you can omit --target-namespace; Schema2Class will try to infer the target namespace from your composer.json file. If that fails, the last segment of the target directory is used as a fallback.
    • Also note: At the moment it is not possible to generate classes without a namespace.

Next, use the generated classes in your code:

// App.php
<?php

$someApiResponse = '...';
$userData = json_decode($someApiResponse, true);

$user = \Example\Basic\User::fromInput($userData);

// or, if for some reason you don't care about validation:
$user = \Example\Basic\User::fromInput($userData, false);

// Access object properties via getters:
echo "User name:   " . $user->getName();
echo "User status: " . $user->getStatus();
echo "User street: " . ($user->getAddress()?->getStreet());

// Or, if you used the `--no-getters` option, access them directly:
echo "User name:   " . $user->name;
echo "User status: " . $user->status;
echo "User street: " . ($user->address?->street);

// Update `status` WITHOUT mutating the original object:
$updatedUser = $user->withStatus("customer");

// Confirm:
echo "Old status: " . $user->getStatus();      // not mutated
echo "New status: " . $updatedUser->getStatus(); // 'customer'

// Finally, convert the updated user back to simple data structures:
$userAsArray  = $updatedUser->toArray();
$userAsObject = $updatedUser->toStdClass(); // if you need to distinguish arrays from object

Compatibility

  • The generated code can be made backwards‑compatible down to PHP 5.6. Use the --target-php CLI flag (or the targetPHPVersion option) to set the desired version.
  • The generator itself requires PHP 8.2+.
  • The tool runs on both Windows and Unix‑like systems.

Features

Generator

  • Generate a fully‑featured class from a JSON Schema file with a single command (zero configuration).
  • Hookable and extensible.

Generated classes

The generated classes offer:

  • Typing (PHP 7+ type hints plus PHPDoc) wherever possible
  • When the target PHP version is 8.1 or higher, enum values are emitted as PHP enum classes.
  • Use --no-enums/noEnums: true to keep the pre‑8.1 behaviour and avoid generating enum classes.
  • PHPDoc descriptions derived from schema "description" fields.
  • All PHP properties are private (unless --no-getters is used), with getter methods and explicit return type declarations (PHPDoc for PHP 5 mode).
  • Namespacing: Specify the namespace for all classes with --target-namespace (targetNamespace). If omitted, the generator inspects your composer.json and tries to infer it from the PSR‑4 configuration. If no match is found, the generator falls back to the name of the target directory. Generating classes without namespaces is currently not supported.
  • Class names are derived from the names in the "definitions" section. If input schema is a top-level object (not "definitions"), the class name will be infered from schema file name, unless you set it explicitly with the --class (className) option.
  • Class/enum names for sub‑objects are derived from property names.
  • Classes generated for array items are suffixed with "Item". See Example\Advanced\User::$hobbies.
  • oneOf/anyOf alternatives are suffixed with "AlternativeX", where X is an incrementing integer. See Example\Advanced\User::$payment.
  • The static method fromInput(array $data[, bool $validate = true]) accepts an array (for example the result of json_decode(..., true)), validates it against the schema, and creates the full object graph. No additional mapping is required. Note: Do not instantiate the class directly; always use fromInput(...).
  • To disable validation, pass false as the second argument of fromInput($data, false). Use at your own risk.
  • If your schema has default values and you want to use them to populate the object properties that are not set in the input, pass the parameter materializeDefaults = true to fromInput. The parameter is generated only when the schema has default values.
  • The toArray() method returns a plain PHP array representation of the object
  • The toStdClass() method returns a plain PHP stdClass representation of the object (useful if you need to distinguish objects from arrays — for example, when the object is empty or has numeric keys)
  • Properties are immutable by default; use withX() (or withoutX() for optional values) to create modified copies. Pass --mutable-setters to generate classic setX() methods instead.

Advanced programmatic usage

If you need more control, you can create a GeneratorRequest and pass it to SchemaToClassFactory:

use Helmich\Schema2Class\Generator\GeneratorRequest;
use Helmich\Schema2Class\Generator\SchemaLoader;
use Helmich\Schema2Class\Generator\SchemaToClassFactory;
use Helmich\Schema2Class\Spec\ValidatedSpecificationFilesItem;
use Helmich\Schema2Class\Spec\SpecificationOptions;
use Laminas\Code\Generator\ClassGenerator as LaminasClassGenerator;
use Symfony\Component\Console\Output\NullOutput;

$schema  = (new SchemaLoader())->loadSchema('example.json');
$request = new GeneratorRequest(
    $schema,
    new ValidatedSpecificationFilesItem('MyApp\\TargetDir', 'User', 'src/TargetDir'),
    new SpecificationOptions()
);

// Adding a hook
$hook = new class implements ClassCreatedHook {
    public function onClassCreated(string $name, LaminasClassGenerator $class): void
    {
        $class->addProperty('extra');
    }
};
$request = $request->withHook($hook);

$factory = new SchemaToClassFactory();
$factory->build(new \Helmich\Schema2Class\Writer\FileWriter(new NullOutput()), new NullOutput())
    ->schemaToClass($request);

Hook interfaces

The generator exposes several hook interfaces that let you customize the generated code:

  • ClassCreatedHook – called for every generated class.
  • EnumCreatedHook – called for every generated enum.
  • FileCreatedHook – called before each file is written.

Implement any of these interfaces and register the instance on a GeneratorRequest to adjust the generated output.

Custom $ref links lookup

If your schema contains $ref pointers to definitions that live outside the schema file being processed, or if you want to map some references to classes that already exist in your codebase, you can resolve such references as desired by implementing the ReferenceLookup interface and registering your lookup on a GeneratorRequest.

use Helmich\Schema2Class\Generator\GeneratorRequest;
use Helmich\Schema2Class\Generator\ReferencedType\ReferencedTypeInterface;
use Helmich\Schema2Class\Generator\ReferencedType\ReferencedTypeClass;
use Helmich\Schema2Class\Generator\ReferencedType\ReferencedTypeUnknown;
use Helmich\Schema2Class\Generator\ReferenceLookup\DefinitionsReferenceLookup;
use Helmich\Schema2Class\Generator\ReferenceLookup\ReferenceLookup;

class MyReferenceLookup implements ReferenceLookup
{
    public function __construct(private DefinitionsReferenceLookup $defaultLookup) {}

    // Must return instance of a class implementing `ReferencedTypeInterface`,
    // including the `ReferencedTypeUnknown` when reference cannot be resolved
    public function lookupReference(string $ref, GeneratorRequest $currentRequest): ReferencedTypeInterface
    {
        if ($ref === "#/properties/address") {
            // Point '#/properties/address' ref to existing class
            return new ReferencedTypeClass(CustomerAddress::class, $currentRequest);
        }

        // resolve other refs by built-in Schema2Class lookup mechanism
        $result = $this->defaultLookup->lookupReference($ref, $currentRequest);
        if ($result instanceof ReferencedTypeUnknown) {
            return new ReferencedTypeUnknown($currentRequest);
        }
        return $result;
    }

    // Must return the schema array referenced by the given `$ref` pointer,
    // or empty array if no schema available
    public function lookupSchema(string $ref): array
    {
        if ($ref === "#/properties/address") {
            return [
                'required' => ['city', 'street'],
                'properties' => [
                    'city' => ['type' => 'string', 'maxLength' => 32],
                    'street' => ['type' => 'string'],
                ],
            ];
        }

        return $this->defaultLookup->lookupSchema($ref);
    }
}

Then use it:

// use MyReferenceLookup\MyReferenceLookup;
use Helmich\Schema2Class\Generator\GeneratorRequest;
use Helmich\Schema2Class\Generator\SchemaToClassFactory;
use Helmich\Schema2Class\Generator\ReferenceLookup\DefinitionsReferenceLookup;

$req = new GeneratorRequest(/* ... */);
$req = $req->withReferenceLookup(
  new MyReferenceLookup(new DefinitionsReferenceLookup($schema['definitions'] ?? []))
);
(new SchemaToClassFactory())->build($writer, $output)->schemaToClass($req);

About

Generate PHP classes from JSON schemas

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • PHP 100.0%