API Export and Import Entity
Two generic API Platform operations that export any entity to a JSON file and import it back, without having to write one operation per entity.
The operations are declared on two DTOs:
PHPReaction\Api\Dto\CommonBundle\EntityJsonExport—GET /{module}/{object}/{id}/export/jsonPHPReaction\Api\Dto\CommonBundle\EntityJsonImport—POST /{module}/{object}/import/json
Both are routed under the API Platform prefix (/{api_prefix}/v3, see config/routes/annotations.yaml).
Goal
Move a configuration entity from one environment (or one instance) to another: export it as JSON on
the source, import the file on the target. The import is an upsert on the slug: an entity with
the same slug is updated, otherwise a new one is created. Slugs are stable across instances (they are
generated once and carried in the JSON), so the same file can be imported repeatedly without creating
duplicates.
Typical use case: sharing dashboard reports, widgets, data collectors, templates, translations, etc.
Path parameters
| Parameter | Requirement | Description |
|---|---|---|
module | \w+ | Module name, without the Bundle suffix (ex: product, dataAnalysis). |
object | \w+ | Entity name (ex: type, dataCollector). |
id | \d+ | Entity ID (export only). |
module and object are resolved to an entity class by
ModuleNameObjectNameHelper::generateClassNameByModuleAndObjectName():
PHPReaction\Entity\<ucfirst(module)>Bundle\<ucfirst(object)>So /dataAnalysis/dataCollector/12/export/json targets
PHPReaction\Entity\DataAnalysisBundle\DataCollector. Only the first letter is upper-cased: the rest
of the segment must already match the class name casing.
Security
Access is checked with the module/object action roles built by
ModuleNameObjectNameHelper::generateActionRole():
| Operation | Required role |
|---|---|
| Export | ROLE_MOD_<MODULE>_<OBJECT>_SHOW |
| Import | ROLE_MOD_<MODULE>_<OBJECT>_ADD |
When module and object are identical, the object part is omitted (ROLE_MOD_<MODULE>_<ACTION>).
A missing role results in a 403 (AccessDeniedHttpException).
Export
Endpoint: GET /{module}/{object}/{id}/export/json
Response: application/octet-stream — a JSON file streamed back through a BinaryFileResponse.
Flow (EntityJsonExportProvider → PHPReaction\Import\EntityExporter::exportToJson()):
- Check the
showrole for the module/object. - Resolve the entity class and fetch the entity by ID (
404if not found). - Set the in-memory entity ID to
nullso the exported JSON carries no database ID (the entity is never flushed, nothing is written to the database). - Serialize with the
exportserialization group and'iri' => false. - Write the JSON to a temporary file through
TmpFileManagerand stream it.
The generated file name is <module>_<object>_<uniqueId>.json, lower-cased for the module and object
parts. Since the ID was nulled just before, getUniqueId() only yields the entity shortcode
(or # when the entity has none).
Import
Endpoint: POST /{module}/{object}/import/json
Body: multipart/form-data with a single file property containing the JSON file.
Constraints (declared on EntityJsonImport::$file): max size 12M, extension json.
Response: the imported entity, serialized with the standardShow, timestampable and id
groups, exposed under the properties key of EntityJsonImportOutput.
Flow (ImportEntityFromJsonProcessor → PHPReaction\Import\EntityImporter::importFromJson()):
- Check the
addrole for the module/object. - Resolve the entity class.
- Read and decode the uploaded file; a missing
slugproperty aborts with a400(The received JSON entity does not have a slug! ERR: 987824FHFRP). - Look up an existing entity with the same slug.
- Deserialize the JSON into that entity (
OBJECT_TO_POPULATE) when it exists, or into a new one, using theimportserialization group. - Persist (when new) and flush, then return the serialized entity.
Because the payload is deserialized with the import group, only properties exposed in that group
are written; everything else in the file is ignored.
Making an entity exportable / importable
There is nothing to declare per entity besides the serialization groups:
-
Add the
exportgroup to every property that must end up in the file, and theimportgroup to every property that must be written back. They are usually declared together:#[Groups(['dataAnalysisDataCollectorShow', 'export', 'import'])] private ?string $name = null; -
Make sure the entity is sluggable (
SluggableTrait) — thesluggetter already carries theexportandimportgroups, and the import refuses any JSON without a slug. -
Relations are only exported when the related properties themselves carry the
exportgroup; otherwise export the related entities separately. -
Declare the corresponding
..._SHOWand..._ADDroles for the users who need the operations.
Errors
| Status | When |
|---|---|
400 | Missing module / object / id path parameter, or JSON without a slug. |
403 | Missing SHOW (export) or ADD (import) role. |
404 | No entity found for the given ID (export). |
422 | Uploaded file fails the Assert\File constraints (size or extension). |
Involved classes
| Class | Role |
|---|---|
PHPReaction\Api\Dto\CommonBundle\EntityJsonExport | Declares the export operation. |
PHPReaction\Api\State\Provider\CommonBundle\EntityJsonExportProvider | Reads the URI variables and returns the file response. |
PHPReaction\Import\EntityExporter | Authorization, fetch, serialization, temporary file. |
PHPReaction\Api\Dto\CommonBundle\EntityJsonImport | Declares the import operation and the uploaded file constraints. |
PHPReaction\Api\State\Processor\CommonBundle\ImportEntityFromJsonProcessor | Reads the URI variables and wraps the result. |
PHPReaction\Api\Dto\CommonBundle\EntityJsonImportOutput | Import response payload (properties). |
PHPReaction\Import\EntityImporter | Authorization, slug lookup, deserialization, persistence. |
PHPReaction\Helper\CommonBundle\ModuleNameObjectNameHelper | Module/object to class name and role name resolution. |