├── composer.json ├── readme.md └── src └── ComposerBackslasher ├── Backslasher.php ├── Collector.php └── Plugin.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dg/composer-backslasher", 3 | "type": "composer-plugin", 4 | "description": "Composer plugin that speeds up your application by adding backslashes to all PHP internal functions and constants.", 5 | "keywords": ["composer"], 6 | "license": ["BSD-3-Clause"], 7 | "authors": [ 8 | { 9 | "name": "David Grudl", 10 | "homepage": "https://davidgrudl.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.1", 15 | "composer-plugin-api": "^1.0 || ^2.0", 16 | "nikic/php-parser": "^2.0 || ^3.0 || ^4.0" 17 | }, 18 | "require-dev": { 19 | "nette/tester": "^2.2", 20 | "phpstan/phpstan": "^0.12" 21 | }, 22 | "autoload": { 23 | "classmap": ["src/"] 24 | }, 25 | "scripts": { 26 | "phpstan": "phpstan analyse", 27 | "tester": "tester tests -s" 28 | }, 29 | "extra": { 30 | "class": "DG\\ComposerBackslasher\\Plugin" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Composer Backslasher 2 | ==================== 3 | 4 | [![Downloads this Month](https://img.shields.io/packagist/dm/dg/composer-backslasher.svg)](https://packagist.org/packages/dg/composer-backslasher) 5 | [![Build Status](https://travis-ci.org/dg/composer-backslasher.svg?branch=master)](https://travis-ci.org/dg/composer-backslasher) 6 | 7 | Composer plugin that speeds up your application by adding backslashes to all PHP internal functions and constants in `/vendor`. 8 | Does not modify files of your application. 9 | 10 | Installation 11 | ------------ 12 | 13 | ``` 14 | composer require dg/composer-backslasher 15 | ``` 16 | 17 | Then simply use `composer update`. 18 | 19 | 20 | How it works? 21 | ------------- 22 | 23 | It simply turns this code: 24 | 25 | ```php 26 | namespace A; 27 | 28 | if (preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, PREG_OFFSET_CAPTURE)) { 29 | // ... 30 | } 31 | ``` 32 | 33 | into this code: 34 | 35 | ```php 36 | namespace A; 37 | 38 | if (\preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, \PREG_OFFSET_CAPTURE)) { 39 | // ... 40 | } 41 | ``` 42 | 43 | to avoid double lookup for global functions and constants. 44 | 45 | 46 | Configuration 47 | ------------- 48 | 49 | If you want to ignore certain functions or constants, specify them in the configuration. 50 | Simply add a `extra > backslasher-ignore` section to `composer.json` file: 51 | 52 | ```js 53 | { 54 | "extra": { 55 | "backslasher-ignore": [ 56 | "GuzzleHttp\\Promise\\each" 57 | ] 58 | } 59 | } 60 | ``` 61 | 62 | Support Project 63 | --------------- 64 | 65 | Do you like Composer Backslasher? Are you looking forward to the new features? 66 | 67 | [![Donate](https://files.nette.org/icons/donation-1.svg?)](https://nette.org/make-donation?to=composer-backslasher) 68 | -------------------------------------------------------------------------------- /src/ComposerBackslasher/Backslasher.php: -------------------------------------------------------------------------------- 1 | io = $io; 28 | $this->ignored = array_flip($ignored); 29 | $this->parser = (new PhpParser\ParserFactory)->create( 30 | PhpParser\ParserFactory::PREFER_PHP7, 31 | new PhpParser\Lexer(['usedAttributes' => ['startFilePos']]) 32 | ); 33 | } 34 | 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function processDir($vendorDir) 40 | { 41 | $count = 0; 42 | 43 | foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($vendorDir)) as $entry) { 44 | if (!$entry->isFile() || $entry->getExtension() !== 'php') { 45 | continue; 46 | } 47 | 48 | $input = file_get_contents((string) $entry); 49 | $output = $this->processCode($input, (string) $entry); 50 | if ($output !== $input) { 51 | file_put_contents((string) $entry, $output); 52 | $count += strlen($output) - strlen($input); 53 | } 54 | } 55 | 56 | $this->io->write("Composer Backslasher: Added $count backslashes."); 57 | } 58 | 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function processCode($code, $file = null) 64 | { 65 | try { 66 | $nodes = $this->parser->parse($code); 67 | } catch (PhpParser\Error $e) { 68 | $this->io->write("$file : {$e->getMessage()}", true, IOInterface::VERBOSE); 69 | return $code; 70 | } 71 | 72 | $collector = new Collector; 73 | $collector->ignored = $this->ignored; 74 | $traverser = new PhpParser\NodeTraverser; 75 | $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); 76 | $traverser->addVisitor($collector); 77 | $traverser->traverse($nodes); 78 | 79 | foreach (array_reverse($collector->positions) as $pos) { 80 | $code = substr_replace($code, '\\', $pos, 0); 81 | } 82 | 83 | return $code; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ComposerBackslasher/Collector.php: -------------------------------------------------------------------------------- 1 | inNamespace = true; 27 | 28 | } elseif (!$this->inNamespace) { 29 | // ignore 30 | } elseif ($node instanceof Expr\FuncCall 31 | && $node->name instanceof Node\Name 32 | && !$node->name instanceof Node\Name\FullyQualified 33 | && function_exists((string) $node->name) 34 | && !isset($this->ignored[(string) $node->name->getAttribute('namespacedName')]) 35 | ) { 36 | $this->positions[] = $node->name->getAttribute('startFilePos'); 37 | 38 | } elseif ($node instanceof Expr\ConstFetch 39 | && $node->name instanceof Node\Name 40 | && !$node->name instanceof Node\Name\FullyQualified 41 | && defined((string) $node->name) 42 | && !preg_match('~true|false|null~i', (string) $node->name) 43 | && !isset($this->ignored[(string) $node->name->getAttribute('namespacedName')]) 44 | ) { 45 | $this->positions[] = $node->name->getAttribute('startFilePos'); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ComposerBackslasher/Plugin.php: -------------------------------------------------------------------------------- 1 | 'run', 35 | ScriptEvents::POST_INSTALL_CMD => 'run', 36 | ]; 37 | } 38 | 39 | 40 | public function run(Event $event) 41 | { 42 | $vendorDir = $event->getComposer()->getConfig()->get('vendor-dir'); 43 | $extra = $event->getComposer()->getPackage()->getExtra(); 44 | $ignored = isset($extra['backslasher-ignore']) ? (array) $extra['backslasher-ignore'] : []; 45 | $backslasher = new Backslasher($event->getIO(), $ignored); 46 | $backslasher->processDir($vendorDir); 47 | } 48 | } 49 | --------------------------------------------------------------------------------