Medowar 167ee0bab5 implementing manage-client 1 місяць тому
..
README.md 167ee0bab5 implementing manage-client 1 місяць тому

README.md

Migrations

Migrations convert existing installations when a release changes the shape of something in data/. They ship inside the release package and run automatically as part of the post-update step, right after the files are deployed and before shopAfterUpdate().

Which migrations have already run is recorded in data/manage/migrations.json, which is never part of a release. Pending migrations are listed on Admin → Einstellungen and can be re-run from there after a failure.

When you need one

Only for changes that existing data cannot survive on its own. Adding a new optional field with a sensible default is handled by normalizeProductRecord() and friends in includes/functions.php — that needs no migration. Renaming a field, splitting one file into two, or changing a value format does.

Naming

YYYY-MM-DD-NN-short-slug.php

for example 2026-08-21-01-add-category-id.php. Files run in filename order, so the date prefix and the two-digit counter decide the sequence. The filename without .php is the migration id — renaming an applied migration makes it run again.

Shape

Return a closure. (A file defining a global up() also works, but two such files in one run would collide.)

<?php

return function (array $context): void {
    $file = $context['app_root'] . '/data/products.json';

    $products = json_decode((string) file_get_contents($file), true);
    if (!is_array($products)) {
        throw new RuntimeException('products.json ist nicht lesbar.');
    }

    foreach ($products as &$product) {
        if (!isset($product['category_ids'])) {
            $product['category_ids'] = isset($product['category'])
                ? [$product['category']]
                : [];
        }
        unset($product['category']);
    }
    unset($product);

    file_put_contents(
        $file,
        json_encode($products, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
    );
};

$context carries app_root, instance, from_version, to_version, backup_dir, run_id and migration_id.

Rules

  • Throw on failure. A thrown exception stops the run at that migration; everything after it stays pending and the admin sees the error. Returning quietly on a problem hides a half-migrated installation.
  • Be idempotent where you can. Guard with isset() rather than assuming the old shape, so a re-run after a partial failure is harmless.
  • Do not require the new code. A migration runs in the process that was loaded from the previous release. Do the work with plain file operations instead of calling shop functions that may only exist in the new version.
  • Never delete the only copy. An automatic backup is not taken before an update; the aside copies in data/manage/updates/ cover files, not data. Take a backup from the settings page before rolling out a release that migrates data.