VOOZH about

URL: https://deepwiki.com/MahoCommerce/maho-composer-plugin/3.2-file-preservation-system

⇱ File Preservation System | MahoCommerce/maho-composer-plugin | DeepWiki


Loading...
Menu

File Preservation System

Purpose and Scope

The File Preservation System is a configuration-driven mechanism within the FileCopyPlugin that prevents specific files from being overwritten during Composer install, update, or create-project operations. This allows developers to maintain customizations to static assets (CSS, JavaScript, images, configuration files) that would otherwise be replaced when the mahocommerce/maho core package or module packages are updated.

This document covers the configuration format, the preservation decision algorithm, and the logging system. For information about the broader file copying process that preservation integrates with, see Core File Copying Process. For details on how packages are discovered and processed, see Package Type Handling and Asset Discovery.

Configuration Format

The File Preservation System is configured through the root project's composer.json file under the extra.maho.preserve-files key. The configuration consists of an array of relative file paths from the project root.

Configuration Schema

Configuration KeyTypeDescription
extra.maho.preserve-filesstring[]Array of relative file paths to preserve during copy operations

Example Configuration:


The preservation system expects paths relative to the project root directory (where composer.json is located), not relative to the public/ directory or any other subdirectory.

Sources: src/FileCopyPlugin.php63-73

Configuration Loading Process

Configuration Discovery and Validation

The FileCopyPlugin loads the preservation configuration during the onPostCmd event handler, before any file copying begins. The loading process includes type validation to ensure configuration integrity.


Configuration Loading Algorithm:

  1. Root Package Access: The plugin retrieves the root package object from the Composer instance via $composer->getPackage() src/FileCopyPlugin.php64
  2. Extra Data Extraction: Calls getExtra() to retrieve the extra section from composer.json src/FileCopyPlugin.php64
  3. Namespace Validation: Checks that extra['maho'] exists and is an array src/FileCopyPlugin.php65
  4. Configuration Key Validation: Verifies that extra['maho']['preserve-files'] exists and is an array src/FileCopyPlugin.php65
  5. Entry Type Validation: Iterates through each entry and validates that it is a string src/FileCopyPlugin.php68-71
  6. Population: Valid string entries are added to the $this->preserveFiles property src/FileCopyPlugin.php70

Non-string entries in the configuration array are silently ignored, providing graceful degradation if the configuration is malformed.

Sources: src/FileCopyPlugin.php63-73

Preservation Decision Algorithm

Runtime Decision Flow

During file copying operations, the preservation system evaluates each file individually to determine whether it should be preserved or overwritten.


Preservation Criteria:

A file is preserved if both of the following conditions are met:

  1. Configuration Match: The file's relative path (from project root) exists in the $this->preserveFiles array
  2. File Existence: The destination file already exists on disk

The requirement that the destination file must exist prevents preservation logic from blocking initial deployment of files. Only files that were previously deployed and subsequently customized are preserved.

Sources: src/FileCopyPlugin.php130-138

Path Resolution and Comparison

Relative Path Calculation

The preservation system uses relative paths from the project root for all comparisons. This ensures that the configuration is portable across different deployment environments.

Path Calculation Process:

  1. Project Directory Storage: The project root directory is determined via getcwd() and stored in $this->projectDir src/FileCopyPlugin.php57-61
  2. Absolute Destination Path: During copying, the full destination path is constructed: $dstPath = $dst . '/' . $file src/FileCopyPlugin.php125
  3. Relative Path Extraction: The relative path is calculated by removing the project directory prefix: str_replace($this->projectDir . '/', '', $dstPath) src/FileCopyPlugin.php131
  4. Array Membership Check: The relative path is compared against the $this->preserveFiles array using strict comparison: in_array($relativePath, $this->preserveFiles, true) src/FileCopyPlugin.php132

Example Path Transformation:

Project Directory: /var/www/myproject
Destination Path: /var/www/myproject/public/skin/frontend/default/modern/css/custom.css
Relative Path: public/skin/frontend/default/modern/css/custom.css

The relative path format matches the format required in the extra.maho.preserve-files configuration, enabling direct string comparison.

Sources: src/FileCopyPlugin.php19 src/FileCopyPlugin.php57-61 src/FileCopyPlugin.php131-132

Preservation Scope and Behavior

What Gets Preserved

The preservation system operates during the recursive directory copying performed by copyDirectory() src/FileCopyPlugin.php110-143 It applies to all file copy operations initiated by FileCopyPlugin:

Copy OperationSourceDestinationPreservation Applies
Core public assetsvendor/mahocommerce/maho/publicpublic/Yes
Module public assets{packagePath}/publicpublic/Yes
Module skin assets{packagePath}/skinpublic/skin/Yes
Module JavaScript{packagePath}/jspublic/js/Yes
Maho CLI executablevendor/mahocommerce/maho/maho./mahoNo*

* The maho executable is copied via direct copy() call src/FileCopyPlugin.php78 not through copyDirectory(), so preservation logic does not apply to it.

Directory Handling

The preservation system only evaluates regular files, not directories. When copyDirectory() encounters a subdirectory, it recursively descends into it without checking preservation rules src/FileCopyPlugin.php127-128 This means:

  • You cannot preserve an entire directory by listing the directory path
  • You must list each individual file within the directory that should be preserved
  • Empty directories are always created if they don't exist

Sources: src/FileCopyPlugin.php76-107 src/FileCopyPlugin.php127-129

Logging and Observability

Verbose Logging Output

When a file is preserved (skipped), the FileCopyPlugin emits a log message at the VERBOSE level using Composer's IOInterface.

Log Message Format:

 - Skipping preserved file: {relativePath}

Logging Implementation:


The logging call includes:

  • Message: Prefixed with two spaces and a hyphen for visual consistency
  • Newline Parameter: true to ensure the message appears on its own line
  • Verbosity Level: IOInterface::VERBOSE to prevent cluttering default output

Viewing Preservation Logs:

To see which files are being preserved during Composer operations, run Composer with verbose flag:


The -vv (very verbose) flag activates VERBOSE level output, making preservation messages visible.

Sources: src/FileCopyPlugin.php135

Integration with Copy Operation

Execution Context

The preservation system is invoked during the main file copying loop within copyDirectory(). The integration points are:


Decision Point Location:

The preservation decision occurs at src/FileCopyPlugin.php130-138 specifically:

  1. Line 131: Relative path calculation
  2. Line 132: Preservation condition evaluation (array membership + file existence)
  3. Lines 134-138: Conditional branching based on $shouldPreserve
    • If true: Log and skip
    • If false: Perform copy()

Sources: src/FileCopyPlugin.php110-143

Use Cases and Scenarios

Common Preservation Scenarios

Scenario 1: Custom .htaccess Rules

Developers often customize the public/.htaccess file with environment-specific redirect rules, security headers, or performance optimizations.

Configuration:


Behavior: During composer update, the core mahocommerce/maho package may ship an updated .htaccess, but the customized version is preserved.

Scenario 2: Theme Customizations

Custom CSS and JavaScript for a specific theme should not be overwritten when theme packages are updated.

Configuration:


Scenario 3: Robots.txt for Production

Different robots.txt rules for production vs. staging environments.

Configuration:


Initial Deployment Behavior

On first installation (when destination files don't exist), the preservation system does not block file deployment:


This design ensures that the preservation system only activates after files have been initially deployed and potentially customized, preventing it from blocking the initial setup of a project.

Sources: src/FileCopyPlugin.php132

Limitations and Edge Cases

Known Limitations

  1. No Pattern Matching: The preservation configuration does not support wildcards or regular expressions. Each file must be listed explicitly by its full relative path.

  2. No Directory Preservation: You cannot preserve an entire directory by specifying the directory path. Each file within the directory must be listed individually.

  3. Case Sensitivity: Path comparison is case-sensitive. On case-insensitive file systems (e.g., macOS default HFS+), this may cause unexpected behavior if paths differ only in case.

  4. No Preservation Feedback on Copy Success: When preservation activates, the message is only visible at VERBOSE log level. Developers running Composer without -vv won't see which files were preserved.

  5. No Validation of Configured Paths: If a path listed in preserve-files does not correspond to any actual copied file, there is no warning. The entry is silently ignored.

  6. Maho Executable Not Preservable: The ./maho CLI executable is copied via direct copy() call outside the copyDirectory() recursion, so it cannot be preserved src/FileCopyPlugin.php78

Edge Case: Strict Type Validation

The configuration loading process uses strict type checking src/FileCopyPlugin.php69 This means:

  • null values in the array are ignored
  • Integer or boolean values are ignored
  • Objects and nested arrays are ignored

Only string values are added to $this->preserveFiles. This prevents type-related errors during the in_array() check but may silently ignore misconfigured entries.

Sources: src/FileCopyPlugin.php68-71 src/FileCopyPlugin.php78 src/FileCopyPlugin.php132

Technical Implementation Details

Property Storage

The preservation configuration is stored in a private property of the FileCopyPlugin class:


Property Characteristics:

  • Type: Array of strings (array<string>)
  • Visibility: Private (only accessible within FileCopyPlugin)
  • Initialization: Empty array (no files preserved by default)
  • Lifecycle: Populated once during onPostCmd(), then read multiple times during copyDirectory() calls

Sources: src/FileCopyPlugin.php16-17

String Comparison Details

The preservation check uses PHP's in_array() function with the strict comparison flag:


Strict Comparison Impact:

  • The third parameter true enables strict type and value comparison
  • Prevents unexpected matches due to type coercion
  • Ensures that only exact string matches trigger preservation

This is combined with a file existence check using a logical AND operator, meaning both conditions must be true for preservation to occur.

Sources: src/FileCopyPlugin.php132

Performance Characteristics

Configuration Loading: O(n) where n is the number of entries in preserve-files array. Occurs once per Composer command execution.

Per-File Preservation Check: O(m) where m is the number of preserved files. Uses linear search via in_array(). Occurs for every file copied.

For large numbers of preserved files, this could impact copy performance. However, typical usage involves preserving a small number of highly customized files (usually less than 20), making the linear search acceptable.

Sources: src/FileCopyPlugin.php68-71 src/FileCopyPlugin.php132