├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── autoload.php ├── composer.json ├── phpcs.xml ├── phpmd.xml ├── phpstan.neon ├── phpunit.xml ├── src ├── Alias │ ├── Alias.php │ ├── AliasInterface.php │ ├── AliasIterator.php │ ├── AliasIteratorInterface.php │ ├── Finder │ │ ├── AliasFinder.php │ │ └── AliasFinderInterface.php │ └── Renderer │ │ ├── AliasRenderer.php │ │ └── AliasRendererInterface.php ├── Formatter │ ├── CamelCaseFormatter.php │ ├── CamelCaseTrait.php │ ├── FunctionFormatter.php │ ├── FunctionFormatterInterface.php │ └── NamespacePrefixFormatter.php └── Hasher │ ├── FunctionHasherInterface.php │ └── Md5FunctionHasher.php └── tests ├── Alias ├── AliasIteratorTest.php ├── AliasTest.php ├── Finder │ └── AliasFinderTest.php └── Renderer │ └── AliasRendererTest.php ├── CreateFunctionTrait.php ├── Fixture └── functions.php ├── Formatter ├── CamelCaseFormatterTest.php ├── CamelCaseTraitTest.php ├── FunctionFormatterTest.php └── NamespacePrefixFormatterTest.php ├── Hasher └── Md5FunctionHasherTest.php └── Integration └── InternalFunctionAliasTest.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: tests/Logs/clover.xml 2 | json_path: tests/Logs/coveralls-upload.json 3 | service_name: travis-ci -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | vendor/ 4 | aliases.*.php 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | dist: trusty 3 | php: 4 | - '7.1' 5 | - '7.2' 6 | install: 7 | - composer update 8 | script: 9 | - ./vendor/bin/phpunit --coverage-clover ./tests/Logs/clover.xml 10 | after_script: 11 | - php vendor/bin/php-coveralls -v -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐪💼 PHP Camel Caser 2 | 3 | [![Build Status](https://travis-ci.org/DivineOmega/php-camel-caser.svg?branch=master)](https://travis-ci.org/DivineOmega/php-camel-caser) 4 | [![Coverage Status](https://coveralls.io/repos/github/DivineOmega/php-camel-caser/badge.svg?branch=master)](https://coveralls.io/github/DivineOmega/php-camel-caser?branch=master) 5 | [![StyleCI](https://github.styleci.io/repos/147511192/shield?branch=master)](https://github.styleci.io/repos/147511192) 6 | [![Packagist](https://img.shields.io/packagist/dt/divineomega/php-camel-caser.svg)](https://packagist.org/packages/divineomega/php-camel-caser/stats) 7 | 8 | This package lets you use built-in PHP functions in camel case. 9 | 10 | ## Installation 11 | 12 | 13 | PHP Camel Caser can be easily installed using Composer. Just run the following command from the root of your project. 14 | 15 | ``` 16 | composer require divineomega/php-camel-caser 17 | ``` 18 | 19 | If you have never used the Composer dependency manager before, head to the [Composer website](https://getcomposer.org/) for more information on how to get started. 20 | 21 | ## Usage 22 | 23 | After installing PHP Camel Caser, the new functions are available straight away. 24 | 25 | Some example usage is shown below. 26 | 27 | ```php 28 | require_once __DIR__.'/vendor/autoload.php'; 29 | 30 | strReplace('c', 'b', 'cat'); // bat 31 | strWordCount("Hello world!"); // 2 32 | inArray('Picard', ['Picard', 'Janeway']); // true 33 | 34 | // and so on... 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | fwrite($render); 54 | } 55 | } 56 | 57 | try { 58 | require_once $storage; 59 | } catch (Throwable $exception) { 60 | unlink($storage); 61 | 62 | throw new RuntimeException( 63 | 'Failed to register aliases for internal functions!', 64 | 0, 65 | $exception 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "divineomega/php-camel-caser", 3 | "description": "Lets you use built-in PHP functions in camel case", 4 | "type": "library", 5 | "license": "LGPL-3.0-only", 6 | "authors": [ 7 | { 8 | "name": "Jordan Hall", 9 | "email": "jordan@hall05.co.uk" 10 | }, 11 | { 12 | "name": "Jan-Marten de Boer", 13 | "email": "github@johmanx.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^7.1", 18 | "ext-json": ">=1.5" 19 | }, 20 | "require-dev": { 21 | "mediact/testing-suite": "@stable", 22 | "kint-php/kint": "^2.2", 23 | "php-coveralls/php-coveralls": "^2.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "DivineOmega\\CamelCaser\\": "src" 28 | }, 29 | "files": [ 30 | "autoload.php" 31 | ] 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "DivineOmega\\CamelCaser\\Tests\\": "tests" 36 | }, 37 | "files": [ 38 | "tests/Fixture/functions.php" 39 | ] 40 | }, 41 | "extra": { 42 | "grumphp": { 43 | "config-default-path": "vendor/mediact/testing-suite/config/default/grumphp.yml" 44 | } 45 | }, 46 | "archive": { 47 | "exclude": [ 48 | "/.gitignore", 49 | "/phpunit.xml", 50 | "/phpmd.xml", 51 | "/phpstan.neon", 52 | "/phpcs.xml", 53 | "/tests" 54 | ] 55 | }, 56 | "config": { 57 | "sort-packages": true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | PHPCS 8 | 9 | 10 | 11 | 12 | tests/Fixture/functions.php 13 | 14 | 15 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | PHPMD 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | excludes_analyse: 3 | # - %rootDir%/../../../path/to/exclude/* 4 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | tests 7 | 8 | 9 | tests/Integration 10 | 11 | 12 | tests/Alias 13 | tests/Hasher 14 | tests/Formatter 15 | 16 | 17 | 18 | 19 | src 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Alias/Alias.php: -------------------------------------------------------------------------------- 1 | original = $original; 31 | $this->alias = $alias; 32 | $this->reflection = $reflection; 33 | } 34 | 35 | /** 36 | * Get the original function name. 37 | * 38 | * @return string 39 | */ 40 | public function getOriginal(): string 41 | { 42 | return $this->original; 43 | } 44 | 45 | /** 46 | * Get the function alias. 47 | * 48 | * @return string 49 | */ 50 | public function getAlias(): string 51 | { 52 | return $this->alias; 53 | } 54 | 55 | /** 56 | * Get a reflection of the original function. 57 | * 58 | * @return ReflectionFunctionAbstract 59 | */ 60 | public function getReflection(): ReflectionFunctionAbstract 61 | { 62 | return $this->reflection; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Alias/AliasInterface.php: -------------------------------------------------------------------------------- 1 | originalFormatter = $originalFormatter; 30 | $this->aliasFormatter = $aliasFormatter; 31 | } 32 | 33 | /** 34 | * Find aliases for the given list of functions. 35 | * 36 | * @param ReflectionFunctionAbstract ...$functions 37 | * 38 | * @return AliasIteratorInterface 39 | */ 40 | public function __invoke( 41 | ReflectionFunctionAbstract ...$functions 42 | ): AliasIteratorInterface { 43 | return new AliasIterator( 44 | ...array_values( 45 | array_reduce( 46 | $functions, 47 | function ( 48 | array $carry, 49 | ReflectionFunctionAbstract $function 50 | ): array { 51 | $original = $this->originalFormatter->__invoke($function); 52 | $alias = $this->aliasFormatter->__invoke($function); 53 | 54 | if ($alias !== $original 55 | && preg_match('/.*?([^\\\\]+)$/', $alias) 56 | && function_exists($original) 57 | && !function_exists($alias) 58 | ) { 59 | $carry[$alias] = new Alias( 60 | $original, 61 | $alias, 62 | $function 63 | ); 64 | } 65 | 66 | return $carry; 67 | }, 68 | [] 69 | ) 70 | ) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Alias/Finder/AliasFinderInterface.php: -------------------------------------------------------------------------------- 1 | groupNamespaces(...$aliases) as $namespace => $group) { 28 | $code .= sprintf('namespace %s {', $namespace).PHP_EOL; 29 | 30 | foreach ($group as $alias) { 31 | try { 32 | $code .= $this->renderFunction($alias); 33 | } catch (RuntimeException $exception) { 34 | // Skip the erroneous function. 35 | continue; 36 | } 37 | } 38 | 39 | $code .= '}'.PHP_EOL; 40 | } 41 | 42 | return $code; 43 | } 44 | 45 | /** 46 | * Group the given list of alias by their namespace. 47 | * 48 | * @param AliasInterface ...$aliases 49 | * 50 | * @return AliasInterface[][] 51 | */ 52 | private function groupNamespaces(AliasInterface ...$aliases): array 53 | { 54 | return array_reduce( 55 | $aliases, 56 | function (array $carry, AliasInterface $alias): array { 57 | $carry[$alias->getReflection()->getNamespaceName()][] = $alias; 58 | 59 | return $carry; 60 | }, 61 | [] 62 | ); 63 | } 64 | 65 | /** 66 | * Render a function for the given alias. 67 | * 68 | * @param AliasInterface $alias 69 | * 70 | * @return string 71 | */ 72 | private function renderFunction(AliasInterface $alias): string 73 | { 74 | return sprintf( 75 | <<<'FUNCTION' 76 | if (!function_exists('\%2$s')) { 77 | /**%3$s 78 | * @see %1$s 79 | * %5$s 80 | */ 81 | function %4$s%2$s(%6$s) 82 | { 83 | return %1$s(...func_get_args()); 84 | } 85 | } 86 | 87 | FUNCTION 88 | , 89 | $alias->getOriginal(), 90 | preg_replace( 91 | '/.*?([^\\\\]+)$/', 92 | '$1', 93 | $alias->getAlias() 94 | ), 95 | $this->renderParameterDoc( 96 | ' * ', 97 | ...$alias 98 | ->getReflection() 99 | ->getParameters() 100 | ), 101 | $alias->getReflection()->returnsReference() ? '&' : '', 102 | $this->renderReturnTypeDoc( 103 | $alias->getReflection()->getReturnType() 104 | ), 105 | $this->renderParameters( 106 | str_repeat(' ', 8), 107 | ...$alias->getReflection()->getParameters() 108 | ) 109 | ).PHP_EOL; 110 | } 111 | 112 | /** 113 | * Render a list of parameters for the doc block. 114 | * 115 | * @param string $prefix 116 | * @param ReflectionParameter ...$parameters 117 | * 118 | * @return string 119 | */ 120 | private function renderParameterDoc( 121 | string $prefix, 122 | ReflectionParameter ...$parameters 123 | ): string { 124 | return array_reduce( 125 | $parameters, 126 | function ( 127 | string $carry, 128 | ReflectionParameter $parameter 129 | ) use ($prefix): string { 130 | return $carry.PHP_EOL.$prefix.sprintf( 131 | '@param %s %s$%s', 132 | $this->renderTypeDoc($parameter->getType()), 133 | $parameter->isVariadic() ? '...' : '', 134 | static::camelCase( 135 | $parameter->getName() 136 | ) 137 | ); 138 | }, 139 | '' 140 | ); 141 | } 142 | 143 | /** 144 | * Render the type. 145 | * 146 | * @param null|ReflectionType $type 147 | * 148 | * @return string 149 | */ 150 | private function renderTypeDoc(?ReflectionType $type): string 151 | { 152 | $types = []; 153 | 154 | if ($type === null) { 155 | $types[] = 'mixed'; 156 | } else { 157 | if ($type->allowsNull()) { 158 | $types[] = 'null'; 159 | } 160 | 161 | $types[] = (string) $type; 162 | } 163 | 164 | return implode('|', $types); 165 | } 166 | 167 | /** 168 | * Render a return type for the doc block. 169 | * 170 | * @param null|ReflectionType $returnType 171 | * 172 | * @return string 173 | */ 174 | private function renderReturnTypeDoc( 175 | ?ReflectionType $returnType 176 | ): string { 177 | return sprintf( 178 | '@return %s', 179 | $this->renderTypeDoc($returnType) 180 | ); 181 | } 182 | 183 | /** 184 | * Render a list of parameters. 185 | * 186 | * @param string $indent 187 | * @param ReflectionParameter ...$parameters 188 | * 189 | * @throws RuntimeException When duplicate parameters are encountered. 190 | * 191 | * @return string 192 | */ 193 | private function renderParameters( 194 | string $indent, 195 | ReflectionParameter ...$parameters 196 | ): string { 197 | $list = array_reduce( 198 | $parameters, 199 | function ( 200 | array $carry, 201 | ReflectionParameter $parameter 202 | ) use ($indent): array { 203 | $name = static::camelCase( 204 | $parameter->getName() 205 | ); 206 | 207 | $value = $this->getDefaultParameterValue($parameter); 208 | 209 | // @see https://bugs.php.net/bug.php?id=76918 210 | if (array_key_exists($name, $carry)) { 211 | throw new RuntimeException( 212 | sprintf( 213 | 'Encountered duplicate parameter "%s" for %s.', 214 | $name, 215 | $parameter->getDeclaringFunction()->getName() 216 | ) 217 | ); 218 | } 219 | 220 | $carry[$name] = $indent.' '.trim( 221 | sprintf( 222 | '%1$s %2$s$%3$s%4$s', 223 | $parameter->hasType() 224 | ? sprintf( 225 | '%s%s', 226 | $parameter->getType()->allowsNull() ? '?' : '', 227 | $parameter->getType() 228 | ) 229 | : '', 230 | $parameter->isVariadic() ? '...' : '', 231 | $name, 232 | empty($value) ? '' : sprintf(' = %s', $value) 233 | ) 234 | ); 235 | 236 | return $carry; 237 | }, 238 | [] 239 | ); 240 | 241 | $list = implode(','.PHP_EOL, $list); 242 | 243 | if (strlen($list) > 0) { 244 | $list = PHP_EOL.$list.PHP_EOL.$indent; 245 | } 246 | 247 | return $list; 248 | } 249 | 250 | /** 251 | * Get the default value of the given parameter. 252 | * 253 | * @param ReflectionParameter $parameter 254 | * 255 | * @return string 256 | */ 257 | private function getDefaultParameterValue( 258 | ReflectionParameter $parameter 259 | ): string { 260 | // Variadic parameter cannot have a default value. 261 | // This needs to be checked before isOptional. 262 | if ($parameter->isVariadic()) { 263 | return ''; 264 | } 265 | 266 | // Cannot determine default value for internal functions. 267 | if ($parameter->getDeclaringFunction()->isInternal()) { 268 | return ''; 269 | } 270 | 271 | try { 272 | if ($parameter->isDefaultValueConstant()) { 273 | return $parameter->getDefaultValueConstantName(); 274 | } 275 | 276 | if ($parameter->isDefaultValueAvailable()) { 277 | return json_encode($parameter->getDefaultValue()); 278 | } 279 | } catch (Throwable $exception) { 280 | return ''; 281 | } 282 | 283 | // @codeCoverageIgnoreStart 284 | // No scenario known for this state. 285 | return ''; 286 | // @codeCoverageIgnoreEnd 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Alias/Renderer/AliasRendererInterface.php: -------------------------------------------------------------------------------- 1 | getShortName() 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Formatter/CamelCaseTrait.php: -------------------------------------------------------------------------------- 1 | getShortName(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Formatter/FunctionFormatterInterface.php: -------------------------------------------------------------------------------- 1 | formatter = $formatter; 20 | } 21 | 22 | /** 23 | * Format the given function reflection into a string. 24 | * 25 | * @param ReflectionFunctionAbstract $function 26 | * 27 | * @return string 28 | */ 29 | public function __invoke(ReflectionFunctionAbstract $function): string 30 | { 31 | $formatted = $this->formatter->__invoke($function); 32 | 33 | return $function->inNamespace() 34 | ? sprintf( 35 | '\\%s\\%s', 36 | $function->getNamespaceName(), 37 | $formatted 38 | ) 39 | : sprintf('\\%s', $formatted); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Hasher/FunctionHasherInterface.php: -------------------------------------------------------------------------------- 1 | getName(); 21 | }, 22 | $functions 23 | ); 24 | 25 | sort($names); 26 | 27 | return md5( 28 | implode(':', $names) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Alias/AliasIteratorTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(AliasIterator::class, $subject); 29 | $this->assertEquals($aliases, iterator_to_array($subject)); 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | public function aliasProvider(): array 36 | { 37 | return [ 38 | [], 39 | [$this->createMock(AliasInterface::class)], 40 | [ 41 | $this->createMock(AliasInterface::class), 42 | $this->createMock(AliasInterface::class), 43 | $this->createMock(AliasInterface::class), 44 | ], 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Alias/AliasTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Alias::class, $subject); 31 | $this->assertEquals('in_array', $subject->getOriginal()); 32 | $this->assertEquals('inArray', $subject->getAlias()); 33 | $this->assertInstanceOf( 34 | \ReflectionFunctionAbstract::class, 35 | $subject->getReflection() 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Alias/Finder/AliasFinderTest.php: -------------------------------------------------------------------------------- 1 | createMock(FunctionFormatterInterface::class); 39 | 40 | /** @var FunctionFormatterInterface|MockObject $aliasFormatter */ 41 | $aliasFormatter = $this->createMock(FunctionFormatterInterface::class); 42 | 43 | $subject = new AliasFinder($originalFormatter, $aliasFormatter); 44 | 45 | $originalFormatter 46 | ->expects(self::exactly(count($functions))) 47 | ->method('__invoke') 48 | ->with(self::isInstanceOf(ReflectionFunctionAbstract::class)) 49 | ->willReturnCallback( 50 | function (ReflectionFunctionAbstract $function): string { 51 | return $function->getName(); 52 | } 53 | ); 54 | 55 | $aliasFormatter 56 | ->expects(self::exactly(count($functions))) 57 | ->method('__invoke') 58 | ->with(self::isInstanceOf(ReflectionFunctionAbstract::class)) 59 | ->willReturnCallback($aliasCallback); 60 | 61 | $result = $subject->__invoke(...$functions); 62 | 63 | $this->assertInstanceOf(AliasIteratorInterface::class, $result); 64 | $this->assertCount($expected, iterator_to_array($result)); 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function functionProvider(): array 71 | { 72 | return [ 73 | [ 74 | 0, 75 | function (): void { 76 | // Nothing to do. 77 | }, 78 | ], 79 | [ 80 | 0, 81 | function (ReflectionFunctionAbstract $function): string { 82 | return $function->getName(); 83 | }, 84 | $this->createFunction('in_array'), 85 | ], 86 | [ 87 | 0, 88 | function (): string { 89 | return ''; 90 | }, 91 | $this->createFunction('in_array'), 92 | ], 93 | [ 94 | 1, 95 | function (ReflectionFunctionAbstract $function): string { 96 | return md5($function->getName()); 97 | }, 98 | $this->createFunction('in_array'), 99 | ], 100 | [ 101 | 1, 102 | function (ReflectionFunctionAbstract $function): string { 103 | return md5($function->getName()); 104 | }, 105 | $this->createFunction('in_array'), 106 | $this->createFunction('in_array'), 107 | $this->createFunction('in_array'), 108 | ], 109 | [ 110 | 2, 111 | function (ReflectionFunctionAbstract $function): string { 112 | return md5($function->getName()); 113 | }, 114 | $this->createFunction('in_array'), 115 | $this->createFunction('array_key_exists'), 116 | $this->createFunction('in_array'), 117 | ], 118 | ]; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/Alias/Renderer/AliasRendererTest.php: -------------------------------------------------------------------------------- 1 | assertStringStartsWith( 41 | '__invoke(...$aliases) 43 | ); 44 | } 45 | 46 | /** 47 | * @param string $original 48 | * @param string $alias 49 | * @param ReflectionFunctionAbstract|null $reflection 50 | * 51 | * @return AliasInterface 52 | */ 53 | public function createAlias( 54 | string $original, 55 | string $alias, 56 | ReflectionFunctionAbstract $reflection = null 57 | ): AliasInterface { 58 | /** @var AliasInterface|MockObject $mock */ 59 | $mock = $this->createMock(AliasInterface::class); 60 | 61 | $mock 62 | ->expects(self::any()) 63 | ->method('getOriginal') 64 | ->willReturn($original); 65 | 66 | $mock 67 | ->expects(self::any()) 68 | ->method('getAlias') 69 | ->willReturn($alias); 70 | 71 | $mock 72 | ->expects(self::any()) 73 | ->method('getReflection') 74 | ->willReturn( 75 | $reflection ?? new ReflectionFunction($original) 76 | ); 77 | 78 | return $mock; 79 | } 80 | 81 | /** 82 | * @return array 83 | */ 84 | public function aliasProvider(): array 85 | { 86 | return [ 87 | [], 88 | array_map( 89 | function (string $original): AliasInterface { 90 | return $this->createAlias( 91 | $original, 92 | $this->camelCase($original) 93 | ); 94 | }, 95 | get_defined_functions(true)['internal'] ?? [] 96 | ), 97 | array_map( 98 | function (string $original): AliasInterface { 99 | return $this->createAlias( 100 | $original, 101 | $this->camelCase($original) 102 | ); 103 | }, 104 | [ 105 | '\DivineOmega\CamelCaser\Tests\Fixture\foo_default_constant_value', 106 | '\DivineOmega\CamelCaser\Tests\Fixture\bar_default_value_available', 107 | '\DivineOmega\CamelCaser\Tests\Fixture\baz_parameter_optional', 108 | '\DivineOmega\CamelCaser\Tests\Fixture\quz_parameter_required', 109 | '\DivineOmega\CamelCaser\Tests\Fixture\quu_parameter_nullable', 110 | ] 111 | ), 112 | ]; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/CreateFunctionTrait.php: -------------------------------------------------------------------------------- 1 | createMock(ReflectionFunctionAbstract::class); 20 | 21 | $function 22 | ->expects(TestCase::any()) 23 | ->method('getName') 24 | ->willReturn($name); 25 | 26 | $function 27 | ->expects(TestCase::any()) 28 | ->method('getShortName') 29 | ->willReturn( 30 | preg_replace( 31 | '/.*?([^\\\\]+)$/', 32 | '$1', 33 | $name 34 | ) 35 | ); 36 | 37 | $function 38 | ->expects(TestCase::any()) 39 | ->method('inNamespace') 40 | ->willReturn( 41 | strpos($name, '\\') !== false 42 | ); 43 | 44 | $function 45 | ->expects(TestCase::any()) 46 | ->method('getNamespaceName') 47 | ->willReturn( 48 | trim( 49 | preg_replace( 50 | '/(.*?)([^\\\\]+)$/', 51 | '$1', 52 | $name 53 | ), 54 | '\\' 55 | ) 56 | ); 57 | 58 | return $function; 59 | } 60 | 61 | /** 62 | * @param string|string[] $originalClassName 63 | * 64 | * @return MockObject 65 | */ 66 | abstract protected function createMock($originalClassName): MockObject; 67 | } 68 | -------------------------------------------------------------------------------- /tests/Fixture/functions.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $subject->__invoke($function)); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function functionProvider(): array 40 | { 41 | return [ 42 | [ 43 | $this->createFunction('foo'), 44 | 'foo', 45 | ], 46 | [ 47 | $this->createFunction('foo_bar'), 48 | 'fooBar', 49 | ], 50 | [ 51 | $this->createFunction('fooBar'), 52 | 'fooBar', 53 | ], 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Formatter/CamelCaseTraitTest.php: -------------------------------------------------------------------------------- 1 | getMockForTrait(CamelCaseTrait::class); 27 | 28 | $this->assertEquals($expected, $subject::camelCase($original)); 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function stringProvider(): array 35 | { 36 | return [ 37 | ['foo_bar', 'fooBar'], 38 | ['foo-bar', 'fooBar'], 39 | ['fooBar', 'fooBar'], 40 | ['foo-bar_bazQux', 'fooBarBazQux'], 41 | ['FOOBAR', 'fOOBAR'], 42 | ['FOO-BAR', 'fOOBAR'], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Formatter/FunctionFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $subject->__invoke($function)); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function functionProvider(): array 40 | { 41 | return [ 42 | [ 43 | $this->createFunction('foo'), 44 | 'foo', 45 | ], 46 | [ 47 | $this->createFunction('foo_bar'), 48 | 'foo_bar', 49 | ], 50 | [ 51 | $this->createFunction('fooBar'), 52 | 'fooBar', 53 | ], 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Formatter/NamespacePrefixFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 28 | NamespacePrefixFormatter::class, 29 | new NamespacePrefixFormatter( 30 | $this->createMock(FunctionFormatterInterface::class) 31 | ) 32 | ); 33 | } 34 | 35 | /** 36 | * @dataProvider functionProvider 37 | * 38 | * @param ReflectionFunctionAbstract $function 39 | * @param string $expected 40 | * 41 | * @return void 42 | * 43 | * @covers ::__invoke 44 | */ 45 | public function testInvoke( 46 | ReflectionFunctionAbstract $function, 47 | string $expected 48 | ): void { 49 | /** @var FunctionFormatterInterface|MockObject $formatter */ 50 | $formatter = $this->createMock(FunctionFormatterInterface::class); 51 | $subject = new NamespacePrefixFormatter($formatter); 52 | 53 | $formatter 54 | ->expects(self::once()) 55 | ->method('__invoke') 56 | ->with(self::isInstanceOf(ReflectionFunctionAbstract::class)) 57 | ->willReturnCallback( 58 | function (ReflectionFunctionAbstract $function): string { 59 | return strtoupper($function->getShortName()); 60 | } 61 | ); 62 | 63 | $this->assertEquals($expected, $subject->__invoke($function)); 64 | } 65 | 66 | /** 67 | * @return array 68 | */ 69 | public function functionProvider(): array 70 | { 71 | return [ 72 | [ 73 | $this->createFunction('foo'), 74 | '\FOO', 75 | ], 76 | [ 77 | $this->createFunction('Foo\Bar\baz'), 78 | '\Foo\Bar\BAZ', 79 | ], 80 | ]; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Hasher/Md5FunctionHasherTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $subject->__invoke(...$functions)); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function functionProvider(): array 40 | { 41 | return [ 42 | ['d41d8cd98f00b204e9800998ecf8427e'], 43 | [ 44 | 'acbd18db4cc2f85cedef654fccc4a4d8', 45 | $this->createFunction('foo'), 46 | ], 47 | [ 48 | 'c0c27d0b79228a06ef81a8111136edea', 49 | $this->createFunction('foo'), 50 | $this->createFunction('bar'), 51 | $this->createFunction('baz'), 52 | ], 53 | [ 54 | 'c0c27d0b79228a06ef81a8111136edea', 55 | $this->createFunction('baz'), 56 | $this->createFunction('bar'), 57 | $this->createFunction('foo'), 58 | ], 59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Integration/InternalFunctionAliasTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( 33 | function_exists($original), 34 | 'The original function must always exist.' 35 | ); 36 | 37 | $this->assertTrue( 38 | function_exists($expected), 39 | 'The alias function must always exist.' 40 | ); 41 | 42 | $originalReflection = new ReflectionFunction($original); 43 | $this->assertTrue( 44 | $originalReflection->isInternal(), 45 | 'Original function must be an internal function.' 46 | ); 47 | 48 | $aliasReflection = new ReflectionFunction($expected); 49 | $this->assertTrue( 50 | $aliasReflection->isUserDefined(), 51 | 'Alias function must be user defined.' 52 | ); 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function functionProvider(): array 59 | { 60 | return array_reduce( 61 | get_defined_functions(EXCLUDE_DISABLED_FUNCTIONS)['internal'] ?? [], 62 | function (array $carry, string $function): array { 63 | $alias = self::camelCase($function); 64 | 65 | if ($alias !== $function 66 | && !empty($alias) 67 | && $this->functionHasValidSignature($function) 68 | // In some cases, internal functions have internal aliases, 69 | // although not officially documented. 70 | && !$this->isInternal($alias) 71 | ) { 72 | $carry[] = [$function, $alias]; 73 | } 74 | 75 | return $carry; 76 | }, 77 | [] 78 | ); 79 | } 80 | 81 | /** 82 | * Determine whether the supplied function is an existing internal function. 83 | * 84 | * @param string $function 85 | * 86 | * @return bool 87 | */ 88 | private function isInternal(string $function): bool 89 | { 90 | try { 91 | $reflection = new ReflectionFunction($function); 92 | 93 | return $reflection->isInternal(); 94 | } catch (Throwable $exception) { 95 | return false; 96 | } 97 | } 98 | 99 | /** 100 | * Verify that the signature of the given function does not hold multiple 101 | * parameters with the same name. 102 | * 103 | * @see https://bugs.php.net/bug.php?id=76918 104 | * 105 | * @param string $function 106 | * 107 | * @return bool 108 | */ 109 | private function functionHasValidSignature(string $function): bool 110 | { 111 | $reflection = new ReflectionFunction($function); 112 | $parameters = array_map( 113 | function (ReflectionParameter $parameter): string { 114 | return $parameter->getName(); 115 | }, 116 | $reflection->getParameters() 117 | ); 118 | 119 | return $parameters === array_unique($parameters); 120 | } 121 | } 122 | --------------------------------------------------------------------------------