[FEATURE] Allow .ts file extension for static typoscript templates
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_autoloader.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2011 Dmitry Dulepov <dmitry@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * This class contains TYPO3 autoloader for classes.
30 * It handles:
31 * - The core of TYPO3
32 * - All extensions with an ext_autoload.php file
33 * - All extensions that stick to the 'extbase' like naming convention
34 * - Resolves registered XCLASSes
35 *
36 * @author Dmitry Dulepov <dmitry@typo3.org>
37 * @author Martin Kutschker <masi@typo3.org>
38 * @author Oliver Hader <oliver@typo3.org>
39 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
40 * @author Christian Kuhn <lolli@schwarzbu.ch>
41 */
42 class t3lib_autoloader {
43
44 /**
45 * Class name to file mapping. Key: class name. Value: fully qualified file name.
46 *
47 * @var array
48 */
49 protected static $classNameToFileMapping = array();
50
51 /**
52 * Name of cache entry identifier in autoload cache
53 *
54 * @var string
55 */
56 protected static $autoloadCacheIdentifier = NULL;
57
58 /**
59 * Track if the cache file written to disk should be updated.
60 * This is set to TRUE if during script run new classes are
61 * found (for example due to new requested extbase classes)
62 * and is used in unregisterAutoloader() to decide whether or not
63 * the cache file should be re-written.
64 *
65 * @var bool True if mapping changed
66 */
67 protected static $cacheUpdateRequired = FALSE;
68
69 /**
70 * The autoloader is static, thus we do not allow instances of this class.
71 */
72 private function __construct() {
73 }
74
75 /**
76 * Installs TYPO3 autoloader, and loads the autoload registry for the core.
77 *
78 * @return boolean TRUE in case of success
79 */
80 public static function registerAutoloader() {
81 self::loadCoreAndExtensionRegistry();
82 return spl_autoload_register('t3lib_autoloader::autoload', TRUE, TRUE);
83 }
84
85 /**
86 * Unload TYPO3 autoloader and write any additional classes
87 * found during the script run to the cache file.
88 *
89 * This method is called during shutdown of the framework.
90 *
91 * @return boolean TRUE in case of success
92 */
93 public static function unregisterAutoloader() {
94 if (self::$cacheUpdateRequired) {
95 self::updateRegistryCacheEntry(self::$classNameToFileMapping);
96 self::$cacheUpdateRequired = FALSE;
97 }
98 self::$classNameToFileMapping = array();
99
100 return spl_autoload_unregister('t3lib_autoloader::autoload');
101 }
102
103 /**
104 * Autoload function for TYPO3.
105 *
106 * This method looks up class names in the registry
107 * (which contains extensions and core files)
108 *
109 * @param string $className Class name
110 * @return void
111 */
112 public static function autoload($className) {
113 // Use core and extension registry
114 $classPath = self::getClassPathByRegistryLookup($className);
115
116 if ($classPath) {
117 // Include the required file that holds the class
118 t3lib_div::requireFile($classPath);
119 } else {
120 try {
121 // Regular expression for a valid classname taken from
122 // http://www.php.net/manual/en/language.oop5.basic.php
123 if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $className)) {
124 spl_autoload($className);
125 }
126 } catch (LogicException $exception) {
127 }
128 }
129 }
130
131 /**
132 * Load registry from cache file if available or search
133 * for all loaded extensions and create a cache file
134 *
135 * @return void
136 */
137 protected static function loadCoreAndExtensionRegistry() {
138 /** @var $phpCodeCache t3lib_cache_frontend_PhpFrontend */
139 $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_core');
140
141 // Create autoload cache file if it does not exist yet
142 if ($phpCodeCache->has(self::getAutoloadCacheIdentifier())) {
143 $classRegistry = $phpCodeCache->requireOnce(self::getAutoloadCacheIdentifier());
144 } else {
145 self::$cacheUpdateRequired = TRUE;
146 $classRegistry = self::lowerCaseClassRegistry(self::createCoreAndExtensionRegistry());
147 }
148
149 // This can only happen if the autoloader was already registered
150 // in the same call once, the requireOnce of the cache file then
151 // does not give the cached array back. In this case we just read
152 // all cache entries manually again.
153 // This can happen in unit tests and if the cache backend was
154 // switched to NullBackend for example to simplify development
155 if (!is_array($classRegistry)) {
156 self::$cacheUpdateRequired = TRUE;
157 $classRegistry = self::lowerCaseClassRegistry(self::createCoreAndExtensionRegistry());
158 }
159
160 self::$classNameToFileMapping = $classRegistry;
161 }
162
163 /**
164 * Get the full path to a class by looking it up in the registry.
165 * If not found, returns NULL.
166 *
167 * Warning: This method is public as it is needed by t3lib_div::makeInstance(),
168 * but it is _not_ part of the public API and should not be used in own extensions!
169 *
170 * @param string $className Class name to find source file of
171 * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
172 * @internal
173 */
174 public static function getClassPathByRegistryLookup($className) {
175 $classPath = NULL;
176
177 $classNameLower = t3lib_div::strtolower($className);
178
179 // Try to resolve extbase naming scheme if class is not already in cache file
180 if (!array_key_exists($classNameLower, self::$classNameToFileMapping)) {
181 self::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
182 }
183
184 // Look up class name in cache file
185 if (array_key_exists($classNameLower, self::$classNameToFileMapping)) {
186 $classPath = self::$classNameToFileMapping[$classNameLower];
187 }
188
189 if (
190 $classPath === NULL
191 && substr($classNameLower, 0, 3) === 'ux_'
192 && !array_key_exists($classNameLower, self::$classNameToFileMapping)
193 ) {
194 self::$cacheUpdateRequired = TRUE;
195 self::$classNameToFileMapping[$classNameLower] = NULL;
196 }
197
198 return $classPath;
199 }
200
201 /**
202 * Find all ext_autoload files and merge with core_autoload.
203 *
204 * @return array
205 */
206 protected static function createCoreAndExtensionRegistry() {
207 $classRegistry = require(PATH_t3lib . 'core_autoload.php');
208 // At this point during bootstrap the local configuration is initialized,
209 // extMgm is ready to get the list of enabled extensions
210 foreach (t3lib_extMgm::getLoadedExtensionListArray() as $extensionKey) {
211 try {
212 $extensionAutoloadFile = t3lib_extMgm::extPath($extensionKey, 'ext_autoload.php');
213 if (@file_exists($extensionAutoloadFile)) {
214 $classRegistry = array_merge($classRegistry, require($extensionAutoloadFile));
215 }
216 } catch (BadFunctionCallException $e) {
217 // The extension is not available, therefore ignore it
218 }
219 }
220 return $classRegistry;
221 }
222
223 /**
224 * Try to load a given class name based on 'extbase' naming convention into the registry.
225 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
226 * array to the file system to save this lookup for next call.
227 *
228 * @param string $className Class name to find source file of
229 * @return void
230 */
231 protected static function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
232
233 $delimiter = '_';
234 $tempClassName = $className;
235
236 // To handle namespaced class names, get rid of the first backslash
237 // and replace the remaining ones with underscore. This will simulate
238 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
239 if(strpos($className, '\\') !== FALSE) {
240 $tempClassName = ltrim($className, '\\');
241 $delimiter = '\\';
242 }
243 $classNameParts = explode($delimiter, $tempClassName, 3);
244 $extensionKey = t3lib_div::camelCaseToLowerCaseUnderscored($classNameParts[1]);
245 if ($extensionKey) {
246 try {
247 // This will throw a BadFunctionCallException if the extension is not loaded
248 $extensionPath = t3lib_extMgm::extPath($extensionKey);
249 $classFilePathAndName = $extensionPath . 'Classes/' . strtr($classNameParts[2], $delimiter, '/') . '.php';
250 self::addClassToCache($classFilePathAndName, $className);
251 } catch (BadFunctionCallException $exception) {
252 // Catch the exception and do nothing to give
253 // other registered autoloaders a chance to find the file
254 }
255 }
256 }
257
258 /**
259 * Adds a single class to autoloader cache.
260 *
261 * @static
262 * @param string $classFilePathAndName Physical path of file containing $className
263 * @param string $className Class name
264 * @return void
265 */
266 protected static function addClassToCache($classFilePathAndName, $className) {
267 if (file_exists($classFilePathAndName)) {
268 self::$cacheUpdateRequired = TRUE;
269 self::$classNameToFileMapping[t3lib_div::strtolower($className)] = $classFilePathAndName;
270 }
271 }
272
273 /**
274 * Set or update autoloader cache entry.
275 * It is expected that all class names (keys) are already lowercased!
276 *
277 * @param array $registry Current registry entries
278 * @return void
279 */
280 protected static function updateRegistryCacheEntry(array $registry) {
281 $cachedFileContent = 'return array(';
282 foreach ($registry as $className => $classLocation) {
283 $nullOrLocation = is_string($classLocation) ? '\'' . $classLocation . '\',' : 'NULL,';
284 $cachedFileContent .= LF . '\'' . $className . '\' => ' . $nullOrLocation;
285 }
286 $cachedFileContent .= LF . ');';
287 $GLOBALS['typo3CacheManager']->getCache('cache_core')->set(
288 self::getAutoloadCacheIdentifier(),
289 $cachedFileContent
290 );
291 }
292
293 /**
294 * Gets the identifier used for caching the registry files.
295 * The identifier depends on the current TYPO3 version and the
296 * installation path of the TYPO3 site (PATH_site).
297 *
298 * In effect, a new registry cache file will be created
299 * when moving to a newer version with possible new core classes
300 * or moving the webroot to another absolute path.
301 *
302 * @return string identifier
303 */
304 protected static function getAutoloadCacheIdentifier() {
305 if (is_null(self::$autoloadCacheIdentifier)) {
306 self::$autoloadCacheIdentifier = 'autoload_' . sha1(TYPO3_version . PATH_site . 'autoload');
307 }
308 return self::$autoloadCacheIdentifier;
309 }
310
311 /**
312 * Lowercase all keys of the class registry.
313 *
314 * Use the multi byte safe version of strtolower from t3lib_div,
315 * so array_change_key_case() can not be used
316 *
317 * @param array $registry Given registry entries
318 * @return array with lower cased keys
319 */
320 protected static function lowerCaseClassRegistry($registry) {
321 $lowerCasedClassRegistry = array();
322 foreach ($registry as $className => $classFile) {
323 $lowerCasedClassRegistry[t3lib_div::strtolower($className)] = $classFile;
324 }
325 return $lowerCasedClassRegistry;
326 }
327 }
328 ?>