├── .gitignore ├── Source ├── Commands │ ├── RenamespaceCommand.php │ └── ScanUsesCommand.php ├── Models │ ├── Configuration.php │ ├── Package.php │ └── Project.php └── functions.php ├── bin └── namespacer ├── composer.json ├── patches.config.php ├── readme.md ├── sample.composer.json └── sample.config.php /.gitignore: -------------------------------------------------------------------------------- 1 | plugin 2 | dest 3 | build 4 | temp 5 | vendor 6 | composer.lock 7 | -------------------------------------------------------------------------------- /Source/Commands/RenamespaceCommand.php: -------------------------------------------------------------------------------- 1 | rootDir = trailingslashit($rootDir); 23 | } 24 | 25 | protected function configure() { 26 | $this->setDescription("Renamespaces a composer.json project for use in WordPress plugins."); 27 | 28 | $this->addArgument("dest", InputArgument::REQUIRED, "The path to save the renamespaced libraries to."); 29 | 30 | $this->addOption("composer", null, InputOption::VALUE_REQUIRED, "The path to the composer.json containing the packages to renamespace.", null); 31 | $this->addOption("source", null, InputOption::VALUE_REQUIRED, "The path to the directory containing the composer.json to renamespace. This directory should already have had `composer update` run in it.", null); 32 | 33 | $this->addOption("package", null, InputOption::VALUE_REQUIRED, "The prefix to add to packages", "mcloud"); 34 | $this->addOption("namespace", null, InputOption::VALUE_REQUIRED, "The prefix to add to namespaces", "MediaCloud\\Vendor\\"); 35 | 36 | $this->addOption("config", null, InputOption::VALUE_REQUIRED, "The path to the configuration to use, if required.", null); 37 | } 38 | 39 | protected function execute(InputInterface $input, OutputInterface $output) { 40 | //region Directories 41 | 42 | if (empty($input->getOption('composer')) && empty($input->getOption('source'))) { 43 | $output->writeln("You must specify either the --composer or --source option."); 44 | return Command::FAILURE; 45 | } 46 | 47 | $packagePrefix = $input->getOption('package'); 48 | $namespacePrefix = $input->getOption('namespace'); 49 | 50 | $namespacePrefix = rtrim($namespacePrefix, "\\")."\\"; 51 | 52 | $tempPath = trailingslashit($this->rootDir.uniqid()); 53 | @mkdir($tempPath, 0755, true); 54 | 55 | if (!empty($input->getOption('composer'))) { 56 | $originalComposer = $input->getOption('composer'); 57 | if (strpos($originalComposer, '/') !== 0) { 58 | $originalComposer = $this->rootDir.$originalComposer; 59 | } else { 60 | $originalComposer = $originalComposer; 61 | } 62 | 63 | if (!file_exists($originalComposer)) { 64 | rmdir($tempPath); 65 | $output->writeln("Composer file does not exist at $originalComposer."); 66 | return Command::FAILURE; 67 | } 68 | 69 | $sourcePath = trailingslashit($tempPath.'project'); 70 | @mkdir($sourcePath, 0755, true); 71 | 72 | copy($originalComposer, $sourcePath.'composer.json'); 73 | 74 | $output->writeln("Creating project ... "); 75 | $output->writeln(""); 76 | `cd $sourcePath && composer update`; 77 | $output->writeln(""); 78 | } else { 79 | $sourcePath = $input->getOption('source'); 80 | if (strpos($sourcePath, '/') !== 0) { 81 | $sourcePath = trailingslashit($this->rootDir.$sourcePath); 82 | } else { 83 | $sourcePath = trailingslashit($sourcePath); 84 | } 85 | 86 | if (!file_exists($sourcePath)) { 87 | $output->writeln("Input directory $sourcePath does not exist."); 88 | return Command::FAILURE; 89 | } 90 | } 91 | 92 | $outputPath = $input->getArgument('dest'); 93 | if (strpos($outputPath, '/') !== 0) { 94 | $outputPath = trailingslashit($this->rootDir.$outputPath); 95 | } else { 96 | $outputPath = trailingslashit($outputPath); 97 | } 98 | 99 | if (file_exists($outputPath)) { 100 | if (file_exists($outputPath.'lib')) { 101 | `rm -rf {$outputPath}lib`; 102 | } 103 | } else { 104 | if(!mkdir($outputPath, 0755, true)) { 105 | $output->writeln("Could not create output directory."); 106 | return Command::FAILURE; 107 | } 108 | } 109 | 110 | $projectOutputPath = $tempPath.'build/'; 111 | $libraryOutputPath = $tempPath.'library/'; 112 | 113 | @mkdir($projectOutputPath, 0755, true); 114 | @mkdir($libraryOutputPath, 0755, true); 115 | 116 | $configPath = null; 117 | if (!empty($input->getOption('config'))) { 118 | $configPath = $input->getOption('config'); 119 | if (strpos($configPath, '/') !== 0) { 120 | $configPath = $this->rootDir.$configPath; 121 | } 122 | 123 | if (!file_exists($configPath)) { 124 | $output->writeln("Config file $configPath does not exist."); 125 | return Command::FAILURE; 126 | } 127 | } 128 | 129 | //endregion 130 | 131 | //region Project Info 132 | 133 | $project = new Project($sourcePath); 134 | 135 | $output->writeln(""); 136 | 137 | $table = new Table($output); 138 | $table 139 | ->setHeaderTitle("Settings") 140 | ->setHeaders(['Setting', 'Value']) 141 | ->setRows([ 142 | ["Package Prefix", $packagePrefix], 143 | ["Namespace Prefix", $namespacePrefix], 144 | ["Source", $sourcePath], 145 | ["Destination", $outputPath], 146 | ["Config", $configPath], 147 | ]) 148 | ->render(); 149 | 150 | $output->writeln(""); 151 | 152 | $table = new Table($output); 153 | $table 154 | ->setHeaderTitle("Found Packages") 155 | ->setHeaders(['Package', 'Version']) 156 | ->setRows($project->getPackageTableData()) 157 | ->render(); 158 | 159 | $output->writeln(""); 160 | 161 | //endregion 162 | 163 | //region Package Processing 164 | $configuration = new Configuration($configPath); 165 | 166 | $packageSection = $output->section(); 167 | $packageSection->writeln("Processing packages ..."); 168 | 169 | $packageProgressSection = $output->section(); 170 | $packageProgress = new ProgressBar($packageProgressSection, count($project->getPackages())); 171 | $packageProgress->setBarWidth(50); 172 | $packageProgress->start(); 173 | 174 | $sourceFileCount = 0; 175 | $allNamespaces = []; 176 | foreach($project->getPackages() as $packageName => $package) { 177 | $packageSection->overwrite("Processing package $packageName ..."); 178 | $packageProgress->advance(); 179 | $package->process($configuration, $packagePrefix, $namespacePrefix, $libraryOutputPath, $project->getPackages()); 180 | $allNamespaces = array_merge($allNamespaces, $package->getNamespaces()); 181 | $sourceFileCount += count($package->getSourceFiles()); 182 | } 183 | 184 | $packageSection->overwrite("Finished processing packages."); 185 | $packageProgress->finish(); 186 | 187 | $packageProgressSection->clear(); 188 | 189 | $output->writeln(""); 190 | $output->writeln("Found ".count($allNamespaces)." namespaces in {$sourceFileCount} source files."); 191 | 192 | //endregion 193 | 194 | //region Re-namespacing 195 | 196 | $packageSection = $output->section(); 197 | $packageSection->writeln("Re-namespacing packages ..."); 198 | 199 | $packageProgressSection = $output->section(); 200 | $packageProgress = new ProgressBar($packageProgressSection, $sourceFileCount); 201 | $packageProgress->setFormat('very_verbose'); 202 | $packageProgress->setBarWidth(50); 203 | $packageProgress->start(); 204 | 205 | foreach($project->getPackages() as $packageName => $package) { 206 | $packageSection->overwrite("Re-namespacing package $packageName ..."); 207 | $packageProgress->advance(); 208 | $package->renamespace($configuration, $packageSection, $packageProgress, $namespacePrefix, $allNamespaces); 209 | } 210 | 211 | $packageSection->overwrite("Finished re-namespacing packages."); 212 | $packageProgress->finish(); 213 | 214 | $packageProgressSection->clear(); 215 | 216 | $output->writeln(""); 217 | 218 | //endregion 219 | 220 | //region Finished 221 | 222 | $project->save($projectOutputPath, $packagePrefix); 223 | $output->writeln("Saved project. Running composer ..."); 224 | $output->writeln(""); 225 | 226 | `cd {$projectOutputPath} && composer update`; 227 | `rm -rf {$projectOutputPath}vendor/bin`; 228 | rename($projectOutputPath.'vendor', $outputPath.'lib'); 229 | `rm -rf $tempPath`; 230 | 231 | $output->writeln(""); 232 | $output->writeln("Finished."); 233 | 234 | //endregion 235 | 236 | return Command::SUCCESS; 237 | } 238 | } -------------------------------------------------------------------------------- /Source/Commands/ScanUsesCommand.php: -------------------------------------------------------------------------------- 1 | rootDir = trailingslashit($rootDir); 21 | } 22 | 23 | protected function configure() { 24 | $this->addArgument("input", InputArgument::REQUIRED, "The path to the directory containing the PHP files to scan."); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output) { 28 | //region Directories 29 | $sourcePath = $input->getArgument('input'); 30 | if (strpos($sourcePath, '/') !== 0) { 31 | $sourcePath = trailingslashit($this->rootDir.$sourcePath); 32 | } else { 33 | $sourcePath = trailingslashit($sourcePath); 34 | } 35 | 36 | if (!file_exists($sourcePath)) { 37 | $output->writeln("Input directory $sourcePath does not exist."); 38 | return Command::FAILURE; 39 | } 40 | 41 | $finder = new Finder(); 42 | $finder 43 | ->followLinks() 44 | ->name("*.php") 45 | ->name("*.inc"); 46 | 47 | $sourceFiles = []; 48 | /** @var SplFileInfo $file */ 49 | foreach($finder->in(trailingslashit($sourcePath)) as $file) { 50 | $sourceFiles[] = $file->getRealPath(); 51 | } 52 | 53 | 54 | $output->writeln("Found ".count($sourceFiles)." source files."); 55 | $uses = []; 56 | foreach($sourceFiles as $sourceFile) { 57 | $source = file_get_contents($sourceFile); 58 | preg_match_all('#^\s*use\s+([^;]+)#m', $source, $matches, PREG_SET_ORDER); 59 | if (count($matches) > 0) { 60 | foreach($matches as $match) { 61 | if (!in_array($match[1], $uses)) { 62 | if (strpos($match[1], 'function ') === 0) { 63 | continue; 64 | } 65 | 66 | $uses[] = $match[1]; 67 | } 68 | } 69 | } 70 | } 71 | 72 | sort($uses); 73 | 74 | $output->writeln(""); 75 | $output->writeln($uses); 76 | $output->writeln(""); 77 | $output->writeln("Finished."); 78 | 79 | return Command::SUCCESS; 80 | } 81 | } -------------------------------------------------------------------------------- /Source/Models/Configuration.php: -------------------------------------------------------------------------------- 1 | config = include $configFile; 11 | } 12 | } 13 | 14 | public function prepare(string $package, array $config, string $path, string $namespacePrefix) { 15 | if (isset($this->config['prepare'])) { 16 | foreach($this->config['prepare'] as $func) { 17 | $config = call_user_func($func, $package, $config, $path, $namespacePrefix); 18 | } 19 | } 20 | 21 | return $config; 22 | } 23 | 24 | public function start(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 25 | if (isset($this->config['start'])) { 26 | foreach($this->config['start'] as $func) { 27 | $source = call_user_func($func, $source, $currentNamespace, $namespacePrefix, $package, $file); 28 | } 29 | } 30 | 31 | return $source; 32 | } 33 | 34 | public function before(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 35 | if (isset($this->config['before'])) { 36 | foreach($this->config['before'] as $func) { 37 | $source = call_user_func($func, $source, $namespace, $currentNamespace, $namespacePrefix, $package, $file); 38 | } 39 | } 40 | 41 | return $source; 42 | } 43 | 44 | public function after(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 45 | if (isset($this->config['after'])) { 46 | foreach($this->config['after'] as $func) { 47 | $source = call_user_func($func, $source, $namespace, $currentNamespace, $namespacePrefix, $package, $file); 48 | } 49 | } 50 | 51 | return $source; 52 | } 53 | 54 | public function end(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 55 | if (isset($this->config['end'])) { 56 | foreach($this->config['end'] as $func) { 57 | $source = call_user_func($func, $source, $currentNamespace, $namespacePrefix, $package, $file); 58 | } 59 | } 60 | 61 | return $source; 62 | } 63 | } -------------------------------------------------------------------------------- /Source/Models/Package.php: -------------------------------------------------------------------------------- 1 | name = $name; 31 | $this->path = trailingslashit($path); 32 | $this->version = $version; 33 | } 34 | 35 | //region Properties 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getName() { 41 | return $this->name; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getPath() { 48 | return $this->path; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getVersion(): string { 55 | return $this->version; 56 | } 57 | 58 | /** 59 | * @return array 60 | */ 61 | public function getNamespaces(): array { 62 | return $this->namespaces; 63 | } 64 | 65 | /** 66 | * @return array 67 | */ 68 | public function getSourceFiles(): array { 69 | return $this->sourceFiles; 70 | } 71 | 72 | //endregion 73 | 74 | //region Processing 75 | 76 | /** 77 | * @param Configuration $configuration 78 | * @param string $packagePrefix 79 | * @param string $namespacePrefix 80 | * @param string $outputPath 81 | * @param Package[] $packages 82 | */ 83 | public function process($configuration, string $packagePrefix, string $namespacePrefix, string $outputPath, array $packages) { 84 | $outputPath = trailingslashit($outputPath); 85 | $this->outputPath = $outputPath; 86 | 87 | $composerFile = $this->path.'composer.json'; 88 | if (!file_exists($composerFile)) { 89 | throw new \Exception("Missing composer.json for package {$this->name}."); 90 | } 91 | 92 | $config = json_decode(file_get_contents($composerFile), true); 93 | 94 | $config = $configuration->prepare($this->name, $config, $this->path, $namespacePrefix); 95 | 96 | $config['name'] = $packagePrefix.'-'.$config['name']; 97 | $config['version'] = $this->getVersion(); 98 | 99 | if (isset($config['require'])) { 100 | foreach($config['require'] as $packageName => $version) { 101 | if (strpos($packageName, 'ext-') === 0) { 102 | continue; 103 | } 104 | 105 | if (strpos($packageName, 'lib-') === 0) { 106 | continue; 107 | } 108 | 109 | if ($packageName === 'php') { 110 | continue; 111 | } 112 | 113 | if ($packageName === 'composer-plugin-api') { 114 | unset($config['require'][$packageName]); 115 | continue; 116 | } 117 | 118 | if ($packageName === 'kylekatarnls/update-helper') { 119 | unset($config['require'][$packageName]); 120 | continue; 121 | } 122 | 123 | if (!isset($packages[$packageName])) { 124 | throw new \Exception("Cannot find related package $packageName for {$this->name}."); 125 | } 126 | 127 | $package = $packages[$packageName]; 128 | unset($config['require'][$packageName]); 129 | $config['require'][$packagePrefix.'-'.$packageName] = $package->getVersion(); 130 | } 131 | } 132 | 133 | if (isset($config['autoload'])) { 134 | if (isset($config['autoload']['psr-0'])) { 135 | foreach($config['autoload']['psr-0'] as $namespace => $directory) { 136 | $tempPsr0Path = $this->path.'tmp/'; 137 | $psr0Path = trailingslashit($this->path.$directory); 138 | mkdir($tempPsr0Path, 0755, true); 139 | `mv {$psr0Path}* $tempPsr0Path`; 140 | 141 | $namespacePath = ltrim(str_replace("\\", "/", $namespacePrefix), '\\'); 142 | $newPsr0Path = trailingslashit(trailingslashit($this->path.$directory).$namespacePath); 143 | mkdir($newPsr0Path, 0755, true); 144 | `mv {$tempPsr0Path}* $newPsr0Path`; 145 | @rmdir($tempPsr0Path); 146 | 147 | $config['autoload']['psr-0'][$namespacePrefix.$namespace] = $directory; 148 | unset($config['autoload']['psr-0'][$namespace]); 149 | } 150 | } 151 | 152 | if (isset($config['autoload']['psr-4'])) { 153 | foreach($config['autoload']['psr-4'] as $namespace => $directory) { 154 | $config['autoload']['psr-4'][$namespacePrefix.$namespace] = $directory; 155 | unset($config['autoload']['psr-4'][$namespace]); 156 | } 157 | } 158 | } 159 | 160 | unset($config['extra']['branch-alias']['dev-master']); 161 | 162 | @mkdir("$outputPath{$this->name}", 0755, true); 163 | `cp -r {$this->path} $outputPath{$this->name}/`; 164 | 165 | file_put_contents($outputPath.$this->name.'/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 166 | 167 | $finder = new Finder(); 168 | $finder 169 | ->followLinks() 170 | ->name("*.php") 171 | ->name("*.inc"); 172 | 173 | $this->sourceFiles = []; 174 | /** @var SplFileInfo $file */ 175 | foreach($finder->in(trailingslashit($outputPath.$this->name))->exclude('composer') as $file) { 176 | $this->sourceFiles[] = $file->getRealPath(); 177 | } 178 | 179 | $namespaceRegex = '/^\s*namespace\s+([^\s;]+)/m'; 180 | $idiotNamespaceRegex = '/^\s*\<\?php\s+namespace\s+([^\s;]+)/m'; 181 | foreach($this->sourceFiles as $file) { 182 | $matches = []; 183 | preg_match_all($namespaceRegex, file_get_contents($file), $matches, PREG_SET_ORDER, 0); 184 | if (count($matches) > 0) { 185 | foreach($matches as $match) { 186 | if ($match[1] == "'.__NAMESPACE__.'") { 187 | continue; 188 | } 189 | 190 | if (!in_array($match[1], $this->namespaces)) { 191 | $this->namespaces[] = $match[1]; 192 | } 193 | } 194 | } else { 195 | preg_match_all($idiotNamespaceRegex, file_get_contents($file), $matches, PREG_SET_ORDER, 0); 196 | if (count($matches) > 0) { 197 | foreach($matches as $match) { 198 | if ($match[1] == "'.__NAMESPACE__.'") { 199 | continue; 200 | } 201 | 202 | if (!in_array($match[1], $this->namespaces)) { 203 | $this->namespaces[] = $match[1]; 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | /** 212 | * @param Configuration $configuration 213 | * @param ConsoleSectionOutput $output 214 | * @param ProgressBar $progressBar 215 | * @param string $namespacePrefix 216 | * @param array $namespaces 217 | */ 218 | public function renamespace($configuration, $output, $progressBar, string $namespacePrefix, array $namespaces) { 219 | $namespacePrefixString = str_replace("\\", "\\\\", $namespacePrefix); 220 | $namespacePrefixStringRegex = str_replace("\\", "\\\\", $namespacePrefixString); 221 | 222 | foreach($this->sourceFiles as $file) { 223 | if (!empty($output) && !empty($this->outputPath)) { 224 | $relativeFile = str_replace($this->outputPath.$this->name, '', $file); 225 | $output->overwrite("Re-namespacing package {$this->name} ... $relativeFile"); 226 | } 227 | 228 | if (!empty($progressBar)) { 229 | $progressBar->advance(); 230 | } 231 | 232 | $source = file_get_contents($file); 233 | 234 | $currentNamespace = null; 235 | preg_match("#^\s*namespace\s+([^;]+)#m", $source, $matches); 236 | if (count($matches) > 1) { 237 | $currentNamespace = $matches[1]; 238 | } else { 239 | preg_match("#^\s*\<\?php\s+namespace\s+([^;]+)#m", $source, $matches); 240 | if (count($matches) > 1) { 241 | $currentNamespace = $matches[1]; 242 | } 243 | } 244 | 245 | $source = $configuration->start($source, $currentNamespace, $namespacePrefix, $this->name, $file); 246 | 247 | $currentNamespaceRegexSafe = str_replace("\\","\\\\", $currentNamespace); 248 | $source = preg_replace("#^\s*namespace\s+$currentNamespaceRegexSafe\s*;#m", "\nnamespace $namespacePrefix$currentNamespace;", $source, -1, $count); 249 | $source = preg_replace("#^\s*\<\?php\s+namespace\s+$currentNamespaceRegexSafe\s*;#m", "before($source, $namespace, $currentNamespace, $namespacePrefix, $this->name, $file); 254 | 255 | $namespace = $namespace."\\"; 256 | $stringNamespace = str_replace("\\", "\\\\", $namespace); 257 | $stringNamespaceRegex = str_replace("\\", "\\\\", $stringNamespace); 258 | 259 | $namespaceTrimmed = rtrim($namespace, "\\"); 260 | $stringNamespaceTrimmed = rtrim(str_replace("\\", "\\\\", $namespace), "\\"); 261 | 262 | 263 | $source = preg_replace("#^\\s*use\\s+$stringNamespace#m", "use $namespacePrefix$namespace", $source, -1, $count); 264 | $changes += $count; 265 | 266 | $source = preg_replace("#^\\s*use\\s+$stringNamespaceTrimmed;#m", "use $namespacePrefix$namespaceTrimmed;", $source, -1, $count); 267 | $changes += $count; 268 | 269 | $source = preg_replace("#\\s+$stringNamespaceRegex#m", " $namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 270 | $changes += $count; 271 | 272 | $source = preg_replace("#\\\"$stringNamespaceRegex#m", "\"$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 273 | $changes += $count; 274 | 275 | $source = preg_replace("#\\'$stringNamespaceRegex#m", "'$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 276 | $changes += $count; 277 | 278 | $source = preg_replace("#\\s+\\\\\\\\$stringNamespaceRegex#m", " \\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 279 | $changes += $count; 280 | 281 | $source = preg_replace("#\\\"\\\\\\\\$stringNamespaceRegex#m", "\"\\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 282 | $changes += $count; 283 | 284 | $source = preg_replace("#\\'\\\\\\\\$stringNamespaceRegex#m", "'\\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count); 285 | $changes += $count; 286 | 287 | $source = preg_replace("#\\s+$stringNamespace#m", " $namespacePrefix$namespace", $source, -1, $count); 288 | $changes += $count; 289 | 290 | $source = preg_replace("#\\\"$stringNamespace#m", "\"$namespacePrefix$namespace", $source, -1, $count); 291 | $changes += $count; 292 | 293 | $source = preg_replace("#\\'$stringNamespace#m", "'$namespacePrefix$namespace", $source, -1, $count); 294 | $changes += $count; 295 | 296 | $source = preg_replace("#\\'\\\\$stringNamespace#m", "'\\$namespacePrefix$namespace", $source, -1, $count); 297 | $changes += $count; 298 | 299 | $source = preg_replace("#\\(\s*\\\\$stringNamespace#m", "(\\$namespacePrefix$namespace", $source, -1, $count); 300 | $changes += $count; 301 | 302 | $source = preg_replace("#\\s+\\\\$stringNamespace#m", " \\$namespacePrefix$namespace", $source, -1, $count); 303 | $changes += $count; 304 | 305 | $source = preg_replace("#\\s+$namespacePrefixString(.*)\s*\(#m", ' \\NAMESPACEPLACEHOLDER$1(', $source, -1, $count); 306 | $source = str_replace('NAMESPACEPLACEHOLDER', $namespacePrefix, $source); 307 | $changes += $count; 308 | 309 | $source = $configuration->after($source, $namespace, $currentNamespace, $namespacePrefix, $this->name, $file); 310 | } 311 | 312 | $source = $configuration->end($source, $currentNamespace, $namespacePrefix, $this->name, $file); 313 | 314 | file_put_contents($file, $source); 315 | } 316 | } 317 | 318 | //endregion 319 | 320 | } -------------------------------------------------------------------------------- /Source/Models/Project.php: -------------------------------------------------------------------------------- 1 | sourcePath = trailingslashit($sourcePath); 19 | 20 | $composerLock = $sourcePath.'composer.lock'; 21 | if (!file_exists($composerLock)) { 22 | throw new \Exception("Composer lock file missing."); 23 | } 24 | 25 | $lockConfig = json_decode(file_get_contents($composerLock), true); 26 | if (!isset($lockConfig['packages'])) { 27 | throw new \Exception("No installed packages."); 28 | } 29 | 30 | foreach($lockConfig['packages'] as $package) { 31 | $packageName = $package['name']; 32 | $version = $package['version']; 33 | $path = trailingslashit($sourcePath.'vendor/'.$packageName); 34 | 35 | $package = new Package($packageName, $path, $version); 36 | $this->packages[$packageName] = $package; 37 | $this->packageTableData[] = [$packageName, $version]; 38 | } 39 | 40 | $composerFile = $sourcePath.'composer.json'; 41 | if (!file_exists($composerFile)) { 42 | throw new \Exception("Composer file missing."); 43 | } 44 | 45 | $this->config = json_decode(file_get_contents($composerFile), true); 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function getPackages(): array { 52 | return $this->packages; 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getPackageTableData(): array { 59 | return $this->packageTableData; 60 | } 61 | 62 | public function addRepository(string $packageName) { 63 | $this->config['repositories'][] = [ 64 | 'type' => 'path', 65 | 'url' => '../library/'.$packageName, 66 | 'options' => [ 67 | 'symlink' => false 68 | ] 69 | ]; 70 | } 71 | 72 | public function save(string $savePath, string $packagePrefix) { 73 | $composerFile = trailingslashit($savePath).'composer.json'; 74 | 75 | foreach($this->packages as $packageName => $package) { 76 | $this->addRepository($packageName); 77 | } 78 | 79 | if (isset($this->config['require'])) { 80 | foreach($this->config['require'] as $packageName => $version) { 81 | if (strpos($packageName, $packagePrefix.'-') === 0) { 82 | continue; 83 | } 84 | 85 | if (strpos($packageName, 'ext-') === 0) { 86 | continue; 87 | } 88 | 89 | if (strpos($packageName, 'lib-') === 0) { 90 | continue; 91 | } 92 | 93 | if ($packageName === 'php') { 94 | continue; 95 | } 96 | 97 | if ($packageName === 'composer-plugin-api') { 98 | unset($this->config['require'][$packageName]); 99 | continue; 100 | } 101 | 102 | if (!isset($this->packages[$packageName])) { 103 | continue; 104 | } 105 | 106 | $package = $this->packages[$packageName]; 107 | unset($this->config['require'][$packageName]); 108 | $this->config['require'][$packagePrefix.'-'.$packageName] = $package->getVersion(); 109 | } 110 | } 111 | 112 | file_put_contents($composerFile, json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 113 | } 114 | } -------------------------------------------------------------------------------- /Source/functions.php: -------------------------------------------------------------------------------- 1 | add($renamespaceCommand); 26 | $application->add($scanCommand); 27 | $application->setDefaultCommand('renamespace', true); 28 | $application->run(); 29 | 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ilab/namespacer", 3 | "description": "Tool for re-namespacing composer packages for use in WordPress plugins.", 4 | "type": "library", 5 | "license": "LGPL-3.0-or-later", 6 | "keywords": [ 7 | "namespace", 8 | "wordpress" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Jon Gilkison", 13 | "email": "jon@interfacelab.com" 14 | } 15 | ], 16 | "require": { 17 | "symfony/finder": "^5.1", 18 | "symfony/console": "^5.1", 19 | "ext-json": "*" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "ILAB\\Namespacer\\": "Source" 24 | }, 25 | "files": [ 26 | "Source/functions.php" 27 | ] 28 | }, 29 | "bin": ["bin/namespacer"] 30 | } 31 | -------------------------------------------------------------------------------- /patches.config.php: -------------------------------------------------------------------------------- 1 | [ 5 | function(string $package, array $config, string $path, string $namespacePrefix) { 6 | if (($package == 'kraken-io/kraken-php') && isset($config['autoload']['psr-0']['Kraken'])) { 7 | $srcDir = trailingslashit($path.$config['autoload']['psr-0']['Kraken']); 8 | $finder = new Symfony\Component\Finder\Finder(); 9 | $files = []; 10 | foreach($finder->in($srcDir) as $fileInfo) { 11 | $files[] = $fileInfo->getRealPath(); 12 | } 13 | 14 | if (file_exists($srcDir.'Kraken.php')) { 15 | $source = file_get_contents($srcDir.'Kraken.php'); 16 | $source = str_replace(' [ 32 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 33 | if ($package === 'duncan3dc/blade') { 34 | $filename = pathinfo($file, PATHINFO_BASENAME); 35 | if ($filename == 'BladeInstance.php') { 36 | $source = str_replace("private function getViewFinder(", "protected function getViewFinder(", $source); 37 | $source = str_replace("private function getViewFactory(", "protected function getViewFactory(", $source); 38 | } 39 | } else if ($package === 'smalot/pdfparser') { 40 | $filename = pathinfo($file, PATHINFO_BASENAME); 41 | if ($filename == 'Font.php') { 42 | $source = str_replace('$details[\'Encoding\'] = ($this->has(\'Encoding\') ? (string) $this->get(\'Encoding\') : \'Ansi\');', '$details[\'Encoding\'] = \'Ansi\';', $source); 43 | } 44 | } 45 | 46 | return $source; 47 | }, 48 | ], 49 | ]; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Namespacer 2 | Using composer when building WordPress plugins sound like a good idea at first, but it won't be long until you run into 3 | trouble when other plugins are loading incompatible versions of libraries that you are using. Our plugin, 4 | [MediaCloud](https://mediacloud.press), uses packages extensively - over 65 different libraries - many of them fairly 5 | common. I'd say over half of our support requests are due to conflicts with other plugins users have installed that 6 | are using outdated or newer versions of the same libraries we are using. The only way to really do that is to 7 | re-namespace the packages we are using and to be able to do that easily and safely. 8 | 9 | Namespacer allows you to re-namespace any composer packages you are using by adding a namespace prefix to all of the 10 | namespaces as well as prefixing the package names too. It will then generate a folder called "lib" which you can safely 11 | include in your plugin. 12 | 13 | Yes, I'm aware of projects out there like PHP-Scoper and Imposter and others. I've had issues with all of them, which 14 | is why I built this. Yoast and other plugins happily used PHP-Scoper, but their plugins, believe it or not, are much 15 | smaller than [MediaCloud](https://mediacloud.press). Yoast, for example, uses 4 composer packages. 16 | 17 | ## Installation 18 | You can install this globally, but I think you'd be better off using it as the basis of a project via composer. 19 | 20 | ```bash 21 | composer require ilab/namespacer 22 | ``` 23 | 24 | ## Usage 25 | Once installed: 26 | 27 | ```bash 28 | ./vendor/bin/renamespace [--composer COMPOSER] [--source SOURCE] [--package PACKAGE] [--namespace NAMESPACE] [--config CONFIG] 29 | ``` 30 | 31 | ### Arguments 32 | | Argument | Description | 33 | | ----------- | ----------- | 34 | | `composer` | The path (full or relative) to the composer file containing all the package dependencies you want to renamespace. You must specify this argument OR the `source` argument. | 35 | | `source` | The path (full or relative) to a directory containing a composer file and an existing vendor directory. When using `source` the vendor directory must already exist (`composer update` must already have been run). You must specify this argument OR the `composer` argument. | 36 | | `package` | The prefix to append to package names, for example specifying `--package mcloud` will turn `nesbot/carbon` into `mcloud-nesbot/carbon`. Default is `mcloud`. | 37 | | `namespace` | The prefix to append to namespaces, for example specifying `--namespace MediaCloud\Vendor` will transform `namespace Aws;` into `namespace MediaCloud\Vendor\Aws;`. Default is `MediaClound\Vendor`. | 38 | | `config` | An optional PHP configuration file for inserting filters into the namespacing process. | 39 | | `` | The destination directory. Namespacer will create a directory named `lib` inside that directory, removing it first if it already exists. | 40 | 41 | For example, you might run it: 42 | 43 | ```bash 44 | ./vendor/bin/namespacer --composer sample.composer.json --config patches.config.php --package mypackage --namespace MyNamespace\Vendor build/ 45 | ``` 46 | 47 | In this example, we're pointing to a `composer.json` file containing the packages we want to re-namespace and to a 48 | config file that contains filters that will apply more manual patches during the re-namespace process. The output 49 | of the processing will be put into the `build/` folder. 50 | 51 | ### Filtering (Patching in PHP-Scoper parlance) 52 | You can see some example configurations in `vendor/ilab/namespacer/sample.config.php` and 53 | `vendor/ilab/namespacer/patches.config.php` that will demonstrate how to insert your own code into the namespacing 54 | process to catch special cases. 55 | 56 | ## Reporting Bugs 57 | If you run into issues, please open a ticket and attach the composer.json you were trying to process with a clear 58 | description of the problem. -------------------------------------------------------------------------------- /sample.composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "ilab/ilab-media-tools", 3 | "description": "Tools for cropping, uploading to S3, serving through Imgix", 4 | "type": "wordpress-plugin", 5 | "license": "LGPL-3.0-or-later", 6 | "keywords": ["s3","imgix","cropping", "wordpress"], 7 | "repositories": [ 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Jon Gilkison", 12 | "email": "jon@interfacelab.com" 13 | } 14 | ], 15 | "config": { 16 | "platform": { 17 | "php": "7.1" 18 | } 19 | }, 20 | "require" : { 21 | "php": ">=7.1", 22 | "duncan3dc/blade": ">=3.3", 23 | "fasterimage/fasterimage": ">=1.1", 24 | "google/cloud-storage": ">=1.21", 25 | "google/cloud-vision": ">=0.19.0", 26 | "guzzlehttp/guzzle": ">=6.3.3", 27 | "ilab/b2-sdk-php": ">=1.4", 28 | "aws/aws-sdk-php": ">=3.0", 29 | "imgix/imgix-php": ">=1.0", 30 | "mikey179/vfsstream": "*", 31 | "monolog/monolog": ">=1.23", 32 | "psr/http-message-implementation": "*", 33 | "ralouphie/mimey": ">=2.1", 34 | "smalot/pdfparser": ">=0.13.2", 35 | "trntv/probe": ">=1.0", 36 | "zumba/amplitude-php": ">=1.0", 37 | "paragonie/easyrsa": ">=0.5.2", 38 | "kraken-io/kraken-php": ">=1.6", 39 | "shortpixel/shortpixel-php": ">=1.6", 40 | "wp-media/imagify-php": ">=1.1", 41 | "tinify/tinify": ">=1.5", 42 | "firebase/php-jwt": ">=5.2", 43 | "muxinc/mux-php": ">=0.4.0", 44 | "ivopetkov/html5-dom-document-php": ">=2.2", 45 | "dragonmantank/cron-expression": ">=2.3", 46 | "lorisleiva/cron-translator": ">=0.1.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sample.config.php: -------------------------------------------------------------------------------- 1 | [ 6 | function(string $package, array $config, string $path, string $namespacePrefix) { 7 | /** 8 | * @var string $package The name of the composer package 9 | * @var array $config The parsed composer.json config for the package 10 | * @var string $path The full path to the package 11 | * @var string $namespacePrefix The namespace prefix 12 | */ 13 | 14 | return $config; // You should always return the $config after manipulating it 15 | } 16 | ], 17 | 18 | /** These functions are called once the source file has been loaded but before all of the namespace changes are processed. */ 19 | "start" => [ 20 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 21 | /** 22 | * @var string $source The PHP source file contents 23 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix) 24 | * @var string $namespacePrefix The new namespace prefix 25 | * @var string $package The name of the composer package 26 | * @var string $file The complete path to the source file 27 | */ 28 | return $source; // You should always return the $source after manipulating it 29 | }, 30 | ], 31 | 32 | /** These functions are called once per namespace being processed before the regex's are run. */ 33 | "before" => [ 34 | function(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 35 | /** 36 | * @var string $source The PHP source file contents 37 | * @var string $namespace The namespace currently being processed 38 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix) 39 | * @var string $namespacePrefix The new namespace prefix 40 | * @var string $package The name of the composer package 41 | * @var string $file The complete path to the source file 42 | */ 43 | return $source; // You should always return the $source after manipulating it 44 | } 45 | ], 46 | 47 | /** These functions are called once per namespace being processed after the regex's are run. */ 48 | "after" => [ 49 | function(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 50 | /** 51 | * @var string $source The PHP source file contents 52 | * @var string $namespace The namespace currently being processed 53 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix) 54 | * @var string $namespacePrefix The new namespace prefix 55 | * @var string $package The name of the composer package 56 | * @var string $file The complete path to the source file 57 | */ 58 | return $source; // You should always return the $source after manipulating it 59 | } 60 | ], 61 | 62 | /** These functions are called before the changed source file is saved, after all the processing has taken place. */ 63 | "end" => [ 64 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) { 65 | /** 66 | * @var string $source The PHP source file contents 67 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix) 68 | * @var string $namespacePrefix The new namespace prefix 69 | * @var string $package The name of the composer package 70 | * @var string $file The complete path to the source file 71 | */ 72 | return $source; // You should always return the $source after manipulating it 73 | }, 74 | ] 75 | ]; --------------------------------------------------------------------------------