├── php7to5 ├── src ├── Exceptions │ ├── InvalidPhpCode.php │ └── InvalidParameter.php ├── NodeVisitors │ ├── ReturnTypesRemover.php │ ├── StrictTypesDeclarationRemover.php │ ├── ClassConstantVisibilityModifiersRemover.php │ ├── EmptyDeclareStatementRemover.php │ ├── DefineArrayReplacer.php │ ├── ScalarTypeHintsRemover.php │ ├── GroupUseReplacer.php │ ├── SpaceshipOperatorReplacer.php │ ├── NullCoalesceReplacer.php │ └── AnonymousClassReplacer.php ├── Console │ ├── Application.php │ └── ConvertCommand.php ├── Converter.php └── DirectoryConverter.php ├── .editorconfig ├── CHANGELOG.md ├── LICENSE.md ├── composer.json ├── CONTRIBUTING.md └── README.md /php7to5: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 | run(); -------------------------------------------------------------------------------- /src/Exceptions/InvalidPhpCode.php: -------------------------------------------------------------------------------- 1 | returnType = null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Console/Application.php: -------------------------------------------------------------------------------- 1 | add(new ConvertCommand()); 14 | } 15 | 16 | public function getLongVersion() 17 | { 18 | return parent::getLongVersion().' by Hannes Van De Vreken & Freek Van der Herten'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NodeVisitors/StrictTypesDeclarationRemover.php: -------------------------------------------------------------------------------- 1 | key === 'strict_type') { 22 | return NodeTraverser::REMOVE_NODE; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NodeVisitors/ClassConstantVisibilityModifiersRemover.php: -------------------------------------------------------------------------------- 1 | flags = 0; // Remove constant modifier 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `7to5` will be documented in this file 4 | 5 | ## 1.3.0 - 2018-04-13 6 | 7 | - add option to clean destination directory 8 | - add ability to remove class constant visibility 9 | 10 | ## 1.2.2 - 2018-01-20 11 | 12 | - Fix deps 13 | 14 | ## 1.2.1 - 2017-12-01 15 | 16 | - Fix option `--overwrite` 17 | 18 | ## 1.2.0 - 2017-09-25 19 | 20 | - fix null coaleascing operation conversion 21 | - add `extension` and `exlclude` options 22 | 23 | ## 1.1.1 - 2017-09-25 24 | 25 | - allow higher level of nesting 26 | 27 | ## 1.1.0 - 2017-04-17 28 | 29 | - add initial support for php7.1 and removal nullable type hints 30 | 31 | ## 1.0.1 - 2016-09-29 32 | 33 | - fix missing illuminate/support dependency 34 | -------------------------------------------------------------------------------- /src/NodeVisitors/EmptyDeclareStatementRemover.php: -------------------------------------------------------------------------------- 1 | key !== 'strict_types'; 24 | }, $node->declares)); 25 | 26 | if (empty($result)) { 27 | return NodeTraverser::REMOVE_NODE; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NodeVisitors/DefineArrayReplacer.php: -------------------------------------------------------------------------------- 1 | name != 'define') { 24 | return; 25 | } 26 | 27 | $nameNode = $node->args[0]->value; 28 | $valueNode = $node->args[1]->value; 29 | 30 | if (!$valueNode instanceof Node\Expr\Array_) { 31 | return; 32 | } 33 | 34 | $constNode = new Node\Const_($nameNode->value, $valueNode); 35 | 36 | return new Node\Stmt\Const_([$constNode]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/NodeVisitors/ScalarTypeHintsRemover.php: -------------------------------------------------------------------------------- 1 | type instanceof Node\NullableType) { 21 | $node->type = $node->type->type; 22 | if (!$node->default) { 23 | $node->default = new Node\Expr\ConstFetch( 24 | new Node\Name('null') 25 | ); 26 | } 27 | } 28 | 29 | if ($this->isScalar($node->type)) { 30 | $node->type = null; 31 | } 32 | } 33 | 34 | /** 35 | * @param string|null $type 36 | * 37 | * @return bool 38 | */ 39 | protected function isScalar($type) 40 | { 41 | return in_array($type, ['int', 'integer', 'float', 'string', 'bool', 'boolean']); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/NodeVisitors/GroupUseReplacer.php: -------------------------------------------------------------------------------- 1 | prefix->parts; 20 | 21 | $seperateUseStatements = array_map(function ($useNode) use ($nodePrefixParts) { 22 | return $this->createUseNode($nodePrefixParts, $useNode); 23 | }, $node->uses); 24 | 25 | return $seperateUseStatements; 26 | } 27 | 28 | protected function createUseNode(array $nodePrefixParts, Node $useNode) 29 | { 30 | $fullClassName = array_merge($nodePrefixParts, [$useNode->name]); 31 | 32 | $nameNode = new Node\Name($fullClassName); 33 | 34 | $alias = ($useNode->alias == $useNode->name) ? null : $useNode->alias; 35 | 36 | $useNode = new Node\Stmt\Use_([new Node\Stmt\UseUse($nameNode, $alias)], $useNode->type); 37 | 38 | return $useNode; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NodeVisitors/SpaceshipOperatorReplacer.php: -------------------------------------------------------------------------------- 1 | $b 28 | * with 29 | * $a < $b ? -1 : ($a == $b ? 0 : 1) 30 | */ 31 | 32 | $attributes = $node->getAttributes(); 33 | 34 | $smaller = new UnaryMinus(new LNumber(1, $attributes), $attributes); 35 | $equal = new LNumber(0, $attributes); 36 | $larger = new LNumber(1, $attributes); 37 | 38 | $isEqual = new Equal($node->left, $node->right, $attributes); 39 | $isSmaller = new Smaller($node->left, $node->right, $attributes); 40 | 41 | $else = new Ternary($isEqual, $equal, $larger, $attributes); 42 | 43 | return new Ternary($isSmaller, $smaller, $else, $attributes); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NodeVisitors/NullCoalesceReplacer.php: -------------------------------------------------------------------------------- 1 | left instanceof Node\Expr\FuncCall: 22 | case $node->left instanceof Node\Expr\MethodCall: 23 | case $node->left instanceof Node\Expr\StaticCall: 24 | $notEmptyCall = new Node\Expr\BooleanNot(new Node\Expr\FuncCall(new Node\Name('empty'), [$node->left])); 25 | return new Node\Expr\Ternary($notEmptyCall, $node->left, $node->right); 26 | case $node->left instanceof Node\Expr\BinaryOp: 27 | $issetCall = new Node\Expr\FuncCall(new Node\Name('isset'), [$node->left->right]); 28 | $node->left->right = new Node\Expr\Ternary($issetCall, $node->left->right, $node->right); 29 | return $node->left; 30 | default: 31 | $issetCall = new Node\Expr\FuncCall(new Node\Name('isset'), [$node->left]); 32 | return new Node\Expr\Ternary($issetCall, $node->left, $node->right); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/7to5", 3 | "description": "Convert PHP 7 code to PHP 5 code", 4 | "keywords": [ 5 | "spatie", 6 | "7to5", 7 | "convert", 8 | "php7", 9 | "abstract", 10 | "syntax", 11 | "tree" 12 | ], 13 | "homepage": "https://github.com/spatie/7to5", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Hannes Van de Vreken", 18 | "homepage": "http://madewithlove.be/", 19 | "role": "Developer" 20 | }, 21 | { 22 | "name": "Freek Van der Herten", 23 | "email": "freek@spatie.be", 24 | "homepage": "https://spatie.be", 25 | "role": "Developer" 26 | } 27 | ], 28 | "require": { 29 | "php" : "^5.6|^7.0|^7.1", 30 | "nikic/php-parser": "^3.0", 31 | "illuminate/support": "~5.2.0|~5.3.0", 32 | "symfony/console": "^3.0|^4.0", 33 | "symfony/finder": "^3.0|^4.0" 34 | }, 35 | "require-dev": { 36 | "phpunit/phpunit": "^5.7", 37 | "illuminate/filesystem": "~5.2.0|~5.3.0", 38 | "symfony/process": "^3.0|^4.0" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Spatie\\Php7to5\\": "src" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Spatie\\Php7to5\\Test\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "test": "vendor/bin/phpunit" 52 | }, 53 | "bin": [ 54 | "php7to5" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidParameter.php: -------------------------------------------------------------------------------- 1 | pathToPhp7Code = $pathToPhp7Code; 26 | } 27 | 28 | /** 29 | * @param string $destination 30 | */ 31 | public function saveAsPhp5($destination) 32 | { 33 | file_put_contents($destination, $this->getPhp5Code()); 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getPhp5Code() 40 | { 41 | ini_set('xdebug.max_nesting_level', 9000); 42 | 43 | $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); 44 | 45 | $php7code = file_get_contents($this->pathToPhp7Code); 46 | 47 | $php7Statements = $parser->parse($php7code); 48 | 49 | $traverser = $this->getTraverser(); 50 | 51 | $php5Statements = $traverser->traverse($php7Statements); 52 | 53 | return (new \PhpParser\PrettyPrinter\Standard())->prettyPrintFile($php5Statements); 54 | } 55 | 56 | /** 57 | * @return \PhpParser\NodeTraverser 58 | */ 59 | public static function getTraverser() 60 | { 61 | $traverser = new NodeTraverser(); 62 | 63 | foreach (glob(__DIR__.'/NodeVisitors/*.php') as $nodeVisitorFile) { 64 | $className = pathinfo($nodeVisitorFile, PATHINFO_FILENAME); 65 | 66 | $fullClassName = '\\Spatie\\Php7to5\\NodeVisitors\\'.$className; 67 | 68 | $traverser->addVisitor(new $fullClassName()); 69 | } 70 | 71 | return $traverser; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /src/NodeVisitors/AnonymousClassReplacer.php: -------------------------------------------------------------------------------- 1 | class; 30 | if (!$classNode instanceof Node\Stmt\Class_) { 31 | return; 32 | } 33 | 34 | $newClassName = 'AnonymousClass'.count($this->anonymousClassNodes); 35 | 36 | $classNode->name = $newClassName; 37 | 38 | $this->anonymousClassNodes[] = $classNode; 39 | 40 | // Generate new code that instantiate our new class 41 | $newNode = new Node\Expr\New_( 42 | new Node\Expr\ConstFetch( 43 | new Node\Name($newClassName) 44 | ) 45 | ); 46 | 47 | return $newNode; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function afterTraverse(array $nodes) 54 | { 55 | if (count($this->anonymousClassNodes) === 0) { 56 | return $nodes; 57 | } 58 | 59 | $anonymousClassStatements = $this->anonymousClassNodes; 60 | 61 | $anonymousClassStatements = $this->convertToPhp5Statements($anonymousClassStatements); 62 | 63 | $hookIndex = $this->getAnonymousClassHookIndex($nodes); 64 | 65 | $nodes = $this->moveAnonymousClassesToHook($nodes, $hookIndex, $anonymousClassStatements); 66 | 67 | return $nodes; 68 | } 69 | 70 | /** 71 | * Find the index of the first statement that is not a declare, use or namespace statement. 72 | * 73 | * @param array $statements 74 | * 75 | * @return int 76 | * 77 | * @throws \Spatie\Php7to5\Exceptions\InvalidPhpCode 78 | */ 79 | protected function getAnonymousClassHookIndex(array $statements) 80 | { 81 | $hookIndex = false; 82 | 83 | foreach ($statements as $index => $statement) { 84 | if (!$statement instanceof Declare_ && 85 | !$statement instanceof Use_ && 86 | !$statement instanceof Namespace_) { 87 | $hookIndex = $index; 88 | } 89 | } 90 | 91 | if ($hookIndex === false) { 92 | throw InvalidPhpCode::noValidLocationFoundToInsertClasses(); 93 | } 94 | 95 | return $hookIndex; 96 | } 97 | 98 | /** 99 | * @param array $nodes 100 | * @param $hookIndex 101 | * @param $anonymousClassStatements 102 | * 103 | * @return array 104 | */ 105 | protected function moveAnonymousClassesToHook(array $nodes, $hookIndex, $anonymousClassStatements) 106 | { 107 | $preStatements = array_slice($nodes, 0, $hookIndex); 108 | $postStatements = array_slice($nodes, $hookIndex); 109 | 110 | return array_merge($preStatements, $anonymousClassStatements, $postStatements); 111 | } 112 | 113 | /** 114 | * @param array $php7statements 115 | * 116 | * @return \PhpParser\Node[] 117 | */ 118 | public function convertToPhp5Statements(array $php7statements) 119 | { 120 | $converter = Converter::getTraverser($php7statements); 121 | 122 | $php5Statements = $converter->traverse($php7statements); 123 | 124 | return $php5Statements; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/DirectoryConverter.php: -------------------------------------------------------------------------------- 1 | sourceDirectory = $sourceDirectory; 39 | $this->extensions = array_map('mb_strtolower', $extensions); 40 | $this->excludes = $excludes; 41 | } 42 | 43 | public function setLogger(OutputInterface $output) 44 | { 45 | $this->logger = $output; 46 | } 47 | 48 | public function log($sourceItem, $target) 49 | { 50 | if (is_null($this->logger)) { 51 | return; 52 | } 53 | $targetRealPath = realpath($target); 54 | 55 | $this->logger->writeln("Converting {$sourceItem} to {$targetRealPath}..."); 56 | } 57 | 58 | /** 59 | * @return $this 60 | */ 61 | public function alsoCopyNonPhpFiles() 62 | { 63 | $this->copyNonPhpFiles = true; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * @return $this 70 | */ 71 | public function cleanDestinationDirectory() 72 | { 73 | $this->cleanDestinationDirectory = true; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * @return $this 80 | */ 81 | public function doNotCopyNonPhpFiles() 82 | { 83 | $this->copyNonPhpFiles = false; 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * @param string $destinationDirectory 90 | * 91 | * @throws \Spatie\Php7to5\Exceptions\InvalidParameter 92 | */ 93 | public function savePhp5FilesTo($destinationDirectory) 94 | { 95 | if ($destinationDirectory === '') { 96 | throw InvalidParameter::directoryIsRequired(); 97 | } 98 | 99 | if($this->cleanDestinationDirectory){ 100 | $this->removeDirectory($destinationDirectory); 101 | } 102 | 103 | $this->copyDirectory($this->sourceDirectory, $destinationDirectory); 104 | } 105 | 106 | /** 107 | * @param string $sourceDirectory 108 | * @param string $destinationDirectory 109 | */ 110 | protected function copyDirectory($sourceDirectory, $destinationDirectory) 111 | { 112 | if (!is_dir($destinationDirectory)) { 113 | mkdir($destinationDirectory); 114 | } 115 | 116 | $finder = new Finder(); 117 | $finder->in($sourceDirectory); 118 | if (!$this->copyNonPhpFiles) { 119 | foreach ($this->extensions as $extension) { 120 | $finder->name('*.'.$extension); 121 | } 122 | } 123 | 124 | if ($this->excludes) { 125 | foreach ($this->excludes as $exclude) { 126 | $finder->notPath('/^'.preg_quote($exclude, '/').'/'); 127 | } 128 | } 129 | 130 | foreach ($finder as $item) { 131 | $target = $destinationDirectory.'/'.$item->getRelativePathname(); 132 | 133 | if ($item->isFile()) { 134 | $isPhpFile = $this->isPhpFile($target); 135 | if ($isPhpFile || $this->copyNonPhpFiles) { 136 | $targetDir = dirname($target); 137 | if ($targetDir && !is_dir($targetDir)) { 138 | mkdir($targetDir, 0755, true); 139 | } 140 | copy($item->getRealPath(), $target); 141 | 142 | $this->log($item->getRelativePath(), $target); 143 | 144 | if ($isPhpFile) { 145 | $this->convertToPhp5($target); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * @param string $path 154 | */ 155 | protected function removeDirectory($path) 156 | { 157 | if (PHP_OS === 'Windows') { 158 | $command = 'rd /s /q %s'; 159 | } else { 160 | $command = 'rm -rf %s'; 161 | } 162 | 163 | exec(sprintf($command, escapeshellarg($path))); 164 | } 165 | 166 | /** 167 | * @param string $filePath 168 | */ 169 | protected function convertToPhp5($filePath) 170 | { 171 | $converter = new Converter($filePath); 172 | 173 | $converter->saveAsPhp5($filePath); 174 | } 175 | 176 | /** 177 | * @param string $filePath 178 | * 179 | * @return bool 180 | */ 181 | protected function isPhpFile($filePath) 182 | { 183 | return in_array(strtolower(pathinfo($filePath, PATHINFO_EXTENSION)), $this->extensions, true); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Console/ConvertCommand.php: -------------------------------------------------------------------------------- 1 | setName('convert') 19 | ->setDescription('Convert PHP 7 code to PHP 5 code') 20 | ->addArgument( 21 | 'source', 22 | InputArgument::REQUIRED, 23 | 'A PHP 7 file or a directory containing PHP 7 files' 24 | ) 25 | ->addArgument( 26 | 'destination', 27 | InputArgument::REQUIRED, 28 | 'The file or path where the PHP 5 code should be saved' 29 | ) 30 | ->addOption( 31 | 'extension', 32 | 'e', 33 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 34 | 'PHP extensions', 35 | ['php'] 36 | ) 37 | ->addOption( 38 | 'exclude', 39 | null, 40 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 41 | 'Exclude path' 42 | ) 43 | ->addOption( 44 | 'copy-all', 45 | null, 46 | InputOption::VALUE_NONE, 47 | 'If set, will copy all files in a directory, not only php' 48 | ) 49 | ->addOption( 50 | 'overwrite', 51 | null, 52 | InputOption::VALUE_NONE, 53 | 'If set, will overwrite existing destination file or directory' 54 | ); 55 | } 56 | 57 | /** 58 | * @param \Symfony\Component\Console\Input\InputInterface $input 59 | * @param \Symfony\Component\Console\Output\OutputInterface $output 60 | * 61 | * @return int 62 | * 63 | * @throws \Spatie\Php7to5\Exceptions\InvalidParameter 64 | */ 65 | protected function execute(InputInterface $input, OutputInterface $output) 66 | { 67 | $output->writeln("Start converting {$input->getArgument('source')}"); 68 | 69 | $source = $input->getArgument('source'); 70 | 71 | if (!file_exists($source)) { 72 | throw InvalidParameter::sourceDoesNotExist($source); 73 | } 74 | 75 | if (is_file($source)) { 76 | $this->convertFile($input); 77 | } 78 | if (is_dir($source)) { 79 | $this->convertPHPFilesInDirectory($input, $output); 80 | } 81 | $output->writeln('All done!'); 82 | 83 | return 0; 84 | } 85 | 86 | protected function convertFile(InputInterface $input) 87 | { 88 | $converter = new Converter($input->getArgument('source')); 89 | $destination = $input->getArgument('destination'); 90 | 91 | if (file_exists($destination) && !$input->getOption('overwrite')) { 92 | throw InvalidParameter::destinationExist(); 93 | } 94 | $converter->saveAsPhp5($destination); 95 | } 96 | 97 | protected function convertPHPFilesInDirectory(InputInterface $input, OutputInterface $output) 98 | { 99 | $source = $input->getArgument('source'); 100 | $destination = $input->getArgument('destination'); 101 | $extensions = $input->getOption('extension'); 102 | $excludes = $input->getOption('exclude'); 103 | $converter = new DirectoryConverter($source, $extensions, $excludes); 104 | 105 | if (!$input->getOption('overwrite')) { 106 | $this->isDestinationASourceDirectory($source, $destination); 107 | } 108 | 109 | $this->isDestinationDifferentThanSource($source, $destination); 110 | 111 | if (!$input->getOption('copy-all')) { 112 | $converter->doNotCopyNonPhpFiles(); 113 | } 114 | 115 | if (file_exists($destination) && !$input->getOption('overwrite')) { 116 | throw InvalidParameter::destinationExist(); 117 | } 118 | 119 | $converter->setLogger($output); 120 | $converter->savePhp5FilesTo($destination); 121 | } 122 | 123 | /** 124 | * @param string $source 125 | * @param string $destination 126 | * 127 | * @throws \Spatie\Php7to5\Exceptions\InvalidParameter 128 | */ 129 | protected function isDestinationASourceDirectory($source, $destination) 130 | { 131 | $this->isEqual($source, $destination); 132 | } 133 | 134 | /** 135 | * @param string $source 136 | * @param string $destination 137 | * 138 | * @throws \Spatie\Php7to5\Exceptions\InvalidParameter 139 | */ 140 | protected function isDestinationDifferentThanSource($source, $destination) 141 | { 142 | $path_parts = pathinfo($destination); 143 | $this->isEqual($source, $path_parts['dirname']); 144 | } 145 | 146 | /** 147 | * @param string $source 148 | * @param string $destination 149 | * 150 | * @throws \Spatie\Php7to5\Exceptions\InvalidParameter 151 | */ 152 | protected function isEqual($source, $destination) 153 | { 154 | if (!ends_with($destination, DIRECTORY_SEPARATOR)) { 155 | $destination = $destination.DIRECTORY_SEPARATOR; 156 | } 157 | if (!ends_with($source, DIRECTORY_SEPARATOR)) { 158 | $source = $source.DIRECTORY_SEPARATOR; 159 | } 160 | 161 | if ($destination === $source) { 162 | throw InvalidParameter::destinationDirectoryIsSource(); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This package has been abandoned** 2 | 3 | We are not maintaining this package any more. Feel free to fork our code and maintain your own copy. 4 | 5 | # Convert PHP 7.0 code to PHP 5 code 6 | 7 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/7to5.svg?style=flat-square)](https://packagist.org/packages/spatie/7to5) 8 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 9 | [![Build Status](https://img.shields.io/travis/spatie/7to5/master.svg?style=flat-square)](https://travis-ci.org/spatie/7to5) 10 | [![Quality Score](https://img.shields.io/scrutinizer/g/spatie/7to5.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/7to5) 11 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/7to5.svg?style=flat-square)](https://packagist.org/packages/spatie/7to5) 12 | 13 | This package can convert PHP 7.0 code to PHP 5. This can be handy when you are running PHP 7 in development, but 14 | PHP 5 in production. 15 | 16 | You can convert an entire directory with PHP 7.0 code with a the console command: 17 | 18 | ```bash 19 | php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code} 20 | ``` 21 | 22 | Here's an example of what it can do. It'll convert this code with PHP 7 features: 23 | ```php 24 | class Test 25 | { 26 | public function test() 27 | { 28 | $class = new class() { 29 | public function method(string $parameter = '') : string { 30 | return $parameter ?? 'no parameter set'; 31 | } 32 | }; 33 | 34 | $class->method(); 35 | 36 | $anotherClass = new class() { 37 | public function anotherMethod(int $integer) : int { 38 | return $integer > 3; 39 | } 40 | }; 41 | } 42 | 43 | } 44 | 45 | ``` 46 | 47 | to this equivalent PHP 5 code: 48 | 49 | ```php 50 | class AnonymousClass0 51 | { 52 | public function method($parameter = '') 53 | { 54 | return isset($parameter) ? $parameter : 'no parameter set'; 55 | } 56 | } 57 | class AnonymousClass1 58 | { 59 | public function anotherMethod($integer) 60 | { 61 | return $integer < 3 ? -1 : ($integer == 3 ? 0 : 1); 62 | } 63 | } 64 | class Test 65 | { 66 | public function test() 67 | { 68 | $class = new AnonymousClass0(); 69 | $class->method(); 70 | $anotherClass = new AnonymousClass1(); 71 | } 72 | } 73 | ``` 74 | 75 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 76 | 77 | ## Installation 78 | 79 | If you plan on use [the console command](#using-the-console-command) we recommend installing the package globally: 80 | 81 | ``` bash 82 | $ composer global require spatie/7to5 83 | ``` 84 | 85 | If you want to [integrate the package in your own code](#programmatically-convert-files) require the package like usual: 86 | 87 | ``` bash 88 | $ composer require spatie/7to5 89 | ``` 90 | 91 | ## The conversion process 92 | 93 | This package converts PHP 7 code to equivalent PHP 5 code by: 94 | 95 | - removing scalar type hints 96 | - removing return type hints 97 | - removing the strict type declaration 98 | - replacing the spaceship operator by an equivalent PHP 5 code 99 | - replacing null coalesce statements by equivalent PHP 5 code 100 | - replacing group use declarations by equivalent PHP 5 code 101 | - replacing defined arrays by equivalent PHP 5 code 102 | - converting anonymous classes to regular classes 103 | 104 | Because there are a lot of things that cannot be detected and/or converted properly we do not guarantee that the converted code will work. We highly recommend running your automated tests against the converted code to determine if it works. 105 | 106 | ## Using the console command 107 | 108 | This package provides a console command `php7to5` to convert files and directories. 109 | 110 | This is how a entire directory can be converted: 111 | 112 | ```bash 113 | $ php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code} 114 | ``` 115 | 116 | Want to convert a single file? That's cool too! You can use the same command. 117 | 118 | ```bash 119 | $ php7to5 convert {$sourceFileWithPHP7Code} {$destinationFileWithPHP5Code} 120 | ``` 121 | 122 | By default the command will only copy over `php`-files. Want to copy over all files? Use the `copy-all` option: 123 | 124 | ```bash 125 | $ php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code} --copy-all 126 | ``` 127 | 128 | By default the command will only convert files with a php extension, but you can customize that by using the `--extension` option. 129 | 130 | ```bash 131 | $ php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code} --extension=php --extension=phtml 132 | ``` 133 | 134 | If necessary, you can exclude directories / files. 135 | 136 | ```bash 137 | $ php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code} --exсlude=cache 138 | ``` 139 | 140 | ## Programmatically convert files 141 | 142 | You can convert a single file by running this code: 143 | 144 | ```php 145 | $converter = new Converter($pathToPhp7Code); 146 | 147 | $converter->saveAsPhp5($pathToWherePhp5CodeShouldBeSaved); 148 | ``` 149 | 150 | An entire directory can be converted as well: 151 | 152 | ```php 153 | $converter = new DirectoryConverter($sourceDirectory); 154 | 155 | $converter->savePhp5FilesTo($destinationDirectory); 156 | ``` 157 | 158 | By default this will recursively copy all to files to the destination directory, even the non php files. 159 | 160 | If you only want to copy over the php files do this: 161 | 162 | ```php 163 | $converter = new DirectoryConverter($sourceDirectory); 164 | 165 | $converter 166 | ->doNotCopyNonPhpFiles() 167 | ->savePhp5FilesTo($destinationDirectory); 168 | ``` 169 | 170 | ## Changelog 171 | 172 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 173 | 174 | ## Testing 175 | 176 | ``` bash 177 | $ composer test 178 | ``` 179 | 180 | ## Contributing 181 | 182 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 183 | 184 | ## Security 185 | 186 | If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker. 187 | 188 | ## Postcardware 189 | 190 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 191 | 192 | Our address is: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium. 193 | 194 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 195 | 196 | ## Credits 197 | 198 | - [Hannes Van De Vreken](https://twitter.com/hannesvdvreken) 199 | - [Freek Van der Herten](https://github.com/freekmurze) 200 | - [All Contributors](../../contributors) 201 | 202 | Original idea: [Jens Segers](https://twitter.com/jenssegers) 203 | 204 | ## Support us 205 | 206 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 207 | 208 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/spatie). 209 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 210 | 211 | ## License 212 | 213 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 214 | --------------------------------------------------------------------------------