Skip to content

Commit 09e01f3

Browse files
committed
Bump
0 parents  commit 09e01f3

File tree

10 files changed

+1025
-0
lines changed

10 files changed

+1025
-0
lines changed

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Ignore hidden files
2+
.*
3+
4+
# Ignore temporary files
5+
*~
6+
7+
# Annoying Windows files
8+
Thumbs.db
9+
ehthumbs.db
10+
Desktop.ini
11+
$RECYCLE.BIN/
12+
13+
# Generated content
14+
/vendor/
15+
16+
# Exceptions
17+
!.gitignore
18+
!.gitkeep
19+
!/.php_cs

.php_cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
return PhpCsFixer\Config::create()
4+
->setUsingCache(false)
5+
->setRules([
6+
'@PSR2' => true,
7+
'@Symfony' => true,
8+
'array_syntax' => ['syntax' => 'short'],
9+
'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => null],
10+
'concat_space' => ['spacing' => 'one'],
11+
'ordered_imports' => true,
12+
13+
// Overrides
14+
'phpdoc_summary' => false,
15+
])
16+
->setFinder(PhpCsFixer\Finder::create()
17+
->in(['.'])
18+
->exclude('vendor')
19+
->exclude('tests')
20+
);

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Composer Dependency Isolation
2+
3+
**What this plugin does**
4+
5+
This plugin prefixes all vendor namespaces with a chosen value. This
6+
includes all declared namespaces, use statements, fully qualified
7+
references, and most string values that reference the namespace.
8+
9+
All vendor code and composer autoload mappings are updated to reference
10+
the prefixed namespace.
11+
12+
**What this plugin does not do**
13+
14+
It will not touch any code in your project. It only affects code in the
15+
vendor directory, and it only affects code referencing the affected
16+
namespaces. You must update all references in your code yourself if you
17+
apply this to an existing project.
18+
19+
**Why would I want to use this?**
20+
21+
This plugin allows you to run two projects that utilize composer
22+
dependencies in the same runtime, without worrying about conflicting
23+
dependencies. The most common example is in a WordPress environment,
24+
where all plugins execute in the same runtime, and may rely on the same
25+
composer dependencies. Each project utilizing the plugin can't conflict
26+
with any other project unless the vendor code is not namespaced (in which
27+
case there aren't many options...).
28+
29+
## Usage
30+
31+
Using the plugin is straightforward. Install the plugin by requiring it
32+
in your project: `composer require 0x6d617474/isolate`.
33+
34+
Configure the plugin by adding the following to your `composer.json`:
35+
```
36+
"config" : {
37+
"isolate": {
38+
"prefix": "Your\\Prefix\\Here\\",
39+
"blacklist": [],
40+
"autorun": false,
41+
"require-dev": false,
42+
"replacements" : {}
43+
}
44+
}
45+
```
46+
47+
The only required value is the `prefix`.
48+
49+
After you have configured the plugin, run the isolation:
50+
```
51+
composer isolate
52+
composer dump
53+
```
54+
55+
Your vendor code is now prefixed!
56+
57+
Be sure to `dump` after you `isolate`, or your autoload mappings will
58+
be incorrect!
59+
60+
## Configuration
61+
62+
**prefix**
63+
64+
This is the value that will be prepended to all vendor namespaces. It
65+
should be a valid namespace, and should be unique to your project. I
66+
recommend you don't use your main project namespace, or at least add
67+
`\\Vendor` to the end.
68+
69+
**blacklist**
70+
71+
This is a list of packages you don't want to prefix. Matching packages
72+
will not be scanned for namespaces, but will still have code rewritten
73+
if it contains namespaces from other non-blacklisted packages.
74+
75+
**autorun**
76+
77+
Setting this value to true automatically runs the isolation process
78+
before every `dump`.
79+
80+
**require-dev**
81+
82+
By default, only `require` packages are scanned for namespaces, and
83+
`require-dev` packages are ignored (as above, they will still have code
84+
rewritten if they contain namespaces from other packages).
85+
86+
Setting this value to `true` includes the `require-dev` packages in the
87+
scan, and any found namespaces will be prefixed.
88+
89+
**replacements**
90+
91+
This is a place for manually fixing things in the vendor code that either
92+
were not detected and replaced, or replaced when they should not have been.
93+
94+
After each file has been parsed and rewritten, if there is an entry in the
95+
replacements list, it will do a string replace on the file.
96+
97+
String replacements should be idempotent, or things will break on multiple
98+
executions.
99+
100+
The syntax is:
101+
```
102+
"replacements" : {
103+
"path/relative/to/vendor/root/file.php" : {
104+
"search" : "replace",
105+
"search" : "replace",
106+
},
107+
"path/relative/to/vendor/root/file.php" : {
108+
"search" : "replace",
109+
"search" : "replace",
110+
}
111+
}
112+
```

composer.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "0x6d617474/isolate",
3+
"description": "Isolates composer dependencies by prefixing namespace for all vendor code within a project",
4+
"license": "MIT",
5+
"minimum-stability": "alpha",
6+
"type": "composer-plugin",
7+
"require": {
8+
"composer-plugin-api": "^1.1",
9+
"nikic/php-parser": "^2.1"
10+
},
11+
"require-dev": {
12+
"composer/composer": "^1.4",
13+
"friendsofphp/php-cs-fixer": "^2.3"
14+
},
15+
"autoload":{
16+
"psr-4":{
17+
"Ox6d617474\\Isolate\\":"src/"
18+
}
19+
},
20+
"extra": {
21+
"class": "Ox6d617474\\Isolate\\Plugin"
22+
}
23+
}

src/Command.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Ox6d617474\Isolate;
4+
5+
use Composer\Command\BaseCommand;
6+
use Symfony\Component\Console\Input\InputInterface;
7+
use Symfony\Component\Console\Output\OutputInterface;
8+
9+
final class Command extends BaseCommand
10+
{
11+
/**
12+
* {@inheritdoc}
13+
*/
14+
protected function configure()
15+
{
16+
$this->setName('isolate');
17+
$this->setDescription('Isolate dependencies');
18+
}
19+
20+
/**
21+
* {@inheritdoc}
22+
*/
23+
protected function execute(InputInterface $input, OutputInterface $output)
24+
{
25+
$this->getComposer()->getEventDispatcher()->dispatch('__isolate-dependencies');
26+
}
27+
}

src/CommandProvider.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Ox6d617474\Isolate;
4+
5+
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
6+
7+
final class CommandProvider implements CommandProviderCapability
8+
{
9+
/**
10+
* {@inheritdoc}
11+
*/
12+
public function getCommands()
13+
{
14+
return [new Command()];
15+
}
16+
}

src/DiscoveryVisitor.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Ox6d617474\Isolate;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\Namespace_;
7+
use PhpParser\NodeVisitorAbstract;
8+
9+
final class DiscoveryVisitor extends NodeVisitorAbstract
10+
{
11+
/**
12+
* Discovered namespaces
13+
*
14+
* @var array
15+
*/
16+
private $namespaces;
17+
18+
/**
19+
* Class constructor
20+
*/
21+
public function __construct()
22+
{
23+
$this->namespaces = [];
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function enterNode(Node $node)
30+
{
31+
if ($node instanceof Namespace_) {
32+
if (isset($node->name)) {
33+
$this->namespaces[implode($node->name->parts, '\\')] = true;
34+
}
35+
}
36+
}
37+
38+
/**
39+
* Get the list of discovered namespaces
40+
*
41+
* @return array
42+
*/
43+
public function getNamespaces()
44+
{
45+
return $this->namespaces;
46+
}
47+
}

src/NamespaceChecker.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace Ox6d617474\Isolate;
4+
5+
final class NamespaceChecker
6+
{
7+
/**
8+
* Discovered namespaces
9+
*
10+
* @var array
11+
*/
12+
private $namespaces;
13+
14+
/**
15+
* Prefix
16+
*
17+
* @var string
18+
*/
19+
private $prefix;
20+
21+
/**
22+
* Class constructor
23+
*
24+
* @param array $namespaces
25+
* @param string $prefix
26+
*/
27+
public function __construct(array $namespaces, $prefix)
28+
{
29+
$this->namespaces = $namespaces;
30+
$this->prefix = $prefix;
31+
}
32+
33+
/**
34+
* Is the given string a valid namespace
35+
*
36+
* @param string $string
37+
*
38+
* @return bool
39+
*/
40+
public static function isNamespace($string)
41+
{
42+
// Must contain a backslash, and may only contain alphanumeric and underscore
43+
if (!preg_match('/^[0-9a-z_\\\]+$/i', $string) || !preg_match('/[\\\]+/i', $string)) {
44+
return false;
45+
}
46+
47+
// Don't match only slashes...
48+
if (preg_match('/^[\\\]+$/i', $string)) {
49+
return false;
50+
}
51+
// Don't match a single word between slashes...
52+
if (preg_match('/^[\\\]+[0-9a-z_]+[\\\]+$/i', $string)) {
53+
return false;
54+
}
55+
56+
// Sections should not begin with a number
57+
$parts = array_filter(explode('\\', $string));
58+
foreach ($parts as $part) {
59+
if (preg_match('/^[0-9]+/', $part)) {
60+
return false;
61+
break;
62+
}
63+
}
64+
65+
return true;
66+
}
67+
68+
/**
69+
* Should the given namespace be transformed?
70+
*
71+
* @param string $string
72+
*
73+
* @return bool
74+
*/
75+
public function shouldTransform($string)
76+
{
77+
// Never transform non-namespace strings
78+
if (!self::isNamespace($string)) {
79+
return false;
80+
}
81+
82+
// Trim ends to ensure valid matches
83+
$string = trim($string, '\\');
84+
85+
// We never want to match our own prefix
86+
if (preg_match('/^' . preg_quote($this->prefix, '/') . '/i', $string)) {
87+
return false;
88+
}
89+
90+
// We only want to match our list of namespaces
91+
return isset($this->namespaces[$string]);
92+
}
93+
}

0 commit comments

Comments
 (0)