basilicom/pimcore-fixtures

Load yml fixtures in pimcore

Maintainers

Package info

github.com/basilicom/pimcore-fixtures

Type:pimcore-bundle

pkg:composer/basilicom/pimcore-fixtures

Statistics

Installs: 49

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2026.3 2026-05-20 11:32 UTC

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:

  1. Classification Store config is applied first so that group/key/collection definitions are in place before any data objects reference them.
  2. Assets are loaded next.
  3. Document shells are pre-loaded so that data objects can resolve document relations.
  4. 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.
  5. Document data is fully applied in a final pass.
  6. Roles are loaded last so workspace cpath references 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 id field 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-store overwrites 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 storeId is 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; use parentId: 1 for 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; use parentId: 0 for the root.
  • Roles are matched by name — existing roles and folders are updated in place (idempotent).
  • Workspaces are resolved by cpath at load time. If the referenced element does not exist, the workspace row is skipped with a warning.
  • workspacesObject rows additionally accept lEdit, lView, and layouts (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.

  1. 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 from AbstractDataExtractor::convertFieldDefinitionToExpression().
  2. Loading — add a hydrator implementing ChainedPropertyHydratorInterface in src/Generation/Hydrator/Pimcore/. Use InspectingFieldDefinitionTrait for field-definition lookup so it works both for Concrete objects and for Fieldcollection\Data\AbstractData items.
  3. Register the hydrator service with the tag basilicom_pimcore_fixtures.property_hydrator and a priority (higher = earlier).
  4. 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, final classes 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 Configuration class) 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.