├── .editorconfig
├── LICENSE
├── README.md
├── composer.json
├── res
└── php
│ └── alias-loader-include.tmpl.php
├── src
├── ClassAliasLoader.php
├── ClassAliasMap.php
├── ClassAliasMapGenerator.php
├── Config.php
├── IncludeFile.php
├── IncludeFile
│ ├── CaseSensitiveToken.php
│ ├── PrependToken.php
│ └── TokenInterface.php
└── Plugin.php
└── tests
└── Unit
├── BaseTestCase.php
├── ClassAliasLoaderTest.php
├── ConfigTest.php
└── IncludeFileTest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [**.php]
13 | indent_style = space
14 | indent_size = 4
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Helmut Hummel
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Class Alias Loader [](https://github.com/TYPO3/class-alias-loader/actions/workflows/tests.yml)
2 | ==================
3 |
4 | ## Introduction
5 | The idea behind this composer package is, to provide backwards compatibility for libraries that want to rename classes
6 | but still want to stay compatible for a certain amount of time with consumer packages of these libraries.
7 |
8 | ## What it does?
9 | It provides an additional class loader which amends the composer class loader by rewriting the `vendor/autoload.php`
10 | file when composer dumps the autoload information. This is only done if any of the packages that are installed by composer
11 | provide a class alias map file, which is configured in the respective `composer.json`.
12 |
13 | ## How does it work?
14 | If a package provides a mapping file which holds the mapping from old to new class name, the class loader registers itself
15 | and transparently calls `class_alias()` for classes with an alias. If an old class name is requested, the original class
16 | is loaded and the alias is established, so that third party packages can use old class names transparently.
17 |
18 | ## Configuration in composer.json
19 |
20 | You can define multiple class alias map files in the extra section of the `composer.json` like this:
21 |
22 | ```
23 | "extra": {
24 | "typo3/class-alias-loader": {
25 | "class-alias-maps": [
26 | "Migrations/Code/ClassAliasMap.php"
27 | ]
28 | }
29 | },
30 | ```
31 |
32 | Currently these files must be PHP files which return an associative array, where the keys are the old class names and the values the new class names.
33 | Such a mapping file can look like this:
34 |
35 | ```
36 | \TYPO3\CMS\About\Controller\AboutController::class,
39 | 'Tx_About_Domain_Model_Extension' => \TYPO3\CMS\About\Domain\Model\Extension::class,
40 | 'Tx_About_Domain_Repository_ExtensionRepository' => \TYPO3\CMS\About\Domain\Repository\ExtensionRepository::class,
41 | 'Tx_Aboutmodules_Controller_ModulesController' => \TYPO3\CMS\Aboutmodules\Controller\ModulesController::class,
42 | );
43 | ```
44 |
45 | The '::class' constant is not available before PHP 5.5. Under a PHP before 5.5 the mapping file can look like this:
46 |
47 | ```
48 | 'TYPO3\\CMS\\About\\Controller\\AboutController',
51 | 'Tx_About_Domain_Model_Extension' => 'TYPO3\\CMS\\About\\Domain\\Model\\Extension',
52 | 'Tx_About_Domain_Repository_ExtensionRepository' => 'TYPO3\\CMS\\About\\Domain\\Repository\\ExtensionRepository',
53 | 'Tx_Aboutmodules_Controller_ModulesController' => 'TYPO3\\CMS\\Aboutmodules\\Controller\\ModulesController',
54 | );
55 | ```
56 |
57 | In your *root* `composer.json` file, you can decide whether to allow classes to be found that are requested with wrong casing.
58 | Since PHP is case insensitive for class names, but PSR class loading standards bound file names to class names, class names de facto
59 | become case sensitive. For legacy packages it may be useful however to allow class names to be loaded even if wrong casing is provided.
60 | For this to work properly, you need to use the composer [optimize class loading information feature](https://getcomposer.org/doc/03-cli.md#global-options).
61 |
62 |
63 | You can activate this feature like this:
64 |
65 | ```
66 | "extra": {
67 | "typo3/class-alias-loader": {
68 | "autoload-case-sensitivity": false
69 | }
70 | },
71 | ```
72 |
73 | The default value of this option is `true`.
74 |
75 | If no alias mapping is found and case sensitivity is set to `true` then by default this package does nothing. It means no additional class loading information is dumped
76 | and the `vendor/autoload.php` is not changed. This enables library vendors to deliver compatibility packages which provide such aliases
77 | for backwards compatibility, but keep the library clean (and faster) for new users.
78 |
79 | In case you want your application to add alias maps during runtime, it may be useful however if the alias loader is always initialized.
80 | Therefore it is possible to set the following option in your *root* `composer.json`:
81 |
82 | ```
83 | "extra": {
84 | "typo3/class-alias-loader": {
85 | "always-add-alias-loader": true
86 | }
87 | },
88 | ```
89 |
90 |
91 | ## Using the API
92 |
93 | The public API is pretty simple and consists of only one class with only three static methods, `TYPO3\ClassAliasLoader\ClassAliasMap::getClassNameForAlias`
94 | being the most important one.
95 | You can use this class method if you have places in your application that deals with class names in strings and want to provide backwards compatibility there.
96 | The API returns the original (new) class name if there is one, or the class name given if no alias is found.
97 |
98 | The remaining methods, deal with adding alias maps during runtime, which generally is not recommended to do.
99 |
100 | ## Feedback appreciated
101 |
102 | I'm happy for feedback, be it [feature requests](https://github.com/TYPO3/class-alias-loader/issues) or [bug reports](https://github.com/TYPO3/class-alias-loader/issues).
103 |
104 | ## Contribute
105 |
106 | If you feel like contributing, please do a regular [pull request](https://github.com/TYPO3/class-alias-loader/pulls).
107 | The package is pretty small. The only thing to respect is to follow [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard
108 | and to add some tests for functionality you add or change.
109 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typo3/class-alias-loader",
3 | "type": "composer-plugin",
4 | "license": "MIT",
5 | "description": "Amends the composer class loader to support class aliases to provide backwards compatibility for packages",
6 | "keywords": [
7 | "composer", "autoloader", "classloader", "alias"
8 | ],
9 | "homepage": "http://github.com/TYPO3/class-alias-loader",
10 | "authors": [
11 | {
12 | "name": "Helmut Hummel",
13 | "email": "info@helhum.io"
14 | }
15 | ],
16 | "autoload": {
17 | "psr-4": { "TYPO3\\ClassAliasLoader\\": "src/"}
18 | },
19 | "autoload-dev": {
20 | "psr-4": { "TYPO3\\ClassAliasLoader\\Test\\": "tests/"}
21 | },
22 | "require": {
23 | "php": ">=7.1",
24 | "composer-plugin-api": "^1.0 || ^2.0"
25 | },
26 | "require-dev": {
27 | "composer/composer": "^1.1@dev || ^2.0@dev",
28 | "mikey179/vfsstream": "~1.4.0@dev",
29 | "phpunit/phpunit": ">4.8 <9"
30 | },
31 | "replace": {
32 | "helhum/class-alias-loader": "*"
33 | },
34 | "extra": {
35 | "class": "TYPO3\\ClassAliasLoader\\Plugin",
36 | "branch-alias": {
37 | "dev-main": "1.1.x-dev"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/res/php/alias-loader-include.tmpl.php:
--------------------------------------------------------------------------------
1 | setAliasMap($classAliasMap);
7 | $classAliasLoader->setCaseSensitiveClassLoading('{$sensitive-loading}');
8 | $classAliasLoader->register('{$prepend}');
9 | TYPO3\ClassAliasLoader\ClassAliasMap::setClassAliasLoader($classAliasLoader);
10 |
--------------------------------------------------------------------------------
/src/ClassAliasLoader.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Autoload\ClassLoader as ComposerClassLoader;
14 |
15 | /**
16 | * The main class loader that amends the composer class loader.
17 | * It deals with the alias maps and the case insensitive class loading if configured.
18 | */
19 | class ClassAliasLoader
20 | {
21 | /**
22 | * @var ComposerClassLoader
23 | */
24 | protected $composerClassLoader;
25 |
26 | /**
27 | * @var array
28 | */
29 | protected $aliasMap = array(
30 | 'aliasToClassNameMapping' => array(),
31 | 'classNameToAliasMapping' => array()
32 | );
33 |
34 | /**
35 | * @deprecated
36 | * @var bool
37 | */
38 | protected $caseSensitiveClassLoading = true;
39 |
40 | /**
41 | * @param ComposerClassLoader $composerClassLoader
42 | */
43 | public function __construct(ComposerClassLoader $composerClassLoader)
44 | {
45 | $this->composerClassLoader = $composerClassLoader;
46 | }
47 |
48 | /**
49 | * Set the alias map
50 | *
51 | * @param array $aliasMap
52 | */
53 | public function setAliasMap(array $aliasMap)
54 | {
55 | $this->aliasMap = $aliasMap;
56 | }
57 |
58 | /**
59 | * @deprecated
60 | * @param bool $caseSensitiveClassLoading
61 | */
62 | public function setCaseSensitiveClassLoading($caseSensitiveClassLoading)
63 | {
64 | $this->caseSensitiveClassLoading = $caseSensitiveClassLoading;
65 | }
66 |
67 | /**
68 | * Adds an alias map and merges it with already available map
69 | *
70 | * @param array $aliasMap
71 | */
72 | public function addAliasMap(array $aliasMap)
73 | {
74 | foreach ($aliasMap['aliasToClassNameMapping'] as $alias => $originalClassName) {
75 | $lowerCaseAlias = strtolower($alias);
76 | $this->aliasMap['aliasToClassNameMapping'][$lowerCaseAlias] = $originalClassName;
77 | $this->aliasMap['classNameToAliasMapping'][$originalClassName][$lowerCaseAlias] = $lowerCaseAlias;
78 | }
79 | }
80 |
81 | /**
82 | * Get final class name of alias
83 | *
84 | * @param string $aliasOrClassName
85 | * @return string
86 | */
87 | public function getClassNameForAlias($aliasOrClassName)
88 | {
89 | $lookUpClassName = strtolower($aliasOrClassName);
90 |
91 | return isset($this->aliasMap['aliasToClassNameMapping'][$lookUpClassName]) ? $this->aliasMap['aliasToClassNameMapping'][$lookUpClassName] : $aliasOrClassName;
92 | }
93 |
94 | /**
95 | * Registers this instance as an autoloader.
96 | *
97 | * @param bool $prepend Whether to prepend the autoloader or not
98 | */
99 | public function register($prepend = false)
100 | {
101 | $this->composerClassLoader->unregister();
102 | spl_autoload_register(array($this, 'loadClassWithAlias'), true, $prepend);
103 | }
104 |
105 | /**
106 | * Unregisters this instance as an autoloader.
107 | */
108 | public function unregister()
109 | {
110 | spl_autoload_unregister(array($this, 'loadClassWithAlias'));
111 | }
112 |
113 | /**
114 | * Main class loading method registered with spl_autoload_register()
115 | *
116 | * @param string $className
117 | * @return bool
118 | */
119 | public function loadClassWithAlias($className)
120 | {
121 | $originalClassName = $this->getOriginalClassName($className);
122 |
123 | return $originalClassName
124 | ? $this->loadOriginalClassAndSetAliases($originalClassName)
125 | : $this->loadClass($className);
126 | }
127 |
128 | /**
129 | * Load class with the option to respect case insensitivity
130 | * @deprecated
131 | *
132 | * @param string $className
133 | * @return bool|null
134 | */
135 | public function loadClass($className)
136 | {
137 | $classFound = $this->composerClassLoader->loadClass($className);
138 | if (!$classFound && !$this->caseSensitiveClassLoading) {
139 | $classFound = $this->composerClassLoader->loadClass(strtolower($className));
140 | }
141 | return $classFound;
142 | }
143 |
144 | /**
145 | * Looks up the original class name from the alias map
146 | *
147 | * @param string $aliasOrClassName
148 | * @return string|NULL NULL if no alias mapping is found or the original class name as string
149 | */
150 | protected function getOriginalClassName($aliasOrClassName)
151 | {
152 | // Is an original class which has an alias
153 | if (array_key_exists($aliasOrClassName, $this->aliasMap['classNameToAliasMapping'])) {
154 | return $this->aliasMap['classNameToAliasMapping'][$aliasOrClassName] === array()
155 | ? null
156 | : $aliasOrClassName
157 | ;
158 | }
159 | // Is an alias (we're graceful ignoring casing for alias definitions)
160 | $lowerCasedClassName = strtolower($aliasOrClassName);
161 | if (array_key_exists($lowerCasedClassName, $this->aliasMap['aliasToClassNameMapping'])) {
162 | return $this->aliasMap['aliasToClassNameMapping'][$lowerCasedClassName];
163 | }
164 | // No alias registered for this class name, return and remember that info
165 | $this->aliasMap['classNameToAliasMapping'][$aliasOrClassName] = array();
166 | return null;
167 | }
168 |
169 | /**
170 | * Load classes and set aliases.
171 | * The class_exists calls are safety guards to avoid fatals when
172 | * class files were included or aliases were set manually in userland code.
173 | *
174 | * @param string $originalClassName
175 | * @return bool|null
176 | */
177 | protected function loadOriginalClassAndSetAliases($originalClassName)
178 | {
179 | if ($this->classOrInterfaceExists($originalClassName) || $this->loadClass($originalClassName)) {
180 | foreach ($this->aliasMap['classNameToAliasMapping'][$originalClassName] as $aliasClassName) {
181 | if (!$this->classOrInterfaceExists($aliasClassName)) {
182 | class_alias($originalClassName, $aliasClassName);
183 | }
184 | }
185 |
186 | return true;
187 | }
188 |
189 | return null;
190 | }
191 |
192 | /**
193 | * @param string $className
194 | * @return bool
195 | */
196 | protected function classOrInterfaceExists($className)
197 | {
198 | $classOrInterfaceExists = class_exists($className, false) || interface_exists($className, false);
199 | if ($classOrInterfaceExists) {
200 | return true;
201 | }
202 | if (function_exists('trait_exists')) {
203 | return trait_exists($className, false);
204 | }
205 |
206 | return false;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/ClassAliasMap.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | /**
14 | * This class is the only public API of this package (besides the composer.json configuration)
15 | * Use the only method in cases described below.
16 | */
17 | class ClassAliasMap
18 | {
19 | /**
20 | * @var ClassAliasLoader
21 | */
22 | protected static $classAliasLoader;
23 |
24 | /**
25 | * You can use this method in your code if you compare class names as strings and want to provide compatibility for that as well.
26 | * The impact is pretty low and boils down to a method call. In case no aliases are present in the composer installation,
27 | * the class name given is returned as is, because the vendor/autoload.php will not be rewritten and thus the static member of this
28 | * class will not be set.
29 | *
30 | * @param string $classNameOrAlias
31 | * @return string
32 | */
33 | public static function getClassNameForAlias($classNameOrAlias)
34 | {
35 | if (!static::$classAliasLoader) {
36 | return $classNameOrAlias;
37 | }
38 | return static::$classAliasLoader->getClassNameForAlias($classNameOrAlias);
39 | }
40 |
41 | /**
42 | * Whether or not alias maps are already registered
43 | *
44 | * @return bool
45 | */
46 | public static function hasAliasMaps()
47 | {
48 | return is_object(static::$classAliasLoader);
49 | }
50 |
51 | /**
52 | * Adds an alias map if the alias loader is registered, throws an exception otherwise.
53 | *
54 | * @param array $aliasMap
55 | * @throws \RuntimeException
56 | */
57 | public static function addAliasMap(array $aliasMap)
58 | {
59 | if (!static::$classAliasLoader) {
60 | throw new \RuntimeException('Cannot set an alias map as the alias loader is not registered!', 1439228111);
61 | }
62 |
63 | static::$classAliasLoader->addAliasMap($aliasMap);
64 | }
65 |
66 | /**
67 | * @param ClassAliasLoader $classAliasLoader
68 | */
69 | public static function setClassAliasLoader(ClassAliasLoader $classAliasLoader)
70 | {
71 | if (static::$classAliasLoader) {
72 | throw new \RuntimeException('Cannot set the alias loader, as it is already registered!', 1439228112);
73 | }
74 | static::$classAliasLoader = $classAliasLoader;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/ClassAliasMapGenerator.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Composer;
14 | use Composer\IO\IOInterface;
15 | use Composer\IO\NullIO;
16 | use Composer\Package\PackageInterface;
17 | use Composer\Util\Filesystem;
18 | use TYPO3\ClassAliasLoader\Config;
19 | use TYPO3\ClassAliasLoader\IncludeFile\CaseSensitiveToken;
20 | use TYPO3\ClassAliasLoader\IncludeFile\PrependToken;
21 |
22 | /**
23 | * This class loops over all packages that are installed by composer and
24 | * looks for configured class alias maps (in composer.json).
25 | * If at least one is found, the vendor/autoload.php file is rewritten to amend the composer class loader.
26 | * Otherwise it does nothing.
27 | */
28 | class ClassAliasMapGenerator
29 | {
30 | /**
31 | * @var Composer
32 | */
33 | protected $composer;
34 |
35 | /**
36 | * @var IOInterface
37 | */
38 | protected $io;
39 |
40 | /**
41 | * @var Config
42 | */
43 | private $config;
44 |
45 | /**
46 | * @param Composer $composer
47 | * @param IOInterface $io
48 | */
49 | public function __construct(Composer $composer, ?IOInterface $io = null, $config = null)
50 | {
51 | $this->composer = $composer;
52 | $this->io = $io ?: new NullIO();
53 | if (\is_bool($config)) {
54 | // Happens during upgrade from older versions, so try to be graceful
55 | $config = new Config($this->composer->getPackage());
56 | }
57 | $this->config = $config ?: new Config($this->composer->getPackage());
58 | }
59 |
60 | /**
61 | * @deprecated
62 | * @throws \Exception
63 | */
64 | public function generateAliasMap()
65 | {
66 | // Is called during upgrade from older plugin versions, so try to be graceful, but output verbose message
67 | $this->io->writeError(' ┌─────────────────────────────────────────────────────────────┐ ');
68 | $this->io->writeError(' │ Upgraded typo3/class-alias-loader from older plugin version.│ ');
69 | $this->io->writeError(' │ Please run "composer dumpautoload" to complete the upgrade. │ ');
70 | $this->io->writeError(' └─────────────────────────────────────────────────────────────┘ ');
71 | }
72 |
73 | /**
74 | * @throws \Exception
75 | * @return bool
76 | */
77 | public function generateAliasMapFiles()
78 | {
79 | $config = $this->composer->getConfig();
80 |
81 | $filesystem = new Filesystem();
82 | $basePath = $filesystem->normalizePath(substr($config->get('vendor-dir'), 0, -strlen($config->get('vendor-dir', $config::RELATIVE_PATHS))));
83 | $vendorPath = $config->get('vendor-dir');
84 | $targetDir = $vendorPath . '/composer';
85 | $filesystem->ensureDirectoryExists($targetDir);
86 |
87 | $mainPackage = $this->composer->getPackage();
88 | $autoLoadGenerator = $this->composer->getAutoloadGenerator();
89 | $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
90 | $packageMap = $autoLoadGenerator->buildPackageMap($this->composer->getInstallationManager(), $mainPackage, $localRepo->getCanonicalPackages());
91 |
92 | $aliasToClassNameMapping = array();
93 | $classNameToAliasMapping = array();
94 | $classAliasMappingFound = false;
95 |
96 | foreach ($packageMap as $item) {
97 | /** @var PackageInterface $package */
98 | list($package, $installPath) = $item;
99 | $aliasLoaderConfig = new Config($package, $this->io);
100 | if ($aliasLoaderConfig->get('class-alias-maps') !== null) {
101 | if (!is_array($aliasLoaderConfig->get('class-alias-maps'))) {
102 | throw new \Exception('Configuration option "class-alias-maps" must be an array');
103 | }
104 | foreach ($aliasLoaderConfig->get('class-alias-maps') as $mapFile) {
105 | $mapFilePath = ($installPath ?: $basePath) . '/' . $filesystem->normalizePath($mapFile);
106 | if (!is_file($mapFilePath)) {
107 | $this->io->writeError(sprintf('The class alias map file "%s" configured in package "%s" was not found!', $mapFile, $package->getName()));
108 | continue;
109 | }
110 | $packageAliasMap = require $mapFilePath;
111 | if (!is_array($packageAliasMap)) {
112 | throw new \Exception('Class alias map files must return an array', 1422625075);
113 | }
114 | if (!empty($packageAliasMap)) {
115 | $classAliasMappingFound = true;
116 | }
117 | foreach ($packageAliasMap as $aliasClassName => $className) {
118 | $lowerCasedAliasClassName = strtolower($aliasClassName);
119 | $aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
120 | $classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
121 | }
122 | }
123 | }
124 | }
125 |
126 | $alwaysAddAliasLoader = $this->config->get('always-add-alias-loader');
127 | $caseSensitiveClassLoading = $this->config->get('autoload-case-sensitivity');
128 |
129 | if (!$alwaysAddAliasLoader && !$classAliasMappingFound && $caseSensitiveClassLoading) {
130 | // No mapping found in any package and no insensitive class loading active. We return early and skip rewriting
131 | // Unless user configured alias loader to be always added
132 | return false;
133 | }
134 |
135 | $includeFile = new IncludeFile(
136 | $this->io,
137 | $this->composer,
138 | array(
139 | new CaseSensitiveToken(
140 | $this->io,
141 | $this->config
142 | ),
143 | new PrependToken(
144 | $this->io,
145 | $this->composer->getConfig()
146 | ),
147 | )
148 | );
149 | $includeFile->register();
150 |
151 | $this->io->write('Generating ' . ($classAliasMappingFound ? '' : 'empty ') . 'class alias map file');
152 | $this->generateAliasMapFile($aliasToClassNameMapping, $classNameToAliasMapping, $targetDir);
153 |
154 | return true;
155 | }
156 |
157 | /**
158 | * @deprecated will be removed with 2.0
159 | * @param $optimizeAutoloadFiles
160 | * @return bool
161 | */
162 | public function modifyComposerGeneratedFiles($optimizeAutoloadFiles = false)
163 | {
164 | $caseSensitiveClassLoading = $this->config->get('autoload-case-sensitivity');
165 | $vendorPath = $this->composer->getConfig()->get('vendor-dir');
166 | if (!$caseSensitiveClassLoading) {
167 | $this->io->writeError('Re-writing class map to support case insensitive class loading is deprecated');
168 | if (!$optimizeAutoloadFiles) {
169 | $this->io->writeError('Case insensitive class loading only works reliably if you use the optimize class loading feature of composer');
170 | }
171 | $this->rewriteClassMapWithLowerCaseClassNames($vendorPath . '/composer');
172 | }
173 |
174 | return true;
175 | }
176 |
177 | /**
178 | * @param array $aliasToClassNameMapping
179 | * @param array $classNameToAliasMapping
180 | * @param string $targetDir
181 | */
182 | protected function generateAliasMapFile(array $aliasToClassNameMapping, array $classNameToAliasMapping, $targetDir)
183 | {
184 | $exportArray = array(
185 | 'aliasToClassNameMapping' => $aliasToClassNameMapping,
186 | 'classNameToAliasMapping' => $classNameToAliasMapping
187 | );
188 |
189 | $fileContent = ' /', function ($match) {
206 | return strtolower($match[0]);
207 | }, $classMapContents);
208 | file_put_contents($targetDir . '/autoload_classmap.php', $classMapContents);
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/Config.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\IO\IOInterface;
14 | use Composer\IO\NullIO;
15 | use Composer\Package\PackageInterface;
16 |
17 | /**
18 | * Class Config
19 | */
20 | class Config
21 | {
22 | /**
23 | * Default values
24 | *
25 | * @var array
26 | */
27 | protected $config = array(
28 | 'class-alias-maps' => null,
29 | 'always-add-alias-loader' => false,
30 | 'autoload-case-sensitivity' => true
31 | );
32 |
33 | /**
34 | * @var IOInterface
35 | */
36 | protected $io;
37 |
38 | /**
39 | * @param PackageInterface $package
40 | * @param IOInterface $io
41 | */
42 | public function __construct(PackageInterface $package, ?IOInterface $io = null)
43 | {
44 | $this->io = $io ?: new NullIO();
45 | $this->setAliasLoaderConfigFromPackage($package);
46 | }
47 |
48 | /**
49 | * @param string $configKey
50 | * @return mixed
51 | */
52 | public function get($configKey)
53 | {
54 | if (empty($configKey)) {
55 | throw new \InvalidArgumentException('Configuration key must not be empty', 1444039407);
56 | }
57 | // Extract parts of the path
58 | $configKey = str_getcsv($configKey, '.', '"', '\\');
59 |
60 | // Loop through each part and extract its value
61 | $value = $this->config;
62 | foreach ($configKey as $segment) {
63 | if (array_key_exists($segment, $value)) {
64 | // Replace current value with child
65 | $value = $value[$segment];
66 | } else {
67 | return null;
68 | }
69 | }
70 | return $value;
71 | }
72 |
73 | /**
74 | * @param PackageInterface $package
75 | */
76 | protected function setAliasLoaderConfigFromPackage(PackageInterface $package)
77 | {
78 | $extraConfig = $this->handleDeprecatedConfigurationInPackage($package);
79 | if (isset($extraConfig['typo3/class-alias-loader']['class-alias-maps'])) {
80 | $this->config['class-alias-maps'] = (array)$extraConfig['typo3/class-alias-loader']['class-alias-maps'];
81 | }
82 | if (isset($extraConfig['typo3/class-alias-loader']['always-add-alias-loader'])) {
83 | $this->config['always-add-alias-loader'] = (bool)$extraConfig['typo3/class-alias-loader']['always-add-alias-loader'];
84 | }
85 | if (isset($extraConfig['typo3/class-alias-loader']['autoload-case-sensitivity'])) {
86 | $this->config['autoload-case-sensitivity'] = (bool)$extraConfig['typo3/class-alias-loader']['autoload-case-sensitivity'];
87 | }
88 | }
89 |
90 | /**
91 | * Ensures backwards compatibility for packages which used helhum/class-alias-loader
92 | *
93 | * @param PackageInterface $package
94 | * @return array
95 | */
96 | protected function handleDeprecatedConfigurationInPackage(PackageInterface $package)
97 | {
98 | $extraConfig = $package->getExtra();
99 | $messages = array();
100 | if (!isset($extraConfig['typo3/class-alias-loader'])) {
101 | if (isset($extraConfig['helhum/class-alias-loader'])) {
102 | $extraConfig['typo3/class-alias-loader'] = $extraConfig['helhum/class-alias-loader'];
103 | $messages[] = sprintf(
104 | 'The package "%s" uses "helhum/class-alias-loader" section to define class alias maps, which is deprecated. Please use "typo3/class-alias-loader" instead!',
105 | $package->getName()
106 | );
107 | } else {
108 | $extraConfig['typo3/class-alias-loader'] = array();
109 | if (isset($extraConfig['class-alias-maps'])) {
110 | $extraConfig['typo3/class-alias-loader']['class-alias-maps'] = $extraConfig['class-alias-maps'];
111 | $messages[] = sprintf(
112 | 'The package "%s" uses "class-alias-maps" section on top level, which is deprecated. Please move this config below the top level key "typo3/class-alias-loader" instead!',
113 | $package->getName()
114 | );
115 | }
116 | if (isset($extraConfig['autoload-case-sensitivity'])) {
117 | $extraConfig['typo3/class-alias-loader']['autoload-case-sensitivity'] = $extraConfig['autoload-case-sensitivity'];
118 | $messages[] = sprintf(
119 | 'The package "%s" uses "autoload-case-sensitivity" section on top level, which is deprecated. Please move this config below the top level key "typo3/class-alias-loader" instead!',
120 | $package->getName()
121 | );
122 | }
123 | }
124 | }
125 | if (!empty($messages)) {
126 | $this->io->writeError($messages);
127 | }
128 | return $extraConfig;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/IncludeFile.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Composer;
14 | use Composer\IO\IOInterface;
15 | use Composer\Util\Filesystem;
16 | use TYPO3\CMS\Composer\Plugin\Core\IncludeFile\TokenInterface;
17 |
18 | class IncludeFile
19 | {
20 | const INCLUDE_FILE = '/typo3/alias-loader-include.php';
21 | const INCLUDE_FILE_TEMPLATE = '/res/php/alias-loader-include.tmpl.php';
22 |
23 | /**
24 | * @var TokenInterface[]
25 | */
26 | private $tokens;
27 |
28 | /**
29 | * @var Filesystem
30 | */
31 | private $filesystem;
32 |
33 | /**
34 | * @var IOInterface
35 | */
36 | private $io;
37 |
38 | /**
39 | * @var Composer
40 | */
41 | private $composer;
42 |
43 | /**
44 | * IncludeFile constructor.
45 | *
46 | * @param IOInterface $io
47 | * @param Composer $composer
48 | * @param TokenInterface[] $tokens
49 | * @param Filesystem $filesystem
50 | */
51 | public function __construct(IOInterface $io, Composer $composer, array $tokens, ?Filesystem $filesystem = null)
52 | {
53 | $this->io = $io;
54 | $this->composer = $composer;
55 | $this->tokens = $tokens;
56 | $this->filesystem = $filesystem ?: new Filesystem();
57 | }
58 |
59 | public function register()
60 | {
61 | $this->io->writeError('Register typo3/class-alias-loader file in root package autoload definition', true, IOInterface::VERBOSE);
62 |
63 | // Generate and write the file
64 | $includeFile = $this->composer->getConfig()->get('vendor-dir') . self::INCLUDE_FILE;
65 | file_put_contents($includeFile, $this->getIncludeFileContent(dirname($includeFile)));
66 |
67 | // Register the file in the root package
68 | $rootPackage = $this->composer->getPackage();
69 | $autoloadDefinition = $rootPackage->getAutoload();
70 | $autoloadDefinition['files'][] = $includeFile;
71 | $rootPackage->setAutoload($autoloadDefinition);
72 | }
73 |
74 | /**
75 | * Constructs the include file content
76 | *
77 | * @param string $includeFilePath
78 | * @throws \RuntimeException
79 | * @throws \InvalidArgumentException
80 | * @return string
81 | */
82 | protected function getIncludeFileContent($includeFilePath)
83 | {
84 | $includeFileTemplate = $this->filesystem->normalizePath(dirname(__DIR__) . self::INCLUDE_FILE_TEMPLATE);
85 | $includeFileContent = file_get_contents($includeFileTemplate);
86 | foreach ($this->tokens as $token) {
87 | $includeFileContent = self::replaceToken($token->getName(), $token->getContent($includeFilePath), $includeFileContent);
88 | }
89 |
90 | return $includeFileContent;
91 | }
92 |
93 | /**
94 | * Replaces a token in the subject (PHP code)
95 | *
96 | * @param string $name
97 | * @param string $content
98 | * @param string $subject
99 | * @return string
100 | */
101 | private static function replaceToken($name, $content, $subject)
102 | {
103 | return str_replace('\'{$' . $name . '}\'', $content, $subject);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/IncludeFile/CaseSensitiveToken.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\IO\IOInterface;
14 | use TYPO3\ClassAliasLoader\Config;
15 |
16 | /**
17 | * @deprecated
18 | */
19 | class CaseSensitiveToken implements TokenInterface
20 | {
21 | /**
22 | * @var string
23 | */
24 | private $name = 'sensitive-loading';
25 |
26 | /**
27 | * @var IOInterface
28 | */
29 | private $io;
30 |
31 | /**
32 | * @var Config
33 | */
34 | private $config;
35 |
36 | /**
37 | * BaseDirToken constructor.
38 | *
39 | * @param IOInterface $io
40 | * @param Config $config
41 | */
42 | public function __construct(IOInterface $io, Config $config)
43 | {
44 | $this->io = $io;
45 | $this->config = $config;
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getName()
52 | {
53 | return $this->name;
54 | }
55 |
56 | /**
57 | * @param string $includeFilePath
58 | * @throws \InvalidArgumentException
59 | * @return string
60 | */
61 | public function getContent($includeFilePath)
62 | {
63 | return $this->config->get('autoload-case-sensitivity') ? 'true' : 'false';
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/IncludeFile/PrependToken.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Config;
14 | use Composer\IO\IOInterface;
15 |
16 | class PrependToken implements TokenInterface
17 | {
18 | /**
19 | * @var string
20 | */
21 | private $name = 'prepend';
22 |
23 | /**
24 | * @var IOInterface
25 | */
26 | private $io;
27 |
28 | /**
29 | * @var Config
30 | */
31 | private $config;
32 |
33 | /**
34 | * BaseDirToken constructor.
35 | *
36 | * @param IOInterface $io
37 | * @param Config $config
38 | */
39 | public function __construct(IOInterface $io, Config $config)
40 | {
41 | $this->io = $io;
42 | $this->config = $config;
43 | }
44 |
45 | /**
46 | * @return string
47 | */
48 | public function getName()
49 | {
50 | return $this->name;
51 | }
52 |
53 | /**
54 | * @param string $includeFilePath
55 | * @throws \InvalidArgumentException
56 | * @return string
57 | */
58 | public function getContent($includeFilePath)
59 | {
60 | return $this->config->get('prepend-autoloader') === false ? 'false' : 'true';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/IncludeFile/TokenInterface.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | interface TokenInterface
14 | {
15 | /**
16 | * The name of the token that shall be replaced
17 | *
18 | * @return string
19 | */
20 | public function getName();
21 |
22 | /**
23 | * The content the token should be replaced with
24 | *
25 | * @param string $includeFilePath
26 | * @return string
27 | */
28 | public function getContent($includeFilePath);
29 | }
30 |
--------------------------------------------------------------------------------
/src/Plugin.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Composer;
14 | use Composer\EventDispatcher\EventSubscriberInterface;
15 | use Composer\IO\IOInterface;
16 | use Composer\Plugin\PluginInterface;
17 | use Composer\Script\Event;
18 |
19 | /**
20 | * Class Plugin
21 | */
22 | class Plugin implements PluginInterface, EventSubscriberInterface
23 | {
24 | /**
25 | * @var Composer
26 | */
27 | protected $composer;
28 |
29 | /**
30 | * @var IOInterface
31 | */
32 | protected $io;
33 |
34 | /**
35 | * @var ClassAliasMapGenerator
36 | */
37 | private $aliasMapGenerator;
38 |
39 | /**
40 | * Apply plugin modifications to composer
41 | *
42 | * @param Composer $composer
43 | * @param IOInterface $io
44 | */
45 | public function activate(Composer $composer, IOInterface $io)
46 | {
47 | $this->composer = $composer;
48 | $this->io = $io;
49 | $this->aliasMapGenerator = new ClassAliasMapGenerator(
50 | $this->composer,
51 | $this->io
52 | );
53 | }
54 |
55 | public function deactivate(Composer $composer, IOInterface $io)
56 | {
57 | // Nothing to do
58 | }
59 |
60 | public function uninstall(Composer $composer, IOInterface $io)
61 | {
62 | // Nothing to do
63 | }
64 |
65 | /**
66 | * Returns an array of event names this subscriber wants to listen to.
67 | *
68 | * The array keys are event names and the value can be:
69 | *
70 | * * The method name to call (priority defaults to 0)
71 | * * An array composed of the method name to call and the priority
72 | * * An array of arrays composed of the method names to call and respective
73 | * priorities, or 0 if unset
74 | *
75 | * For instance:
76 | *
77 | * * array('eventName' => 'methodName')
78 | * * array('eventName' => array('methodName', $priority))
79 | * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
80 | *
81 | * @return array The event names to listen to
82 | */
83 | public static function getSubscribedEvents()
84 | {
85 | return array(
86 | 'pre-autoload-dump' => array('onPreAutoloadDump'),
87 | 'post-autoload-dump' => array('onPostAutoloadDump'),
88 | );
89 | }
90 |
91 | /**
92 | * @param Event $event
93 | * @throws \Exception
94 | * @return bool
95 | */
96 | public function onPreAutoloadDump(Event $event)
97 | {
98 | return $this->aliasMapGenerator->generateAliasMapFiles();
99 | }
100 |
101 | /**
102 | * @param Event $event
103 | * @return bool
104 | */
105 | public function onPostAutoloadDump(Event $event)
106 | {
107 | $flags = $event->getFlags();
108 | $config = $event->getComposer()->getConfig();
109 | $optimizeAutoloadFiles = !empty($flags['optimize']) || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
110 |
111 | return $this->aliasMapGenerator->modifyComposerGeneratedFiles($optimizeAutoloadFiles);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/Unit/BaseTestCase.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Autoload\ClassLoader as ComposerClassLoader;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 | use TYPO3\ClassAliasLoader\ClassAliasLoader;
16 |
17 | /**
18 | * Test case for ClassAliasLoader
19 | */
20 | class ClassAliasLoaderTest extends BaseTestCase
21 | {
22 | /**
23 | * @var ClassAliasLoader
24 | */
25 | protected $subject;
26 |
27 | /**
28 | * @var ComposerClassLoader|MockObject|PHPUnit_Framework_MockObject_MockObject
29 | */
30 | protected $composerClassLoaderMock;
31 |
32 | /**
33 | * @before
34 | */
35 | public function setMeUp()
36 | {
37 | $this->composerClassLoaderMock = $this->getMockBuilder('Composer\\Autoload\\ClassLoader')->getMock();
38 | $this->subject = new ClassAliasLoader($this->composerClassLoaderMock);
39 | }
40 |
41 | /**
42 | * @after
43 | */
44 | public function tearMeDown()
45 | {
46 | $this->subject->unregister();
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function registeringTheAliasLoaderUnregistersComposerClassLoader()
53 | {
54 | $this->composerClassLoaderMock->expects($this->once())->method('unregister');
55 | $this->subject->register();
56 | }
57 |
58 | /**
59 | * @test
60 | */
61 | public function composerLoadClassIsCalledOnlyOnceWhenCaseSensitiveClassLoadingIsOn()
62 | {
63 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass');
64 | $this->subject->loadClassWithAlias('TestClass');
65 | }
66 |
67 | /**
68 | * @test
69 | */
70 | public function composerLoadClassIsCalledOnlyOnceWhenCaseSensitiveClassLoadingIsOffButClassIsFound()
71 | {
72 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass')->willReturn(true);
73 | $this->subject->setCaseSensitiveClassLoading(false);
74 | $this->subject->loadClassWithAlias('TestClass');
75 | }
76 |
77 | /**
78 | * @test
79 | */
80 | public function composerLoadClassIsCalledTwiceWhenCaseSensitiveClassLoadingIsOffAndClassIsNotFound()
81 | {
82 | $this->composerClassLoaderMock->expects($this->exactly(2))->method('loadClass');
83 | $this->subject->setCaseSensitiveClassLoading(false);
84 | $this->subject->loadClassWithAlias('TestClass');
85 | }
86 |
87 | /**
88 | * @test
89 | */
90 | public function loadsClassIfNoAliasIsFound()
91 | {
92 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
93 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass')->willReturnCallback(function ($className) {
94 | eval('class ' . $className . ' {}');
95 | return true;
96 | });
97 | $this->subject->loadClassWithAlias($testClassName);
98 | $this->assertTrue(class_exists($testClassName, false));
99 | }
100 |
101 | /**
102 | * @test
103 | */
104 | public function callingLoadClassMultipleTimesInEdgeCasesWillStillWork()
105 | {
106 | $this->composerClassLoaderMock
107 | ->expects($this->exactly(2))
108 | ->method('loadClass')
109 | ->willReturnOnConsecutiveCalls(false, true);
110 | $this->assertFalse($this->subject->loadClassWithAlias('TestClass'));
111 | $this->assertTrue($this->subject->loadClassWithAlias('TestClass'));
112 | }
113 |
114 | /**
115 | * @test
116 | */
117 | public function loadClassWithOriginalClassNameSetsAliases()
118 | {
119 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
120 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
121 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
122 |
123 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass')->willReturnCallback(function ($className) {
124 | eval('class ' . $className . ' {}');
125 | return true;
126 | });
127 |
128 | $this->subject->setAliasMap(array(
129 | 'aliasToClassNameMapping' => array(
130 | strtolower($testAlias1) => $testClassName,
131 | strtolower($testAlias2) => $testClassName,
132 | ),
133 | 'classNameToAliasMapping' => array(
134 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
135 | ),
136 | ));
137 |
138 | $this->subject->loadClassWithAlias($testClassName);
139 | $this->assertTrue(class_exists($testAlias1, false));
140 | $this->assertTrue(class_exists($testAlias2, false));
141 | }
142 |
143 | /**
144 | * @test
145 | */
146 | public function getClassNameForAliasReturnsClassNameForEachAlias()
147 | {
148 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
149 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
150 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
151 |
152 | $this->subject->setAliasMap(array(
153 | 'aliasToClassNameMapping' => array(
154 | strtolower($testAlias1) => $testClassName,
155 | strtolower($testAlias2) => $testClassName,
156 | ),
157 | 'classNameToAliasMapping' => array(
158 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
159 | ),
160 | ));
161 |
162 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testAlias1));
163 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testAlias2));
164 | }
165 |
166 | /**
167 | * @test
168 | */
169 | public function addAliasMapAddsAliasesCorrectlyToTheMap()
170 | {
171 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
172 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
173 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
174 |
175 | $this->subject->setAliasMap(array(
176 | 'aliasToClassNameMapping' => array(
177 | strtolower($testAlias1) => $testClassName,
178 | ),
179 | 'classNameToAliasMapping' => array(
180 | $testClassName => array(strtolower($testAlias1))
181 | ),
182 | ));
183 |
184 | $this->subject->addAliasMap(array(
185 | 'aliasToClassNameMapping' => array(
186 | $testAlias2 => $testClassName,
187 | ),
188 | ));
189 |
190 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testAlias1));
191 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testAlias2));
192 | }
193 |
194 | /**
195 | * @test
196 | */
197 | public function getClassNameForAliasReturnsClassNameForClassName()
198 | {
199 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
200 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
201 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
202 |
203 | $this->subject->setAliasMap(array(
204 | 'aliasToClassNameMapping' => array(
205 | strtolower($testAlias1) => $testClassName,
206 | strtolower($testAlias2) => $testClassName,
207 | ),
208 | 'classNameToAliasMapping' => array(
209 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
210 | ),
211 | ));
212 |
213 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testClassName));
214 | }
215 |
216 | /**
217 | * @test
218 | */
219 | public function getClassNameForAliasReturnsClassNameForClassNameWithNoAliasMapSet()
220 | {
221 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
222 | $this->assertEquals($testClassName, $this->subject->getClassNameForAlias($testClassName));
223 | }
224 |
225 | /**
226 | * @test
227 | */
228 | public function loadClassWithAliasClassNameSetsAliasesAndLoadsOriginalClass()
229 | {
230 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
231 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
232 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
233 |
234 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass')->willReturnCallback(function ($className) {
235 | eval('class ' . $className . ' {}');
236 | return true;
237 | });
238 |
239 | $this->subject->setAliasMap(array(
240 | 'aliasToClassNameMapping' => array(
241 | strtolower($testAlias1) => $testClassName,
242 | strtolower($testAlias2) => $testClassName,
243 | ),
244 | 'classNameToAliasMapping' => array(
245 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
246 | ),
247 | ));
248 |
249 | $this->subject->loadClassWithAlias($testAlias1);
250 | $this->assertTrue(class_exists($testClassName, false), 'Class name is not loaded');
251 | $this->assertTrue(class_exists($testAlias1, false), 'First alias is not loaded');
252 | $this->assertTrue(class_exists($testAlias2, false), 'Second alias is not loaded');
253 | }
254 |
255 | /**
256 | * @test
257 | */
258 | public function aliasesInstancesHaveOriginalClassName()
259 | {
260 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
261 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
262 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
263 |
264 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass')->willReturnCallback(function ($className) {
265 | eval('class ' . $className . ' {}');
266 | return true;
267 | });
268 |
269 | $this->subject->setAliasMap(array(
270 | 'aliasToClassNameMapping' => array(
271 | strtolower($testAlias1) => $testClassName,
272 | strtolower($testAlias2) => $testClassName,
273 | ),
274 | 'classNameToAliasMapping' => array(
275 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
276 | ),
277 | ));
278 |
279 | $this->subject->loadClassWithAlias($testClassName);
280 |
281 | $testObject1 = new $testAlias1();
282 | $testObject2 = new $testAlias2();
283 |
284 | $this->assertSame($testClassName, get_class($testObject1));
285 | $this->assertSame($testClassName, get_class($testObject2));
286 | }
287 |
288 | /**
289 | * @test
290 | */
291 | public function classAliasesAreGracefullySetIfClassAlreadyExists()
292 | {
293 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
294 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
295 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
296 | $this->composerClassLoaderMock->expects($this->never())->method('loadClass');
297 |
298 | $this->subject->setAliasMap(array(
299 | 'aliasToClassNameMapping' => array(
300 | strtolower($testAlias1) => $testClassName,
301 | strtolower($testAlias2) => $testClassName,
302 | ),
303 | 'classNameToAliasMapping' => array(
304 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
305 | ),
306 | ));
307 |
308 | eval('class ' . $testClassName . ' {}');
309 |
310 | $this->subject->loadClassWithAlias($testClassName);
311 |
312 | $testObject1 = new $testAlias1();
313 | $testObject2 = new $testAlias2();
314 |
315 | $this->assertSame($testClassName, get_class($testObject1));
316 | $this->assertSame($testClassName, get_class($testObject2));
317 | }
318 |
319 | /**
320 | * @test
321 | */
322 | public function interfaceAliasesAreGracefullySetIfInterfaceAlreadyExists()
323 | {
324 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
325 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
326 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
327 | $this->composerClassLoaderMock->expects($this->never())->method('loadClass');
328 |
329 | $this->subject->setAliasMap(array(
330 | 'aliasToClassNameMapping' => array(
331 | strtolower($testAlias1) => $testClassName,
332 | strtolower($testAlias2) => $testClassName,
333 | ),
334 | 'classNameToAliasMapping' => array(
335 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
336 | ),
337 | ));
338 |
339 | eval('interface ' . $testClassName . ' {}');
340 |
341 | $this->subject->loadClassWithAlias($testClassName);
342 |
343 | $this->assertTrue(interface_exists($testAlias1, false), 'First alias is not loaded');
344 | $this->assertTrue(interface_exists($testAlias2, false), 'Second alias is not loaded');
345 | }
346 |
347 | /**
348 | * @test
349 | */
350 | public function classAliasesAreNotReEstablishedIfTheyAlreadyExist()
351 | {
352 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
353 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
354 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
355 | $this->composerClassLoaderMock->expects($this->never())->method('loadClass');
356 |
357 | $this->subject->setAliasMap(array(
358 | 'aliasToClassNameMapping' => array(
359 | strtolower($testAlias1) => $testClassName,
360 | strtolower($testAlias2) => $testClassName,
361 | ),
362 | 'classNameToAliasMapping' => array(
363 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
364 | ),
365 | ));
366 |
367 | eval('class ' . $testClassName . ' {}');
368 | class_alias($testClassName, $testAlias1);
369 |
370 | $this->subject->loadClassWithAlias($testClassName);
371 |
372 | $this->assertTrue(class_exists($testAlias2, false), 'Second alias is not loaded');
373 | }
374 |
375 | /**
376 | * @test
377 | */
378 | public function loadClassWithAliasReturnsNullIfComposerClassLoaderCannotFindClass()
379 | {
380 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass');
381 | $this->assertNull($this->subject->loadClassWithAlias('TestClass'));
382 | }
383 |
384 | /**
385 | * @test
386 | */
387 | public function loadClassWithAliasReturnsNullIfComposerClassLoaderCannotFindClassEvenIfItExistsInMap()
388 | {
389 | $testClassName = 'TestClass' . md5(uniqid('bla', true));
390 | $testAlias1 = 'TestAlias' . md5(uniqid('bla', true));
391 | $testAlias2 = 'TestAlias' . md5(uniqid('bla', true));
392 |
393 | $this->subject->setAliasMap(array(
394 | 'aliasToClassNameMapping' => array(
395 | strtolower($testAlias1) => $testClassName,
396 | strtolower($testAlias2) => $testClassName,
397 | ),
398 | 'classNameToAliasMapping' => array(
399 | $testClassName => array(strtolower($testAlias1), strtolower($testAlias2))
400 | ),
401 | ));
402 |
403 | $this->composerClassLoaderMock->expects($this->once())->method('loadClass');
404 | $this->assertNull($this->subject->loadClassWithAlias($testClassName));
405 | }
406 | }
407 |
--------------------------------------------------------------------------------
/tests/Unit/ConfigTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\IO\IOInterface;
14 | use Composer\Package\PackageInterface;
15 | use TYPO3\ClassAliasLoader\Config;
16 |
17 | /**
18 | * Test case for Config
19 | */
20 | class ConfigTest extends BaseTestCase
21 | {
22 | /**
23 | * @var Config
24 | */
25 | protected $subject;
26 |
27 | /**
28 | * @var IOInterface|\PHPUnit_Framework_MockObject_MockObject
29 | */
30 | protected $ioMock;
31 |
32 | /**
33 | * @var PackageInterface|\PHPUnit_Framework_MockObject_MockObject
34 | */
35 | protected $packageMock;
36 |
37 | /**
38 | * @before
39 | */
40 | public function setMeUp()
41 | {
42 | $this->ioMock = $this->getMockBuilder('Composer\\IO\\IOInterface')->getMock();
43 | $this->packageMock = $this->getMockBuilder('Composer\\Package\\PackageInterface')->getMock();
44 |
45 | $this->subject = new Config($this->packageMock, $this->ioMock);
46 | }
47 |
48 | /**
49 | * @test
50 | */
51 | public function throwsExceptionForEmptyKey()
52 | {
53 | // Use this instead when old PHP versions are dropped and minimum phpunit version can be raised:
54 | /**
55 | $this->expectException('\\InvalidArgumentException');
56 | $this->expectExceptionCode(1444039407);
57 | $this->subject->get(null);
58 | */
59 | try {
60 | $result = false;
61 | $this->subject->get(null);
62 | } catch (\InvalidArgumentException $e) {
63 | if ($e->getCode() === 1444039407) {
64 | $result = true;
65 | }
66 | }
67 | $this->assertTrue($result, 'Expected exception with expected code not received');
68 | }
69 |
70 | /**
71 | * @test
72 | */
73 | public function defaultConfigIsAppliedWhenNothingIsConfiguredInPackage()
74 | {
75 | $this->assertFalse($this->subject->get('always-add-alias-loader'));
76 | $this->assertTrue($this->subject->get('autoload-case-sensitivity'));
77 | $this->assertNull($this->subject->get('class-alias-maps'));
78 | }
79 |
80 | /**
81 | * @test
82 | */
83 | public function aliasMapConfigIsExtracted()
84 | {
85 | $this->packageMock->expects($this->any())->method('getExtra')->willReturn(
86 | array(
87 | 'typo3/class-alias-loader' => array(
88 | 'class-alias-maps' => array(
89 | 'path/map.php'
90 | )
91 | )
92 | )
93 | );
94 |
95 | $subject = new Config($this->packageMock, $this->ioMock);
96 |
97 | $this->assertSame(array('path/map.php'), $subject->get('class-alias-maps'));
98 | }
99 |
100 | /**
101 | * @test
102 | */
103 | public function aliasMapConfigIsExtractedFromDeprecatedKey()
104 | {
105 | $this->packageMock->expects($this->any())->method('getExtra')->willReturn(
106 | array(
107 | 'helhum/class-alias-loader' => array(
108 | 'class-alias-maps' => array(
109 | 'path/map.php'
110 | )
111 | )
112 | )
113 | );
114 | $this->ioMock->expects($this->once())->method('writeError');
115 |
116 | $subject = new Config($this->packageMock, $this->ioMock);
117 |
118 | $this->assertSame(array('path/map.php'), $subject->get('class-alias-maps'));
119 | }
120 |
121 | /**
122 | * @test
123 | */
124 | public function otherConfigIsExtracted()
125 | {
126 | $this->packageMock->expects($this->any())->method('getExtra')->willReturn(
127 | array(
128 | 'typo3/class-alias-loader' => array(
129 | 'always-add-alias-loader' => true,
130 | 'autoload-case-sensitivity' => false,
131 | )
132 | )
133 | );
134 |
135 | $subject = new Config($this->packageMock, $this->ioMock);
136 |
137 | $this->assertTrue($subject->get('always-add-alias-loader'));
138 | $this->assertFalse($subject->get('autoload-case-sensitivity'));
139 | }
140 |
141 | /**
142 | * @test
143 | */
144 | public function otherConfigIsExtractedFromDeprecatedKey()
145 | {
146 | $this->packageMock->expects($this->any())->method('getExtra')->willReturn(
147 | array(
148 | 'helhum/class-alias-loader' => array(
149 | 'always-add-alias-loader' => true,
150 | 'autoload-case-sensitivity' => false,
151 | )
152 | )
153 | );
154 |
155 | $subject = new Config($this->packageMock, $this->ioMock);
156 |
157 | $this->assertTrue($subject->get('always-add-alias-loader'));
158 | $this->assertFalse($subject->get('autoload-case-sensitivity'));
159 | }
160 |
161 | /**
162 | * @test
163 | */
164 | public function caseSensitivityConfigIsExtractedFromVeryDeprecatedKey()
165 | {
166 | $this->packageMock->expects($this->any())->method('getExtra')->willReturn(
167 | array(
168 | 'autoload-case-sensitivity' => false,
169 | )
170 | );
171 |
172 | $subject = new Config($this->packageMock, $this->ioMock);
173 |
174 | $this->assertFalse($subject->get('autoload-case-sensitivity'));
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/tests/Unit/IncludeFileTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | use Composer\Composer;
14 | use Composer\IO\IOInterface;
15 | use Composer\Package\PackageInterface;
16 | use PHPUnit\Framework\MockObject\MockObject;
17 | use TYPO3\ClassAliasLoader\Config;
18 | use TYPO3\ClassAliasLoader\IncludeFile;
19 |
20 | /**
21 | * Test case for IncludeFile
22 | */
23 | final class IncludeFileTest extends BaseTestCase
24 | {
25 | /**
26 | * @var IncludeFile
27 | */
28 | private $subject;
29 |
30 | /**
31 | * @var IOInterface|MockObject
32 | */
33 | private $ioMock;
34 |
35 | /**
36 | * @var PackageInterface|MockObject
37 | */
38 | private $packageMock;
39 |
40 | /**
41 | * @var Composer|MockObject
42 | */
43 | private $composerMock;
44 |
45 | private $testDir = __DIR__;
46 |
47 | /**
48 | * @before
49 | */
50 | public function setMeUp()
51 | {
52 | $this->ioMock = $this->getMockBuilder('Composer\\IO\\IOInterface')->getMock();
53 | $this->packageMock = $this->getMockBuilder('Composer\\Package\\RootPackageInterface')->getMock();
54 | $this->composerMock = $this->getMockBuilder('Composer\\Composer')->getMock();
55 | $configMock = $this->getMockBuilder('Composer\\Config')->getMock();
56 | $testDir = $this->testDir;
57 | $configMock->expects(self::any())
58 | ->method('get')
59 | ->willReturnCallback(function ($key) use ($testDir) {
60 | switch ($key) {
61 | case 'prepend-autoloader':
62 | return true;
63 | case 'vendor-dir':
64 | return $testDir;
65 | default:
66 | throw new \RuntimeException('Not expected to be called with ' . $key);
67 | }
68 | });
69 | mkdir($testDir . '/typo3');
70 | $this->composerMock->expects(self::any())
71 | ->method('getPackage')
72 | ->willReturn($this->packageMock);
73 | $this->composerMock->expects(self::any())
74 | ->method('getConfig')
75 | ->willReturn($configMock);
76 |
77 | $this->subject = new IncludeFile(
78 | $this->ioMock,
79 | $this->composerMock,
80 | array(
81 | new IncludeFile\PrependToken($this->ioMock, $configMock),
82 | new IncludeFile\CaseSensitiveToken($this->ioMock, new Config($this->packageMock, $this->ioMock))
83 | )
84 | );
85 | }
86 |
87 | /**
88 | * @after
89 | */
90 | public function tearMeDown()
91 | {
92 | unlink($this->testDir . IncludeFile::INCLUDE_FILE);
93 | rmdir(dirname($this->testDir . IncludeFile::INCLUDE_FILE));
94 | }
95 |
96 |
97 | public function testIncludeFileCanPeWritten()
98 | {
99 | $this->subject->register();
100 | self::assertTrue(file_exists($this->testDir . IncludeFile::INCLUDE_FILE));
101 | }
102 | }
103 |
--------------------------------------------------------------------------------