VOOZH about

URL: https://deepwiki.com/MahoCommerce/maho-composer-plugin/5.2-symlink-creation-and-management

⇱ Symlink Creation and Management | MahoCommerce/maho-composer-plugin | DeepWiki


Loading...
Menu

Symlink Creation and Management

The ModmanPlugin class implements a symlink deployment system that creates isolated module symlinks in vendor/mahocommerce/maho-modman-symlinks/. This system provides deploy/undeploy lifecycle management, wildcard pattern expansion, conflict detection, and backward compatibility with the Cotya/magento-composer-installer extra.map format.

For modman file parsing details, see page 5.1. This page documents symlink creation after mappings are resolved.

Symlink Directory Structure

The ModmanPlugin creates symlinks in an isolated directory structure to avoid polluting the project root. Each package receives a dedicated subdirectory.

Directory Path Construction

The getSymlinkDir() method at src/ModmanPlugin.php78-81 constructs the path:

{composer-vendor-dir}/mahocommerce/maho-modman-symlinks/{package-name}

Where {package-name} is the full package name including vendor (e.g., vendor-name/module-name).

Method Signature

MethodParametersReturn Type
getSymlinkDir()PackageInterface $packagestring
Implementation$this->composer->getConfig()->get('vendor-dir') . '/mahocommerce/maho-modman-symlinks/' . $package->getName()Absolute path

Example Structure

project-root/vendor/
├── mahocommerce/
│ └── maho-modman-symlinks/
│ ├── vendor1/package1/ ← symlink target directory
│ │ ├── app/code/community/Module/ ← symlink
│ │ └── skin/frontend/theme/ ← symlink
│ └── vendor2/package2/ ← symlink target directory
│ └── app/code/local/Module/ ← symlink
├── vendor1/package1/ ← symlink source
└── vendor2/package2/ ← symlink source

Sources: src/ModmanPlugin.php78-81

Deploy Process

The deploy() method at src/ModmanPlugin.php91-168 orchestrates symlink creation. It executes after package install/update events via the event handlers defined at src/ModmanPlugin.php50-64

Deploy Execution Flow


Sources: src/ModmanPlugin.php91-168

Mapping Resolution

The deploy() method resolves source-to-target mappings using a two-tier priority system at src/ModmanPlugin.php100-110:

Resolution Priority

PrioritySourceMethodValidation
1modman fileparseModman($packageDir)See page 5.1
2composer.json extra.map$package->getExtra()['map']Array of [$source, $target] string pairs

Fallback Logic

The implementation at src/ModmanPlugin.php104-110:

if (count($map) === 0 && is_array($package->getExtra()['map'] ?? null)) {
 foreach ($package->getExtra()['map'] as [$source, $target]) {
 if (is_string($source) && is_string($target)) {
 $map[] = [$source, $target];
 }
 }
}

This provides backward compatibility with the Cotya/magento-composer-installer package format.

Sources: src/ModmanPlugin.php100-110

Symlink Building and Path Validation

The symlink building loop at src/ModmanPlugin.php113-130 constructs the $symlinks array with security validation and wildcard expansion.

Security Check

Directory traversal prevention at src/ModmanPlugin.php115-117:

if (str_contains($source, '..') || str_contains($target, '..')) {
 continue;
}

Any mapping containing .. is rejected.

Building Algorithm


Sources: src/ModmanPlugin.php113-130

Straight Mapping Optimization

At src/ModmanPlugin.php132-136 the plugin implements an optimization for identity mappings:

$unique = array_filter($symlinks, fn ($link) => $link[0] !== $link[1]);
if (count($unique) === 0) {
 return;
}

When all mappings have $source === $target, no symlinks are created. This occurs with tools like generate-modman that produce identity mappings for packages already in the correct structure.

Sources: src/ModmanPlugin.php132-136

Symlink Sorting and Creation

The plugin sorts symlinks at src/ModmanPlugin.php145 before creation:

usort($symlinks, fn ($a, $b) => $a[1] <=> $b[1]);

This lexicographic sort by target path ($a[1], $b[1]) ensures shortest paths are processed first, making conflict detection reliable.

Creation Loop with Conflict Detection


Sources: src/ModmanPlugin.php147-167

Undeploy Process

The undeploy() method at src/ModmanPlugin.php83-89 removes symlink directories:

Method Flow


The method recursively removes the entire package symlink directory if the package type is maho-module or magento-module.

Event Integration

The event handlers at src/ModmanPlugin.php50-71 use this pattern:

HandlerEventOperationPattern
onPostPackageInstall()POST_PACKAGE_INSTALLInstallOperationundeploy()deploy()
onPostPackageUpdate()POST_PACKAGE_UPDATEUpdateOperationundeploy()deploy()
onPostPackageUninstall()POST_PACKAGE_UNINSTALLUninstallOperationundeploy()

The undeploy-before-deploy pattern ensures stale symlinks are removed before new ones are created.

Sources: src/ModmanPlugin.php83-89 src/ModmanPlugin.php50-71

Wildcard Expansion

Wildcard patterns in source paths expand to multiple symlinks. The implementation at src/ModmanPlugin.php118-126 uses PHP's glob() function.

Expansion Algorithm


Example

Modman mapping:

patches/*.patch patches

Package structure:

vendor/author/package/patches/001-fix.patch
vendor/author/package/patches/002-improve.patch

Expands to $symlinks:

[
 ['patches/001-fix.patch', 'patches/001-fix.patch'],
 ['patches/002-improve.patch', 'patches/002-improve.patch']
]

Implementation Steps

OperationLocationMethod
Detectionsrc/ModmanPlugin.php118str_contains($source, '*')
Expansionsrc/ModmanPlugin.php119glob("$packageDir/$source")
Path calculationsrc/ModmanPlugin.php124$filesystem->findShortestPath()
Target buildsrc/ModmanPlugin.php125"$target/" . basename($file)

Sources: src/ModmanPlugin.php118-126

Conflict Detection

The conflict detection at src/ModmanPlugin.php154-163 prevents overlapping symlinks using prefix checking.

Detection Logic


Algorithm

At src/ModmanPlugin.php154-155:

$conflicts = array_filter($created, fn ($link) => str_starts_with($target, $link));
if (count($conflicts) > 0) { /* skip */ }

The check uses str_starts_with() to detect if any existing symlink path is a prefix of the current target.

Conflict Examples

First TargetSecond TargetConflictReason
app/code/Moduleapp/code/Module/Controller.phpYesSecond is child of first
app/code/ModuleAapp/code/ModuleBNoSiblings, different paths
app/design/themeapp/design/themeYesExact match (is prefix)

Error Output

Conflicts generate this message format at src/ModmanPlugin.php156-161:

- Could not symlink {package}/{source} -> {target} because it conflicts with another target.

Sources: src/ModmanPlugin.php154-163

Relative Symlink Creation

The plugin creates relative symlinks at src/ModmanPlugin.php165 using Filesystem::relativeSymlink(). Relative symlinks are portable across different filesystem locations.

Path Construction


Example

Given:

$packageDir = /project/vendor/author/module
$symlinkDir = /project/vendor/mahocommerce/maho-modman-symlinks/author/module
mapping: app/code/local/Module → app/code/local/Module

Creates:

Source (absolute): /project/vendor/author/module/app/code/local/Module
Target (absolute): /project/vendor/mahocommerce/maho-modman-symlinks/author/module/app/code/local/Module
Symlink content: ../../../../../author/module/app/code/local/Module

The relativeSymlink() method computes the relative path from target to source.

Sources: src/ModmanPlugin.php149-166

Event-Driven Lifecycle

The ModmanPlugin subscribes to Composer package events at src/ModmanPlugin.php41-48 via getSubscribedEvents().

Event Subscription Map


Handler Implementations

At src/ModmanPlugin.php50-71:

MethodEvent ConstantOperation TypePackage ExtractionCalls
onPostPackageInstall()POST_PACKAGE_INSTALLInstallOperationgetPackage()undeploy()deploy()
onPostPackageUpdate()POST_PACKAGE_UPDATEUpdateOperationgetTargetPackage()undeploy()deploy()
onPostPackageUninstall()POST_PACKAGE_UNINSTALLUninstallOperationgetPackage()undeploy()

The update handler uses getTargetPackage() to process the new package version.

Sources: src/ModmanPlugin.php41-71

Summary

The symlink creation and management system provides a robust, secure, and flexible mechanism for deploying module code according to modman mappings. Key features include:

FeatureImplementationBenefit
Isolated directory structurevendor/mahocommerce/maho-modman-symlinks/{package}No pollution of project tree
Relative symlinksFilesystem::relativeSymlink()Portable across different paths
Security validation.. rejection in pathsPrevents directory traversal
Wildcard expansionglob() pattern matchingFlexible multi-file mappings
Conflict detectionPrefix checking with str_starts_with()Prevents symlink corruption
Cotya compatibilityextra.map fallbackSmooth migration path
Clean lifecycleUndeploy before deployNo stale symlinks

The symlink system integrates seamlessly with Composer's package lifecycle events, ensuring that module deployments are always synchronized with package installation state.

Sources: src/ModmanPlugin.php1-231