An OpenApi-compatible parameter processing library
This library aims to provide a reusable and framework independent codebase for describing and processing parameters compatible with the OpenApi 3.x standard.
It is useful for people who want to create RESTful APIs that are PHP code-driven (i.e. spec doesn't come from a YAML or JSON file, but is defined by the code itself).
- Provides an OpenApi3-compatible low-level API to define and describe parameters
- Validation via the Symfony Validator component
- IDE auto-completion friendly
- Support for parameter dependencies
- Bottom-up, code-based approach
- PSR-4/PSR-12 compliant
- As close to 100% test coverage as is practically achievable
use OpenApiParams\OpenApiParams;
use OpenApiParams\PreparationStep\CallbackStep;
// Create an empty parameter list
$queryParams = OpenApiParams::queryParams();
// Add a string
$queryParams->addAlphaNumeric('test1', '_')
->makeRequired()
->setDescription('Test parameter')
->addPreparationStep(new CallbackStep('strtoupper', 'convert string to uppercase'))
->addDependsOn('test2');
// Add an integer
$queryParams->addInteger('test2')
->makeRequired()
->setDescription('Another test parameter')
->setMinimum(5)
->setMaximum(10)
->addPreparationStep(new CallbackStep('abs', 'Return the absolute value of the item passed'));
// Add a number
$queryParams->addNumber('test3')
->makeOptional()
->setDescription('A number parameter')
->min(10.05)
->max(25.35)
->setRequireDecimal(true);
// Add a 'yes/no' string
$queryParams->addYesNo('test4')
->setDescription('Boolean parameter');
// Prepared is an instance of OpenApi-Params\Model\ParameterValues
$prepared = $queryParams->prepare([
'test1' => 'ABC_123',
'test2' => -9,
'test3' => 15.25,
'test4' => 'on'
]);
/* $prepared will be:
*
* [
* 'test1' => 'abc_123' (string)
* 'test2' => 9 (integer)
* 'test3' => 15.25 (decimal)
* 'test4' => true (boolean)
* ]
*/
// The API documentation is an array
var_dump($queryParams->getApiDocumentation());
The concepts and abstractions in this library are based off of theOpenApi v3 specification. There are a few definitions:
- Parameter
- This is a strictly-defined, basic primitive data type OpenApi parameter.
- A parameter value can appear in the query string, body, cookie, header, or path.
- In this library, parameter types are represented by classes in the
Type
namespace, and they are fixed (you cannot create custom types):- String
- Boolean
- Object
- Array
- Number (float or double)
- Integer (int32 or int64)
- Parameter Value
- This library makes a distinction between parameters (definitions) and parameter values.
- You define parameter definitions and then pass the definitions a series of values. The library then checks the values against the definition rules.
- Format
- An OpenApi format.
- In this library, formats are represented by classes in the
Format
namespace, and they are extensible (you can create your own formats). - This library includes all the built-in formats defined by the OpenApi specification, as well as a few others.
- Preparation Step
- A class that transforms a value in some way (see details below).
- For example, the
SanitizeStep
sanitizes string parameters. - You can add as many custom preparation steps as you need to.
- Deserializer
- A class that deserializes arrays and objects
- This library includes one deserializer, and it behaves as follows:
- Path parameters are deserialized with
style=simple
,explode=true
- Query parameters are deserialized with
style=form
,explode=false
- Header parameters are deserialized with
style=simple
,explode=true
- Cookie parameters are deserialized with
style=form
,explode=false
- Body parameters are not deserialized
- Path parameters are deserialized with
- You can define a custom deserializer if the default one doesn't suit your needs
- Parameter Validation Rule
- A combination of a Symfony Constraint and a description of what the rule does.
OpenApi-Params supports all the built-in OpenApi v3 parameter types
and formats. Types are in the OpenApi-Params\Type
namespace, and formats are in the OpenApi-Params\Format
namespace.
In addition, OpenApi-Params provides a couple of extra built-in "convenience" formats:
- AlphanumericFormat - Accepts and validates alphanumeric values, with optional additional parameters
- EmailFormat - Accepts and validates any email address via the
egulias/email-validator
package - TemporalFormat - Accepts and converts to instance of
CarbonImmutable
any string supported by PHP'sstrtotime
function - UuidFormat - Accepts and validates any valid UUID
- YesNoFormat - Accepts, validates, and converts to boolean any "truthy" string, including 'true/false', '1/0', 'yes/no', or 'on/off'
OpenApi3 doesn't allow you to specify custom data types, but it does allow custom formats for data types.
Simply implement the Contract\ParamFormat
interface.
Or, for convenience, extend the Model\AbstractParamFormat
class:
use OpenApiParams\Model\AbstractParamFormat;
use OpenApiParams\Model\ParameterValidationRule;
use OpenApiParams\Type\StringParameter;
class IpAddressFormat extends AbstractParamFormat
{
// This is a required constant
public const TYPE_CLASS = StringParameter::class;
/**
* Get built-in validation rules
*
* These are added to the validation preparation step automatically
*
* @return array|ParameterValidationRule
*/
public function getValidationRules() : array
{
// TODO: Provide example of getValidationRules() method in action
return [];
}
}
In addition, if the parameter format does something to prepare or transform
the value, override the getPreparationSteps()
method.
An example of this is in the Format\CsvFormat
, which deserializes the
value into an array.
Finally, if you want the format to append to the description
in the API
documentation generated by this library, override the getDocumentation()
method.
By default, it returns NULL
, which means to not append anything to the description
.
For an example of a format that adds a description, refer to the
Format\Alphanumeric
format (which describes which characters are allowed
in the string).
Object values consist of nested Parameter objects. For example, consider the following JSON:API data structure:
{
"data": {
"id": "512",
"type": "people",
"attributes": {
"displayName": "Alice Jones",
"title": "CEO"
}
}
}
You would configure this structure via the following code:
import OpenApiParams\Type\ObjectParameter;
import OpenApiParams\Type\StringParameter;
use Respect\Validation\Validator as v;
$objParam = new ObjectParameter('data');
$objParam->addProperty((new StringParameter('id'))->addValidationRule(v::numericVal()));
$objParam->addProperty((new StringParameter('people'))->addValidationRule(v::alnum('_')));
$attrParam = (new ObjectParameter('attributes'))
->addProperty((new \OpenApiParams\Type\StringParameter('displayName'))->setSanitize(true))
->addProperty((new \OpenApiParams\Type\StringParameter('title')));
$objParam->addProperty($attrParam);
// Prepare data
$input = json_decode($rawInputValue);
$preparedData = $objParam->prepare($input);
This will recursively prepare all parameters defined as part of the object, and return an exception with all the errors listed that occurred during processing.
This library provides a built-in preparation step is for validation using the Symfony Validator Component library.
Rules are wrapped in the ParameterValidationRule
class, because most
rules require documentation (which is the point of the OpenApi specification).
However, you can add a rule to a parameter without documentation if you believe that documentation is not necessary (e.g. the validation is self-evident in the parameter format).
use OpenApiParams\Model\ParameterValidationRule;
use OpenApiParams\OpenApiParams;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\PasswordStrength;
$paramList = OpenApiParms::bodyParams();
$pwParam = $paramList->addPassword('password', required: true);
$ruleOne = new Length(min: 8);
$ruleTwo = new Regex('[/A-Za-z0-9_-!#]+/');
$ruleThree = new PasswordStrength(minScore: 3)
$pwParam->addValidationRules($ruleOne, $ruleTwo, $ruleThree);
$prepared = $paramList->prepare(['password' => 'correctHorseBatteryStaple!']);
$prepared->getPreparedValue('password');
Each parameter runs through a series of preparation steps which run in serial, one after the other. If everything succeeds, then the prepared value is returned. This allows you to transform the value into whatever the consuming library needs (an entity object, for example).
If an error or exception occurs during a step, subsequent steps are not run. See the "Handling Errors" section below for details.
Built-in preparation steps are automatically added for specific types and formats, and are in the
OpenApiParams\PreparationStep
namespace. The built-in steps are as follows, in alphabetical order:
Step Class | What it does |
---|---|
AllowNullPreparationStep |
Allows NULL values if specified in allowNullable is TRUE |
ArrayDeserializeStep |
Deserialize an array if there is a deserializer in the context |
ArrayItemsPreparationStep |
Prepares individual items in an array parameter |
CallbackStep |
Calls a custom callback (see below) |
DependencyCheckStep |
If there are parameter dependencies (e.g., param 'x' allowed only if 'y'), this step checks them |
EnsureCorrectDataTypeStep |
Checks if the data matches the expected type, and if typecasting is allowed, attempts to typecast the value |
EnumCheckStep |
Checks value against a list of allowed values (if specified) |
ObjectDeserializeStep |
Deserialize an object if there is a deserializer in the context |
PrepareObjectPropertiesStep |
Prepares individual properties in an object if they are specified |
ValidationStep |
Runs built-in validation rules (see above) |
SanitizeStep |
Optionally sanitizes string parameters with filter_var (off by default) |
In addition to the above built-in steps, this library provides for the fairly common use-case of needing to perform a custom action on a parameter. For example, converting a value into a database entity, processing a filter, or processing pagination info.
To create a custom callback for a parameter... todo: Complete this!
OpenApi-Params was designed around the assumption that errors would be most commonly turned into HTTP messages.
todo.. document in-depth
OpenApi-Params .. todo: document in-depth