Download the PHP package luciansabo/fields-options without Composer
On this page you can find all versions of the php package luciansabo/fields-options. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download luciansabo/fields-options
More information about luciansabo/fields-options
Files in luciansabo/fields-options
Package fields-options
Short Description Standard format for retrieving nested fields and field options in RESTful APIs
License MIT
Informations about the package fields-options
Motivation
Restful APIs try to obtain the same flexibility as GraphQL ones by implementing a fields
parameter used to
specify the fields that should be included in the response.
Most APIs use a query parameter and specify the fields separating them by commas.
This library tries to fill this gap.
Presenting the proposed standardized syntax
Assuming this structure:
Fields
A field can be part of a nested structure. You can specify a path to a field or a nested field in a similar way to GraphQL.
Root fields
In its basic form you specify the fields as keys with true
or false
as value.
true
means return me that field. It also means you want its default nested fields, if this is an objectfalse
means do not return me that field (if you omit a field it is considered that you don't want that field)
?fields=<url-encoded-json>
This is equivalent to not providing the profile
field, because if you start providing fields, the missing fields are considered unwanted.
Field groups
Field groups can be useful to group certain fields and ask to return/not return them.
The fields groups do not support any options, and they can only be true
or false
.
There are two special groups: _defaults
and _all
.
- If you want to indicate that the endpoint should return you the default fields or not use
_defauls
as field. - If you want to indicate that the endpoint should return you all available fields or not use
_all
as field.
_defaults group
_defaults
is assumed true
for a field nested fields if you only specify the parent field, and you don't provide a list of nested fields.
_defaults
is implicit true
only when you don't have a list of fields for root or for a sub-field.
When you specify a list of fields, it is considered false
, and you have to be explicit to include the default fields too.
The default fields logic should be embedded into the serialized object.
In this case there is no list of fields, so we will assume you want to export the default fields from profile:
This is equivalent to:
or with
Since, the root definition contains a list of fields (profile), then we will assume you don't want the default fields from the root,
and you only want the profile
object.
But if you want the root defaults in addition to the profile, you can specify _defaults
on the root:
If you don't want the defaults from the profile, but you only want a specific field like the profile id you can ask it and by leaving _defaults not specified we assume you don't want the defaults, because you asked for a specific field:
which is equivalent to
Specifying _defaults: false
only makes sense with a list of non-default fields included.
This is valid but not very useful, because without any additional fields in profile and with defaults disabled you will get null.
will get you after applying these:
These 3 examples are equivalent, and they all ask for the defaults in profile
:
Note how an empty object translates to _defaults: true:
_all group
Since _all
is false
by default, there is no point in setting it to false
.
_all
only makes sense if you want all fields, so use it with _all: true
As an example let's assume that the Profile DTO serializes by default only two fields: id
and name
.
The fields age
and education
will only be exported if specifically requested or with _all: true
.
We also assume the root DTO exports all fields by default (both id
and profile
).
This brings all fields exception profile
:
This brings all fields from profile
:
Precedence rules for built-in groups
_all
and_defaults
are mutually exclusive_all
has precedence over_defaults
Custom groups
You can also declare your own field groups and put a custom logic behind them. It is required to prefix group names with underscore, so they are detected.
You can still have field names starting with underscore, but they will not support options, just boolean values. If possible, avoid fields names starting with underscore.
Nested fields
The nested fields use the dot notation.
Example 1
To only retrieve the id
from the root and name
from profile
:
Decoded fields options:
Actual request with URL encoded fields options:
?fields=%7B%22id%22%3Atrue%2C%22profile%22%3A%7B%22name%22%3Atrue%7D%7D
Result:
Nested fields support field groups too.
Example 2
To only retrieve profile
field, and from profile
the default fields + age (assuming id
and name
are defaults and age
and education
are optional):
Decoded fields options:
Result:
Field options
The field options are specified using the _opt
key on the field you need them.
Example 1
To retrieve the id
from the root and from the profile
only the first institution, ordered by startYear
:
Actual request with URL encoded fields options:
?fields=%7B%22id%22%3Atrue%2C%22profile%22%3A%7B%22education%22%3A%7B%22_opt%22%3A%7B%22limit%22%3A1%2C%22sort%22%3A%22startYear%22%2C%22sortDir%22%3A%22asc%22%7D%7D%7D%7D
The result contains the default fields from the education object. Assuming all are retrieved by default:
Example 2
To retrieve from the profile
's education all fields except the institution's name, ordered by startYear
you can make _all
true
and set the institutionName
to false
.
Result:
Using the library
The FieldsOptions
object encapsulates the provided options and can be constructed from an array (coming from a request)
or using the FieldsOptionsBuilder
if you want to configure them programmatically.
It is up to the caller to honor these field options, but the library comes with a class called FieldsOptionsObjectApplier
that can be used to recursively apply the options on an object such as a DTO and his nested properties, so that the object will serialize with only the desired fields.
To specify a field path use the dot notation.
The internal structure should never be specified manually. You should always use the FieldsOptions
or FieldsOptionsBuilder
classes.
If you feel the need to manually build/alter the data array, then you are doing something wrong.
The library tries to hide these details, so it can protect you against BC breaks and structure changes.
Assuming this json structure was sent on the request:
FieldsOptionsBuilder
The earlier json structure of fields options can be configured programmatically using the builder. In general, it is recommended to use the builder instead of manually creating the array.
You can optionally use a validator that receives a sample DTO. The validator will receive an object (preferred) or an array representing the schema of the response.
You can include or exclude multiple fields at once from a given path
You also have methods to set all the options for a field at once
public function setFieldOptions(string $fieldPath, array $options): self
You can set a custom group field included
public function setGroupFieldIncluded(string $groupField, ?string $fieldPath = null): self
You can also create a builder with initial data. One scenario would be to add options to existing fields options.
Using the FieldsOptions class
Note the difference between isFieldIncluded()
and isFieldSpecified()
.
isFieldSpecified()
is simply a way to determine if the field was specified or not on the options, either with true
or false
.
isFieldIncluded()
will also check if the field is set to true
.
Validation if fields
This library comes with a Validator implementation that should work with array and object prototypes: Lucian\FieldsOptions\Validator
.
You can provide the validator to both FieldsOptions
and FieldsOptionsBuilder
.
A basic validator will be used in case it is not provider, that will only check the data structure.
If the validator is constructed with tbe prototype/schema, then including invalid fields will trigger a RuntimeException.
How validation works: To validate fields, the prototype is analyzed using reflection. All properties are considered valid fields. Typed properties have no issues, if they are scalar or classes. For properties without type-hinting the validator tries to inspect the value. If the value contains a collection, PHP does not have a Collection
type, and we cannot rely on phpdoc. In that case, you have to populate the array with at least an object, and we assume they are all the same type.
Testing field groups
Getting a list of included fields for a path
Using the FieldsOptionsObjectApplier class
Applying the options means making sure the data is serialized as expected by the given options. The approach in FieldsOptionsObjectApplier is to provide your DTO and your implementation of an ExportApplierInterface.