Skip to Content
BackendAPICommonbundleAPI Export and Import Entity

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\EntityJsonExportGET /{module}/{object}/{id}/export/json
  • PHPReaction\Api\Dto\CommonBundle\EntityJsonImportPOST /{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

ParameterRequirementDescription
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():

OperationRequired role
ExportROLE_MOD_<MODULE>_<OBJECT>_SHOW
ImportROLE_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 (EntityJsonExportProviderPHPReaction\Import\EntityExporter::exportToJson()):

  1. Check the show role for the module/object.
  2. Resolve the entity class and fetch the entity by ID (404 if not found).
  3. Set the in-memory entity ID to null so the exported JSON carries no database ID (the entity is never flushed, nothing is written to the database).
  4. Serialize with the export serialization group and 'iri' => false.
  5. Write the JSON to a temporary file through TmpFileManager and 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 (ImportEntityFromJsonProcessorPHPReaction\Import\EntityImporter::importFromJson()):

  1. Check the add role for the module/object.
  2. Resolve the entity class.
  3. Read and decode the uploaded file; a missing slug property aborts with a 400 (The received JSON entity does not have a slug! ERR: 987824FHFRP).
  4. Look up an existing entity with the same slug.
  5. Deserialize the JSON into that entity (OBJECT_TO_POPULATE) when it exists, or into a new one, using the import serialization group.
  6. 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:

  1. Add the export group to every property that must end up in the file, and the import group to every property that must be written back. They are usually declared together:

    #[Groups(['dataAnalysisDataCollectorShow', 'export', 'import'])] private ?string $name = null;
  2. Make sure the entity is sluggable (SluggableTrait) — the slug getter already carries the export and import groups, and the import refuses any JSON without a slug.

  3. Relations are only exported when the related properties themselves carry the export group; otherwise export the related entities separately.

  4. Declare the corresponding ..._SHOW and ..._ADD roles for the users who need the operations.

Errors

StatusWhen
400Missing module / object / id path parameter, or JSON without a slug.
403Missing SHOW (export) or ADD (import) role.
404No entity found for the given ID (export).
422Uploaded file fails the Assert\File constraints (size or extension).

Involved classes

ClassRole
PHPReaction\Api\Dto\CommonBundle\EntityJsonExportDeclares the export operation.
PHPReaction\Api\State\Provider\CommonBundle\EntityJsonExportProviderReads the URI variables and returns the file response.
PHPReaction\Import\EntityExporterAuthorization, fetch, serialization, temporary file.
PHPReaction\Api\Dto\CommonBundle\EntityJsonImportDeclares the import operation and the uploaded file constraints.
PHPReaction\Api\State\Processor\CommonBundle\ImportEntityFromJsonProcessorReads the URI variables and wraps the result.
PHPReaction\Api\Dto\CommonBundle\EntityJsonImportOutputImport response payload (properties).
PHPReaction\Import\EntityImporterAuthorization, slug lookup, deserialization, persistence.
PHPReaction\Helper\CommonBundle\ModuleNameObjectNameHelperModule/object to class name and role name resolution.
Last updated on