synatos / porta
OpenAPI 3.0 Validation
Requires
- php: >=8.1
- ext-json: *
- gm314/nitria: *
- symfony/yaml: *
Requires (Dev)
- codeception/codeception: 4.2.*
This package is not auto-updated.
Last update: 2024-11-22 13:23:37 UTC
README
Porta is greek and means door. This project can be used as door for your Rest API endpoints. The Open API standard allows to describe exactly what information in a rest api is coming in and leaving a server. The standard can be used for 1.) Description / Documentation of interfaces with tools like swagger 2.) Validation of incoming and outgoing Rest Messages 3.) Generation of Request/Response classes that allow to transfer untyped array/json to typed Objects.
Description
Porta is an Open API 3.0 Schema Validator for Http Requests. Key features of this implementation:
- Validate JSON request and responses
- Manipulate/Build Open API schema with objects
- Register your own format validator
- Generate Request and Response classes for the given schema
- Compile a Open API Schema to a class.
Installation
composer require synatos/porta
Import/Export
You can easily import export JSON/Yaml Files
use Synatos\Porta\Model\OpenAPI; $openAPI = new OpenAPI(); # yaml $openAPI->fromYamlFile(__DIR__ . "/my-open-api.yml"); $openAPI->toYamlFile(__DIR__ . "/export.yml"); # json $openAPI->fromJSONFile("my-open-api.json"); $openAPI->toJSONFile(__DIR__ . "/export.json");
Validation
Request Validation
Request can easily be validated against a given schema. Important: make sure the content type for the request is set in the http header. Open API allows several artifacts to be validated:
- Query Parameter
- Route Parameter
- Http Header
- Cookies
- Request Body
The example below shows how a http request is validated against the open API Schema.
use Synatos\Porta\Http\ContentType; use Synatos\Porta\Http\HttpHeader; use Synatos\Porta\Model\OpenAPI; use Synatos\Porta\Porta; require_once '../vendor/autoload.php'; $openAPI = new OpenAPI(); $openAPI->fromJSONFile(__DIR__ . '/open-api.json'); $porta = new Porta(); $porta->setOpenAPI($openAPI); $path = "/api/security/login-with-password"; $method = "POST"; $header = [ HttpHeader::CONTENT_TYPE => ContentType::APPLICATION_JSON ]; $query = []; $requestBody = json_encode([ "email" => "email@email.com", "password" => "abcd1234" ]); $validationMessageList = $porta->validateRequest($path, $method, $header,$query, $requestBody);
Register your own format validator
The Open API Specification allows to define formats to specify a datatype in more detail
"However, format is an open value, so you can use any formats, even not those defined by the OpenAPI Specification, such as:" (Source https://swagger.io/docs/specification/data-models/data-types/#string)
With porta you can register your own validator to handle formats. See the example below:
use Synatos\Porta\Contract\Validator; use Synatos\Porta\Http\ContentType; use Synatos\Porta\Http\HttpHeader; use Synatos\Porta\Model\OpenAPI; use Synatos\Porta\Model\Schema; use Synatos\Porta\Porta; use Synatos\Porta\Validator\FormatValidatorFactory; require_once '../vendor/autoload.php'; class EmailValidator implements Validator { public function validate(Schema $schema, $value, array $propertyPath): array { echo "validating " . $value . PHP_EOL; return []; } } FormatValidatorFactory::addFormatValidator("email", new EmailValidator());
Generate PHP Class from Schema
Schemas get easily get larger and parsing json/yaml including the file load can become time consuming. Therefore an existing schema can be compiled into a class the has the schema as array inline
use Synatos\Porta\Generator\OpenAPIClassGenerator; use Synatos\Porta\Model\OpenAPI; require_once '../vendor/autoload.php'; $openAPI = new OpenAPI(); $openAPI->fromJSONFile(__DIR__ . '/open-api.json'); $fullyQualifiedClassName = "Example\\CompiledSchema"; $psrPrefix = ""; $baseDir = __DIR__; $openAPIClassGenerator = new OpenAPIClassGenerator(); $openAPIClassGenerator->generate($openAPI, $fullyQualifiedClassName, $psrPrefix, $baseDir);
This compile a class with the following content:
class CompiledSchema { /** * @var OpenAPI */ private static $openAPI; /** * * @return OpenAPI */ public static function getOpenAPI() : OpenAPI { if (self::$openAPI === null) { self::$openAPI = new OpenAPI(); self::$openAPI->fromArray([ /* schema will be compiled into an array */]); } return self::$openAPI; } }
Generate Open API Class
You can generate for a given schema a class that will contain all the typed properties. Futhermore two method
\JsonSerializable
and fromArray(array $data)
allow to initialize from array
and generated an array again. This way you do not need to access
arrays anymore but can create the Schema object from an array and work fully typed with
the object.
$schemaContent = json_decode(file_get_contents(__DIR__ . '/request-schema.json'), true); $schema = new Schema(); $schema->fromArray($schemaContent); $psrPrefix = ""; $baseDir = __DIR__; $namespace = "Example"; $className = "UserRequest"; $generator = new SchemaToPHPGenerator($psrPrefix, $baseDir); $generator->generateSchema($namespace, $className, $schema); // see example/compile-schema-class.php
The generated class will look like this
class UserRequest implements \JsonSerializable { /** * @var string */ protected $id; /** * @param string|null $id * * @return void */ public function setId(?string $id) { $this->id = $id; } /** * * @return string|null */ public function getId() : ?string { return $this->id; } /** * @param array $array * * @return void */ public function fromArray(array $array) {/* ... */} /** * * @return array */ public function jsonSerialize() : array {/* ... */}
Model Manipulation
Every artifact in Open API has a object representation. So you can easily build up Schema, Operations or any other part
use Synatos\Porta\Http\ContentType; use Synatos\Porta\Model\MediaType; use Synatos\Porta\Model\OpenAPI; use Synatos\Porta\Model\Operation; use Synatos\Porta\Model\PathItem; use Synatos\Porta\Model\RequestBody; use Synatos\Porta\Model\Schema; require_once '../vendor/autoload.php'; $intSchema = new Schema(); $intSchema->setType(Schema::TYPE_INTEGER); $intSchema->setNullable(false); $objectSchema = new Schema(); $objectSchema->setNullable(true); $objectSchema->setProperties([ "intProperty" => $intSchema ]); $requestMediaType = new MediaType(); $requestMediaType->setSchema($objectSchema); $requestBody = new RequestBody(); $requestBody->setRequired(true); $requestBody->setContent([ ContentType::APPLICATION_JSON => $requestMediaType ]); $operation = new Operation(); $operation->setRequestBody($requestBody); $pathItem = new PathItem(); $pathItem->setOperationByMethod(PathItem::METHOD_POST, $operation); $openAPI = new OpenAPI(); $openAPI->setPaths([ "/api/do/something" => $pathItem ]);
Further Readings
- Introduction https://swagger.io/docs/specification/about/
- Open API Schema https://swagger.io/docs/specification/data-models/
- Specification https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md