[TASK] Introduce object implementation registry
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Core / ClassLoaderTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Core;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 /**
28 * Testcase for TYPO3\CMS\Core\Core\ClassLoader
29 *
30 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
31 */
32 class ClassLoaderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
33
34 /**
35 * @var boolean Enable backup of global and system variables
36 */
37 protected $backupGlobals = TRUE;
38
39 /**
40 * @var array Backup of typo3CacheManager
41 */
42 protected $typo3CacheManager = NULL;
43
44 /**
45 * @var array Register of temporary extensions in typo3temp
46 */
47 protected $fakedExtensions = array();
48
49 /**
50 * Fix a race condition that t3lib_div is not available
51 * during tearDown if fiddling with the autoloader where
52 * backupGlobals is not set up again yet
53 */
54 public function setUp() {
55 $this->typo3CacheManager = $GLOBALS['typo3CacheManager'];
56 }
57
58 /**
59 * Clean up
60 * Warning: Since phpunit itself is php and we are fiddling with php
61 * autoloader code here, the tests are a bit fragile. This tearDown
62 * method ensures that all main classes are available again during
63 * tear down of a testcase.
64 * This construct will fail if the class under test is changed and
65 * not compatible anymore. Make sure to always run the whole test
66 * suite if fiddling with the autoloader unit tests to ensure that
67 * there is no fatal error thrown in other unit test classes triggered
68 * by errors in this one.
69 */
70 public function tearDown() {
71 $GLOBALS['typo3CacheManager'] = $this->typo3CacheManager;
72 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
73 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
74 foreach ($this->fakedExtensions as $extension) {
75 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir(PATH_site . 'typo3temp/' . $extension, TRUE);
76 }
77 }
78
79 /**
80 * Creates a fake extension inside typo3temp/. No configuration is created,
81 * just the folder, plus the extension is registered in $TYPO3_LOADED_EXT
82 *
83 * @return string The extension key
84 */
85 protected function createFakeExtension() {
86 $extKey = strtolower(uniqid('testing'));
87 $absExtPath = PATH_site . 'typo3temp/' . $extKey . '/';
88 $relPath = 'typo3temp/' . $extKey . '/';
89 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($absExtPath);
90 $GLOBALS['TYPO3_LOADED_EXT'][$extKey] = array(
91 'siteRelPath' => $relPath
92 );
93 $GLOBALS['TYPO3_CONF_VARS']['EXT']['extListArray'][] = $extKey;
94 $this->fakedExtensions[] = $extKey;
95 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::clearExtensionKeyMap();
96 return $extKey;
97 }
98
99 /**
100 * @test
101 */
102 public function unregisterAndRegisterAgainDoesNotFatal() {
103 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
104 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
105 // If this fatals the autoload re registering went wrong
106 \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\TimeTracker\\NullTimeTracker');
107 }
108
109 /**
110 * @test
111 */
112 public function unregisterAutoloaderSetsCacheEntryWithT3libNoTags() {
113 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
114 // Expect the mock cache set method to be called
115 // once with t3lib_autoloader as third parameter
116 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->anything(), array());
117 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
118 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
119 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
120 }
121
122 /**
123 * @test
124 * @expectedException \RuntimeException
125 */
126 public function autoloadFindsClassFileDefinedInExtAutoloadFile() {
127 $extKey = $this->createFakeExtension();
128 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
129 $autoloaderFile = $extPath . 'ext_autoload.php';
130 $class = strtolower('tx_{' . $extKey . '}_' . uniqid(''));
131 $file = $extPath . uniqid('') . '.php';
132 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1310203812);' . LF . '?>');
133 file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
134 // Inject a dummy for the core_phpcode cache to force the autoloader
135 // to re calculate the registry
136 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
137 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
138 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
139 // Re-initialize autoloader registry to force it to recognize the new extension
140 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
141 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
142 // Expect the exception of the file to be thrown
143 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
144 }
145
146 /**
147 * @test
148 */
149 public function unregisterAutoloaderWritesLowerCasedClassFileToCache() {
150 $extKey = $this->createFakeExtension();
151 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
152 $autoloaderFile = $extPath . 'ext_autoload.php';
153 // A case sensitive key (FooBar) in ext_autoload file
154 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
155 $file = $extPath . uniqid('') . '.php';
156 file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
157 // Inject a dummy for the core_phpcode cache to force the autoloader
158 // to re calculate the registry
159 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
160 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
161 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
162 // Expect that the lower case version of the class name is written to cache
163 $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains(strtolower($class), FALSE));
164 // Re-initialize autoloader registry to force it to recognize the new extension
165 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
166 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
167 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
168 }
169
170 /**
171 * @test
172 * @expectedException \RuntimeException
173 */
174 public function autoloadFindsClassFileIfExtAutoloadEntryIsCamelCased() {
175 $extKey = $this->createFakeExtension();
176 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
177 // A case sensitive key (FooBar) in ext_autoload file
178 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
179 $file = $extPath . uniqid('') . '.php';
180 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1336756850);' . LF . '?>');
181 $extAutoloadFile = $extPath . 'ext_autoload.php';
182 file_put_contents($extAutoloadFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
183 // Inject cache and return false, so autoloader is forced to read ext_autoloads from extensions
184 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
185 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
186 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
187 $mockCache->expects($this->any())->method('has')->will($this->returnValue(FALSE));
188 // Re-initialize autoloader registry to force it to recognize the new extension
189 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
190 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
191 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
192 }
193
194 /**
195 * @test
196 * @expectedException \RuntimeException
197 */
198 public function autoloadFindsCamelCasedClassFileIfExtAutoloadEntryIsReadLowerCasedFromCache() {
199 $extKey = $this->createFakeExtension();
200 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
201 // A case sensitive key (FooBar) in ext_autoload file
202 $class = 'tx_{' . $extKey . '}_' . uniqid('FooBar');
203 $file = $extPath . uniqid('') . '.php';
204 file_put_contents($file, '<?php' . LF . 'throw new \RuntimeException(\'\', 1336756850);' . LF . '?>');
205 // Inject cache mock and let the cache entry return the lowercased class name as key
206 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
207 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
208 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
209 $mockCache->expects($this->any())->method('has')->will($this->returnValue(TRUE));
210 $mockCache->expects($this->once())->method('requireOnce')->will($this->returnValue(array(array(strtolower($class) => $file))));
211 // Re-initialize autoloader registry to force it to recognize the new extension
212 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
213 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
214 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
215 }
216
217 /**
218 * @test
219 * @expectedException \RuntimeException
220 */
221 public function autoloadFindsClassFileThatRespectsExtbaseNamingSchemeWithoutExtAutoloadFile() {
222 $extKey = $this->createFakeExtension();
223 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
224 // Create a class named Tx_Extension_Foo123_Bar456
225 // to find file extension/Classes/Foo123/Bar456.php
226 $pathSegment = 'Foo' . uniqid();
227 $fileName = 'Bar' . uniqid();
228 $class = 'Tx_' . ucfirst($extKey) . '_' . $pathSegment . '_' . $fileName;
229
230 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
231 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
232 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1310203813);' . LF . '?>');
233 // Inject a dummy for the core_phpcode cache to cache
234 // the calculated cache entry to a dummy cache
235 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
236 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
237 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
238 // Expect the exception of the file to be thrown
239 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
240 }
241
242 /**
243 * @test
244 */
245 public function unregisterAutoloaderWritesClassFileThatRespectsExtbaseNamingSchemeToCacheFile() {
246 $extKey = $this->createFakeExtension();
247 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
248 $pathSegment = 'Foo' . uniqid();
249 $fileName = 'Bar' . uniqid();
250 $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
251 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
252 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
253 file_put_contents($file, '<?php' . LF . '$foo = \'bar\';' . LF . '?>');
254 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
255 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
256 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
257 // Expect that an entry to the cache is written containing the newly found class
258 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($class), $this->anything()));
259 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
260 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
261 }
262
263 /**
264 * @test
265 */
266 public function unregisterAutoloaderWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeToCacheFile() {
267 $extKey = $this->createFakeExtension();
268 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
269 $pathSegment = 'Foo' . uniqid();
270 $fileName = 'Bar' . uniqid();
271 $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
272 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
273 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
274 file_put_contents($file, '<?php' . LF . '$foo = \'bar\';' . LF . '?>');
275 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
276 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
277 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
278 // Expect that an entry to the cache is written containing the newly found class
279 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($file), $this->anything()));
280 \TYPO3\CMS\Core\Core\ClassLoader::autoload($class);
281 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
282 }
283
284 /**
285 * @test
286 * @expectedException \RuntimeException
287 */
288 public function autoloadFindsClassFileThatRespectsExtbaseNamingSchemeWithNamespace() {
289 $extKey = $this->createFakeExtension();
290 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
291 // Create a class named \Tx\Extension\Foo123\Bar456
292 // to find file extension/Classes/Foo123/Bar456.php
293 $pathSegment = 'Foo' . uniqid();
294 $fileName = 'Bar' . uniqid();
295 $namespacedClass = '\\Vendor\\' . ucfirst($extKey) . '\\' . $pathSegment . '\\' . $fileName;
296 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
297 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
298 file_put_contents($file, '<?php' . LF . 'throw new \\RuntimeException(\'\', 1342800577);' . LF . '?>');
299 // Re-initialize autoloader registry to force it to recognize the new extension
300 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
301 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
302 // Expect the exception of the file to be thrown
303 \TYPO3\CMS\Core\Core\ClassLoader::autoload($namespacedClass);
304 }
305
306 /**
307 * @test
308 */
309 public function unregisterAutoloaderWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeWithNamespaceToCacheFile() {
310 $extKey = $this->createFakeExtension();
311 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
312 $pathSegment = 'Foo' . uniqid();
313 $fileName = 'Bar' . uniqid();
314 $namespacedClass = '\\Tx\\' . $extKey . '\\' . $pathSegment . '\\' . $fileName;
315 $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
316 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($extPath . 'Classes/' . $pathSegment);
317 file_put_contents($file, "<?php\n\n\$foo = 'bar';\n\n?>");
318 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
319 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
320 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
321 // Expect that an entry to the cache is written containing the newly found class
322 $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($file), $this->anything()));
323 \TYPO3\CMS\Core\Core\ClassLoader::autoload($namespacedClass);
324 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
325 }
326
327 /**
328 * @test
329 */
330 public function checkClassNamesNotExtbaseSchemePassAutoloaderUntouched() {
331 $class = '\\Symfony\\Foo\\Bar';
332 $this->assertNull(\TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup($class));
333 }
334
335 /**
336 * @test
337 */
338 public function checkAutoloaderSetsNamespacedClassnamesInExtAutoloadAreWrittenToCache() {
339 $extKey = $this->createFakeExtension();
340 $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
341 $pathSegment = 'Foo' . uniqid();
342 $fileName = 'Bar' . uniqid();
343 $autoloaderFile = $extPath . 'ext_autoload.php';
344 // A case sensitive key (FooBar) in ext_autoload file
345 $namespacedClass = '\\Tx\\' . $extKey . '\\' . $pathSegment . '\\' . $fileName;
346 $classFile = 'EXT:someExt/Classes/Foo/bar.php';
347 file_put_contents($autoloaderFile, '<?php' . LF . 'return ' . var_export(array($namespacedClass => $classFile), TRUE) . ';' . LF . '?>');
348 // Inject a dummy for the core_phpcode cache to force the autoloader
349 // to re calculate the registry
350 $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
351 $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
352 $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
353 // Expect that the lower case version of the class name is written to cache
354 $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains(strtolower(addslashes($namespacedClass)), FALSE));
355 // Re-initialize autoloader registry to force it to recognize the new extension
356 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
357 \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
358 \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
359 }
360 }
361
362 ?>