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.
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:
- Add this to your
composer.jsonand runcomposer install:
{
"require-dev": {
"helmich/schema2class": "dev-enhanced",
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/alexchexes/php-schema2class"
}
]
}- 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-dirA release on Packagist is planned in the near future.
You can use this tool in three different ways:
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- Create a configuration file, for example
my-config.yaml(or the equivalent.jsonfile):
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"- Run the generator:
vendor/bin/s2c generate:fromspec my-config.yaml # or my-config.jsonYou 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.
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.
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.
To demonstrate Schema2Class's capabilities and how to use the generated code, consider the following workflow:
-
You have a JSON Schema file like this (can be
.json,.yml, or.yaml). -
You define the desired behaviour of the generator and run it:
- with a configuration file like this In this case, run:
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 -
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 yourcomposer.jsonfile. 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.
- Note: In a real project you can omit
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- The generated code can be made backwards‑compatible down to PHP 5.6. Use the
--target-phpCLI flag (or thetargetPHPVersionoption) to set the desired version. - The generator itself requires PHP 8.2+.
- The tool runs on both Windows and Unix‑like systems.
- Generate a fully‑featured class from a JSON Schema file with a single command (zero configuration).
- Hookable and extensible.
The generated classes offer:
- Typing (PHP 7+ type hints plus PHPDoc) wherever possible
- When the target PHP version is 8.1 or higher,
enumvalues are emitted as PHPenumclasses. - Use
--no-enums/noEnums: trueto 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-gettersis 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 yourcomposer.jsonand 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/anyOfalternatives are suffixed with "AlternativeX", where X is an incrementing integer. SeeExample\Advanced\User::$payment.- The static method
fromInput(array $data[, bool $validate = true])accepts an array (for example the result ofjson_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 usefromInput(...). - To disable validation, pass
falseas the second argument offromInput($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 = truetofromInput. 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 PHPstdClassrepresentation 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()(orwithoutX()for optional values) to create modified copies. Pass--mutable-settersto generate classicsetX()methods instead.
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);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.
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);