Commit ec2b4f42 authored by Helmut Hummel's avatar Helmut Hummel Committed by Benni Mack
Browse files

[!!!][FEATURE] Discard TYPO3 class loader

Use composer class loader instead of the TYPO3 class loader
when TYPO3 is not installed via composer, thus building the same
class information logic during runtime (and caching it away) as
composer does with its "dump-autoload" command.

Releases: master
Resolves: #67212
Change-Id: Id032411a690d67ca690a724748c3c796d2bb5f6e
Reviewed-on: http://review.typo3.org/39827


Reviewed-by: default avatarThomas Maroschik <tmaroschik@dfau.de>
Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Benni Mack's avatarBenjamin Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenjamin Mack <benni@typo3.org>
parent 318f49fa
......@@ -38,9 +38,10 @@
"phpwhois/idna-convert": "0.8.2",
"swiftmailer/swiftmailer": "5.4.1",
"symfony/console": "2.5.11",
"symfony/finder": "2.6.9",
"doctrine/instantiator": "1.0.4",
"helhum/class-alias-loader": "1.1.4",
"typo3/cms-composer-installers": "1.1.2",
"doctrine/instantiator": "1.0.4"
"typo3/cms-composer-installers": "1.1.2"
},
"require-dev": {
"mikey179/vfsStream": "1.4.*@dev",
......@@ -134,7 +135,9 @@
"TYPO3\\CMS\\Sv\\": "typo3/sysext/sv/Classes/",
"TYPO3\\CMS\\T3skin\\": "typo3/sysext/t3skin/Classes/"
},
"classmap": ["typo3/sysext/core/Resources/PHP/RemoveXSS.php"]
"classmap": [
"typo3/sysext/core/Resources/PHP/"
]
},
"autoload-dev": {
"psr-4": {
......
......@@ -18,6 +18,7 @@ use PHPUnit_Framework_MockObject_MockObject;
use TYPO3\CMS\Backend\BackendModuleRequestHandler;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\FormProtection\AbstractFormProtection;
use TYPO3\CMS\Core\Tests\AccessibleObjectInterface;
use TYPO3\CMS\Core\Tests\UnitTestCase;
......@@ -37,7 +38,7 @@ class BackendModuleRequestHandlerTest extends UnitTestCase {
protected $formProtectionMock;
public function setUp() {
$this->formProtectionMock = $this->getMock('AbstractFormProtection', array('validateToken'));
$this->formProtectionMock = $this->getMockForAbstractClass(AbstractFormProtection::class, array(), '', TRUE, TRUE, TRUE, array('validateToken'));
$this->subject = $this->getAccessibleMock(BackendModuleRequestHandler::class, array('boot', 'getFormProtection'), array(), '', FALSE);
}
......
......@@ -128,7 +128,6 @@ return array(
't3lib_extjs_ExtDirectDebug' => \TYPO3\CMS\Core\ExtDirect\ExtDirectDebug::class,
't3lib_userAuth' => \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class,
't3lib_beUserAuth' => \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::class,
't3lib_autoloader' => \TYPO3\CMS\Core\Core\ClassLoader::class,
't3lib_cache_backend_AbstractBackend' => \TYPO3\CMS\Core\Cache\Backend\AbstractBackend::class,
't3lib_cache_backend_ApcBackend' => \TYPO3\CMS\Core\Cache\Backend\ApcBackend::class,
't3lib_cache_backend_Backend' => \TYPO3\CMS\Core\Cache\Backend\BackendInterface::class,
......
......@@ -651,11 +651,6 @@ abstract class t3lib_userAuth extends \TYPO3\CMS\Core\Authentication\AbstractUse
*/
class t3lib_beUserAuth extends \TYPO3\CMS\Core\Authentication\BackendUserAuthentication {}
/**
* @deprecated since 6.0, removed since 7.0
*/
class t3lib_autoloader extends \TYPO3\CMS\Core\Core\ClassLoader {}
/**
* @deprecated since 6.0, removed since 7.0
*/
......
......@@ -173,9 +173,7 @@ class UnitTestsBootstrap {
protected function includeAndStartCoreBootstrap() {
require_once PATH_site . '/typo3/sysext/core/Classes/Core/Bootstrap.php';
Bootstrap::getInstance()
->baseSetup()
->initializeClassLoader();
Bootstrap::getInstance()->baseSetup();
return $this;
}
......@@ -202,10 +200,10 @@ class UnitTestsBootstrap {
*/
protected function finishCoreBootstrap() {
Bootstrap::getInstance()
->disableCoreAndClassesCache()
->disableCoreCache()
->initializeCachingFramework()
->initializeClassLoaderCaches()
->initializePackageManagement(\TYPO3\CMS\Core\Package\UnitTestPackageManager::class);
->initializePackageManagement(\TYPO3\CMS\Core\Package\UnitTestPackageManager::class)
->ensureClassLoadingInformationExists();
return $this;
}
......
<?php
namespace TYPO3\CMS\Core\Cache\Backend;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* A caching backend customized explicitly for the class loader.
* This backend is NOT public API!
*
* @internal
*/
class ClassLoaderBackend extends SimpleFileBackend {
/**
* This string will be used for writing the require statement in the
* cache file and for getting the required path via regex.
*
* @var string
*/
protected $requireFileTemplate = '<?php require \'%s\';';
/**
* Set a class loader cache content
*
* @TODO: Rename method
* @param string $entryIdentifier
* @param string $filePath
* @throws \InvalidArgumentException
* @internal This is not an API method
*/
public function setLinkToPhpFile($entryIdentifier, $filePath) {
if ($entryIdentifier === '') {
throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1364205170);
}
if (!PathUtility::isAbsolutePath($filePath)) {
throw new \InvalidArgumentException('Only absolute paths are allowed for the class loader, given path was: ' . $filePath, 1381923089);
}
if (!@file_exists($filePath)) {
throw new \InvalidArgumentException('The specified file path (' . $filePath . ') must exist.', 1364205235);
}
if (strtolower(substr($filePath, -4)) !== '.php') {
throw new \InvalidArgumentException('The specified file (' . $filePath . ') must be a php file.', 1364205377);
}
if ($entryIdentifier !== basename($entryIdentifier)) {
throw new \InvalidArgumentException('The specified entry identifier (' . $entryIdentifier . ') must not contain a path segment.', 1364205166);
}
$this->set($entryIdentifier, sprintf($this->requireFileTemplate, $filePath));
}
/**
* Used to set alias for class
*
* @TODO: Rename method
* @param string $entryIdentifier
* @param string $otherEntryIdentifier
* @internal
*/
public function setLinkToOtherCacheEntry($entryIdentifier, $otherEntryIdentifier) {
$otherCacheEntryPathAndFilename = $this->cacheDirectory . $otherEntryIdentifier . $this->cacheEntryFileExtension;
$this->setLinkToPhpFile($entryIdentifier, $otherCacheEntryPathAndFilename);
}
/**
* Loads data from a cache file.
*
* @param string $entryIdentifier An identifier which describes the cache entry to load
* @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
* @throws \InvalidArgumentException If identifier is invalid
* @internal
*/
public function get($entryIdentifier) {
if ($entryIdentifier !== basename($entryIdentifier)) {
throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756880);
}
$pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
if (!@file_exists($pathAndFilename)) {
return FALSE;
}
return file_get_contents($pathAndFilename);
}
/**
* Retrieves the path and filename that is passed to the require
* command in the cache entry with the given identifier.
*
* @param string $entryIdentifier
* @return bool|string FALSE if required path can not be retrieved or the required file path on success
* @internal
*/
public function getPathOfRequiredFileInCacheEntry($entryIdentifier) {
$result = FALSE;
$fileContent = $this->get($entryIdentifier);
if ($fileContent !== FALSE) {
$pattern = '!^' . sprintf(preg_quote($this->requireFileTemplate), '(.+)') . '!i';
$matches = array();
if (preg_match($pattern, $fileContent, $matches) === 1) {
$requireString = $matches[1];
$result = $requireString;
}
}
return $result;
}
/**
* Checks if a cache entry with the specified identifier exists.
*
* @param string $entryIdentifier
* @return bool TRUE if such an entry exists, FALSE if not
* @throws \InvalidArgumentException
* @internal
*/
public function has($entryIdentifier) {
if ($entryIdentifier !== basename($entryIdentifier)) {
throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756879);
}
$pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
return @file_exists($pathAndFilename);
}
/**
* Checks if the given cache entry files are still valid or if their
* lifetime has exceeded.
*
* @param string $cacheEntryPathAndFilename
* @return bool
* @internal
*/
protected function isCacheFileExpired($cacheEntryPathAndFilename) {
return @file_exists($cacheEntryPathAndFilename) === FALSE;
}
/**
* Tries to find the cache entry for the specified identifier.
*
* @TODO: This methods is implemented in simple, file and this backend, but never called?
* @param string $entryIdentifier The cache entry identifier
* @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
*/
protected function findCacheFilesByIdentifier($entryIdentifier) {
$pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
return @file_exists($pathAndFilename) ? array($pathAndFilename) : FALSE;
}
/**
* Loads PHP code from the cache and require_onces it right away.
*
* @param string $entryIdentifier An identifier which describes the cache entry to load
* @return mixed Potential return value from the include operation
* @throws \InvalidArgumentException
* @internal
*/
public function requireOnce($entryIdentifier) {
$pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
return @file_exists($pathAndFilename) ? require_once $pathAndFilename : FALSE;
}
}
<?php
namespace TYPO3\CMS\Core\Cache\Backend;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* A caching backend customized explicitly for the class loader.
* This backend is NOT public API
*
* @internal
*/
class EarlyClassLoaderBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend implements \TYPO3\CMS\Core\Cache\Backend\PhpCapableBackendInterface {
/**
* @var array Holds cache entries
*/
protected $memoryBackend = array();
/**
* Construct this backend
*/
public function __construct() {
parent::__construct('production', array());
}
/**
* Saves data in the cache.
*
* @param string $entryIdentifier An identifier for this specific cache entry
* @param string $data The data to be stored
* @param array $tags Tags to associate with this cache entry. If the backend does not support tags, this option can be ignored.
* @param int $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
* @return void
* @throws \TYPO3\CMS\Core\Cache\Exception if no cache frontend has been set.
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if the data is not a string
* @api
*/
public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
$this->memoryBackend[$entryIdentifier] = $data;
}
/**
* Loads data from the cache.
*
* @param string $entryIdentifier An identifier which describes the cache entry to load
* @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
* @api
*/
public function get($entryIdentifier) {
return isset($this->memoryBackend[$entryIdentifier]) ? $this->memoryBackend[$entryIdentifier] : FALSE;
}
/**
* A method that returns all records
*
* @return array
*/
public function getAll() {
return $this->memoryBackend;
}
/**
* Checks if a cache entry with the specified identifier exists.
*
* @param string $entryIdentifier An identifier specifying the cache entry
* @return bool TRUE if such an entry exists, FALSE if not
* @api
*/
public function has($entryIdentifier) {
return isset($this->memoryBackend[$entryIdentifier]);
}
/**
* Removes all cache entries matching the specified identifier.
* Usually this only affects one entry but if - for what reason ever -
* old entries for the identifier still exist, they are removed as well.
*
* @param string $entryIdentifier Specifies the cache entry to remove
* @return bool TRUE if (at least) an entry could be removed or FALSE if no entry was found
* @api
*/
public function remove($entryIdentifier) {
if (isset($this->memoryBackend[$entryIdentifier])) {
unset($this->memoryBackend[$entryIdentifier]);
return TRUE;
}
return FALSE;
}
/**
* Removes all cache entries of this cache.
*
* @return void
* @api
*/
public function flush() {
$this->memoryBackend = array();
}
/**
* Does garbage collection
*
* @return void
* @api
*/
public function collectGarbage() {
}
/**
* Loads PHP code from the cache and require_onces it right away.
*
* @param string $entryIdentifier An identifier which describes the cache entry to load
* @return mixed Potential return value from the include operation
* @api
*/
public function requireOnce($entryIdentifier) {
return isset($this->memoryBackend[$entryIdentifier]) ? require_once ($this->memoryBackend[$entryIdentifier]) : FALSE;
}
/**
* Used to set alias for class
*
* @TODO: Rename method
* @param string $entryIdentifier
* @param string $filePath
* @internal This is not an API method
*/
public function setLinkToPhpFile($entryIdentifier, $filePath) {
$this->memoryBackend[$entryIdentifier] = $filePath;
}
}
......@@ -205,7 +205,9 @@ class Bootstrap {
*/
public function baseSetup($relativePathPart = '') {
SystemEnvironmentBuilder::run($relativePathPart);
$this->addDynamicClassAliasMapsToComposerClassLoader();
if (!self::$usesComposerClassLoading) {
ClassLoadingInformation::registerClassLoadingInformation();
}
Utility\GeneralUtility::presetApplicationContext($this->applicationContext);
return $this;
}
......@@ -230,26 +232,6 @@ class Bootstrap {
throw new \LogicException('No class loading information found for TYPO3 CMS. Please make sure you installed TYPO3 with composer or the typo3/contrib/vendor folder is present.', 1425153762);
}
/**
* Includes an alias mapping file if present.
* The file is generated during extension install.
*
* @throws \TYPO3\CMS\Core\Exception
*/
protected function addDynamicClassAliasMapsToComposerClassLoader() {
if (self::$usesComposerClassLoading) {
return;
}
$dynamicClassAliasMapFile = PATH_site . 'typo3conf/autoload_classaliasmap.php';
if (file_exists($dynamicClassAliasMapFile)) {
$composerClassLoader = $this->getEarlyInstance(\Composer\Autoload\ClassLoader::class);
$classAliasMap = require $dynamicClassAliasMapFile;
if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) {
$composerClassLoader->addAliasMap($classAliasMap);
}
}
}
/**
* checks if LocalConfiguration.php or PackageStates.php is missing,
* used to see if a redirect to the install tool is needed
......@@ -377,13 +359,11 @@ class Bootstrap {
* @internal This is not a public API method, do not use in own extensions
*/
public function loadConfigurationAndInitialize($allowCaching = TRUE, $packageManagerClassName = \TYPO3\CMS\Core\Package\PackageManager::class) {
$this->initializeClassLoader()
->populateLocalConfiguration();
$this->populateLocalConfiguration();
if (!$allowCaching) {
$this->disableCoreAndClassesCache();
$this->disableCoreCache();
}
$this->initializeCachingFramework()
->initializeClassLoaderCaches()
->initializePackageManagement($packageManagerClassName)
->initializeRuntimeActivatedPackagesFromConfiguration();
......@@ -400,50 +380,9 @@ class Bootstrap {
->initializeExceptionHandling()
->setMemoryLimit()
->defineTypo3RequestTypes();
return $this;
}
/**
* Initializes the Class Loader
*
* @return Bootstrap
* @internal This is not a public API method, do not use in own extensions
*/
public function initializeClassLoader() {
$classLoader = new ClassLoader($this->applicationContext);
$this->setEarlyInstance(\TYPO3\CMS\Core\Core\ClassLoader::class, $classLoader);
$classAliasMap = new ClassAliasMap();
$classAliasMap->injectClassLoader($classLoader);
$classAliasMap->injectComposerClassLoader($this->getEarlyInstance(\Composer\Autoload\ClassLoader::class));
$this->setEarlyInstance(\TYPO3\CMS\Core\Core\ClassAliasMap::class, $classAliasMap);
$classLoader->injectClassAliasMap($classAliasMap);
spl_autoload_register(array($classLoader, 'loadClass'), TRUE, FALSE);
return $this;
}
/**
* Unregister class loader
*
* @return Bootstrap
* @internal This is not a public API method, do not use in own extensions
*/
public function unregisterClassLoader() {
$currentClassLoader = $this->getEarlyInstance(\TYPO3\CMS\Core\Core\ClassLoader::class);
spl_autoload_unregister(array($currentClassLoader, 'loadClass'));
return $this;
}
/**
* Initialize class loader cache.
*
* @return Bootstrap
* @internal This is not a public API method, do not use in own extensions
*/
public function initializeClassLoaderCaches() {
/** @var $classLoader ClassLoader */
$classLoader = $this->getEarlyInstance(\TYPO3\CMS\Core\Core\ClassLoader::class);
$classLoader->injectCoreCache($this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core'));
$classLoader->injectClassesCache($this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_classes'));
if ($allowCaching) {
$this->ensureClassLoadingInformationExists();
}
return $this;
}
......@@ -460,7 +399,6 @@ class Bootstrap {
$packageManager = new $packageManagerClassName();
$this->setEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager);
Utility\ExtensionManagementUtility::setPackageManager($packageManager);
$packageManager->injectClassLoader($this->getEarlyInstance(\TYPO3\CMS\Core\Core\ClassLoader::class));
$packageManager->injectCoreCache($this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core'));
$packageManager->injectDependencyResolver(Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Package\DependencyResolver::class));
$packageManager->initialize($this);
......@@ -468,6 +406,20 @@ class Bootstrap {
return $this;
}
/**
* Writes class loading information if not yet present
*
* @return Bootstrap
* @internal This is not a public API method, do not use in own extensions
*/
public function ensureClassLoadingInformationExists() {
if (!self::$usesComposerClassLoading && !ClassLoadingInformation::classLoadingInformationExists()) {
ClassLoadingInformation::writeClassLoadingInformation();
ClassLoadingInformation::registerClassLoadingInformation();
}
return $this;
}
/**
* Activates a package during runtime. This is used in AdditionalConfiguration.php
* to enable extensions under conditions.
......@@ -530,18 +482,15 @@ class Bootstrap {
}
/**
* Set cache_core to null backend, effectively disabling eg. the autoloader cache
* Set cache_core to null backend, effectively disabling eg. the cache for ext_localconf and PackageManager etc.
*
* @return \TYPO3\CMS\Core\Core\Bootstrap
* @internal This is not a public API method, do not use in own extensions
*/
public function disableCoreAndClassesCache() {
public function disableCoreCache() {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_core']['backend']
= \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_core']['options']);
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_classes']['backend']
= \TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend::class;
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_classes']['options']);
return $this;
}
......
<?php
namespace TYPO3\CMS\Core\Core;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Helhum\ClassAliasLoader\Composer\ClassAliasLoader;
/**
* This class is responsible for setting and containing class aliases
*/
class ClassAliasMap implements \TYPO3\CMS\Core\SingletonInterface {
/**
* Old class name to new class name mapping
*
* @var array
*/