basilicom / pimcore-fixtures
Load yml fixtures in pimcore
Package info
github.com/basilicom/pimcore-fixtures
Type:pimcore-bundle
pkg:composer/basilicom/pimcore-fixtures
Requires
- php: ^8.3
- pimcore/pimcore: ^12.0
- pimcore/platform-version: ^2025.0
- pimcore/symfony-freeze: ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.89
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-05-20 12:03:38 UTC
README
Fixture generation and loading for Pimcore 12 — YAML fixtures, idempotent loading, document and asset support.
Installation
composer require --dev basilicom/pimcore-fixtures
Register the bundle in config/bundles.php:
return [ // ... Basilicom\PimcoreFixtures\PimcoreFixturesBundle::class => ['dev' => true], // ... ];
Configuration
Create config/packages/basilicom_pimcore_fixtures.yaml:
basilicom_pimcore_fixtures: path: '%kernel.project_dir%/fixtures' ignored_fields: - someField ignored_classes: - \Pimcore\Model\DataObject\SomeClass ignored_paths: - /some/path/to/ignore
| Option | Default | Description |
|---|---|---|
path |
var/bundles/PimcoreFixtures |
Root directory for generated and loaded fixture files |
ignored_fields |
[] |
Field names skipped globally during generation |
ignored_classes |
[] |
DataObject classes skipped entirely during generation |
ignored_paths |
[] |
Pimcore object paths skipped during generation |
Generation behaviour:
- Fields that are
null,'', or[]are skipped to keep YAML files clean. - Inherited values are not exported (
AbstractObject::setGetInheritedValues(false)).
Commands
basilicom:pimcore:fixtures:generate
Generates YAML fixture files from existing Pimcore data objects.
bin/console basilicom:pimcore:fixtures:generate bin/console basilicom:pimcore:fixtures:generate --all
| Option | Description |
|---|---|
--all |
Generate from root without interactive prompts (max 100 levels deep) |
--file-sort |
Custom sorting implementation class for fixture file output order |
Without --all the command asks interactively for a root folder and depth.
basilicom:pimcore:fixtures:generate-documents
Generates YAML fixture files from existing Pimcore documents (pages, snippets, folders).
bin/console basilicom:pimcore:fixtures:generate-documents bin/console basilicom:pimcore:fixtures:generate-documents --all
| Option | Description |
|---|---|
--all |
Generate from root without interactive prompts (max 100 levels deep) |
Fixtures are written to {path}/documents/.
basilicom:pimcore:fixtures:generate-classification-store
Exports the Classification Store configuration (stores, groups, keys, collections) to a YAML fixture file.
bin/console basilicom:pimcore:fixtures:generate-classification-store
Fixtures are written to {path}/classification_store/classification_store.yaml. Re-running the command overwrites the file.
basilicom:pimcore:fixtures:generate-roles
Generates YAML fixture files for Pimcore user roles, including permissions, workspaces, and role folders.
bin/console basilicom:pimcore:fixtures:generate-roles
Fixtures are written to {path}/roles/. Users themselves are not exported — only roles and role folders.
basilicom:pimcore:fixtures:load
Loads fixture files and creates/updates Pimcore objects, documents, and assets.
bin/console basilicom:pimcore:fixtures:load
| Option | Description |
|---|---|
--omit-validation |
Skip mandatory field validation when saving objects |
--check-path-exists |
Update an existing object at the same path instead of creating a new one |
--dry-run |
Preview what would be saved without writing anything to the database |
Loading runs in phases:
- Classification Store config is applied first so that group/key/collection definitions are in place before any data objects reference them.
- Assets are loaded next.
- Document shells are pre-loaded so that data objects can resolve document relations.
- Data objects are loaded in two sub-phases:
- Phase 1 — structure: objects are saved in tree order (parent before child) without relations or fieldcollections.
- Phase 2 — relations: objects are re-hydrated with full data and saved in dependency order. Circular relations are supported.
- Document data is fully applied in a final pass.
- Roles are loaded last so workspace
cpathreferences can be resolved against the assets, documents, and objects that were just created.
Objects are published according to the published field in the fixture YAML (true by default).
basilicom:pimcore:fixtures:reset
Resets Pimcore fixture data by truncating the relevant database tables.
bin/console basilicom:pimcore:fixtures:reset --force bin/console basilicom:pimcore:fixtures:reset --force --types=objects,documents,assets bin/console basilicom:pimcore:fixtures:reset --force --types=objects --classes=Product,Category bin/console basilicom:pimcore:fixtures:reset --force --types=objects --exclude=Config --drop-folder
| Option | Default | Description |
|---|---|---|
--force |
— | Required. Safety guard to prevent accidental data loss |
--types |
objects,documents |
Comma-separated list of fixture types to reset. Valid values: objects, documents, assets |
--classes |
all |
Objects only. Comma-separated class names to limit deletion to. Accepts short name (Product) or FQCN |
--exclude |
— | Objects only. Comma-separated class names to exclude from deletion |
--drop-folder |
false |
Also delete folder objects/documents (by default folders are preserved) |
The command always truncates shared tables regardless of --types:
notes, notes_data, edit_lock, search_backend_data, recyclebin.
To add extra tables to that list, override the command in services.yaml:
Basilicom\PimcoreFixtures\Command\DropPimcoreObjectsCommand: arguments: $additionalTables: - custom_table - another_table
basilicom:pimcore:fixtures:enforce-versions
Saves all objects of configured classes to force Pimcore to create a new version entry for each. Only runs in dev and test environments.
Configure the classes to process in services.yaml:
Basilicom\PimcoreFixtures\Command\EnforceVersionsCommand: arguments: $classNames: - Product - Category
Classification Store Config Fixture Format
Classification Store configuration (stores, groups, keys, collections) lives in {path}/classification_store/ as YAML files. This is separate from the field values stored on data objects.
ProductAttributes: id: 1 name: ProductAttributes description: 'Classification store for product attributes' groups: myGroup: name: myGroup description: '' keys: myField: name: myField title: myField description: '' type: input definition: '{"fieldtype":"input","name":"myField","title":"myField","datatype":"data"}' enabled: true sorter: 1 mandatory: false collections: myCollection: name: myCollection description: '' groups: - myGroup
- The
idfield is used to force a specific store ID via a raw DB INSERT so that IDs stay stable across environments. - Groups are created before collections; collection entries reference groups by name.
- Running
basilicom:pimcore:fixtures:generate-classification-storeoverwrites the file from the live database. Edit the generated file to add or remove entries, then commit it. - The seeder is idempotent: existing stores, groups, keys, and collections are updated in place; new ones are created.
Classification Store Field Value Fixture Format
Classification store field values are generated and loaded inline as part of the data object fixture. Each classification store field is represented as a nested map: groupName → keyName → language → value.
Non-localized keys are stored under default; localized keys are stored under their language codes.
\App\Model\DataObject\Product: 001_product_example_abc123def456: key: example-product parentId: 1 published: true classificationAttributes: # classificationstore field name technicalSpecs: # group name weight: default: '2.5 kg' dimensions: default: '10x20x5 cm' marketingData: # group name headline: de: Produktüberschrift en: Product headline teaser: de: Kurzbeschreibung en: Short description
- Group and key names are resolved by name at load time. If a group or key name cannot be found in the store, the entry is silently skipped.
- Setting any value for a key automatically activates the corresponding group.
- Groups that are active but carry no data are not exported.
- The
storeIdis taken from the field definition at load time; multi-store setups are supported as long as each field references the correct store.
Document Fixture Format
Document fixtures live in {path}/documents/ as YAML files.
\Pimcore\Model\Document\Folder: 001_folder_content_abc123def456: key: content parentId: 1 published: true \Pimcore\Model\Document\Page: 002_page_about-us_def456abc123: key: about-us parent: '@001_folder_content_abc123def456' published: true controller: 'App\Controller\DefaultController::defaultAction' template: '@AppBundle/Default/default.html.twig' title: 'About Us' description: 'About our company' prettyUrl: '/about-us' properties: - { name: 'nav_title', type: 'text', data: 'About', inheritable: false } - { name: 'hero_image', type: 'asset', path: '/images/hero.jpg', inheritable: false } - { name: 'related_page', type: 'document', path: '/en/home', inheritable: false }
- Use
parent: '@fixtureId'for cross-fixture parent references; useparentId: 1for the Pimcore root. - Existing documents are updated in place (idempotent by path).
Supported fields:
| Field | Applies to |
|---|---|
key |
All |
parent / parentId |
All |
published |
All |
properties |
All |
controller |
Page, Snippet |
template |
Page, Snippet |
title |
Page |
description |
Page |
prettyUrl |
Page |
Editables, links, hardlinks, and emails are not supported.
Role Fixture Format
Role fixtures live in {path}/roles/ as YAML files. Two top-level classes are used: \Pimcore\Model\User\Role\Folder (role folders) and \Pimcore\Model\User\Role (roles).
\Pimcore\Model\User\Role\Folder: 000_rolefolder_editorial_abc123def456: parentId: 0 name: editorial \Pimcore\Model\User\Role: 001_role_editor_def456abc123: parent: '@000_rolefolder_editorial_abc123def456' name: editor permissions: - documents - assets - objects classes: [Product, Category] docTypes: [] perspectives: [default] websiteTranslationLanguagesView: [en, de] websiteTranslationLanguagesEdit: [en] workspacesAsset: - { cpath: /images, list: true, view: true, publish: false, delete: false, rename: false, create: false, settings: false, versions: false, properties: false } workspacesDocument: - { cpath: /en, list: true, view: true, save: true, publish: true, unpublish: false, delete: false, rename: false, create: true, settings: false, versions: true, properties: true } workspacesObject: - { cpath: /products, list: true, view: true, save: true, publish: true, unpublish: false, delete: false, rename: false, create: true, settings: false, versions: true, properties: true }
- Use
parent: '@fixtureId'for cross-fixture parent references; useparentId: 0for the root. - Roles are matched by
name— existing roles and folders are updated in place (idempotent). - Workspaces are resolved by
cpathat load time. If the referenced element does not exist, the workspace row is skipped with a warning. workspacesObjectrows additionally acceptlEdit,lView, andlayouts(optional, language/layout restrictions).
Extension
Custom Property Hydrators
Implement ChainedPropertyHydratorInterface and tag the service:
App\Fixtures\Hydrator\MyCustomHydrator: tags: - { name: 'basilicom_pimcore_fixtures.property_hydrator', priority: 10 }
Hydrators are executed in descending priority order. Execution for a single property stops once a hydrator handles it. The DelegatingPropertyHydrator (priority -1) acts as a fallback via Symfony's property accessor.
Development
This section is for contributors working on the bundle itself. For higher-level architecture notes see AGENTS.md.
Requirements
- Docker (with Docker Compose v2)
make
You do not need a local PHP or Composer installation — every task runs in containers.
Quick start
git clone git@github.com:basilicom/pimcore-fixtures.git
cd pimcore-fixtures
make setup
make setup pulls the required Docker images, prepares the tmp/ build directory, and installs Composer dependencies. After it finishes the bundle is ready for development.
Common tasks
| Command | Purpose |
|---|---|
make setup |
One-shot bootstrap of a fresh checkout |
make composer-install |
Install PHP dependencies |
make composer-update |
Update PHP dependencies |
make composer-audit |
Check dependencies for known vulnerabilities |
make lint |
Run code-style and static analysis checks |
make lint-php |
PHP-CS-Fixer in dry-run mode |
make lint-php-fix |
PHP-CS-Fixer with auto-fix |
make lint-php-static |
PHPStan analysis |
make test-unit |
Run the PHPUnit unit tests |
make help |
List all available targets |
Run make lint and make test-unit before opening a pull request.
Project layout
| Path | Purpose |
|---|---|
src/Generation/ |
Fixture generation from live Pimcore objects |
src/Generation/Hydrator/Pimcore/ |
Per-field-type hydrators used during loading |
src/Generation/DataTransformer/ |
Pimcore field value → YAML expression transformers |
src/Processor/ |
Pre/post processors run around each object save |
src/Service/ |
FixtureLoader, FixtureSorter, FixtureGenerator |
src/Seeder/ |
ObjectSeeder, AssetSeeder |
src/Fixture/ |
Native value types (FixtureObject, FixtureProperty, HydrationContext, FixtureSet) |
src/Loader/ |
Native YAML loader (PimcoreLoader) and PropertyHydratorInterface |
src/Exception/ |
Typed exceptions thrown by the loader |
src/Persistence/ |
PimcorePersister (save/update logic) |
src/Command/ |
Symfony console commands exposed by the bundle |
tests/Unit/ |
PHPUnit unit tests |
Adding support for a new Pimcore field type
Generation and loading are two separate sides — both need to know about the new field type.
- Generation — add a transformer in
src/Generation/DataTransformer/if the raw Pimcore value needs to be turned into a YAML-safe expression, and dispatch it fromAbstractDataExtractor::convertFieldDefinitionToExpression(). - Loading — add a hydrator implementing
ChainedPropertyHydratorInterfaceinsrc/Generation/Hydrator/Pimcore/. UseInspectingFieldDefinitionTraitfor field-definition lookup so it works both forConcreteobjects and forFieldcollection\Data\AbstractDataitems. - Register the hydrator service with the tag
basilicom_pimcore_fixtures.property_hydratorand a priority (higher = earlier). - Test by adding a unit test under
tests/Unit/that covers the new transformer/hydrator pair.
Adding a new processor
Processors live in src/Processor/ and extend AbstractProcessor. They run around each ObjectSeeder::populate() call. Tag them either basilicom_pimcore_fixtures.pre_processor or basilicom_pimcore_fixtures.post_processor depending on when they should fire.
Coding standards
- PHP 8.3, strict types,
finalclasses where possible. - Code style is enforced by PHP-CS-Fixer (
.php-cs-fixer.dist.php). - Static analysis level is configured in
phpstan.neon.dist. - Public APIs (commands, services tagged for extension, the bundle's
Configurationclass) are considered stable — breaking changes need a major version bump.
Tests
Unit tests live in tests/Unit/ and run with PHPUnit 10. Run them via:
make test-unit
# or, with a local PHP:
vendor/bin/phpunit --testsuite Unit
There are no integration tests yet. When testing hydrators or processors, mock the native fixture types from Basilicom\PimcoreFixtures\Fixture\ (FixtureObject, FixtureProperty, HydrationContext).
Releasing
The bundle follows semantic versioning. Releases are tagged on main; the tag is the source of truth for the Composer version.