PHPackages                             veronalabs/wp-scoper - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. veronalabs/wp-scoper

ActiveComposer-plugin[Utility &amp; Helpers](/categories/utility)

veronalabs/wp-scoper
====================

A Composer plugin that prefixes namespaces in WordPress plugin dependencies to prevent conflicts

1.2.4(2mo ago)4604↓35%1MITPHPPHP ^7.4|^8.0CI passing

Since Feb 18Pushed 2mo agoCompare

[ Source](https://github.com/veronalabs/wp-scoper)[ Packagist](https://packagist.org/packages/veronalabs/wp-scoper)[ RSS](/packages/veronalabs-wp-scoper/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (9)Dependencies (12)Versions (11)Used By (1)

WP Scoper
=========

[](#wp-scoper)

A Composer plugin that prefixes namespaces in WordPress plugin dependencies to prevent fatal PHP conflicts.

The Problem
-----------

[](#the-problem)

WordPress plugins sharing the same Composer dependencies (e.g., Guzzle, GeoIP2) cause fatal errors because PHP cannot load two versions of the same class. When Plugin A requires `geoip2/geoip2 ^2.0` and Plugin B requires `geoip2/geoip2 ^3.0`, one will crash.

WP Scoper solves this by adding a unique prefix to all namespaces in your vendored dependencies:

```
GeoIp2\Database\Reader → WP_Statistics\Deps\GeoIp2\Database\Reader

```

Each plugin gets its own isolated copy of dependencies. No conflicts.

Comparison
----------

[](#comparison)

FeatureWP ScoperMozartPHP-ScoperInstallation`composer require --dev`Global / PHARGlobal / PHARWorks on any machine✅ Yes❌ No❌ NoComposer plugin✅ Yes (auto-runs)❌ No❌ NoNamespace prefixing✅ Yes✅ Yes✅ YesGlobal class prefixing✅ Yes✅ Yes✅ YesConstant prefixing✅ Yes❌ No✅ YesTemplate/view safety✅ Auto-detect + config❌ No⚠️ Manual excludeWordPress function safety✅ Safe✅ Safe❌ Breaks WP functionsProperty name bug✅ Fixed❌ Had bugs➖ N/A (AST)Autoloader generation✅ Yes (classmap)❌ No⚠️ PartialDev-dependency support✅ Yes❌ No❌ NoUpdate host source files✅ Yes❌ No➖ N/APHP version support7.4+8.1+7.2+Installation
------------

[](#installation)

```
composer require --dev veronalabs/wp-scoper
```

Add configuration to your `composer.json`:

```
{
    "require": {
        "geoip2/geoip2": "^2.0"
    },
    "require-dev": {
        "veronalabs/wp-scoper": "^1.0"
    },
    "extra": {
        "wp-scoper": {
            "namespace_prefix": "WP_Statistics\\Deps",
            "packages": ["geoip2/geoip2"]
        }
    }
}
```

Run `composer install` or `composer wp-scope`. That's it.

Output
------

[](#output)

After running, WP Scoper displays a summary of what was done:

```
+------------------------------------------------+
| WP Scoper - Your dependencies, your namespace! |
+---------------------+--------------------------+
| Packages            | 7                        |
| PHP Files Prefixed  | 90                       |
| Files Excluded      | 60                       |
| Namespaces Prefixed | 6                        |
| Global Classes      | 3                        |
| Constants           | 0                        |
| Call Sites Updated  | 0                        |
| Output Size         | 2.3 MB / 2.7 MB (-14%)   |
| Target Directory    | packages                 |
+------------------------------------------------+

```

StatDescription**Packages**Number of vendor packages processed (including transitive dependencies)**PHP Files Prefixed**PHP files copied and namespace-prefixed**Files Excluded**Files skipped by built-in and custom exclude patterns (tests, docs, configs)**Namespaces Prefixed**Unique vendor namespaces that were rewritten**Global Classes**Non-namespaced classes that were prefixed (e.g. `Spyc` -&gt; `WP_Statistics_Spyc`)**Constants**`define()` constants that were prefixed**Call Sites Updated**Files in your `src/` whose `use` statements were auto-updated**Output Size**Output size vs original size with reduction percentage**Target Directory**Where prefixed files were writtenHow It Works
------------

[](#how-it-works)

1. Reads your `extra.wp-scoper` config from `composer.json`
2. Discovers listed packages and their transitive dependencies
3. Copies them to a target directory (default: `vendor-prefixed/`)
4. Prefixes all namespaces, global classes, and constants
5. Generates a classmap-based autoloader
6. Optionally updates `use` statements in your own source files

Configuration Reference
-----------------------

[](#configuration-reference)

OptionRequiredDefaultDescription`namespace_prefix`**Yes**-Prefix for all namespaces (e.g., `WP_Statistics\\Deps`)`packages`**Yes**-Which vendor packages to prefix (transitive deps auto-included)`target_directory`No`vendor-prefixed`Where prefixed files are copied`class_prefix`NoAuto-derivedPrefix for global (non-namespaced) classes`constant_prefix`NoAuto-derivedPrefix for `define()` constants`exclude_packages`No`[]`Transitive deps to skip`exclude_patterns`No`[]`Additional regex patterns for files to skip (merged with built-in patterns)`exclude_directories`No`["views", "templates", "resources"]`Directories with template files (copied but not prefixed)`delete_vendor_packages`No`false`Remove originals from `vendor/` after copy`update_call_sites`No`true`Update `use` statements in host project files. `true` scans `src/`, or pass an array of directories (see below)`dev_packages`No`null`Config for prefixing `require-dev` packages separately### Full Configuration Example

[](#full-configuration-example)

```
{
    "extra": {
        "wp-scoper": {
            "target_directory": "packages",
            "namespace_prefix": "WP_Statistics\\Deps",
            "class_prefix": "WP_Statistics_",
            "constant_prefix": "WP_STATISTICS_",
            "packages": ["geoip2/geoip2", "matomo/device-detector"],
            "exclude_packages": ["psr/log"],
            "exclude_patterns": ["/\\.md$/", "/tests?\\//i"],
            "exclude_directories": ["views", "templates", "resources"],
            "delete_vendor_packages": false,
            "update_call_sites": true,
            "dev_packages": {
                "enabled": true,
                "target_directory": "tests/vendor-prefixed",
                "packages": ["fakerphp/faker"]
            }
        }
    }
}
```

Project Layout
--------------

[](#project-layout)

Recommended layout with `vendor/` gitignored:

```
my-plugin/
├── .gitignore              # /vendor/ is gitignored
├── composer.json
├── vendor/                 # NOT committed
│   └── autoload.php
├── packages/               # COMMITTED - prefixed vendor packages
│   ├── autoload.php        # Generated by wp-scoper
│   ├── geoip2/
│   └── maxmind/
├── src/
│   └── Plugin.php          # Your code
└── tests/

```

In your plugin's main file:

```
require_once __DIR__ . '/packages/autoload.php';

use WP_Statistics\Deps\GeoIp2\Database\Reader;

$reader = new Reader('/path/to/GeoLite2-City.mmdb');
```

Usage
-----

[](#usage)

### As a Composer Plugin (automatic)

[](#as-a-composer-plugin-automatic)

WP Scoper runs automatically after `composer install` and `composer update`.

### Manual Command

[](#manual-command)

```
composer wp-scope
composer wp-scope --dry-run
```

### Standalone CLI

[](#standalone-cli)

```
vendor/bin/wp-scoper
vendor/bin/wp-scoper /path/to/project
vendor/bin/wp-scoper --dry-run
```

Automatic Call Site Updates (`update_call_sites`)
-------------------------------------------------

[](#automatic-call-site-updates-update_call_sites)

When dependencies get prefixed, your own source code still references the original namespaces. Normally you'd have to manually find and replace every `use` statement, every `new` call, and every type hint across your entire project. With `update_call_sites` enabled (the default), wp-scoper does this for you automatically.

By default it scans all PHP files in your `src/` directory. You can also pass an array of directories to scan additional locations:

```
{
    "update_call_sites": ["src", "includes"]
}
```

You write your code using the original package namespaces, and wp-scoper handles the rest.

### Example: Before and After

[](#example-before-and-after)

Say you have this file in your project:

**`src/GeoIP/GeoIPService.php` - before running wp-scoper:**

```
